提问人:StainlessSteelRat 提问时间:11/10/2022 更新时间:7/10/2023 访问量:1371
使用 AVCaptureDeviceTypeBuiltInTripleCamera 时选择合适的相机进行条形码扫描
Choosing suitable camera for barcode scanning when using AVCaptureDeviceTypeBuiltInTripleCamera
问:
多年来,我的 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];
}
答:
值得庆幸的是,在reddit的帮助下,我能够弄清楚解决方案只是简单地替换
AVCaptureDeviceTypeBuiltInTripleCamera
跟
AVCaptureDeviceTypeBuiltInWideAngleCamera
公认的答案有效,但并非总是有效。由于镜头具有不同的最小焦距,因此设备更难对焦于小条形码,因为您必须将设备放得太近(在最小对焦距离之前)。这样,它永远不会自动对焦于小条形码。它曾经适用于自动对焦为 10-12 厘米的旧镜头,但较新的镜头,尤其是距离为 14 厘米的 iPhone 20 Pro 上的镜头会有问题。
解决方案是理想地使用并设置放大一点,以便条形码很好地聚焦。该值应根据输入视频属性和条形码的最小大小进行计算。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
}
}
评论
以下是 @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
}
评论