AVFoundation 图像方向在预览中偏离了 90 度,但在相机胶卷中很好

Posted

技术标签:

【中文标题】AVFoundation 图像方向在预览中偏离了 90 度,但在相机胶卷中很好【英文标题】:AVFoundation Image orientation off by 90 degrees in the preview but fine in Camera roll 【发布时间】:2013-04-11 19:02:48 【问题描述】:

发生了一些非常奇怪的事情,我正在尝试使用 AVFoundation 捕获图像,相机胶卷图像看起来还不错,但图像预览将图像旋转了 90 度。

这是我用来捕捉图像的代码

AVCaptureConnection *videoConnection = nil;
for (AVCaptureConnection *connection in stillImageOutput.connections)

    for (AVCaptureInputPort *port in [connection inputPorts])
    
        if ([[port mediaType] isEqual:AVMediaTypeVideo] )
        
            videoConnection = connection;
            break;
        
    
    if (videoConnection)
    
        break;
    


//NSLog(@"about to request a capture from: %@", stillImageOutput);
[stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error)
 
     CFDictionaryRef exifAttachments = CMGetAttachment( imageSampleBuffer, kCGImagePropertyExifDictionary, NULL);
     if (exifAttachments)
     
         // Do something with the attachments.
         //NSLog(@"attachements: %@", exifAttachments);
      else 
         NSLog(@"no attachments");
     

     NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];

     UIImage *image = [[UIImage alloc] initWithData:imageData];

     self.vImage.image = image;

     UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
 ];

【问题讨论】:

为什么顺便投了反对票?解决方案是微不足道的吗?如果是请赐教!这是一个学习不被回避的地方。我正在尽我最大的努力学习,就像你在某个时间点所做的一样! 已经让这个预览工作正常,如果是,请与我们分享这个 ***.com/a/69580115/9440709 【参考方案1】:

是的,当您以设备的纵向方向捕获图像并在应用程序中使用该图像时会发生这种情况,因为图像的默认方向是任何 ios 设备中的横向,因此您需要在选择后更改图像的方向在您的应用中使用的图库。

我已经放了代码来实现这个

Objective-C 代码

- (UIImage *)fixOrientationOfImage:(UIImage *)image 

    // No-op if the orientation is already correct
    if (image.imageOrientation == UIImageOrientationUp) return image;

    // We need to calculate the proper transformation to make the image upright.
    // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored.
    CGAffineTransform transform = CGAffineTransformIdentity;

    switch (image.imageOrientation) 
        case UIImageOrientationDown:
        case UIImageOrientationDownMirrored:
            transform = CGAffineTransformTranslate(transform, image.size.width, image.size.height);
            transform = CGAffineTransformRotate(transform, M_PI);
            break;

        case UIImageOrientationLeft:
        case UIImageOrientationLeftMirrored:
            transform = CGAffineTransformTranslate(transform, image.size.width, 0);
            transform = CGAffineTransformRotate(transform, M_PI_2);
            break;

        case UIImageOrientationRight:
        case UIImageOrientationRightMirrored:
            transform = CGAffineTransformTranslate(transform, 0, image.size.height);
            transform = CGAffineTransformRotate(transform, -M_PI_2);
            break;
        case UIImageOrientationUp:
        case UIImageOrientationUpMirrored:
            break;
    

    switch (image.imageOrientation) 
        case UIImageOrientationUpMirrored:
        case UIImageOrientationDownMirrored:
            transform = CGAffineTransformTranslate(transform, image.size.width, 0);
            transform = CGAffineTransformScale(transform, -1, 1);
            break;

        case UIImageOrientationLeftMirrored:
        case UIImageOrientationRightMirrored:
            transform = CGAffineTransformTranslate(transform, image.size.height, 0);
            transform = CGAffineTransformScale(transform, -1, 1);
            break;
        case UIImageOrientationUp:
        case UIImageOrientationDown:
        case UIImageOrientationLeft:
        case UIImageOrientationRight:
            break;
    

    // Now we draw the underlying CGImage into a new context, applying the transform
    // calculated above.
    CGContextRef ctx = CGBitmapContextCreate(NULL, image.size.width, image.size.height,
                                             CGImageGetBitsPerComponent(image.CGImage), 0,
                                             CGImageGetColorSpace(image.CGImage),
                                             CGImageGetBitmapInfo(image.CGImage));
    CGContextConcatCTM(ctx, transform);
    switch (image.imageOrientation) 
        case UIImageOrientationLeft:
        case UIImageOrientationLeftMirrored:
        case UIImageOrientationRight:
        case UIImageOrientationRightMirrored:
            // Grr...
            CGContextDrawImage(ctx, CGRectMake(0,0,image.size.height,image.size.width), image.CGImage);
            break;

        default:
            CGContextDrawImage(ctx, CGRectMake(0,0,image.size.width,image.size.height), image.CGImage);
            break;
    

    // And now we just create a new UIImage from the drawing context
    CGImageRef cgimg = CGBitmapContextCreateImage(ctx);
    UIImage *img = [UIImage imageWithCGImage:cgimg];
    CGContextRelease(ctx);
    CGImageRelease(cgimg);
    return img;

