使用 AVCaptureDeviceTypeBuiltInTripleCamera 时选择合适的相机进行条形码扫描

Choosing suitable camera for barcode scanning when using AVCaptureDeviceTypeBuiltInTripleCamera

提问人:StainlessSteelRat 提问时间:11/10/2022 更新时间:7/10/2023 访问量:1371

问:

多年来,我的 iOS 应用程序中有一些条形码扫描代码。最近,用户开始抱怨它不适用于 iPhone 13 Pro。

在调查过程中,如果有的话,我似乎应该使用内置的三摄像头。这样做确实为 iPhone 13 Pro 修复了它,但随后在 iPhone 12 Pro 上破坏了它,它似乎在以前的代码中工作正常。

您应该如何为所有设备选择合适的相机?在我看来,苹果突然变得如此难以使用这个以前工作的代码,这似乎很奇怪。

这是我当前的代码。“回退”部分是代码多年来一直使用的。

     _session = [[AVCaptureSession alloc] init];
     // Must use macro camera for barcode scanning on newer devices, otherwise the image is blurry
    if (@available(iOS 13.0, *)) {
        AVCaptureDeviceDiscoverySession * discoverySession =
        [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInTripleCamera]
                                                               mediaType:AVMediaTypeVideo
                                                                position:AVCaptureDevicePositionBack];
        if (discoverySession.devices.count == 0) {
            // no BuiltInTripleCamera
            _device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
        } else {
            _device = discoverySession.devices.firstObject;
        }
    } else {
        // Fallback on earlier versions
        _device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    }
iOS Objective-C iPhone 条码扫描器

评论

1赞 bobwki 11/11/2022
我想我有一个类似的问题——“最小距离”已经从大约 3 英寸(iPh 6 和 8)和 3.75(iPh 13 Pro)增加到大约 5.75 英寸(iPhone 14 Pro)。我还没有更改任何代码。
0赞 StainlessSteelRat 11/11/2022
希望有人有一个通用的解决方案!

答:

0赞 StainlessSteelRat 11/13/2022 #1

值得庆幸的是,在reddit的帮助下,我能够弄清楚解决方案只是简单地替换

AVCaptureDeviceTypeBuiltInTripleCamera

AVCaptureDeviceTypeBuiltInWideAngleCamera
3赞 Tankista 12/22/2022 #2

公认的答案有效,但并非总是有效。由于镜头具有不同的最小焦距,因此设备更难对焦于小条形码,因为您必须将设备放得太近(在最小对焦距离之前)。这样,它永远不会自动对焦于小条形码。它曾经适用于自动对焦为 10-12 厘米的旧镜头,但较新的镜头,尤其是距离为 14 厘米的 iPhone 20 Pro 上的镜头会有问题。

解决方案是理想地使用并设置放大一点,以便条形码很好地聚焦。该值应根据输入视频属性和条形码的最小大小进行计算。AVCaptureDeviceTypeBuiltInWideAngleCameravideoZoomFactorAVCaptureDevice

有关详细信息,请参阅此 WWDC 2019 视频,其中 https://developer.apple.com/videos/play/wwdc2021/10047/?time=133 解决了这个问题。

这是在适合我的设备上设置缩放因子的类的实现。您可以实例化此类,提供设备实例,并在即将提交捕获会话配置之前调用。applyAutomaticZoomFactorIfNeeded()

///
/// Calling this method will automatically zoom the device to increase minimum focus distance. This distance appears to be problematic
/// when scanning barcodes too small or if a device's minimum focus distance is too large (like on iPhone 14 Pro and Max - 20cm, iPhone 13 Pro - 15 cm, older iPhones 12 or less.). By zooming
/// the input the device will be able to focus on a preview and complete the scan more easily.
///
/// - See https://developer.apple.com/videos/play/wwdc2021/10047/?time=133 for more detailed explanation and
/// - See https://developer.apple.com/documentation/avfoundation/capture_setup/avcambarcode_detecting_barcodes_and_faces
/// for implementation instructions.
///
@available(iOS 15.0, *)
final class DeviceAutomaticVideoZoomFactor {
   
    enum Errors : Error {
        case minimumFocusDistanceUnknown
        case deviceLockFailed
    }
    
    private let device: AVCaptureDevice
    private let minimumCodeSize: Float
    
    init(device: AVCaptureDevice, minimumCodeSize: Float) {
        self.device = device
        self.minimumCodeSize = minimumCodeSize
    }
    
