将VNRectangleObservation点转换到其他坐标系

13

我需要将收到的 VNRectangleObservation 中的 CGPoints(bottomLeft,bottomRight,topLeft,topRight)转换为另一个坐标系(例如视图在屏幕上的坐标)。

我定义了一个请求:

    // Rectangle Request
    let rectangleDetectionRequest = VNDetectRectanglesRequest(completionHandler: handleRectangles)
    rectangleDetectionRequest.minimumSize = 0.5
    rectangleDetectionRequest.maximumObservations = 1

我在委托调用中从相机获取sampleBuffer,并执行检测请求:

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {

    guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {return}
    var requestOptions:[VNImageOption:Any] = [:]
    if let cameraIntrinsicData = CMGetAttachment(sampleBuffer, kCMSampleBufferAttachmentKey_CameraIntrinsicMatrix, nil) {
        requestOptions = [.cameraIntrinsics:cameraIntrinsicData]
    }
    let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, orientation: CGImagePropertyOrientation(rawValue:6)!, options: requestOptions)
    do {
        try imageRequestHandler.perform(self.requests)
    } catch {
        print(error)
    }

}

在completionHandler中,我收到了结果:

func handleRectangles (request:VNRequest, error:Error?) {

     guard let results = request.results as? [VNRectangleObservation] else { return }

     let flipTransform = CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -self.previewView.frame.height)
     let scaleTransform = CGAffineTransform.identity.scaledBy(x: self.previewView.frame.width, y: self.previewView.frame.height)

     for rectangle in results {
        let rectangleBounds = rectangle.boundingBox.applying(scaleTransform).applying(flipTransform)
        // convertedTopLeft = conversion(rectangle.topLeft) 
        // convertedTopRight = conversion(rectangle.topRight) 
        // convertedBottomLeft = conversion(rectangle.bottomLeft) 
        // convertedBottomRight = conversion(rectangle.bottomRight) 
    }
}
这适用于 CGRect 类型的 boundingBox,但我需要转换 CGPoints 到另一个视图的坐标系统。 问题是我不知道如何从 sampleBuffer 的 CMSampleBuffer 坐标系转换到 previewView 的坐标系。
谢谢!
3个回答

8

这只是将变换应用于CGPoint本身的简单操作,其中size是目标视图的CGSize,我需要转置四个点。

    let transform = CGAffineTransform.identity
        .scaledBy(x: 1, y: -1)
        .translatedBy(x: 0, y: -size.height)
        .scaledBy(x: size.width, y: size.height)

    let convertedTopLeft = rectangle.topLeft.applying(transform)
    let convertedTopRight = rectangle.topRight.applying(transform)
    let convertedBottomLeft = rectangle.bottomLeft.applying(transform)
    let convertedBottomRight = rectangle.bottomRight.applying(transform)

这很完美,但我如何通过稍微修改其角落来再次删除此变换?我需要再次将这些点传递给CIPerspectiveCorrection。 - Ameet Dhas

2

@mihaicris的回答是正确的,但仅适用于竖屏模式。在横屏模式下,我们需要稍微做出一些不同的调整。

最初的回答:

if UIApplication.shared.statusBarOrientation.isLandscape {
    transform = CGAffineTransform.identity
        .scaledBy(x: -1, y: 1)
        .translatedBy(x: -size.width, y: 0)
        .scaledBy(x: size.width, y: size.height)
} else {
    transform = CGAffineTransform.identity
        .scaledBy(x: 1, y: -1)
        .translatedBy(x: 0, y: -size.height)
        .scaledBy(x: size.width, y: size.height)
}

let convertedTopLeft = rectangle.topLeft.applying(transform)
let convertedTopRight = rectangle.topRight.applying(transform)
let convertedBottomLeft = rectangle.bottomLeft.applying(transform)
let convertedBottomRight = rectangle.bottomRight.applying(transform)

1
我假设您使用了相机的图层,该图层是AVCaptureVideoPreviewLayer。(https://developer.apple.com/documentation/avfoundation/avcapturevideopreviewlayer)。因此,如果您想转换单个点,请使用此函数:layerPointConverted(https://developer.apple.com/documentation/avfoundation/avcapturevideopreviewlayer/1623502-layerpointconverted)。请注意,由于VNRectangleObservation坐标系,y轴是倒置的。
let convertedTopLeft: CGPoint = cameraLayer.layerPointConverted(fromCaptureDevicePoint: CGPoint(x: rectangle.topLeft.x, y: 1 - rectangle.topLeft.y))
let convertedTopRight: CGPoint = cameraLayer.layerPointConverted(fromCaptureDevicePoint: CGPoint(x: rectangle.topRight.x, y: 1 - rectangle.topRight.y))
let convertedBottomLeft: CGPoint = cameraLayer.layerPointConverted(fromCaptureDevicePoint: CGPoint(x: rectangle.bottomLeft.x, y: 1 - rectangle.bottomLeft.y))
let convertedBottomRight: CGPoint = cameraLayer.layerPointConverted(fromCaptureDevicePoint: CGPoint(x: rectangle.bottomRight.x, y: 1 - rectangle.bottomRight.y))

希望它有所帮助。

谢谢,我会在我的代码中检查您的答案,因为它可能是补偿预览层方面填充重心属性的更好解决方案。 - mihaicris
很好,如果有帮助请告诉我。 - Tziki
1
嗨,我尝试了layerPointConverted方法,但它并没有按预期工作。使用捕获设备坐标的角落点(0,0 - 1,0 - 0,1 - 1,1)作为输入点,转换后的点的x坐标偏离了目标视图框架。就像该方法没有考虑previewView的aspectFill视频重力属性一样(它恰好与屏幕大小相同,不会更大)。我以为这个函数知道如何补偿偏移量... - mihaicris
@mihaicris我也遇到了同样的问题,x坐标偏离目标视图框架。但是,如果你去看苹果文档(https://developer.apple.com/documentation/avfoundation/avcapturevideopreviewlayer/1623502-layerpointconverted),它说:“此方法执行的转换考虑了图层的框架大小和接收者的videoGravity属性。” 我会调查这个问题,看看能否找出问题所在。 - kikettas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接