【问题标题】:How to generate an UIImage from AVCapturePhoto with correct orientation?如何从具有正确方向的 AVCapturePhoto 生成 UIImage?
【发布时间】:2017-10-20 15:34:24
【问题描述】:

我正在调用AVFoundation 的委托方法来处理照片捕获,但是我很难将它生成的AVCapturePhoto 转换为具有正确方向的UIImage。虽然下面的例程是成功的,但我总是得到一个右向的UIImage (UIImage.imageOrientation = 3)。使用UIImage(data: image) 时我无法提供方向,尝试首先使用photo.cgImageRepresentation()?.takeRetainedValue() 也无济于事。请帮忙。

图像方向在此处至关重要,因为生成的图像将被输入到 Vision Framework 工作流程中。

func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
    // capture image finished
    print("Image captured.")
    if let imageData = photo.fileDataRepresentation() {
        if let uiImage = UIImage(data: imageData){
            // do stuff to UIImage
        }
    }
}

更新 1: 阅读 Apple 的Photo Capture Programming Guide(iOS11 已过时),我确实设法找到了我做错的一件事:

  1. 在每次捕获调用 (self.capturePhotoOutput.capturePhoto) 时,必须建立与 PhotoOutput 对象的连接并更新其方向以匹配拍摄照片时设备的方向。为此,我创建了UIDeviceOrientation 的扩展,并在我创建的snapPhoto() 函数上使用它来调用捕获例程并等待didFinishProcessingPhoto 委托方法被执行。我添加了代码的快照,因为这里的代码示例分隔符似乎没有正确显示它们。

更新 2 GitHub 上完整项目的链接:https://github.com/agu3rra/Out-Loud

【问题讨论】:

  • 很有趣,可能还有bug to file。如果您将该数据写入文件并使用基于文件的UIImage 初始化程序,它似乎也忽略了方向吗?
  • 在 snapPhoto() 例程(更新 1)上添加连接调用后,它不再忽略方向,但它在我创建的 UIImage 上返回不正确的方向值。
  • 关于您的定向扩展。 Apple 已发布更新的 AVCam。有一个类似的扩展。 extension UIDeviceOrientation { var videoOrientation: AVCaptureVideoOrientation? { switch self { case .portrait: return .portrait case .portraitUpsideDown: return .portraitUpsideDown case .landscapeLeft: return .landscapeRight case .landscapeRight: return .landscapeLeft default: return nil } } }

标签: avfoundation ios11 avcapturesession swift4


【解决方案1】:

