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
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:
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".
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.
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
captureSession?.startRunning()
let scanRect = CGRect(x: 0, y: 0, width: 100, height: 100)
let rectOfInterest = layer.metadataOutputRectConverted(fromLayerRect: scanRect)
metaDataOutput.rectOfInterest = rectOfInterest
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
}
/// 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
}
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:
captureMetadataOutput
-> AVCaptureMetadataOutput _videoPreviewLayer
-> AVCaptureVideoPreviewLayer scanRect
-> Rect où vous voulez que le QRCode soit lu.