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

问题描述 投票:0回答:4

我的 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 barcode-scanner
4个回答
4
投票

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

解决方案是理想地使用

AVCaptureDeviceTypeBuiltInWideAngleCamera
并在
videoZoomFactor
上设置
AVCaptureDevice
来放大一点,这样条形码就会很好地聚焦。该值应根据输入视频属性和条形码的最小尺寸计算。

有关详细信息,请参阅 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
投票

谢天谢地,在reddit的帮助下,我发现解决方案就是简单地替换

AVCaptureDeviceTypeBuiltInTripleCamera

AVCaptureDeviceTypeBuiltInWideAngleCamera

0
投票

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

以 WWDC 视频为蓝本 https://developer.apple.com/videos/play/wwdc2021/10047/?time=133。另请参阅此处的条形码示例应用程序: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
}

0
投票

因为当您使用BuiltInTripleCamera作为设备类型时,我们的应用程序将使用超宽相机作为活动的主要设备。但 iPhone 12 Pro 不支持微距摄影,因此该设备无法正常工作。

您应该首先设置 device.videoZoomFactor = device.virtualDeviceSwitchOverVideoZoomFactors.。 让广角相机可以成为主动的主要设备。并让 iPhone 选择相机自动。

     guard let virtualDeviceSwitchOverVideoZoomFactor = input.device.virtualDeviceSwitchOverVideoZoomFactors.first as? CGFloat else {
            return
        }
        try? input.device.lockForConfiguration()
        input.device.videoZoomFactor = virtualDeviceSwitchOverVideoZoomFactor
        input.device.unlockForConfiguration()
© www.soinside.com 2019 - 2024. All rights reserved.