相机扫码设置及优化

Swift

Posted by FishYan on June 21, 2021

相机扫码设置及优化

使用 AVFoundation 进行相机扫码

优点:扫码速度快,复杂二维码或条码识别率高

缺点:当同时设置条码和二维码识别时,条码识别只能在中心区域识别(范围比较小)详见:Technical Note TN2325

session input output 设置

// session.sessionPreset 建议设置为 high 能提高识别速度
if session.canSetSessionPreset(.high) {
    session.sessionPreset = .high
}
// 设置device 及 input
device = AVCaptureDevice.default(for: .video)
guard let d = device,
        let input = try? AVCaptureDeviceInput(device: d)
else {
    DispatchQueue.main.async {
        self.initCompletion?(false)
    }
    return
}
if session.canAddInput(input) { session.addInput(input) }
// 设置 output 设置为 AVCaptureMetadataOutput 
output.setMetadataObjectsDelegate(self, queue: .main)
if session.canAddOutput(photoOutput) { session.addOutput(photoOutput) }
// 设置 metadataObjectTypes
metaOutput.metadataObjectTypes = []
// metadataOutput 代理
public func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
    if metadataObjects.isEmpty { return }
    guard isScanning,
            let object = metadataObjects.first! as? AVMetadataMachineReadableCodeObject else {
        return
    }
    stop()
}
// CALayer 代理及 CAAction代理
extension ScanCaptureUtils: CAAction, CALayerDelegate {
    func action(for layer: CALayer, forKey event: String) -> CAAction? {
        CATransaction.setAnimationDuration(0)
        if event == kCAOnOrderIn || event == kCAOnOrderOut {
            return self
        }
        return nil
    }

    func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable: Any]?) {
        if event == kCAOnOrderIn {
            if skipIn {
                skipIn = false
                return
            }
            start()
        } else if event == kCAOnOrderOut {
            if skipOut {
                skipOut = true
                return
            }
            stop()
        }
    }
}

基本设置过程比较简单

注意点

  • sessionPreset: 设置 high 高质量有助于提高识别速度
  • rectOfInterest: 设置准确的 rectOfInterest 有助于提高识别速度,二维码和条码同时存在时只对条码有效。
  • rectOfInterest 的范围是(0,0,1,1),可以在设置 previewLayer frame 后用 captureDevicePointConverted 转换,

    reactOfInterest 的实际区域(top/previewLayer.height, right/previewLayer.width, height/previewLayer.height, width/previewLayer.width)

  • previewLayer 的设置应该在 viewDidLayoutSubviews 设置
  • metadataObjectTypes 设置应在添加output之后
  • metadataObjectTypes 同时设置qrcode和barcode后使barcode识别区域锁定为中心点区域,导致条码识别范围变小。详见:Technical Note TN2325。可区分场景分别设置qrcode和barcode提高条码识别速度。
  • metadataObjectTypes 条码类型众多,去除不必要的条码类型有助于加快条码识别速度,各种条码类型作用参考条码常见类型及介绍
  • 扫描结果最好自己做判断,防止结果多次回调。
  • 相机的 start 和 stop 可以在页面 onOrderIn 和 onOrderOut 时处理,优势是不用手动管理相机的开启和停止。
  • onOrderIn 和 onOrderOut 需要设置CALayer delegate 及 CAAction delegate 参考以上代码块

    注意 CALayer delegate 在 iOS12 及以下系统 deinit 后会因野指针 crash,可能在 deinit 后,又使用了 CALayer的delegate,解决方案:1.deinit时将delegate = nil,2.将delegate 指定为暂不会被释放的对象。

  • 相机的 start 和 stop 应在子线程中进行,防止卡住UI

使用 ZXing 扫码

ZXing 是使用相机的 video 模式,将每一帧拿去解码,返回结果

优点:条码识别范围更大

缺点:条码及二维码的识别效率低于 AVFoundation,复杂条码或二维码无法识别

capture = ZXCapture()
guard let _capture = self.capture else { return }
_capture.camera = _capture.back()
_capture.focusMode = .continuousAutoFocus
preview?.layer.insertSublayer(_capture.layer, at: 0)
_capture.layer.frame = UIScreen.main.bounds
_capture.delegate = self
_capture.sessionPreset = AVCaptureSession.Preset.high.rawValue
if let layer = _capture.layer as? AVCaptureVideoPreviewLayer {
    layer.connection?.videoOrientation = .portrait
}
private func updateMetaType() {
    guard let _capture = self.capture else { return }
    allcodeFormats.forEach { _capture.hints.removePossibleFormat($0) }
    let types: [ZXBarcodeFormat]
    switch scanType {
    case .all:
        types = barcodeFormats + qrcodeFormats
    case .qrcode:
        types = qrcodeFormats
    case .barcode:
        types = barcodeFormats
    case .none:
        types = []
    }
    types.forEach { _capture.hints.addPossibleFormat($0) }
}

与AVFoundation类似,设置需要设置 device,layer,delegate,sessionPreset,帮我们简化了一些设置。

注意点

  • capture?.rotation 的设置需要借助 陀螺仪 CMMotionManager 的计算,使条码横屏和竖屏下都能识别
  • CMMotionManager 在不使用时需要停止
  • 因为ZXing 有默认的扫码类型的设置,在设置扫码类型时需要先清空 ZXBarcodeFormat
  • ZXing 不支持空的扫码类型,扫码类型为空时就会默认加上默认的扫码类型。
  • ZXing 在 onOrderIn 和 onOrderOut 时会调用 startStop,运行或停止相机。额外的手动调用停止相机反而会让相机重新运行。
  • ZXing 也使用的CALayer的 delegate,iOS12及以下存在同样的野指针问题。
  • ZXing lastScannedImage 获取最后一帧图片。