最终更新: 我对该应用进行了一些实验,得出以下结论:

  1. kCGImagePropertyOrientation 似乎不会影响应用程序内捕获图像的方向,如果您每次要调用capturePhoto 方法时更新photoOutput 连接,它只会随设备方向而变化。所以:

    func snapPhoto() {
        // prepare and initiate image capture routine
    
        // if I leave the next 4 lines commented, the intented orientation of the image on display will be 6 (right top) - kCGImagePropertyOrientation
        let deviceOrientation = UIDevice.current.orientation // retrieve current orientation from the device
        guard let photoOutputConnection = capturePhotoOutput.connection(with: AVMediaType.video) else {fatalError("Unable to establish input>output connection")}// setup a connection that manages input > output
        guard let videoOrientation = deviceOrientation.getAVCaptureVideoOrientationFromDevice() else {return}
        photoOutputConnection.videoOrientation = videoOrientation // update photo's output connection to match device's orientation
    
        let photoSettings = AVCapturePhotoSettings()
        photoSettings.isAutoStillImageStabilizationEnabled = true
        photoSettings.isHighResolutionPhotoEnabled = true
        photoSettings.flashMode = .auto
        self.capturePhotoOutput.capturePhoto(with: photoSettings, delegate: self) // trigger image capture. It appears to work only if the capture session is running.
    }
    
  2. 在调试器上查看生成的图像向我展示了它们是如何生成的,因此我可以推断出所需的旋转 (UIImageOrientation) 以使其垂直显示。换句话说:更新UIImageOrientation 告诉您应该如何旋转图像,以便您以正确的方向看到它。于是我来到了下表:

  3. 我不得不将我的 UIDeviceOrientation 扩展更新为一种相当不直观的形式:

    extension UIDeviceOrientation {
        func getUIImageOrientationFromDevice() -> UIImageOrientation {
            // return CGImagePropertyOrientation based on Device Orientation
            // This extented function has been determined based on experimentation with how an UIImage gets displayed.
            switch self {
            case UIDeviceOrientation.portrait, .faceUp: return UIImageOrientation.right
            case UIDeviceOrientation.portraitUpsideDown, .faceDown: return UIImageOrientation.left
            case UIDeviceOrientation.landscapeLeft: return UIImageOrientation.up // this is the base orientation
            case UIDeviceOrientation.landscapeRight: return UIImageOrientation.down
            case UIDeviceOrientation.unknown: return UIImageOrientation.up
            }
        }
    }
    
  4. 这就是我的最终委托方法现在的样子。它以预期的方向显示图像。

    func photoOutput(_ output: AVCapturePhotoOutput,
                                     didFinishProcessingPhoto photo: AVCapturePhoto,
                                     error: Error?)
    {
        // capture image finished
        print("Image captured.")
    
        let photoMetadata = photo.metadata
        // Returns corresponting NSCFNumber. It seems to specify the origin of the image
        //                print("Metadata orientation: ",photoMetadata["Orientation"])
    
        // Returns corresponting NSCFNumber. It seems to specify the origin of the image
        print("Metadata orientation with key: ",photoMetadata[String(kCGImagePropertyOrientation)] as Any)
    
        guard let imageData = photo.fileDataRepresentation() else {
            print("Error while generating image from photo capture data.");
            self.lastPhoto = nil; self.controller.goToProcessing();
            return
    
        }
    
        guard let uiImage = UIImage(data: imageData) else {
            print("Unable to generate UIImage from image data.");
            self.lastPhoto = nil; self.controller.goToProcessing();
            return
    
        }
    
        // generate a corresponding CGImage
        guard let cgImage = uiImage.cgImage else {
            print("Error generating CGImage");self.lastPhoto=nil;return
    
        }
    
        guard let deviceOrientationOnCapture = self.deviceOrientationOnCapture else {
            print("Error retrieving orientation on capture");self.lastPhoto=nil;
            return
    
        }
    
        self.lastPhoto = UIImage(cgImage: cgImage, scale: 1.0, orientation: deviceOrientationOnCapture.getUIImageOrientationFromDevice())
    
        print(self.lastPhoto)
        print("UIImage generated. Orientation:(self.lastPhoto.imageOrientation.rawValue)")
        self.controller.goToProcessing()
    }
    
    
    func photoOutput(_ output: AVCapturePhotoOutput, 
                       willBeginCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings) 
                       {
        print("Just about to take a photo.")
        // get device orientation on capture
        self.deviceOrientationOnCapture = UIDevice.current.orientation
        print("Device orientation: \(self.deviceOrientationOnCapture.rawValue)")
    }
    

【讨论】:

  • 什么是getAVCaptureVideoOrientationFromDevice
  • 依赖您设置的deviceOrientationOnCapture 属性并不是一个好主意,因为委托可以在该值已经更改之前或之后返回(例如连续拍摄多张照片)。试试我的答案,它依赖于从实际委托调用返回的元数据,而不是 stackoverflow.com/a/54225440/753964
  • @AntonBelousov 他在回答中省略了它,但可以在他的仓库中找到它:github.com/agu3rra/Out-Loud
  • 如果 iPhone 自动旋转关闭,我们不能依赖 UIDevice.current.orientation,因为它总是返回 .portrait,即使用户在横向拍摄照片。
【解决方案2】:

我已经成功了:

func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {

        let cgImage = photo.cgImageRepresentation()!.takeRetainedValue()
        let orientation = photo.metadata[kCGImagePropertyOrientation as String] as! NSNumber
        let uiOrientation = UIImage.Orientation(rawValue: orientation.intValue)!
        let image = UIImage(cgImage: cgImage, scale: 1, orientation: uiOrientation)

}

它基于 Apple 在其文档中提到的内容:

每次访问此方法时,AVCapturePhoto 都会生成一个新的 CGImageRef。当由压缩容器(如 HEIC)支持时, CGImageRepresentation 根据需要延迟解码。支持时 一种未压缩的格式,例如 BGRA,它被复制到一个单独的 后备缓冲区的生命周期与 AV 捕捉照片。对于一个 12 兆像素的图像,一个 BGRA CGImage 代表 每次调用约 48 兆字节。如果您只打算将 CGImage 用于 屏幕渲染,请改用 previewCGImageRepresentation。 请注意,CGImageRef 的物理旋转与 主图。未应用 Exif 方向。如果你想 使用 UIImage 时应用旋转,您可以通过查询来实现 照片的元数据[kCGImagePropertyOrientation] 值,并通过 它作为 +[UIImage 的方向参数 imageWithCGImage:scale:orientation:]。 RAW 图像总是返回一个 CGImageRepresentation of nil。如果你想从一个 CGImageRef RAW图像,使用CoreImage框架中的CIRAWFilter。

【讨论】:

【解决方案3】:

要创建具有正确方向的图像,我们需要在初始化图像时输入正确的UIImage.Orientation

最好使用从 photoOutput 委托返回的CGImagePropertyOrientation 来获取拍摄照片时相机会话所处的确切方向。这里唯一的问题是,虽然UIImage.OrientationCGImagePropertyOrientation 之间的枚举值相同,但原始值却不同。 Apple 建议使用简单的映射来解决此问题。

https://developer.apple.com/documentation/imageio/cgimagepropertyorientation

这是我的实现:

AVCapturePhotoCaptureDelegate

func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        if let _ = error {
            // Handle Error
        } else if let cgImageRepresentation = photo.cgImageRepresentation(),
            let orientationInt = photo.metadata[String(kCGImagePropertyOrientation)] as? UInt32,
            let imageOrientation = UIImage.Orientation.orientation(fromCGOrientationRaw: orientationInt) {

            // Create image with proper orientation
            let cgImage = cgImageRepresentation.takeUnretainedValue()
            let image = UIImage(cgImage: cgImage,
                                scale: 1,
                                orientation: imageOrientation)
        }
    }

映射扩展

extension UIImage.Orientation {

    init(_ cgOrientation: CGImagePropertyOrientation) {
        // we need to map with enum values becuase raw values do not match
        switch cgOrientation {
        case .up: self = .up
        case .upMirrored: self = .upMirrored
        case .down: self = .down
        case .downMirrored: self = .downMirrored
        case .left: self = .left
        case .leftMirrored: self = .leftMirrored
        case .right: self = .right
        case .rightMirrored: self = .rightMirrored
        }
    }


    /// Returns a UIImage.Orientation based on the matching cgOrientation raw value
    static func orientation(fromCGOrientationRaw cgOrientationRaw: UInt32) -> UIImage.Orientation? {
        var orientation: UIImage.Orientation?
        if let cgOrientation = CGImagePropertyOrientation(rawValue: cgOrientationRaw) {
            orientation = UIImage.Orientation(cgOrientation)
        } else {
            orientation = nil // only hit if improper cgOrientation is passed
        }
        return orientation
    }
}

【讨论】:

  • 很确定这在旋转锁定打开时不起作用。但是其他技术也不起作用,您必须启用陀螺仪并跟踪设备本身。真是个黑客。最近烧了一个DTS事件,想看看有没有别的办法,苹果笑死我了。
  • 不起作用:orientationInt 总是返回相同的值
【解决方案4】:

AVCapturePhoto 中,我很确定你会找到一个metadata 对象,也称为CGImageProperties
在其中您会找到用于方向的 EXIF 字典,下一步就是获取方向并根据它创建图像。
我没有使用AVCapturePhotoOutput 的经验,但我有一些使用旧方法的经验。
请注意,EXIF 字典在 UIImageOrientation 中的映射方式不同。
这是我很久以前写的article,但主要原理仍然有效。
这个question 将向您指出一些实现,它也很老了,我很确定他们在最新版本中发布了更简单的 API,但它仍然会引导您解决问题。

【讨论】:

    【解决方案5】:

    Andre 提供的更新扩展,适用于 Swift 4.2:

    import Foundation
    import UIKit
    
    extension UIDeviceOrientation {
        var imageOrientation: UIImage.Orientation {
            switch self {
            case .portrait, .faceUp:                return .right
            case .portraitUpsideDown, .faceDown:    return .left
            case .landscapeLeft:                    return .up
            case .landscapeRight:                   return .down
            case .unknown:                          return .up
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-30
      相关资源
      最近更新 更多