如何使用 metadataOutputRectOfInterestForRect 方法和 rectOfInterest 属性扫描特定区域? (二维码)

Posted

技术标签:

【中文标题】如何使用 metadataOutputRectOfInterestForRect 方法和 rectOfInterest 属性扫描特定区域? (二维码)【英文标题】:How do I use the metadataOutputRectOfInterestForRect method and rectOfInterest property to scan a specific area? (QR Code) 【发布时间】:2021-11-19 08:29:57 【问题描述】:

我正在使用 Swift 构建一个 QR 码扫描仪,并且在这方面一切正常。我遇到的问题是我试图让整个可见AVCaptureVideoPreviewLayer 的一小部分能够扫描二维码。我发现为了指定屏幕的哪个区域能够读取/捕获 QR 码,我必须使用名为 rectOfInterestAVCaptureMetadataOutput 属性。问题是当我将它分配给 CGRect 时,我无法扫描任何东西。在网上进行了更多研究后,我发现一些建议我需要使用一种名为 metadataOutputRectOfInterestForRect 的方法将 CGRect 转换为属性 rectOfInterest 可以实际使用的正确格式。但是,我现在遇到的一个大问题是,当我使用这种方法metadataoutputRectOfInterestForRect 时,我收到一个错误,指出CGAffineTransformInvert: singular matrix。谁能告诉我为什么我会收到这个错误?我相信我正在根据 Apple 开发人员文档正确使用此方法,并且我相信我需要根据我在网上找到的所有信息来使用此方法来实现我的目标。我将包含到目前为止我找到的文档的链接以及我用来扫描二维码的函数的代码示例

代码示例

func startScan() 
        // Get an instance of the AVCaptureDevice class to initialize a device object and provide the video
        // as the media type parameter.
        let captureDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)

        // Get an instance of the AVCaptureDeviceInput class using the previous device object.
        var error:NSError?
        let input: AnyObject! = AVCaptureDeviceInput.deviceInputWithDevice(captureDevice, error: &error)

        if (error != nil) 
            // If any error occurs, simply log the description of it and don't continue any more.
            println("\(error?.localizedDescription)")
            return
        

        // Initialize the captureSession object.
        captureSession = AVCaptureSession()
        // Set the input device on the capture session.
        captureSession?.addInput(input as! AVCaptureInput)

        // Initialize a AVCaptureMetadataOutput object and set it as the output device to the capture session.
        let captureMetadataOutput = AVCaptureMetadataOutput()
        captureSession?.addOutput(captureMetadataOutput)

        // calculate a centered square rectangle with red border
        let size = 300
        let screenWidth = self.view.frame.size.width
        let xPos = (CGFloat(screenWidth) / CGFloat(2)) - (CGFloat(size) / CGFloat(2))
        let scanRect = CGRect(x: Int(xPos), y: 150, width: size, height: size)

        // create UIView that will server as a red square to indicate where to place QRCode for scanning
        scanAreaView = UIView()
        scanAreaView?.layer.borderColor = UIColor.redColor().CGColor
        scanAreaView?.layer.borderWidth = 4
        scanAreaView?.frame = scanRect
        view.addSubview(scanAreaView!)

        // Set delegate and use the default dispatch queue to execute the call back
        captureMetadataOutput.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue())
        captureMetadataOutput.metadataObjectTypes = [AVMetadataObjectTypeQRCode]



        // Initialize the video preview layer and add it as a sublayer to the viewPreview view's layer.
        videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        videoPreviewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
        videoPreviewLayer?.frame = view.layer.bounds
        captureMetadataOutput.rectOfInterest = videoPreviewLayer!.metadataOutputRectOfInterestForRect(scanRect)
        view.layer.addSublayer(videoPreviewLayer)

        // Start video capture.
        captureSession?.startRunning()

        // Initialize QR Code Frame to highlight the QR code
        qrCodeFrameView = UIView()
        qrCodeFrameView?.layer.borderColor = UIColor.greenColor().CGColor
        qrCodeFrameView?.layer.borderWidth = 2
        view.addSubview(qrCodeFrameView!)
        view.bringSubviewToFront(qrCodeFrameView!)

        // Add a button that will be used to close out of the scan view
        videoBtn.setTitle("Close", forState: .Normal)
        videoBtn.setTitleColor(UIColor.blackColor(), forState: .Normal)
        videoBtn.backgroundColor = UIColor.grayColor()
        videoBtn.layer.cornerRadius = 5.0;
        videoBtn.frame = CGRectMake(10, 30, 70, 45)
        videoBtn.addTarget(self, action: "pressClose:", forControlEvents: .TouchUpInside)
        view.addSubview(videoBtn)


        view.bringSubviewToFront(scanAreaView!)

    

