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では古くなっています)、私は管理しました私が間違っていたことを見つけるために:
self.capturePhotoOutput.capturePhoto
_)で、PhotoOutput
オブジェクトとの接続を設定し、写真を撮る時点でデバイスの向きに合わせて向きを更新する必要があります。そのために、UIDeviceOrientation
の拡張を作成し、作成したsnapPhoto()
関数で使用してキャプチャルーチンを呼び出し、didFinishProcessingPhoto
デリゲートメソッドが実行されるのを待ちました。 。ここにあるコードサンプルの区切り記号が正しく表示されていないように見えるため、コードのスナップショットを追加しました。 Update 2GitHubの完全なプロジェクトへのリンク: https://github.com/agu3rra/Out-Loud
最終更新:私はアプリでいくつかの実験を実行し、次の結論に達しました:
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.
}
デバッガーで生成された画像を表示すると、どのように生成されるかがわかりました。そのため、必要な回転(UIImageOrientation
)を推測して、直立して表示することができました。つまり、UIImageOrientation
を更新すると、正しい向きで画像を表示するために画像をどのように回転させるかがわかります。だから私は次の表に来ました:
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
}
}
}
これが私の最終的なデリゲートメソッドの外観です。予想される向きで画像を表示します。
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)")
}
私はこれをやって成功しました:
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を使用します。
正しい向きで画像を作成するには、正しいUIImage.Orientation
イメージを初期化するとき。
PhotoOutputデリゲートから返されるCGImagePropertyOrientation
を使用して、写真が撮影されたときのカメラセッションの正確な方向を取得するのが最善です。ここでの唯一の問題は、UIImage.Orientation
とCGImagePropertyOrientation
は同じですが、生の値は同じではありません。 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
}
}
AVCapturePhoto
の中には、metadata
とも呼ばれるCGImageProperties
オブジェクトがあります。
内部には、方向付け用のEXIF辞書があります。次の手順は、方向付けを行い、それに応じて画像を作成することです。AVCapturePhotoOutput
の使用経験はありませんが、古い方法を使用している人がいます。
EXIFディクショナリはUIImageOrientationで異なる方法でマップされることに注意してください。
これは 記事 です。私はかなり前に書きましたが、主な原則はまだ有効です。
この question はいくつかの実装を示します。それもかなり古く、最新バージョンではより簡単なAPIをリリースしたと確信していますが、問題。
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
}
}
}