web-dev-qa-db-ja.com

AVCapturePhotoから正しい向きでUIImageを生成する方法は?

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デリゲートメソッドが実行されるのを待ちました。 。ここにあるコードサンプルの区切り記号が正しく表示されていないように見えるため、コードのスナップショットを追加しました。 enter image description hereenter image description here

Update 2GitHubの完全なプロジェクトへのリンク: https://github.com/agu3rra/Out-Loud

14
Andre Guerra

最終更新:私はアプリでいくつかの実験を実行し、次の結論に達しました:

  1. kCGImagePropertyOrientationは、アプリケーション内でキャプチャされた画像の向きに影響を与えないようです。photoOutputメソッドを呼び出すたびにcapturePhoto接続を更新する場合、デバイスの向きによってのみ変化します。そう:

    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を更新すると、正しい向きで画像を表示するために画像をどのように回転させるかがわかります。だから私は次の表に来ました: Which UIImageOrientation to apply according to how the device was at the time of capture

  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)")
    }
    
18
Andre Guerra

私はこれをやって成功しました:

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などの非圧縮形式でバックアップされた場合、AVCapturePhotoの寿命とは関係のない別のバッキングバッファにコピーされます。 12メガピクセルの画像の場合、BGRA CGImageは呼び出しごとに最大48メガバイトを表します。画面上のレンダリングにのみCGImageを使用する場合は、代わりにpreviewCGImageRepresentationを使用します。 CGImageRefの物理的な回転は、メイン画像の回転と一致することに注意してください。 Exif方向が適用されていません。 UIImageで作業するときに回転を適用する場合は、写真のメタデータ[kCGImagePropertyOrientation]値をクエリし、それを方向パラメーターとして+ [UIImage imageWithCGImage:scale:orientation:]に渡すことで適用できます。 RAW画像は常に、nilのCGImageRepresentationを返します。 RAW画像からCGImageRefを作成する場合は、CoreImageフレームワークでCIRAWFilterを使用します。

3
Mark Bridges

正しい向きで画像を作成するには、正しい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
    }
}
1
bmjohns

AVCapturePhotoの中には、metadataとも呼ばれるCGImagePropertiesオブジェクトがあります。
内部には、方向付け用のEXIF辞書があります。次の手順は、方向付けを行い、それに応じて画像を作成することです。
AVCapturePhotoOutputの使用経験はありませんが、古い方法を使用している人がいます。
EXIFディクショナリはUIImageOrientationで異なる方法でマップされることに注意してください。
これは 記事 です。私はかなり前に書きましたが、主な原則はまだ有効です。
この question はいくつかの実装を示します。それもかなり古く、最新バージョンではより簡単なAPIをリリースしたと確信していますが、問題。

1
Andrea

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
        }
    }
}
1
mikro098