请注意,导致错误的感兴趣行是这样的: captureMetadataOutput.rectOfInterest = videoPreviewLayer!.metadataOutputRectOfInterestForRect(scanRect)

我尝试过的其他方法是直接将 CGRect 作为参数传递,这导致了同样的错误。我还传递了scanAreaView!.bounds 作为参数,因为这确实是我正在寻找的确切大小/区域,这也会导致相同的确切错误。我已经在其他人的在线代码示例中看到了这一点,他们似乎没有我遇到的错误。以下是一些示例:

AVCaptureSession barcode scan

Xcode AVCapturesession scan Barcode in specific frame (rectOfInterest is not working)

Apple 文档

metadataOutputRectOfInterestForRect

rectOfInterest

我用作指定区域的 scanAreaView 的图像我正在尝试制作视频预览层的唯一可扫描区域:

【问题讨论】:

如果使用 AVCapturePhotoCaptureDelegate 的 func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) 可以用什么? 【参考方案1】:

我无法真正澄清 metadataOutputRectOfInterestForRect 的问题,但是,您也可以直接设置属性。您需要具有视频的宽度和高度分辨率,您可以提前指定。我很快使用了 640*480 设置。如文档中所述,这些值必须是

“相对于设备的自然方向,从左上角的 (0,0) 延伸到右下角的 (1,1)”。

见https://developer.apple.com/documentation/avfoundation/avcaptureoutput/1616304-metadataoutputrectofinterestforr

下面是我试过的代码

var x = scanRect.origin.x/480
var y = scanRect.origin.y/640
var width = scanRect.width/480
var height = scanRect.height/640
var scanRectTransformed = CGRectMake(x, y, width, height)
captureMetadataOutput.rectOfInterest = scanRectTransformed

我刚刚在 ios 设备上对其进行了测试,它似乎可以工作。

编辑

至少我已经解决了 metadataOutputRectOfInterestForRect 问题。我相信您必须在相机正确设置并运行后执行此操作,因为相机的分辨率尚不可用。

首先,在 viewDidLoad() 中添加一个通知观察者方法

NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("avCaptureInputPortFormatDescriptionDidChangeNotification:"), name:AVCaptureInputPortFormatDescriptionDidChangeNotification, object: nil)

然后添加如下方法

func avCaptureInputPortFormatDescriptionDidChangeNotification(notification: NSNotification) 

    captureMetadataOutput.rectOfInterest = videoPreviewLayer.metadataOutputRectOfInterestForRect(scanRect)


然后您可以在此处重置 rectOfInterest 属性。然后,在您的代码中,您可以在 didOutputMetadataObjects 函数中显示 AVMetadataObject

var rect = videoPreviewLayer.rectForMetadataOutputRectOfInterest(YourAVMetadataObject.bounds)

dispatch_async(dispatch_get_main_queue(),
     self.qrCodeFrameView.frame = rect
)

我试过了,矩形一直在指定区域内。

【讨论】:

感谢您的信息!我尝试使用您提供的代码,但不幸的是它对我不起作用。我将 640*480 值更新为 1024*768,因为我使用的是 iPad 而不是手机,但扫描区域不在我用作目标区域的指定 scanRect 内。我在我的应用程序中向上面的问题添加了 UIView 的图像,以更好地显示我想要实现的目标。 查看更新后的帖子,我已经解决了 metadataOutputRectOfInterestForRect 的问题,它现在似乎可以工作了。 非常感谢!我的以前没有扫描过。修复了我的问题:) 对我来说还不清楚。我使用的是完全相同的代码,整个屏幕都没有被捕获,但我定义的区域也没有。就像 (0, 0) insu 从左上角开始.. 你的第一个选项对我有用,但 metadataOutputRectOfInterestForRect 不是,我尝试了所有方法,但我真的不知道我需要什么才能让它工作。【参考方案2】:

在 iOS 9.3.2 中,我能够在 AVCaptureSessionstartRunning 方法之后立即调用 metadataoutputRectOfInterestForRect 工作:

captureSession.startRunning()
let visibleRect = previewLayer.metadataOutputRectOfInterestForRect(previewLayer.bounds)
captureMetadataOutput.rectOfInterest = visibleRect

【讨论】:

【参考方案3】:

我写了以下内容:

videoPreviewLayer?.frame = view.layer.bounds
videoPreviewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill

这对我有用,但我仍然不知道为什么。

【讨论】:

【参考方案4】:

/// 之后

captureSession.startRunning()

/// 添加这个

if let videoPreviewLayer = self.videoPreviewLayer 
self.captureMetadataOutput.rectOfInterest =
videoPreviewLayer.metadataOutputRectOfInterest(for:
self.getRectOfInterest())