Swift 代码

func fixOrientationOfImage(image: UIImage) -> UIImage? 
   if image.imageOrientation == .Up 
      return image
   

   // We need to calculate the proper transformation to make the image upright.
   // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored.
   var transform = CGAffineTransformIdentity

   switch image.imageOrientation 
   case .Down, .DownMirrored:
      transform = CGAffineTransformTranslate(transform, image.size.width, image.size.height)
      transform = CGAffineTransformRotate(transform, CGFloat(M_PI))
   case .Left, .LeftMirrored:
      transform = CGAffineTransformTranslate(transform, image.size.width, 0)
      transform = CGAffineTransformRotate(transform, CGFloat(M_PI_2))
   case .Right, .RightMirrored:
      transform = CGAffineTransformTranslate(transform, 0, image.size.height)
      transform = CGAffineTransformRotate(transform, -CGFloat(M_PI_2))
   default:
      break
   

   switch image.imageOrientation 
   case .UpMirrored, .DownMirrored:
      transform = CGAffineTransformTranslate(transform, image.size.width, 0)
      transform = CGAffineTransformScale(transform, -1, 1)
   case .LeftMirrored, .RightMirrored:
      transform = CGAffineTransformTranslate(transform, image.size.height, 0)
      transform = CGAffineTransformScale(transform, -1, 1)
   default:
      break
   

   // Now we draw the underlying CGImage into a new context, applying the transform
   // calculated above.
   guard let context = CGBitmapContextCreate(nil, Int(image.size.width), Int(image.size.height), CGImageGetBitsPerComponent(image.CGImage), 0, CGImageGetColorSpace(image.CGImage), CGImageGetBitmapInfo(image.CGImage).rawValue) else 
      return nil
   

   CGContextConcatCTM(context, transform)

   switch image.imageOrientation 
   case .Left, .LeftMirrored, .Right, .RightMirrored:
      CGContextDrawImage(context, CGRect(x: 0, y: 0, width: image.size.height, height: image.size.width), image.CGImage)
   default:
      CGContextDrawImage(context, CGRect(origin: .zero, size: image.size), image.CGImage)
   

   // And now we just create a new UIImage from the drawing context
   guard let CGImage = CGBitmapContextCreateImage(context) else 
      return nil
   

   return UIImage(CGImage: CGImage)

Swift 3.0

func fixOrientationOfImage(image: UIImage) -> UIImage? 
    if image.imageOrientation == .up 
        return image
    

    // We need to calculate the proper transformation to make the image upright.
    // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored.
    var transform = CGAffineTransform.identity

    switch image.imageOrientation 
       case .down, .downMirrored:
        transform = transform.translatedBy(x: image.size.width, y: image.size.height)
        transform = transform.rotated(by: CGFloat(Double.pi))
    case .left, .leftMirrored:
        transform = transform.translatedBy(x: image.size.width, y: 0)
        transform = transform.rotated(by:  CGFloat(Double.pi / 2))
    case .right, .rightMirrored:
        transform = transform.translatedBy(x: 0, y: image.size.height)
        transform = transform.rotated(by:  -CGFloat(Double.pi / 2))
    default:
        break
    

    switch image.imageOrientation 
    case .upMirrored, .downMirrored:
        transform = transform.translatedBy(x: image.size.width, y: 0)
        transform = transform.scaledBy(x: -1, y: 1)
    case .leftMirrored, .rightMirrored:
        transform = transform.translatedBy(x: image.size.height, y: 0)
        transform = transform.scaledBy(x: -1, y: 1)
    default:
        break
    

    // Now we draw the underlying CGImage into a new context, applying the transform
    // calculated above.
    guard let context = CGContext(data: nil, width: Int(image.size.width), height: Int(image.size.height), bitsPerComponent: image.cgImage!.bitsPerComponent, bytesPerRow: 0, space: image.cgImage!.colorSpace!, bitmapInfo: image.cgImage!.bitmapInfo.rawValue) else 
        return nil
    

    context.concatenate(transform)

    switch image.imageOrientation 
      case .left, .leftMirrored, .right, .rightMirrored:
        context.draw(image.cgImage!, in: CGRect(x: 0, y: 0, width: image.size.height, height: image.size.width))
       default:
          context.draw(image.cgImage!, in: CGRect(origin: .zero, size: image.size))
    

    // And now we just create a new UIImage from the drawing context
    guard let CGImage = context.makeImage() else 
        return nil
    

    return UIImage(cgImage: CGImage)

