转换是设备方向以及相机位置(正面或背面)的函数。到目前为止,我发现的最准确的功能是这个 gist(或这个其他答案),它非常适合 Vision 框架。这是相同要点的略微修改版本,保留相同的逻辑:
extension CGImagePropertyOrientation {
init(isUsingFrontFacingCamera: Bool, deviceOrientation: UIDeviceOrientation = UIDevice.current.orientation) {
switch deviceOrientation {
case .portrait:
self = .right
case .portraitUpsideDown:
self = .left
case .landscapeLeft:
self = isUsingFrontFacingCamera ? .down : .up
case .landscapeRight:
self = isUsingFrontFacingCamera ? .up : .down
default:
self = .right
}
}
}
我尝试使用这种方法验证结果::
在 Xcode 11.6 中创建一个新项目
添加NSCameraUsageDescription
到info.plist
.
用下面的代码替换 ViewController.swift。
devicePositionToTest
根据您要测试的内容更新到正面/背面。
替换SEARCH STRING HERE
为您要扫描的一段文本。
运行应用程序,并将其指向文本,同时更改方向。
您将进行以下观察:
- 后置摄像头:
.portrait
:.right
两者.up
都有效。
.landscapeRight
:.down
和.right
。
.portraitUpsideDown
:.left
和.down
。
.landscapeLeft
:.up
和.left
。
- 前置摄像头:
.portrait
:.right
和.up
。
.landscapeRight
:.up
和.left
。
.portraitUpsideDown
:.left
和.down
。
.landscapeLeft
:.down
和.right
。
请注意,无论相机/设备方向如何,总是有两种不同的方向可以工作。这是因为在纵向 + 后置摄像头方向时,从左到右的文本可以正常识别(如您所料),但从上到下的文本也会被识别。
但是,上面列出的第一个方向比第二个更准确。如果你在每一个上使用第二列,你会得到更多的垃圾数据。您可以通过打印allStrings
下面的全部结果来验证这一点。
请注意,这仅针对视觉框架进行了测试。如果您将样本缓冲区用于其他用途,或者相机配置不同,则可能需要不同的转换函数。
import AVFoundation
import UIKit
import Vision
let devicePositionToTest = AVCaptureDevice.Position.back
let expectedString = "SEARCH STRING HERE"
class ViewController: UIViewController {
let captureSession = AVCaptureSession()
override func viewDidLoad() {
super.viewDidLoad()
// 1. Set up input
let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: devicePositionToTest)!
if device.isFocusModeSupported(.continuousAutoFocus) {
try! device.lockForConfiguration()
device.focusMode = .continuousAutoFocus
device.unlockForConfiguration()
}
let input = try! AVCaptureDeviceInput(device: device)
captureSession.addInput(input)
// 2. Set up output
let output = AVCaptureVideoDataOutput()
output.alwaysDiscardsLateVideoFrames = true
output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "com.example"))
captureSession.addOutput(output)
// 3. Set up connection
let connection = output.connection(with: .video)!
assert(connection.isCameraIntrinsicMatrixDeliverySupported)
connection.isCameraIntrinsicMatrixDeliveryEnabled = true
let previewView = CaptureVideoPreviewView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
previewView.videoPreviewLayer.videoGravity = .resizeAspect
previewView.videoPreviewLayer.session = captureSession
view.addSubview(previewView)
captureSession.startRunning()
}
}
extension ViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
let cameraIntrinsicData = CMGetAttachment(sampleBuffer, key: kCMSampleBufferAttachmentKey_CameraIntrinsicMatrix, attachmentModeOut: nil)!
let options: [VNImageOption: Any] = [.cameraIntrinsics: cameraIntrinsicData]
let allCGImageOrientations: [CGImagePropertyOrientation] = [.up, .upMirrored, .down, .downMirrored, .leftMirrored, .right, .rightMirrored, .left]
allCGImageOrientations.forEach { orientation in
let imageRequestHandler = VNImageRequestHandler(
cvPixelBuffer: pixelBuffer,
orientation: orientation,
options: options)
let request = VNRecognizeTextRequest { value, error in
let observations = value.results as! [VNRecognizedTextObservation]
let allStrings = observations.compactMap { $0.topCandidates(1).first?.string.lowercased() }.joined(separator: " ")
if allStrings.contains(expectedString) {
// FOUND MATCH. deviceOrientation: @UIDevice.current.orientation@. exifOrientation: @orientation@.
print("FOUND MATCH. deviceOrientation: \(UIDevice.current.orientation). exifOrientation: \(orientation)")
}
}
request.recognitionLevel = .accurate
request.usesLanguageCorrection = true
try! imageRequestHandler.perform([request])
}
}
}
class CaptureVideoPreviewView: UIView {
override class var layerClass: AnyClass {
return AVCaptureVideoPreviewLayer.self
}
var videoPreviewLayer: AVCaptureVideoPreviewLayer {
layer as! AVCaptureVideoPreviewLayer
}
}