При использовании метода 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.
Есть идеи?
Да; Система координат Mac начинается в левом нижнем углу, и возрастающие значения y
идут вверх. iOS начинается с верхнего левого угла, а увеличивающиеся значения y
идут вниз. Вам часто приходится переворачивать представления (ищите свойство isFlipped
) или применять аффинные преобразования для выравнивания координат. Я не изучал этот код, чтобы понять, каким именно образом вам нужно его исправить, но это то, что вы ищете.
Ребята, вы правы, снова отлаживая его, я обнаружил, что Vision возвращает «перевернутую» систему координат, а CGImage.crop(to:) хочет неперевернутые координаты. Поэтому мне нужно воссоздать прямоугольник, переместив начало координат по оси Y в правильное координатное пространство, что-то вроде CGFloat(cgImage.height) - boundingBox.maxY
Здесь происходит то, что 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
)
система координат начинается слева внизу? ИДК, похоже.