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

DKr*_*mer 31 qr-code ios avcapturesession swift

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

代码示例

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!)

    }
Run Code Online (Sandbox Code Playgroud)

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

我尝试过的其他事情是直接传入CGRect作为参数,并导致同样的错误.我也传入了scanAreaView!.bounds一个参数,因为它确实是我正在寻找的确切大小/区域,并且也会导致相同的确切错误.我已经在其他的在线代码示例中看到了这一点,它们似乎没有我遇到的错误.这里有些例子:

AVCaptureSession条形码扫描

Xcode AVCapturesession扫描特定帧中的条形码(rectOfInterest不工作​​)

Apple文档

metadataOutputRectOfInterestForRect

rectOfInterest

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

在此输入图像描述

pea*_*212 29

我无法用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
Run Code Online (Sandbox Code Playgroud)

我刚刚在iOS设备上测试它,它似乎工作.

编辑

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

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

NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("avCaptureInputPortFormatDescriptionDidChangeNotification:"), name:AVCaptureInputPortFormatDescriptionDidChangeNotification, object: nil)
Run Code Online (Sandbox Code Playgroud)

然后添加以下方法

func avCaptureInputPortFormatDescriptionDidChangeNotification(notification: NSNotification) {

    captureMetadataOutput.rectOfInterest = videoPreviewLayer.metadataOutputRectOfInterestForRect(scanRect)

}
Run Code Online (Sandbox Code Playgroud)

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

var rect = videoPreviewLayer.rectForMetadataOutputRectOfInterest(YourAVMetadataObject.bounds)

dispatch_async(dispatch_get_main_queue(),{
     self.qrCodeFrameView.frame = rect
})
Run Code Online (Sandbox Code Playgroud)

我试过了,矩形总是在指定区域内.


Avt*_*Avt 16

在iOS 9.3.2中,我能够metadataoutputRectOfInterestForRect在以下startRunning方法之后立即调用它AVCaptureSession:

captureSession.startRunning()
let visibleRect = previewLayer.metadataOutputRectOfInterestForRect(previewLayer.bounds)
captureMetadataOutput.rectOfInterest = visibleRect
Run Code Online (Sandbox Code Playgroud)


Lal*_*hna 9

斯威夫特4:

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


Kac*_* Cz 6

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

在:

func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) 
Run Code Online (Sandbox Code Playgroud)

我有以下代码:

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