用作子 VC 时,VNDetectRectangleRequest 中的边界框大小不正确

use*_*ser 3 rectangles ios swift cvpixelbuffer apple-vision

我正在尝试使用VNDetectRectangleRequest苹果的 Vision 框架来自动抓取卡片的图片。然而,当我转换点来绘制矩形时,它是畸形的并且不遵循矩形。我一直在密切关注这篇文章

一个主要区别是我将 CameraCaptureVC 嵌入到另一个 ViewController 中,以便仅当卡位于这个较小的窗口中时才会扫描该卡。

下面是我如何在父 vc 中设置相机 vc(从 viewDidLoad 调用)。

func configureSubviews() {
    clearView.addSubview(cameraVC.view)
    cameraVC.view.autoPinEdgesToSuperviewEdges()
    self.addChild(cameraVC)
    cameraVC.didMove(toParent: self)
}
Run Code Online (Sandbox Code Playgroud)

下面是绘制矩形的代码

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])
}
Run Code Online (Sandbox Code Playgroud)

这是我的结果。红色矩形应该沿着卡片的边框,但它太短了,而且原点甚至不在卡片的顶部。

我尝试过更改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)
Run Code Online (Sandbox Code Playgroud)

将不胜感激任何帮助。也许因为我将它作为子 VC 嵌入,所以我需要第二次转换坐标?我尝试了类似的方法但无济于事,但也许我做错了或者遗漏了一些东西

ahe*_*eze 13

首先让我们看一下boundingBox,它是一个“标准化”矩形。苹果说

坐标被标准化为处理图像的尺寸,原点位于图像的左下角。

这意味着:

  • 位于origin左下角,而不是左上角
  • origin.xwidth整个图像宽度的一部分
  • origin.yheight整个图像高度的一部分

希望这张图能让你更清楚:

你习惯什么 愿景返回什么

上面的函数转换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
}
Run Code Online (Sandbox Code Playgroud)

这考虑了图像视图的框架以及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
}
Run Code Online (Sandbox Code Playgroud)

Image taller than image view, orange bounding box drawn on detected rectangle

Image wider than image view, orange bounding box drawn on detected rectangle

我在这里做了一个示例项目。