fileprivate func getRectOfInterest() -> CGRect 
        let centerX = (self.frame.width / 2) - 100
        let centerY = (self.frame.height / 2) - 100
        let quadr: CGFloat = 200

        let myRect = CGRect(x: centerX, y: centerY, width: quadr, height: quadr)

        return myRect
    

【讨论】:

【参考方案5】:

斯威夫特 4:

captureSession?.startRunning()
let scanRect = CGRect(x: 0, y: 0, width: 100, height: 100)
let rectOfInterest = layer.metadataOutputRectConverted(fromLayerRect: scanRect)
metaDataOutput.rectOfInterest = rectOfInterest

【讨论】:

【参考方案6】:

我设法创造了一种具有感兴趣区域的效果。我尝试了所有建议的解决方案,但该区域是 CGPoint.zero 或大小不合适(将帧转换为 0-1 坐标后)。对于那些无法让regionOfInterest 工作并且没有优化检测的人来说,这实际上是一种 hack。

在:

func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) 

我有以下代码:

let visualCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj)
if self.viewfinderView.frame.contains(visualCodeObject.bounds)  
    //visual code is inside the viewfinder, you can now handle detection

【讨论】:

谢谢,我喜欢这种方法 非常感谢。从所有可能的解决方案中,我发现这个最准确和精确。 太棒了!使它正常工作的唯一一种方法,因为 metadataOutputRectOfInterest 似乎不起作用(至少在我的配置中)并且手动计算框架不会考虑重力(内容缩放)【参考方案7】:

从一个完整的相机视图中读取一个小矩形(特定区域)的二维码/条形码。

<br> **Mandatory to keep the below line after (start running)** <br>
[captureMetadataOutput setRectOfInterest:[_videoPreviewLayer metadataOutputRectOfInterestForRect:scanRect] ];

[_captureSession startRunning];
[captureMetadataOutput setRectOfInterest:[_videoPreviewLayer metadataOutputRectOfInterestForRect:scanRect] ];

注意:

    captureMetadataOutput --> AVCaptureMetadataOutput _videoPreviewLayer --> AVCaptureVideoPreviewLayer scanRect --> 矩形,您希望读取 QRCode。

【讨论】:

【参考方案8】:

我知道已经有解决方案,但已经很晚了,但我通过捕获完整的视图图像然后用特定的矩形裁剪它来实现我的解决方案。

 func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) 

if let imageData = photo.fileDataRepresentation() 
    print(imageData)
    capturedImage = UIImage(data: imageData)

    var crop = cropToPreviewLayer(originalImage: capturedImage!)

    let sb = UIStoryboard(name: "Main", bundle: nil)
    let s = sb.instantiateViewController(withIdentifier: "KeyFobScanned") as! KeyFobScanned
    s.image = crop
    self.navigationController?.pushViewController(s, animated: true)




private func cropToPreviewLayer(originalImage: UIImage) -> UIImage? 
guard let cgImage = originalImage.cgImage else  return nil 

let scanRect = CGRect(x: stackView.frame.origin.x, y: stackView.frame.origin.y, width: innerView.frame.size.width, height: innerView.frame.size.height)

let outputRect = videoPreviewLayer.metadataOutputRectConverted(fromLayerRect: scanRect)

let width = CGFloat(cgImage.width)
let height = CGFloat(cgImage.height)

let cropRect = CGRect(x: outputRect.origin.x * width, y: outputRect.origin.y * height, width: outputRect.size.width * width, height: outputRect.size.height * height)

if let croppedCGImage = cgImage.cropping(to: cropRect) 
    return UIImage(cgImage: croppedCGImage, scale: 1.0, orientation: originalImage.imageOrientation)


return nil

【讨论】:

【参考方案9】:

可能不相关,但对我来说问题是屏幕方向。在我的纵向应用程序上,我想要一个条形码扫描仪,它只检测屏幕中间水平线上的代码。我认为这会起作用:

CGRect(x: 0, y: 0.4, width: 1, height: 0.2)

我不得不将 x 与 y 和 width 与 height 进行切换

CGRect(x: 0.4, y: 0, width: 0.2, height: 1)

【讨论】:

以上是关于如何使用 metadataOutputRectOfInterestForRect 方法和 rectOfInterest 属性扫描特定区域? (二维码)的主要内容,如果未能解决你的问题,请参考以下文章

如何使用本机反应创建登录以及如何验证会话

如何在自动布局中使用约束标识符以及如何使用标识符更改约束? [迅速]

如何使用 AngularJS 的 ng-model 创建一个数组以及如何使用 jquery 提交?

如何使用laravel保存所有行数据每个行名或相等

如何使用 Math.Net 连接矩阵。如何使用 Math.Net 调用特定的行或列?

WSARecv 如何使用 lpOverlapped?如何手动发出事件信号?