Ограничивающий прямоугольник из VNDetectRectangleRequest имеет неправильный размер при использовании в качестве дочернего VC

Я пытаюсь использовать VNDetectRectangleRequest из Apple Vision framework для автоматического получения изображения карты. Однако, когда я конвертирую точки для рисования прямоугольника, он деформирован и не следует за прямоугольником, как должен. Я читал эту статью довольно близко

Одно из основных отличий заключается в том, что я встраиваю свой CameraCaptureVC в другой ViewController, так что карта будет сканироваться только тогда, когда она находится в этом меньшем окне.

Ниже показано, как я настроил камеру vc в родительском vc (вызываемом из viewDidLoad).

func configureSubviews() {
    clearView.addSubview(cameraVC.view)
    cameraVC.view.autoPinEdgesToSuperviewEdges()
    self.addChild(cameraVC)
    cameraVC.didMove(toParent: self)
}

Ниже приведен код для рисования прямоугольника.

func createLayer(in rect: CGRect) {
    let maskLayer = CAShapeLayer()
    maskLayer.frame = rect
    maskLayer.cornerRadius = 10
    maskLayer.opacity = 0.75
    maskLayer.borderColor = UIColor.red.cgColor
    maskLayer.borderWidth = 5.0

    previewLayer.insertSublayer(maskLayer, at: 1)
}

func removeMask() {
    if let sublayer = previewLayer.sublayers?.first(where: { $0 as? CAShapeLayer != nil }) {
        sublayer.removeFromSuperlayer()
    }
}

func drawBoundingBox(rect : VNRectangleObservation) {
    let transform = CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -finalFrame.height)

    let scale = CGAffineTransform.identity.scaledBy(x: finalFrame.width, y: finalFrame.height)

    let bounds = rect.boundingBox.applying(scale).applying(transform)

    createLayer(in: bounds)
}

func detectRectangle(in image: CVPixelBuffer) {
    let request = VNDetectRectanglesRequest { (request: VNRequest, error: Error?) in
        DispatchQueue.main.async {
            guard let results = request.results as? [VNRectangleObservation],
                let rect = results.first else { return }
            self.removeMask()
            self.drawBoundingBox(rect: rect)
        }
    }
    request.minimumAspectRatio = 0.0
    request.maximumAspectRatio = 1.0
    request.maximumObservations = 0
    let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: image, options: [:])
    try? imageRequestHandler.perform([request])
}

Это мой результат. Красный прямоугольник должен следовать за границами карты, но он слишком короткий, а начало координат даже не находится в верхней части карты.

Я пробовал изменить значения в функции drawBoundingBox, но, похоже, ничего не помогает. Я также попытался преобразовать границы другим способом, как показано ниже, но это тот же результат, и изменение этих значений становится хакерским.

    let scaledHeight: CGFloat = originalFrame.width / finalFrame.width * finalFrame.height
    let boundingBox = rect.boundingBox
    let x = finalFrame.width * boundingBox.origin.x
    let height = scaledHeight * boundingBox.height
    let y = scaledHeight * (1 - boundingBox.origin.y) - height
    let width = finalFrame.width * boundingBox.width

    let bounds = CGRect(x: x, y: y, width: width, height: height)
    createLayer(in: bounds)

Был бы признателен за любую помощь. Может быть, поскольку я встраиваю его как дочерний VC, мне нужно преобразовать координаты во второй раз? Я пробовал что-то подобное безрезультатно, но, возможно, я сделал что-то не так или что-то упустил


person user    schedule 09.11.2020    source источник


Ответы (1)


Сначала давайте посмотрим на boundingBox, который представляет собой нормализованный прямоугольник. Apple говорит

Координаты нормализуются к размерам обработанного изображения с началом координат в нижнем левом углу изображения.

Это означает, что:

  • origin находится внизу слева, а не вверху слева
  • origin.x и width представляют собой долю от ширины всего изображения.
  • origin.y и height - это часть высоты всего изображения.

Надеюсь, эта диаграмма проясняет:

What you are used to What Vision returns

Ваша функция выше преобразует boundingBox в координаты finalFrame, которые, как я предполагаю, являются фреймом всего представления. Это намного больше, чем ваш маленький CameraCaptureVC.

Кроме того, слой предварительного просмотра вашего CameraCaptureVC, вероятно, имеет aspectFill гравитацию видео. Вам также нужно будет учитывать выходящие за границы части отображаемого изображения.

Попробуйте вместо этого эту функцию преобразования.

func getConvertedRect(boundingBox: CGRect, inImage imageSize: CGSize, containedIn containerSize: CGSize) -> CGRect {
    
    let rectOfImage: CGRect
    
    let imageAspect = imageSize.width / imageSize.height
    let containerAspect = containerSize.width / containerSize.height
    
    if imageAspect > containerAspect { /// image extends left and right
        let newImageWidth = containerSize.height * imageAspect /// the width of the overflowing image
        let newX = -(newImageWidth - containerSize.width) / 2
        rectOfImage = CGRect(x: newX, y: 0, width: newImageWidth, height: containerSize.height)
        
    } else { /// image extends top and bottom
        let newImageHeight = containerSize.width * (1 / imageAspect) /// the width of the overflowing image
        let newY = -(newImageHeight - containerSize.height) / 2
        rectOfImage = CGRect(x: 0, y: newY, width: containerSize.width, height: newImageHeight)
    }
    
    let newOriginBoundingBox = CGRect(
    x: boundingBox.origin.x,
    y: 1 - boundingBox.origin.y - boundingBox.height,
    width: boundingBox.width,
    height: boundingBox.height
    )
    
    var convertedRect = VNImageRectForNormalizedRect(newOriginBoundingBox, Int(rectOfImage.width), Int(rectOfImage.height))
    
    /// add the margins
    convertedRect.origin.x += rectOfImage.origin.x
    convertedRect.origin.y += rectOfImage.origin.y
    
    return convertedRect
}

При этом учитывается рамка просмотра изображения, а также aspect fill режим содержимого.

Пример (для простоты я использую статическое изображение вместо прямой трансляции с камеры):

/// inside your Vision request completion handler...
guard let image = self.imageView.image else { return }

let convertedRect = self.getConvertedRect(
    boundingBox: observation.boundingBox,
    inImage: image.size,
    containedIn: self.imageView.bounds.size
)
self.drawBoundingBox(rect: convertedRect)

func drawBoundingBox(rect: CGRect) {
    let uiView = UIView(frame: rect)
    imageView.addSubview(uiView)
        
    uiView.backgroundColor = UIColor.clear
    uiView.layer.borderColor = UIColor.orange.cgColor
    uiView.layer.borderWidth = 3
}

 Изображение выше, чем изображение, на обнаруженном прямоугольнике нарисована оранжевая ограничивающая рамка

 Изображение шире, чем изображение, на обнаруженном прямоугольнике нарисована оранжевая ограничительная рамка

Я сделал пример проекта здесь.

person aheze    schedule 04.02.2021