Почему ландшафтные режимы AVCaptureVideoOrientation приводят к переворачиванию неподвижных изображений? - PullRequest
7 голосов
/ 21 октября 2011

Я использую классы 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;
}];

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

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

Ответы [ 3 ]

21 голосов
/ 28 октября 2011

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

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

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

- (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;
}
7 голосов
/ 29 февраля 2012

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

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

0 голосов
/ 30 сентября 2016

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

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

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

О путающей номенклатуре

Ориентация устройства LandscapeLeft => верхняя часть телефона слева.

Итак, просто представьте, где находится начальная точка видео ..... это верно, ТОПВПРАВО угол => Пейзаж вправо.

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

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

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

При отображении ориентация exif преобразуется в соответствующую UIImageOrientation, поэтомубыть без инверсии.

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

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

...