web-dev-qa-db-fra.com

Comment utiliser la méthode metadataOutputRectOfInterestForRect et la propriété rectOfInterest pour analyser une zone spécifique? (QR Code)

Je construis un scanner de code QR avec Swift et tout fonctionne à cet égard. Le problème que j’ai, c’est que j’essaie de ne rendre visible qu’une petite partie de la totalité de la variable visible AVCaptureVideoPreviewLayer à scanner les codes QR. J'ai découvert que pour spécifier quelle zone de l'écran sera capable de lire/capturer les codes QR, je devrais utiliser une propriété de AVCaptureMetadataOutput appelée rectOfInterest. Le problème, c'est que lorsque j'ai assigné cela à un CGRect, je ne pouvais rien scanner. Après avoir effectué davantage de recherches en ligne, j'en ai trouvé qui suggéraient que je devrais utiliser une méthode appelée metadataOutputRectOfInterestForRect pour convertir un CGRect dans un format correct que la propriété rectOfInterest peut réellement utiliser. CEPENDANT, le gros problème que je rencontre maintenant est que lorsque j'utilise cette méthode metadataoutputRectOfInterestForRect, j'obtiens une erreur qui indique CGAffineTransformInvert: singular matrix. Quelqu'un peut-il me dire pourquoi je reçois cette erreur? Je crois que j'utilise correctement cette méthode conformément à la documentation pour les développeurs Apple et je pense que je dois l'utiliser conformément à toutes les informations que j'ai trouvées en ligne pour atteindre mon objectif. Je vais inclure des liens vers la documentation que j'ai trouvée jusqu'à présent, ainsi qu'un exemple de code de la fonction que j'utilise pour scanner les codes QR.

Échantillon de code

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

    }

Veuillez noter que la ligne d’intérêt à l’origine de l’erreur est la suivante: captureMetadataOutput.rectOfInterest = videoPreviewLayer!.metadataOutputRectOfInterestForRect(scanRect)

D'autres choses que j'ai essayées sont de passer un CGRect directement en tant que paramètre, ce qui a provoqué la même erreur. J'ai également passé scanAreaView!.bounds en tant que paramètre car c'est vraiment la taille/zone exacte que je cherche et qui provoque également la même erreur exacte. J'ai vu cela se produire dans d'autres exemples de code en ligne et ils ne semblent pas avoir les erreurs que j'ai. Voici quelques exemples:

Balayage de code à barres AVCaptureSession

Xcode AVCapturesession scan Code-barres dans une image spécifique (rectOfInterest ne fonctionne pas)

Documentation Apple

metadataOutputRectOfInterestForRect

rectOfInterest

Image de scanAreaView J'utilise comme zone désignée la tentative de création de la seule zone analysable de la couche d'aperçu vidéo:

 enter image description here

28
DKrautkramer

Je ne pouvais pas vraiment clarifier le problème avec metadataOutputRectOfInterestForRect, cependant, vous pouvez également définir directement la propriété. Vous devez avoir la résolution en largeur et en hauteur de votre vidéo, que vous pouvez spécifier à l’avance. J'ai rapidement utilisé le paramètre 640 * 480. Comme indiqué dans la documentation, ces valeurs doivent être 

"allant de (0,0) en haut à gauche à (1,1) en bas à droite, par rapport à l'orientation naturelle de l'appareil".

Voir https://developer.Apple.com/documentation/avfoundation/avcaptureoutput/1616304-metadataoutputrectofinterestforr

Ci-dessous le code que j'ai essayé

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

Je viens de le tester sur un appareil iOS et cela semble fonctionner. 

Modifier

Au moins, j'ai résolu le problème metadataOutputRectOfInterestForRect. Je pense que vous devez le faire une fois que la caméra est correctement configurée et fonctionne, car la résolution de la caméra n'est pas encore disponible. 

Tout d'abord, ajoutez une méthode d'observateur de notification dans viewDidLoad ()

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

Puis ajoutez la méthode suivante

func avCaptureInputPortFormatDescriptionDidChangeNotification(notification: NSNotification) {

    captureMetadataOutput.rectOfInterest = videoPreviewLayer.metadataOutputRectOfInterestForRect(scanRect)

}

Ici, vous pouvez ensuite réinitialiser la propriété rectOfInterest. Ensuite, dans votre code, vous pouvez afficher AVMetadataObject dans la fonction didOutputMetadataObjects.

var rect = videoPreviewLayer.rectForMetadataOutputRectOfInterest(YourAVMetadataObject.bounds)

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

J'ai essayé, et le rectangle était toujours dans la zone spécifiée.

26
peacer212

Dans iOS 9.3.2, j’étais capable de faire fonctionner metadataoutputRectOfInterestForRect en l’appelant juste après la méthode startRunning de AVCaptureSession:

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

Swift 4:

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

J'ai réussi à créer un effet d'avoir une région d'intérêt. J'ai essayé toutes les solutions proposées mais la région était soit CGPoint.zero, soit de taille inappropriée (après conversion des images en coordonnées 0-1). C'est en fait un hack pour ceux qui ne peuvent pas faire fonctionner la regionOfInterest et n'optimise pas la détection.

Dans:

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

J'ai le code suivant:

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

/// Après 

captureSession.startRunning()

/// Ajoute ça

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
    }
1
Mihail Salari

Pour lire un QRCode/BarCode à partir d’un petit rect (région spécifique) à partir d’une vue complète de la caméra.

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

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

Remarque:

  1. captureMetadataOutput -> AVCaptureMetadataOutput 
  2. _videoPreviewLayer -> AVCaptureVideoPreviewLayer 
  3. scanRect -> Rect où vous voulez que le QRCode soit lu.
0
Niresh S