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

5

我已经在我的iOS应用程序中使用了一些条形码扫描代码多年了。最近,用户开始抱怨它无法与iPhone 13 Pro配合使用。

调查期间,发现如果可用的话,应该使用内置的三摄像头。这样做确实解决了iPhone 13 Pro的问题,但随后破坏了iPhone 12 Pro,而iPhone 12 Pro似乎在先前的代码中表现良好。

你应该如何为所有设备选择适当的相机?对我来说,苹果公司突然让这个之前可以工作的代码变得如此困难,这似乎很奇怪。

这是我当前的代码。"fallback"部分是代码多年来一直使用的部分。

     _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];
    }

1
我认为我有一个类似的问题 - “最小距离”已经从iPhone 6和8的约3英寸(7.62厘米)以及iPh 13 Pro的3.75英寸(9.52厘米)增加到iPhone 14 Pro的约5.75英寸(14.61厘米)。 我还没有更改任何代码。 - bobwki
希望有人有通用的解决方案! - StainlessSteelRat
3个回答

3
接受的答案有时候有效,但并非总是如此。由于镜头具有不同的最小对焦距离,因此在小条形码上进行对焦更加困难,因为您必须将设备放得太近(在最小对焦距离之前)。这样它永远无法对小条形码进行自动对焦。在旧镜头上工作时,自动对焦距离为10-12厘米,而新镜头(特别是iPhone 14 Pro上的镜头)的距离为20厘米,将会出现问题。
解决方案是使用理想情况下的AVCaptureDeviceTypeBuiltInWideAngleCamera,并在AVCaptureDevice上设置videoZoomFactor来稍微放大一点,以便条形码能够被很好地对焦。该值应基于输入视频属性和条形码的最小尺寸计算。
有关详细信息,请参阅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
    }
}

有趣。我还没有收到用户仍然遇到问题的报告,但我会留意并记住你的解决方案!我最初确实认为我可能需要调整缩放因子,所以这很有道理。 - StainlessSteelRat
所以我开始收到一些用户报告说它对某些用户不起作用,然后我尝试了你的解决方案。它修复了其中一些问题,但对于 iPhone 14 pro 来说,当靠近时仍然看起来非常模糊(但在相机远离时可以工作)。有什么想法吗?我正在使用“minimumCodeSize:20.7264f”作为条形码的最小大小(0.816英寸)。 - StainlessSteelRat
在会话最终确定之前,将其更改为40,并且似乎效果更好。 - StainlessSteelRat
这是最好的答案。 - Buddhisthead

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 static CGFloat DegreesToRadians(CGFloat degrees) {return 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
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接