2

I have an app that uses a CGRect(x: 0, y: 0, width: 335, height: 150) to show the camera for barcode scanning. However when presented a barcode off camera (not in the CGRect) will get scanned. How can I limit the area for scanning to the CGRect in my preview layer? This is using Vision.

let captureSession = AVCaptureSession()
var previewLayer: AVCaptureVideoPreviewLayer!
var activeInput: AVCaptureDeviceInput!

lazy var detectBarcodeRequest = VNDetectBarcodesRequest { request, error in
    guard error == nil else {
        
        print("Barcode Error: \(String(describing: error?.localizedDescription))")
    
        return
    }
    
    self.processBarCode(request)
}

override func viewDidLoad() {
    super.viewDidLoad()
    
    setupSession()
    setupPreview()
    startSession()
}

func setupSession() {
    
    guard let camera = AVCaptureDevice.default(for: .video) else {
        return
        
    }
    do {
        let videoInput = try AVCaptureDeviceInput(device: camera)
        
        for input in [videoInput] {
            if captureSession.canAddInput(input) {
                captureSession.addInput(input)
            }
        }
        activeInput = videoInput
    } catch {
        print("Error setting device input: \(error)")
        return
    }
    
    let captureOutput = AVCaptureVideoDataOutput()
    
    captureOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32BGRA)]
    captureOutput.setSampleBufferDelegate(self, queue: DispatchQueue.global(qos: DispatchQoS.QoSClass.default))
    
    captureSession.addOutput(captureOutput)

}

func setupPreview() {
    
    previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
    
    previewLayer.videoGravity = .resizeAspectFill
    previewLayer.connection?.videoOrientation = .portrait
    previewLayer.frame = CGRect(x: 0, y: 0, width: 335, height: 150)
    
    view.layer.insertSublayer(previewLayer, at: 0)

}//setupPreview

func startSession() {
    if !captureSession.isRunning {
        DispatchQueue.global(qos: .default).async { [weak self] in
            self?.captureSession.startRunning()
        }
    }
}

// MARK: - processBarCode
func processBarCode(_ request: VNRequest) {
    
    
    DispatchQueue.main.async {
        guard let results = request.results as? [VNBarcodeObservation] else {
            return
        }
        
        if let payload = results.first?.payloadStringValue, let symbology = results.first?.symbology {
            print("payload is \(payload) \(symbology) ")
            
        }
        
    }
    
}//processBarCode

Edit:

// MARK: - AVCaptureVideoDataOutputSampleBufferDelegate
extension CameraViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
    
    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        
        guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
        
        let imageRequestHandler = VNImageRequestHandler(
            cvPixelBuffer: pixelBuffer,
            orientation: .up)
        
        do {
            try imageRequestHandler.perform([detectBarcodeRequest])
        } catch {
            print(error)
        }
    }//captureOutput
    
}//extension
Paul S.
  • 1,342
  • 4
  • 22
  • 43

3 Answers3

2
extension AVCaptureVideoPreviewLayer {
    func rectOfInterestConverted(parentRect: CGRect, fromLayerRect: CGRect) -> CGRect {
        let parentWidth = parentRect.width
        let parentHeight = parentRect.height
        let newX = (parentWidth - fromLayerRect.maxX)/parentWidth
        let newY = 1 - (parentHeight - fromLayerRect.minY)/parentHeight
        let width = 1 - (fromLayerRect.minX/parentWidth + newX)
        let height = (fromLayerRect.maxY/parentHeight) - newY

        return CGRect(x: newX, y: newY, width: width, height: height)
    }
}

Usage:

if let rect = videoPreviewLayer?.rectOfInterestConverted(parentRect: self.view.frame, fromLayerRect: scanAreaView.frame) {
    captureMetadataOutput.rectOfInterest = rect
}
  • 1
    To make this answer as helpful as possible, please add explanation what exactly you doing and why. – ManWithBear Sep 29 '21 at 19:38
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Sep 30 '21 at 00:10
2

You can use a property regionOfInterest of VNDetectBarcodesRequest to setup a detection region. The default value is { { 0, 0 }, { 1, 1 } } according to the Apple documentation

1

In func captureOutput(_:didOutput:from:) you most likely passing whole image into VNImageRequestHandler.
Instead you need to crop image to your visible rect.
Another approach would be lock Vision to only visible part of image via regionOfInterest as @HurricaneOnTheMoon proposed below.

ManWithBear
  • 2,787
  • 15
  • 27
  • I've added the code for captureOutput, I seemed to have forgotten it. How would I crop the image? – Paul S. Oct 01 '21 at 13:29