1

CGImagePropertyOrientation中,

当用户手持设备纵向拍摄照片时,iOS 会CGImagePropertyOrientation.right在生成的图像文件中写入一个方向值。

在 Object Tracking in Vision (WWDC 2018) 的示例代码中,使用front camera

func exifOrientationForDeviceOrientation(_ deviceOrientation: UIDeviceOrientation = UIDevice.current.orientation) -> CGImagePropertyOrientation {
    switch deviceOrientation {
       case .portraitUpsideDown:
           return .rightMirrored
       case .landscapeLeft:
           return .downMirrored
       case .landscapeRight:
           return .upMirrored
       default:
           return .leftMirrored
    }
}

根据相机的位置,设备方向和exif方向之间有什么关系?

4

3 回答 3

1

我认为这个话题值得深入研究。无论我处理多少次,我仍然把它弄错了,并通过反复试验来解决它。这里是

(1) 根据在实时捕获中识别对象中的示例代码https://developer.apple.com/documentation/vision/recognizing_objects_in_live_capture

定义是:

public func exifOrientationFromDeviceOrientation() -> CGImagePropertyOrientation {
    let curDeviceOrientation = UIDevice.current.orientation
    let exifOrientation: CGImagePropertyOrientation

    switch curDeviceOrientation {
    case UIDeviceOrientation.portraitUpsideDown:  // Device oriented vertically, home button on the top
        exifOrientation = .left
    case UIDeviceOrientation.landscapeLeft:       // Device oriented horizontally, home button on the right
        exifOrientation = .upMirrored
    case UIDeviceOrientation.landscapeRight:      // Device oriented horizontally, home button on the left
        exifOrientation = .down
    case UIDeviceOrientation.portrait:            // Device oriented vertically, home button on the bottom
        exifOrientation = .up
    default:
        exifOrientation = .up
    }
    return exifOrientation
}

这看起来和你的帖子有点不同。所以只是说这个文件定义了它们的关系可能不会一概而论,必须有更深层次的解释,以帮助更好地理解。

(2) 在您的目标部署信息中,有一个“设备方向”部分。如果我选中“Landscape Left”并将其保持在这个支持的方向,运行上述 exifOrientationFromDeviceOrientation 的运行时调试会给你一个 .down,这意味着它是 UIDeviceOrientation.landscapeRight?!?我只是不明白为什么会有这种矛盾,而且我没有时间去挖掘,不得不继续前进。

(3) 还有另一个与方向相关的属性调用 AVCaptureVideoOrientation 用于设置视频输出方向。对于上述情况,我需要将其设置为landscapeRight,与设备方向一致,但与目标部署信息相反。这至少有一定的意义,视频方向约定最好与 uidevice 方向相同。但是,这让我在调试过程中感到非常困惑。我在 captureOutput 委托中预览了 CVImageBuffer,发现它是颠倒的!但我猜想与 exifOrientationFromDeviceOrientation 合谋,一切正常。笔记:

在一天结束时,我真的很想看到来自苹果的更好的文档,或者一些英雄出面并在博客中解释所有这些。我只是希望我所做的任何事情都能以相同的支持方向携带到其他设备上,因为我没有足够多的苹果软件来测试。

我可以在 git 中发布 POC 项目。我可能会来这里发布链接,您可以使用代码本身检查我在这里谈论的内容。

于 2019-01-08T23:10:11.257 回答
1

转换是设备方向以及相机位置(正面或背面)的函数。到目前为止,我发现的最准确的功能是这个 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
    }
  }
}

我尝试使用这种方法验证结果::

  1. 在 Xcode 11.6 中创建一个新项目

  2. 添加NSCameraUsageDescriptioninfo.plist.

  3. 用下面的代码替换 ViewController.swift。

  4. devicePositionToTest根据您要测试的内容更新到正面/背面。

  5. 替换SEARCH STRING HERE为您要扫描的一段文本。

  6. 运行应用程序,并将其指向文本,同时更改方向。

  7. 您将进行以下观察:

    • 后置摄像头:
      • .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
  }
}

于 2020-07-19T23:55:32.377 回答
0

但是该关系已在您发布的代码段中定义。

放置 iPhone 中的相机,以便在手机处于其中一种横向模式时正确定位图像。

相机不知道方向,总是按原样返回图像数据。然后将那些图像数据包裹在CGImage其中仍然没有方向但包裹在UIImage其中有方向的位置。

由于交换字节以获得正确定向的图像似乎非常浪费,因此最好添加方向数据,从中可以制作变换矩阵以正确呈现图像。还有一个mirrored版本,我相信它主要用于前置摄像头。当您打开相机应用程序并尝试自拍时,请注意,与您在拍摄的照片上看到的相比,您所看到的将被镜像。这是为了模拟镜面效果,同样的逻辑不适用于后置摄像头。

无论如何,根据设备方向,我们需要旋转接收到的CGImage内容,以便正确显示。在您发布的系统中,因此当设备为纵向时,图像应向左旋转并镜像(不知道哪个先出现,也不知道镜像的完成方式,但在文档中进行了描述)。自然倒过来再向右旋转,左或右就是左;当手机向右转动横向(我假设顺时针方向)时,图像设置被设置为相机接收但镜像。

我不确定为什么使用 mirrored 或者为什么(如果你说的是正确的)在纵向 iOS 使用属性right而 exif 使用时left,但它应该取决于这些值是如何定义的。一个系统可能会说right图像是顺时针旋转 (CW) 并且在呈现时需要逆时针旋转 (CCW)。另一个系统可能会说right意味着图像应该顺时针旋转才能正确显示,因为原件是逆时针旋转的。

我希望这能解决你的问题。

于 2018-06-14T08:01:54.100 回答