【问题标题】:Why does AVCaptureVideoOrientation landscape modes result in upside down still images?为什么 AVCaptureVideoOrientation 横向模式会导致静止图像倒置?
【发布时间】:2011-12-12 07:56:00
【问题描述】:

我正在使用 AVFoundation 类在我的应用中实现自定义摄像头。我只捕捉静止图像,而不是视频。我一切正常,但被某些事情难住了。我在捕获静止图像时会考虑设备方向,并适当地设置视频连接的 videoOrientation。一个代码sn-p:

    // set the videoOrientation based on the device orientation to
    // ensure the pic is right side up for all orientations
    AVCaptureVideoOrientation videoOrientation;
    switch ([UIDevice currentDevice].orientation) {
        case UIDeviceOrientationLandscapeLeft:
            // Not clear why but the landscape orientations are reversed
            // if I use AVCaptureVideoOrientationLandscapeLeft here the pic ends up upside down
            videoOrientation = AVCaptureVideoOrientationLandscapeRight;
            break;
        case UIDeviceOrientationLandscapeRight:
            // Not clear why but the landscape orientations are reversed
            // if I use AVCaptureVideoOrientationLandscapeRight here the pic ends up upside down
            videoOrientation = AVCaptureVideoOrientationLandscapeLeft;
            break;
        case UIDeviceOrientationPortraitUpsideDown:
            videoOrientation = AVCaptureVideoOrientationPortraitUpsideDown;
            break;
        default:
            videoOrientation = AVCaptureVideoOrientationPortrait;
            break;
    }

    videoConnection.videoOrientation = videoOrientation;

请注意我在景观案例中的 cmets。我必须反转方向映射,否则生成的图像是颠倒的。我使用以下代码捕获并保存图像:

[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection 
    completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error)
    {
        NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
        self.stillImage = [UIImage imageWithData:imageData];
        // notify observers (image gets saved to the camera roll)                                                           
        [[NSNotificationCenter defaultCenter] postNotificationName:CaptureSessionManagerDidCaptureStillImageNotification object:self];
        self.stillImage = nil;
}];

没有其他图像处理或操作。

我的应用可以使用上面的代码。我只是想了解为什么横向方向的方向常数必须反转。谢谢!

【问题讨论】:

  • 当你真正想使用 UIInterface 方向时,不要使用 UIDevice 方向。
  • 在这种情况下您不能使用UIInterface 方向。拍摄图像时的设备方向始终正确。捕获数据后stillImage 的图像方向不正确。我能够使用 UIDeviceOrientation 解决问题,并在旋转时转换为适当的 UIImageOrientation。当时,没有人在这里插话,所以我没有发布代码。如果我有时间,我会添加代码。
  • @XJones,请用确切的解决方案更新您的答案。

标签: iphone ios avcapturesession image-capture avcapturedevice


【解决方案1】:

嘿,似乎没有人愿意插话这个。事实证明,答案很简单。通过stillImageOutput captureStillImageAsynchronouslyFromConnection:... 方法捕获的图像始终具有以下属性:

  • UIImage 方向 = 始终 UIImageOrientationRight 与设备方向无关
  • UIImage 大小 = W x H(例如纵向宽度 x 纵向高度,取决于您的相机分辨率)
  • CGImage 大小 = 取决于设备方向(例如纵向或横向)

因此,向上旋转图像的解决方案是使用设备方向和 CGI​​mage 大小来应用适当的仿射变换。当我回答自己的问题时,我不是代码中的解决方案,但我最终编写了一个名为:

- (UIImage *)imageRotatedUpForDeviceOrientation:(UIDeviceOrientation)deviceOrientation

在包含各种图像处理增强功能的 UIImage 类别中。

编辑 - 实施示例

我收到了许多关于此功能代码的请求。我已经从一个工作的应用程序中提取了相关的实现。

