DeviceOrientation和ExifOrientation的关系是什么?

时间:2018-06-14 07:16:25

标签: ios

CGImagePropertyOrientation

  

当用户以纵向方向握住设备时拍摄照片时,iOS会在结果图像文件中写入CGImagePropertyOrientation.right的方向值。

使用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方向之间的关系是什么?

3 个答案:

答案 0 :(得分: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)在目标部署信息中,有一个“设备方向”部分。如果我选中“左横向”并将其保持在此受支持的方向,则运行上述exifOrientationFromDeviceOrientation的运行时调试将给您一个.down,这意味着它是UIDeviceOrientation.landscapeRight?!?我只是不明白为什么会出现矛盾,而且我没有时间去挖掘和继续前进。

(3)设置视频输出方向时,还有另一个与方向相关的属性调用AVCaptureVideoOrientation。对于上述情况,我需要将其设置为landscapeRight,与设备方向一致,但与目标部署信息相反。至少在某种意义上,视频方向约定最好与uidevice方向相同。但是,这在调试过程中使我感到困惑。我在captureOutput委托中预览了CVImageBuffer,发现它已经颠倒了!但是我想与exifOrientationFromDeviceOrientation一起串谋,一切都正常。注意:我部署了自己的yolo v2对象检测网络,该网络经过训练并内置于keras中(已用coremltools转换),并试图在iPad上绘制边框,而该iPad只想在一个方向上工作(我认为这对我来说将是另一个繁琐的任务如果需要在所有方向上工作)。

最终,我真的很想看看苹果提供的更好的文档,或者有些英雄挺身而出,并在博客中解释所有这些内容。我只是希望我所做的一切都能以相同的支持方向携带到其他设备上,因为我没有足够多的苹果软件来测试。

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

答案 1 :(得分:1)

转换取决于设备方向以及摄像头位置(正面或背面)。到目前为止,我发现的最准确的功能是this gist(或此other answer),它对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. NSCameraUsageDescription添加到info.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
  }
}

答案 2 :(得分:0)

但是这个关系已经在你发布的代码片段中定义了。

放置iPhone中的相机,以便在其中一种横向模式下保持手机时图像方向正确。

相机不知道方向,并且始终按原样返回图像数据。然后将这些图像数据包裹在CGImage中,该UIImage仍然没有方向,但包含在具有方向的mirrored中。

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

无论如何,根据设备方向,我们需要旋转收到的right,以便正确显示。在您发布的系统中,当设备为纵向时,图像应向左旋转并镜像(不知道哪个先到,镜像完成的方式,但在文档中有描述)。当然,倒置然后向右旋转,左或右是剩下的;当手机向右转为横向时(我假设顺时针方向),图像设置被设置为相机接收但镜像。

我不确定为什么使用镜像或为什么(如果你说的是正确的)在肖像iOS中使用属性left而exif使用right但它应该取决于如何定义这些值。一个系统可能会说right表示图像顺时针旋转(CW)并且在呈现时需要逆时针旋转(CCW)。另一个系统可能会说private void changeText(){ TextView textNotificationView = (TextView) findViewById(R.id.textNotificationView); textNotificationView.setText(R.string.textGotNotification); } 意味着图像应该被CW旋转才能正确显示,因为原件是CCW旋转的。

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