我的 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];
}
接受的答案有效,但并非总是有效。由于镜头具有不同的最小焦距,因此设备更难聚焦于小条形码,因为您必须将设备放得太近(在最小焦距之前)。这样它就永远不会自动聚焦于小条形码。它曾经适用于自动对焦为 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
}
}
谢天谢地,在reddit的帮助下,我发现解决方案就是简单地替换
AVCaptureDeviceTypeBuiltInTripleCamera
与
AVCaptureDeviceTypeBuiltInWideAngleCamera
这是 @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
}
因为当您使用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()