// this method is implemented in your capture session manager (wherever AVCaptureSession is used)
// capture a still image and save the device orientation
- (void)captureStillImage
{
    UIDeviceOrientation currentDeviceOrientation = UIDevice.currentDevice.orientation;
      [self.stillImageOutput
      captureStillImageAsynchronouslyFromConnection:self.videoConnection
      completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
          NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
          if (imageData) {
              UIImage *image = [UIImage imageWithData:imageData];
              NSDictionary *captureInfo = {
                  @"image" : image,
                  @"deviceOrientation" : @(currentDeviceOrientation)
              };
              // TODO: send image & orientation to delegate or post notification to observers
          }
          else {
              // TODO: handle image capture error
          }
    }];
}

// this method rotates the UIImage captured by the capture session manager based on the
// device orientation when the image was captured
- (UIImage *)imageRotatedUpFromCaptureInfo:(NSDictionary *)captureInfo
{
    UIImage *image = [captureInfo objectForKey:@"image"];
    UIDeviceOrientation deviceOrientation = [[captureInfo objectForKey:@"deviceOrientation"] integerValue];
    UIImageOrientation rotationOrientation = [self rotationNeededForImageCapturedWithDeviceOrientation:deviceOrientation];
    // TODO: scale the image if desired
    CGSize newSize = image.size;
    return [imageScaledToSize:newSize andRotatedByOrientation:rotationOrientation];
}

// return a scaled and rotated an image
- (UIImage *)imageScaledToSize:(CGSize)newSize andRotatedByOrientation:(UIImageOrientation)orientation
{
    CGImageRef imageRef = self.CGImage;    
    CGRect imageRect = CGRectMake(0.0, 0.0, newSize.width, newSize.height);
    CGRect contextRect = imageRect;
    CGAffineTransform transform = CGAffineTransformIdentity;

    switch (orientation)
    {
        case UIImageOrientationDown: { // rotate 180 deg
            transform = CGAffineTransformTranslate(transform, imageRect.size.width, imageRect.size.height);
            transform = CGAffineTransformRotate(transform, M_PI);
        } break;

        case UIImageOrientationLeft: { // rotate 90 deg left
            contextRect = CGRectTranspose(contextRect);
            transform = CGAffineTransformTranslate(transform, imageRect.size.height, 0.0);
            transform = CGAffineTransformRotate(transform, M_PI / 2.0);
        } break;

        case UIImageOrientationRight: { // rotate 90 deg right
            contextRect = CGRectTranspose(contextRect);
            transform = CGAffineTransformTranslate(transform, 0.0, imageRect.size.width);
            transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
        } break;

        case UIImageOrientationUp: // no rotation
        default:
            break;
    }

    CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
    CGColorSpaceRef colorSpaceRef = CGImageGetColorSpace(imageRef);

    // madify bitmapInfo to work with PNG if necessary
    if (bitmapInfo == kCGImageAlphaNone) {
        bitmapInfo = kCGImageAlphaNoneSkipLast;
    }
    else if (bitmapInfo == kCGImageAlphaLast) {
        bitmapInfo = kCGImageAlphaPremultipliedLast;
    }

    // Build a context that's the same dimensions as the new size
    CGContextRef context = CGBitmapContextCreate(NULL,
                                                 contextRect.size.width,
                                                 contextRect.size.height,
                                                 CGImageGetBitsPerComponent(imageRef),
                                                 0,
                                                 colorSpaceRef,
                                                 bitmapInfo);


    CGContextConcatCTM(context, transform);
    CGContextDrawImage(context, imageRect, imageRef);

    // Get the rotated image from the context and a UIImage
    CGImageRef rotatedImageRef = CGBitmapContextCreateImage(context);
    UIImage *rotatedImage = [UIImage imageWithCGImage:rotatedImageRef];

    // Clean up
    CGImageRelease(rotatedImageRef);
    CGContextRelease(context);

    return rotatedImage;
}

