CGImage.crop(to:) возвращает странную обрезку в MacOS

При использовании метода CGImage.crop(to:) я столкнулся со странным поведением.

Моя цель — обрезать лицо пользователя с помощью Apple Vision-Framework для обучения на нем моделей на основе искусственного интеллекта, но при запуске изображения в наборе данных utkface я получил совершенно странную обрезку, см. примеры выше.

Вот соответствующий фрагмент кода:


        let image = NSImage(byReferencingFile: imagePath)!
        let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil)!
        
        let visionRequest = VNDetectFaceRectanglesRequest()

        let handler = VNImageRequestHandler(cgImage: cgImage, orientation: .up, options: [:])
        do {
            try await handler.perform([visionRequest])
        } catch {
            print("Failed ... \(error.localizedDescription)")
            return
        }

        let observations = visionRequest.results?
            .filter {
                $0.confidence >= request.faceCaptureThreshold &&
                ($0.boundingBox.size.width >= 0.1 || $0.boundingBox.size.height >= 0.1)
            } ?? []

        for (index, observation) in observations.enumerated() {
            let normalizedBoundingBox = observation.boundingBox
            let boundingBox = VNImageRectForNormalizedRect(normalizedBoundingBox, cgImage.width, cgImage.height)
            let croppedImage = cgImage.cropping(to: boundingBox)!
            // Redacted: store croppedImage on Disk
        }

Во время отладки я смог обнаружить, что все идет нормально, пока не будет вызвана функция crop(to:), что странно, потому что, когда я рисую прямоугольник над исходным изображением с помощью функций, связанных с CoreImage, прямоугольник находится в правильном месте, но урожай совсем другой.

Мне удалось обойти это, воссоздав изображение с помощью CoreImage, что-то вроде:

            let croppedImage = DrawImageInCGContext(size: boundingBox.size) { (context) -> () in
                context.draw(cgImage, in: .init(origin: .init(x: -boundingBox.minX, y: -boundingBox.minY), size: CGSize(width: cgImage.width, height: cgImage.height)), byTiling: true)
            }

Но я не понимал, что я делаю неправильно при использовании CGImage.crop(to:), или это ошибка на стороне Apple.

Есть идеи?

система координат начинается слева внизу? ИДК, похоже.

Christoph Rackwitz 11.07.2024 22:50

Да; Система координат Mac начинается в левом нижнем углу, и возрастающие значения y идут вверх. iOS начинается с верхнего левого угла, а увеличивающиеся значения y идут вниз. Вам часто приходится переворачивать представления (ищите свойство isFlipped) или применять аффинные преобразования для выравнивания координат. Я не изучал этот код, чтобы понять, каким именно образом вам нужно его исправить, но это то, что вы ищете.

Rob Napier 12.07.2024 00:14

Ребята, вы правы, снова отлаживая его, я обнаружил, что Vision возвращает «перевернутую» систему координат, а CGImage.crop(to:) хочет неперевернутые координаты. Поэтому мне нужно воссоздать прямоугольник, переместив начало координат по оси Y в правильное координатное пространство, что-то вроде CGFloat(cgImage.height) - boundingBox.maxY

Lucas Paim 12.07.2024 03:01
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
3
55
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Здесь происходит то, что macOS использует другую систему координат, начиная с нижнего левого угла, а не с верхнего левого угла, как iOS. Но CGImage.crop(to:) ожидает, что прямоугольник, основанный на верхнем левом углу, обрежет изображение, поэтому для его правильного кадрирования необходим перевод.

Небольшого изменения достаточно, чтобы это исправить:

            let normalizedBoundingBox = observation.boundingBox
            let boundingBox = VNImageRectForNormalizedRect(normalizedBoundingBox, cgImage.width, cgImage.height)
            
            let flippedY = CGFloat(cgImage.height) - boundingBox.maxY
            
            let macOSBoundingBox = CGRect(
                origin: .init(x: boundingBox.minX, y: flippedY),
                size: boundingBox.size
            )

Другие вопросы по теме