Почему ландшафтные режимы AVCaptureVideoOrientation приводят к перевернутым неподвижным изображениям?

Я использую классы AVFoundation для реализации пользовательской камеры в своем приложении. Я снимаю только неподвижные изображения, а не видео. У меня все работает, но что-то меня смущает. Я учитываю ориентацию устройства при захвате неподвижного изображения и соответствующим образом устанавливаю videoOrientation видеосоединения. Фрагмент кода:

    // 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;

Обратите внимание на мои комментарии в ландшафтных случаях. Мне нужно изменить отображение ориентации, иначе полученное изображение окажется перевернутым. Я захватываю и сохраняю изображение со следующим кодом:

[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;
}];

Нет никакой другой обработки изображений или манипуляций.

Мое приложение работает с кодом выше. Я просто пытаюсь понять, почему константы ориентации должны быть изменены для альбомной ориентации. Спасибо!


person XJones    schedule 21.10.2011    source источник
comment
Не используйте ориентацию UIDevice, когда вы действительно хотите использовать ориентацию UIInterface.   -  person Till    schedule 29.02.2012
comment
В этом случае вы не можете использовать ориентацию UIInterface. Ориентация устройства во время захвата изображения всегда правильная. ориентация изображения stillImage после захвата этих данных неверна. Мне удалось решить проблему с помощью UIDeviceOrientation и перевода в соответствующий UIImageOrientation при вращении. В то время никто не вмешивался сюда, поэтому я не публиковал код. Если у меня будет время, я добавлю код.   -  person XJones    schedule 29.02.2012
comment
@XJones, пожалуйста, обновите свой ответ точным решением.   -  person Hemang    schedule 25.11.2014


Ответы (3)


Хех, кажется, никто не хотел вмешиваться в это. Оказывается, ответ прост. Изображения, снятые с помощью метода stillImageOutput captureStillImageAsynchronouslyFromConnection:..., всегда имеют следующие свойства:

  • Ориентация UIImage = всегда UIImageOrientationRight независимо от ориентации устройства
  • Размер UIImage = Ш x В (например, ширина портрета x высота портрета, зависит от разрешения вашей камеры)
  • Размер CGImage = зависит от ориентации устройства (например, книжная или альбомная)

Таким образом, решение повернуть изображение вверх состоит в том, чтобы использовать ориентацию устройства в сочетании с размером CGImage для применения соответствующего аффинного преобразования. Поскольку я отвечаю на свой вопрос, я не являюсь решением в коде, но в итоге я написал процедуру под названием:

- (UIImage *)imageRotatedUpForDeviceOrientation:(UIDeviceOrientation)deviceOrientation

в категории UIImage, содержащей различные улучшения обработки изображений.

EDIT — Пример реализации

Я получил ряд запросов на функциональный код для этого. Я извлек соответствующую реализацию из рабочего приложения.

// 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;
}
person XJones    schedule 28.10.2011
comment
Хороший ответ, но несколько строк реализации избавят меня от необходимости печатать! 8-) - person kalperin; 19.01.2012
comment
@XJones Я не мог понять приведенную ниже строку кода, не могли бы вы объяснить это мне? contextRect = CGRectTranspose(contextRect); - person Peer Mohamed Thabib; 14.04.2015
comment
CGRectTranspose WTF - person jjxtra; 11.08.2016
comment
UIDeviceOrientationLandscapeRight приводит к UIImageOrientationDown только для задней камеры. Для фронтальной камеры правильным результатом будет UIImageOrientationUp. То же самое для UIDeviceOrientationLandscapeLeft: UIImageOrientationUp только для задней камеры. Для фронтальной камеры UIImageOrientationDown - person decades; 21.09.2016
comment
Однако есть проблема — поскольку Apple добавила еще 2 ориентации устройств — UIDeviceOrientationFaceUp и UIDeviceOrientationFaceDown. Также не забывайте UIDeviceOrientationUnknown. Вам нужно справиться со всем этим. Не спрашивайте меня, как Coz Idk. - person GeneCode; 23.09.2016
comment
Вы установили ориентацию видео? если вы это сделаете, вы получите правильную ориентацию, а не только UIImageOrientationRight - person Shravya Boggarapu; 30.09.2016
comment
Что делать, если currentDeviceOrientation возвращает лицевую сторону вверх, но устройство находится в альбомной ориентации? - person yatanadam; 10.02.2017

Что касается того, почему вам нужен AVCaptureVideoOrientationLandscapeВправо, когда ориентация устройства UIDeviceOrientationLandscapeВлево, это потому, что по какой-то причине UIDeviceOrientationLandscapeВлево == UIInterfaceOrientationLandscapeПравильно, и AVCaptureVideoOrientations соответствуют соглашению UIInterfaceOrientation.

Кроме того, UIDeviceOrientation включает другие параметры, такие как UIDeviceOrientationFaceUp, UIDeviceOrientationFaceDown и UIDeviceOrientationUnknown. Если ваш интерфейс поворачивается в соответствии с ориентацией устройства, вы можете вместо этого попробовать получить UIDeviceOrientation из [UIApplication sharedApplication].statusBarOrientation.

person JiBB    schedule 28.02.2012
comment
Может ли быть так, что Пейзаж слева/справа связан с использованием передней и задней камеры? - person VaporwareWolf; 11.03.2014
comment
Нет, значения одинаковы для передней и задней камеры. - person Shravya Boggarapu; 30.09.2016

На самом деле, в вашей реализации нет ошибки, и вы можете исключить весь случай переключения.

Почему можно исключить регистр Switch:

Соответствующие значения ориентации устройства и видео численно равны, т. е. если ориентация устройства не является неизвестной, лицевой стороной вверх или лицевой стороной вниз, вы можете приравнять UIDeviceOrientation и AVCaptureVideoOrientation.

О запутанной номенклатуре:

Ориентация устройства ПейзажЛевая => верхняя часть телефона находится слева.

Итак, просто представьте, где находится начальная точка видео... правильно, ВЕРХНИЙ ПРАВЫЙ угол => Пейзаж справа.

Точно так же в другой альбомной ориентации ориентация видео находится в стороне от верхней части телефона. Это не сбивает с толку в случае портрета и вверх ногами

Доказательство

При захвате неподвижного изображения проверьте значение ориентации EXIF. Для альбомной ориентации слева ориентация EXIF ​​равна 3 (инверсия на 180 градусов) Для альбомной ориентации справа ориентация EXIF ​​равна 1 (без поворота)

Когда вы показываете, ваша ориентация exif преобразуется в соответствующую UIImageOrientation, поэтому инверсии быть не должно.

В случае с фронтальной камерой я заметил, что значения EXIF ​​просто следуют значениям ориентации видео (эффект зеркала не учитывается). Используются только 1, 3, 6, 8 значения EXIF.

Кроме того, зеркало всегда расположено вдоль линии от верха устройства к низу. Таким образом, пейзаж будет казаться перевернутым, а портрет и перевернутый портрет просто зеркально отражены.

person Shravya Boggarapu    schedule 30.09.2016