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

Ios 如何使用MetadataOutputRectofFinterestForRect方法和rectOfInterest属性扫描特定区域?(二维码),ios,swift,qr-code,avcapturesession,Ios,Swift,Qr Code,Avcapturesession,我正在建立一个二维码扫描仪与Swift和一切工作在这方面。我遇到的问题是,我试图使整个可视AVCaptureVideoPreviewLayer中的一小部分能够扫描二维码。我发现,为了指定屏幕的哪个区域能够读取/捕获二维码,我必须使用名为rectOfInterest的avcaptureMataOutput属性。问题是,当我将其分配给CGRect时,我无法扫描任何内容。在网上做了更多的研究之后,我发现一些建议,我需要使用一种名为metadataOutputRectOfInterestForRect的

我正在建立一个二维码扫描仪与Swift和一切工作在这方面。我遇到的问题是,我试图使整个可视
AVCaptureVideoPreviewLayer
中的一小部分能够扫描二维码。我发现,为了指定屏幕的哪个区域能够读取/捕获二维码,我必须使用名为
rectOfInterest
avcaptureMataOutput
属性。问题是,当我将其分配给CGRect时,我无法扫描任何内容。在网上做了更多的研究之后,我发现一些建议,我需要使用一种名为
metadataOutputRectOfInterestForRect
的方法,将CGRect转换为属性
rectOfInterest
可以实际使用的正确格式。然而,我现在遇到的一个大问题是,当我使用此方法时,
metadataoutputRectOfInterestForRect
我得到一个错误,状态为
CGAffineTransformInvert:singular matrix
。有人能告诉我为什么我会犯这个错误吗?我相信,根据苹果开发者文档,我正在正确地使用这种方法,我相信我需要根据我在网上找到的所有信息来使用这种方法,以实现我的目标。我将包括到目前为止我找到的文档的链接,以及我用来扫描二维码的功能的代码示例

代码示例

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

    }
请注意,导致错误的兴趣线如下:
captureMataOutput.rectOfInterest=videoPreviewLayer!。metadataOutputRectOfInterestForRect(scanRect)

我尝试过的其他方法是直接将CGRect作为参数传递,这导致了相同的错误。我还传入了
scanAreaView!。边界
作为一个参数,因为这实际上是我要查找的精确大小/区域,并且也会导致相同的精确错误。我在其他人的在线代码示例中看到过这种情况,他们似乎没有我遇到的错误。以下是一些例子:

苹果文档

扫描区域视图的图像我正在使用作为指定区域,我正在尝试创建视频预览层的唯一可扫描区域:


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

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

下面是我试过的代码

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()中添加通知观察者方法

然后添加以下方法

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
方法之后立即调用它:

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

我写了以下内容:

videoPreviewLayer?.frame = view.layer.bounds
videoPreviewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
这对我来说很有效,但我仍然不知道为什么。

///After

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
    }
Swift 4:
我设法创造了一种拥有感兴趣区域的效果。我尝试了所有建议的解决方案,但该区域要么是CGPoint.zero,要么大小不合适(在将帧转换为0-1坐标后)。对于那些无法让
RegionFinterest
工作并且没有优化检测的人来说,这实际上是一种黑客攻击

在:

我有以下代码:

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

从全摄像机视图中的小矩形(特定区域)读取QRCode/BarCode

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

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

**必须在(开始运行)后保留以下行)**
[CaptureMataOutput setRectOfInterest:[u videoPreviewLayer MetadataOutputRectofFinterestForRect:scanRect]; [_CaptureSessionStartrunning]; [CaptureMataOutput setRectOfInterest:[u videoPreviewLayer MetadataOutputRectofFinterestForRect:scanRect];
注:

  • CaptureMataOutput
    -->AVCaptureMataOutput
  • \u videoPreviewLayer
    -->AVCaptureVideoPreviewLayer
  • scanRect
    -->要读取QRCode的Rect

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

     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
    }
    

    可能不相关,但对我来说问题是屏幕方向。在我的肖像只有应用程序,我想有一个条形码扫描仪,只是检测代码在一个水平线在屏幕中部。我认为这会奏效:

    CGRect(x: 0, y: 0.4, width: 1, height: 0.2)
    
    相反,我必须用y来切换x,用高度来切换宽度

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

    谢谢你的信息!我尝试使用您提供的代码,但不幸的是,它对我不起作用。我将640*480的值更新为1024*768,因为我使用的是iPad而不是手机,但扫描区域不在我用作目标区域的指定扫描区域内。我在我的应用程序中添加了UIView的图像,以更好地显示我正在努力实现的目标。请参阅更新的帖子,我已经用metadataOutputRectOfInterestForRect解决了这个问题,现在它似乎可以工作了。非常感谢!我以前根本没有扫描过。修正了我的问题:)对我来说还不清楚。我使用的是完全相同的代码
     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
    }
    
    CGRect(x: 0, y: 0.4, width: 1, height: 0.2)
    
    CGRect(x: 0.4, y: 0, width: 0.2, height: 1)