我正在调用AVFoundation的委托方法来处理照片捕获,但是我很难将它生成的AVCapturePhoto转换为具有正确方向的UIImage。虽然下面的例程是成功的,但我总是得到一个面向右侧的UIImage(UIImage.imageOrientation = 3)。使用UIImage(数据:图像)并尝试首先使用photo.cgImageRepresentation()时,我无法提供方向?takeRetainedValue()也没有帮助。请协助。
在此处,图像方向至关重要,因为生成的图像将被输入Vision框架工作流程。
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),我确实找到了一件我做错的事情:
更新2链接到GitHub上的完整项目:https://github.com/agu3rra/Out-Loud
最后更新:我使用该应用程序进行了一些实验并得出以下结论:
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.
}
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的生命周期无关。对于1200万像素的图像,BGRA CGImage代表每次呼叫约48兆字节。如果您只打算使用CGImage进行屏幕渲染,请改用previewCGImageRepresentation。请注意,CGImageRef的物理旋转与主图像的物理旋转匹配。 Exif方向尚未应用。如果您希望在使用UIImage时应用旋转,可以通过查询照片的元数据[kCGImagePropertyOrientation]值并将其作为orientation参数传递给+ [UIImage imageWithCGImage:scale:orientation:]来实现。 RAW图像始终返回CGImageRepresentation为零。如果您希望从RAW图像制作CGImageRef,请在CoreImage框架中使用CIRAWFilter。
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
}
}
}
要以正确的方向创建图像,我们需要在初始化图像时输入正确的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
}
}