// return the UIImageOrientation needed for an image captured with a specific deviceOrientation
- (UIImageOrientation)rotationNeededForImageCapturedWithDeviceOrientation:(UIDeviceOrientation)deviceOrientation
{
    UIImageOrientation rotationOrientation;
    switch (deviceOrientation) {
        case UIDeviceOrientationPortraitUpsideDown: {
            rotationOrientation = UIImageOrientationLeft;
        } break;

        case UIDeviceOrientationLandscapeRight: {
            rotationOrientation = UIImageOrientationDown;
        } break;

        case UIDeviceOrientationLandscapeLeft: {
            rotationOrientation = UIImageOrientationUp;
        } break;

        case UIDeviceOrientationPortrait:
        default: {
            rotationOrientation = UIImageOrientationRight;
        } break;
    }
    return rotationOrientation;
}

【讨论】:

  • 很好的答案,但几行实施可以节省我的打字时间! 8 -)
  • @XJones 我无法理解下面的代码行,你能帮我解释一下吗? contextRect = CGRectTranspose(contextRect);
  • CGRectTranspose WTF
  • UIDeviceOrientationLandscapeRight 导致 UIImageOrientationDown 仅用于后置摄像头。对于前置摄像头,正确的结果是 UIImageOrientationUp。 UIDeviceOrientationLandscapeLeft 相同: UIImageOrientationUp 仅用于后置摄像头。对于前置摄像头 UIImageOrientationDown
  • 不过有一个问题 - 因为 Apple 添加了另外 2 个设备方向 - UIDeviceOrientationFaceUp 和 UIDeviceOrientationFaceDown。也不要忘记 UIDeviceOrientationUnknown。你需要处理所有这些。不要问我是怎么想的。
【解决方案2】:

至于为什么当设备的方向是 UIDeviceOrientationLandscapeLeft 时需要 AVCaptureVideoOrientationLandscapeRight,那是因为出于某种原因,UIDeviceOrientationLandscapeLeft == UIInterfaceOrientationLandscapeRight,并且 AVCaptureVideoOrientations 遵循 UIInterfaceOrientation 约定。

此外,UIDeviceOrientation 还包含其他选项,例如 UIDeviceOrientationFaceUp、UIDeviceOrientationFaceDown 和 UIDeviceOrientationUnknown。如果您让界面旋转以匹配设备的方向,您可以尝试从 [UIApplication sharedApplication].statusBarOrientation 获取 UIDeviceOrientation。

【讨论】:

  • 景观左/右是否是由于使用前后摄像头造成的?
  • 不,前后摄像头的值相同。
【解决方案3】:

其实你的实现没有错误,可以去掉整个switch case。

为什么可以去掉switch case:

设备和视频方向的对应值在数值上是相等的,即只要设备方向不是未知、正面朝上或正面朝下,您就可以将 UIDeviceOrientation 和 AVCaptureVideoOrientation 等同起来

关于令人困惑的命名法:

设备方向 LandscapeLeft => 手机顶部在左侧。

所以,想象一下视频的起点在哪里.....没错,右上角 => 横向。

与其他横向​​类似,视频方向远离手机顶部。 这在纵向和倒置的情况下并不令人困惑

证明

当您拍摄静止图像时,请检查方向 EXIF 值。 对于横向左侧,EXIF 方向为 3(180 度反转) 对于横向,EXIF 方向为 1(无旋转)

在显示的时候,你的exif方向会转换成对应的UIImageOrientation,所以应该没有反转。

在前置摄像头的情况下,我注意到EXIF值,只需遵循视频方向值(不考虑镜像效果)。 EXIF 只使用了 1、3、6、8 个值。

另外,镜子总是沿着从设备顶部到底部的线 所以,风景会看起来是颠倒的,而肖像和颠倒的肖像只是镜像

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-03-20
    • 2012-02-25
    • 2020-06-18
    • 1970-01-01
    • 2015-05-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多