    ///
    /// Optimize the user experience for scanning QR codes down to smaller sizes (determined by `minimumCodeSize`, for example 2x2 cm).
    /// When scanning a QR code of that size, the user may need to get closer than the camera's minimum focus distance to fill the rect of interest.
    /// To have the QR code both fill the rect and still be in focus, we may need to apply some zoom.
    ///
    func applyAutomaticZoomFactorIfNeeded() throws {
        
        let deviceMinimumFocusDistance = Float(self.device.minimumFocusDistance)
        guard deviceMinimumFocusDistance != -1 else {
            throw Errors.minimumFocusDistanceUnknown
        }
        
        Logger.logIfStaging("Video Zoom Factor", "using device: \(self.device)")
        
        Logger.logIfStaging("Video Zoom Factor", "device minimum focus distance: \(deviceMinimumFocusDistance)")
        
        /*
            Set an inital square rect of interest that is 100% of the view's shortest side.
            This means that the region of interest will appear in the same spot regardless
            of whether the app starts in portrait or landscape.
        */
        let formatDimensions = CMVideoFormatDescriptionGetDimensions(self.device.activeFormat.formatDescription)
        let rectOfInterestWidth = Double(formatDimensions.height) / Double(formatDimensions.width)
        
        let deviceFieldOfView = self.device.activeFormat.videoFieldOfView
        let minimumSubjectDistanceForCode = self.minimumSubjectDistanceForCode(fieldOfView: deviceFieldOfView,
                                                                               minimumCodeSize: self.minimumCodeSize,
                                                                               previewFillPercentage: Float(rectOfInterestWidth))
        
        Logger.logIfStaging("Video Zoom Factor", "minimum subject distance: \(minimumSubjectDistanceForCode)")
        
        guard minimumSubjectDistanceForCode < deviceMinimumFocusDistance else {
            return
        }
        
        let zoomFactor = deviceMinimumFocusDistance / minimumSubjectDistanceForCode
        
        Logger.logIfStaging("Video Zoom Factor", "computed zoom factor: \(zoomFactor)")
        
        try self.device.lockForConfiguration()
        self.device.videoZoomFactor = CGFloat(zoomFactor)
        self.device.unlockForConfiguration()
        
        Logger.logIfStaging("Video Zoom Factor", "applied zoom factor: \(self.device.videoZoomFactor)")
    }
    
    private func minimumSubjectDistanceForCode(fieldOfView: Float,
                                               minimumCodeSize: Float,
                                               previewFillPercentage: Float) -> Float {
        /*
            Given the camera horizontal field of view, we can compute the distance (mm) to make a code
            of minimumCodeSize (mm) fill the previewFillPercentage.
         */
        let radians = self.degreesToRadians(fieldOfView / 2)
        let filledCodeSize = minimumCodeSize / previewFillPercentage
        return filledCodeSize / tan(radians)
    }
    
    private func degreesToRadians(_ degrees: Float) -> Float {
        return degrees * Float.pi / 180
    }
}

评论

0赞 StainlessSteelRat 5/22/2023
因此,我开始收到有关它不适用于某些用户的报告,然后我尝试了您的解决方案。它修复了其中一些问题,但对于 iPhone 14 pro,它在近距离看起来仍然非常模糊(但在相机很远时可以工作)。有什么想法吗?我正在使用它“minimumCodeSize:20.7264f”作为条形码的最小尺寸(.816英寸)'''
0赞 StainlessSteelRat 5/23/2023
将其更改为 40 并将其放在会话结束之前,并且似乎效果更好。
0赞 Buddhisthead 7/10/2023
这是最好的答案。
0赞 Buddhisthead 7/10/2023 #3

以下是 @tankista 的优秀答案的 ObjectiveC 版本:

以WWDC视频 https://developer.apple.com/videos/play/wwdc2021/10047/?time=133 为蓝本。另请参阅他们的 BarCode 示例应用程序:https://developer.apple.com/documentation/avfoundation/capture_setup/avcambarcode_detecting_barcodes_and_faces?language=objc

静态 CGFloat DegreesToRadians(CGFloat degrees) {返回度 * M_PI / 180;};

if (@available(iOS 15.0, *)) {
    AVCaptureDevice *videoDevice = self.videoCamera.inputCamera; // you'll replace this RHS expression with your video device
    int deviceFieldOfView = videoDevice.activeFormat.videoFieldOfView;
    float previewFillPercentage = 0.8; // 0..1, target object will fill 80% of preview window
    float minimumTargetObjectSize = 15.0 * 10.0; // min width of target object in mm (here, it's 15 cm)
    float radians = DegreesToRadians(deviceFieldOfView);
    float filledTargetObjectSize = minimumTargetObjectSize / previewFillPercentage;
    int minimumSubjectDistance = filledTargetObjectSize / tan(radians / 2.0); // Field of View equation

    NSInteger deviceMinimumFocusDistance = videoDevice.minimumFocusDistance;
    
    if (minimumSubjectDistance < deviceMinimumFocusDistance) {
        float zoomFactor = deviceMinimumFocusDistance / minimumSubjectDistance;
        [videoDevice lockForConfiguration: NULL];
        [videoDevice setVideoZoomFactor:zoomFactor];
        [videoDevice unlockForConfiguration];
    }
} else {
    // No API to help here, but these phones won't have macro lenses
}