【讨论】:

Dipen,是的,我最终使用了类似的东西。但我面临另一个问题,我希望将保存的图像裁剪为我在方形视频容器中显示的内容。但事实并非如此,它甚至保存了框中不可见的图像部分。 因为你需要使用一些裁剪方法来裁剪图像,这些方法在 QuartzCore 框架中可用。 @DipenPanchasara - 很棒的东西!!你很棒,迪彭。我需要一些关于这里的逻辑的帮助。我将它复制到我的项目中并使用它,“瞧”我的图像被旋转了。耶!但是当执行到重绘部分时,方向不是我需要的。我试图了解这里的每一步都发生了什么,但还没有得到它。我会继续研究,但如果您或其他人可以插话并启发我,我会很高兴。再次感谢您提供这么棒的帖子! 谢谢先生!我一直在为此寻找几天..现在我只需要找到修复在 stillImageOutput 被触发后 UIImageView 拉伸的代码哈哈。非常感谢! 图像出现黑屏【参考方案2】:

接受的答案有效,但比它需要的复杂得多。您可以使用以下方法来旋转图像。

- (UIImage *)cropImage:(UIImage*)image toRect:(CGRect)rect 
    CGFloat (^rad)(CGFloat) = ^CGFloat(CGFloat deg) 
        return deg / 180.0f * (CGFloat) M_PI;
    ;

    // determine the orientation of the image and apply a transformation to the crop rectangle to shift it to the correct position
    CGAffineTransform rectTransform;
    switch (image.imageOrientation) 
        case UIImageOrientationLeft:
            rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(90)), 0, -image.size.height);
            break;
        case UIImageOrientationRight:
            rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(-90)), -image.size.width, 0);
            break;
        case UIImageOrientationDown:
            rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(-180)), -image.size.width, -image.size.height);
            break;
        default:
            rectTransform = CGAffineTransformIdentity;
    ;

    // adjust the transformation scale based on the image scale
    rectTransform = CGAffineTransformScale(rectTransform, image.scale, image.scale);

    // apply the transformation to the rect to create a new, shifted rect
    CGRect transformedCropSquare = CGRectApplyAffineTransform(rect, rectTransform);
    // use the rect to crop the image
    CGImageRef imageRef = CGImageCreateWithImageInRect(image.CGImage, transformedCropSquare);
    // create a new UIImage and set the scale and orientation appropriately
    UIImage *result = [UIImage imageWithCGImage:imageRef scale:image.scale orientation:image.imageOrientation];
    // memory cleanup
    CGImageRelease(imageRef);

    return result;

要只旋转图像而不是裁剪,您可以这样简单地调用它:

UIImage *image;
[self cropImage:image toRect:rect.bounds];

【讨论】:

@decades 它不适用于哪个方向和设备?另外,请记住,这是查看图像的方向,所以如果它没有记录到图像中(如果你围绕 AV 框架构建自己的框架,我相信你必须手动设置),这将不起作用。 我正在尝试将您的代码与 captureStillImageAsynchronouslyFromConnection 的结果结合使用。也许我在这里错了 最终答案 17 以及我评论的更改是我的问题的完美解决方案***.com/questions/7845520/… @decades 有没有办法旋转requestImageDataForAsset得到的数据 @TejasPadliya 在结果处理程序中,您可以使用 [[UIImage alloc] initWithData:data]NSData 对象实例化 UIImage【参考方案3】:

Dipen Panchasara 的回答很棒,但可能存在一个问题。当您处理大图像(例如来自 iPhoneX)时,会出现大量内存峰值,这在某些情况下可能会成为问题。

所以,也许你想改变那行:

context.draw(image.cgImage!, in: CGRect(x: 0, y: 0, width: image.size.height, height: image.size.width))

对于一些内存优化的东西。例如,这将在 16 (4*4) 步中中断图像绘制,从而显着减少内存消耗:

let partInAxis: CGFloat = 4
let partWidth = image.size.height/partInAxis
let partHeight = image.size.width/partInAxis
for i in 0...Int(partInAxis)-1 
   for j in 0...Int(partInAxis)-1 
      let partialImage = image.cgImage?.cropping(to: CGRect(x: CGFloat(i)*partWidth, y: CGFloat(j)*partHeight, width: partWidth, height: partHeight))
      context.draw(partialImage!, in: CGRect(x: CGFloat(i)*partWidth, y: CGFloat(Int(partInAxis)-1-j)*partHeight, width: partWidth, height: partHeight))
   

请注意,在这种情况下,图像的高度和宽度必须除以 4。

【讨论】:

【参考方案4】:

Swift 5.5 +

您应该在捕获图像之前设置输出的方向。

// set the image orientation in output
  if let photoOutputConnection = self.photoOutput.connection(with: .video) 
        photoOutputConnection.videoOrientation = videoPreviewLayerOrientation!
    

self.photoOutput.capturePhoto(with: photoSettings, delegate: photoCaptureProcessor) // capture image

【讨论】:

【参考方案5】:

我建议使用Yodagama's answer,因为它是单行且更清洁的。但是,如果您需要以另一种方式更正方向,这里是 Dipen's answer,没有强制选项,作为 UIImage 扩展。要获得更好的内存优化,请将其与this answer 混合使用。

extension UIImage 
    var fixedOrientation: UIImage? 
        if imageOrientation == .up 
            return self
        

        // We need to calculate the proper transformation to make the image upright.
        // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored.
        var transform = CGAffineTransform.identity

        switch imageOrientation 
        case .down, .downMirrored:
            transform = transform.translatedBy(x: size.width, y: size.height)
            transform = transform.rotated(by: .pi)
        case .left, .leftMirrored:
            transform = transform.translatedBy(x: size.width, y: 0)
            transform = transform.rotated(by: .pi / 2)
        case .right, .rightMirrored:
            transform = transform.translatedBy(x: 0, y: size.height)
            transform = transform.rotated(by:  -.pi / 2)
        default: break
        

        switch imageOrientation 
        case .upMirrored, .downMirrored:
            transform = transform.translatedBy(x: size.width, y: 0)
            transform = transform.scaledBy(x: -1, y: 1)
        case .leftMirrored, .rightMirrored:
            transform = transform.translatedBy(x: size.height, y: 0)
            transform = transform.scaledBy(x: -1, y: 1)
        default: break
        

        guard let cgImage = cgImage,
              let colorSpace = cgImage.colorSpace else 
            return nil
        

        // Now we draw the underlying CGImage into a new context, applying the transform
        // calculated above.
        let context = CGContext(
            data: nil,
            width: Int(size.width),
            height: Int(size.height),
            bitsPerComponent: cgImage.bitsPerComponent,
            bytesPerRow: 0,
            space: colorSpace,
            bitmapInfo: cgImage.bitmapInfo.rawValue
        )

        guard let context = context else 
            return nil
        

        context.concatenate(transform)

        switch imageOrientation 
          case .left, .leftMirrored, .right, .rightMirrored:
            context.draw(cgImage, in: .init(x: 0, y: 0, width: size.height, height: size.width))
           default:
              context.draw(cgImage, in: .init(origin: .zero, size: size))
        

        // And now we just create a new UIImage from the drawing context
        guard let newCgImage = context.makeImage() else 
            return nil
        

        return .init(cgImage: newCgImage)
    

【讨论】:

以上是关于AVFoundation 图像方向在预览中偏离了 90 度,但在相机胶卷中很好的主要内容,如果未能解决你的问题,请参考以下文章

如何调整图像的方向以显示正确的图像预览?

相机意图图像预览方向

使用 AV Foundation 时正确裁剪捕获的图像

无法让 AVFoundation 有正确的方向

UIImageView 在动画期间显示相机以错误的方向拍摄的图像

二维码的读取