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

我正在用Swift构build一个QR代码扫描器,在这方面一切正常。 我有的问题是,我正在试图使整个可见的AVCaptureVideoPreviewLayer能够扫描QR码只有一小部分。 我已经发现,为了指定屏幕的哪个区域将能够读取/捕获QR码,我将不得不使用称为rectOfInterestAVCaptureMetadataOutput属性。 麻烦的是当我将它分配给CGRect时,我无法扫描任何内容。 在网上做了更多的研究之后,我发现一些暗示,我需要使用一个名为metadataOutputRectOfInterestForRect的方法将CGRect转换为属性rectOfInterest实际可以使用的正确格式。 然而,我现在遇到的大问题是,当我使用这个方法metadataoutputRectOfInterestForRect我得到一个错误,指出CGAffineTransformInvert: singular matrix 。 谁能告诉我为什么我得到这个错误? 我相信我正在根据苹果开发者文档使用这种方法,我相信我需要根据我在网上find的所有信息来实现我的目标。 我将包括到目前为止发现的文档的链接,以及我用来扫描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!) } 

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

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

AVCaptureSession条码扫描

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

苹果文档

metadataOutputRectOfInterestForRect

rectOfInterest

scanAreaView的图像我正在使用指定的区域我试图使video预览图层的唯一可扫描区域:

在这里输入图像说明

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

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

请参阅https://developer.apple.com/documentation/avfoundation/avcaptureoutput/1616304-metadataoutputrectrectofinterestforr

下面是我尝试的代码

 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设备上testing它,它似乎工作。

编辑

至less我已经解决了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 }) 

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

在iOS 9.3.2中,我能够在AVCaptureSession startRunning方法之后startRunning调用metadataoutputRectOfInterestForRect工作:

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

///之后

 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 } 

Swift4代码:

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

我设法创造了一个有兴趣的地区的效果。 我尝试了所有build议的解决scheme,但该区域是CGPoint.zero或不适当的大小(将帧转换为0-1坐标后)。 对于那些无法获得区域regionOfInterest人来说,这实际上是一种黑客攻击,并且不会优化检测。

在:

 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 } 

我写了以下内容:

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

而这对我来说,但我仍然不知道为什么。