Почему AVCaptureSession выводит неправильную ориентацию?

Итак, я выполнил инструкции Apple по захвату видеосеанса с использованием AVCaptureSession: http://developer.apple.com/iphone/library/qa/qa2010/qa1702.html. Одна из проблем, с которыми я сталкиваюсь, заключается в том, что даже несмотря на то, что ориентация камеры / устройства iPhone является вертикальной (а AVCaptureVideoPreviewLayer показывает вертикальный поток камеры), выходное изображение, похоже, находится в альбомном режиме. Я проверил ширину и высоту imageBuffer внутри imageFromSampleBuffer: примера кода и получил 640 пикселей и 480 пикселей соответственно. Кто-нибудь знает, почему это так?

Спасибо!


person Peter    schedule 24.08.2010    source источник


Ответы (11)


Взгляните на заголовок AVCaptureSession.h. Существует определение перечисления AVCaptureVideoOrientation, которое определяет различные ориентации видео. В объекте AVCaptureConnection есть свойство videoOrientation, которое является AVCaptureVideoOrientation. Вы должны иметь возможность установить это, чтобы изменить ориентацию видео. Вы, вероятно, захотите AVCaptureVideoOrientationLandscapeRight или AVCaptureVideoOrientationLandscapeLeft.

Вы можете найти AVCaptureConnections для сеанса, просмотрев выходы для сеанса. У выходов есть свойство connections, которое представляет собой массив соединений для этого выхода.

person Jon Steinmetz    schedule 25.08.2010
comment
@cheesus videoOrientation находится в AVCaptureConnection, а не в AVCaptureSession - person GabCas; 28.08.2013
comment
Но где мне установить это свойство в AVCaptureConnection? Внутри метода делегата? что-то вроде: - (void) captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { [connection setVideoOrientation:AVCaptureVideoOrientationPortrait]; - person rptwsthi; 08.05.2015
comment
@rptwsthi можно установить сразу после добавления вывода в сеанс captureSession.addOutput(videoOutput) let connection = videoOutput.connectionWithMediaType(AVFoundation.AVMediaTypeVideo) connection.videoOrientation = .Portrait captureSession.startRunning() - person gonglong; 16.03.2016

Вы все усложняете задачу.

В DidOutputSampleBuffer просто измените ориентацию перед захватом изображения. Это моно, но у вас есть

    public class OutputRecorder : AVCaptureVideoDataOutputSampleBufferDelegate {    
        public override void DidOutputSampleBuffer (AVCaptureOutput captureOutput, CMSampleBuffer sampleBuffer, AVCaptureConnection connection)
        {
            try {
                connection.videoOrientation = AVCaptureVideoOrientation.LandscapeLeft;

в objC это метод

- ( void ) captureOutput: ( AVCaptureOutput * ) captureOutput
   didOutputSampleBuffer: ( CMSampleBufferRef ) sampleBuffer
      fromConnection: ( AVCaptureConnection * ) connection
person Nick Turner    schedule 05.11.2014
comment
Попробовал, и я получил 'AVCaptureConnection' does not have a member named 'VideoOrientation' - person drewish; 10.12.2014
comment
Это был вопрос капитализации. Надо было videoOrientation так отредактировать ответ. - person drewish; 10.12.2014
comment
только одного я не понимаю. Когда я использую камеру с пейзажем моего iPad, мне нужно инвертировать видеоориентацию. Например: если мой ipad UIDeviceOrientationLeft, я должен сделать connection.videoOrientation = AVCaptureVideoOrientationLandscapeRight и то же самое для другого ландшафта, иначе изображение будет перевернуто. Причина, по которой ориентация видео должна быть инвертирована с точки зрения ориентации устройства, - еще одна непонятная вещь благодаря Apple. - person Duck; 04.02.2015
comment
Работает! Спасибо, сэр! - person Roi Mulia; 31.05.2016
comment
Работает. Просто не забудьте установить ориентацию асинхронно в основной очереди, потому что этот метод вызывается в специальной последовательной очереди. - person Andrzej Filipowicz; 12.08.2016
comment
@SpaceDog Если вы посмотрите определения AVCaptureVideoOrientation и UIDeviceOrientation, вы увидите, что определения ориентации ландшафта в этих перечислениях перевернуты. Почему это так, я не понимаю, но это задокументировано. Я согласен, что это сбивает с толку, и предполагаю, что они отличаются по какой-то устаревшей причине. - person Sami Samhuri; 25.10.2016
comment
@SpaceDog Это потому, что это самолеты, обращенные в противоположные стороны с противоположных сторон телефона. Слева от датчика задней камеры описывается так, как если бы вы смотрели на него спереди, то есть на объектив, но если вашей точкой отсчета является экран на передней панели устройства, то он перевернут, и он находится справа. Для фронтальной камеры это более интуитивно понятно, потому что датчик находится в той же плоскости, что и экран. Это сбивает с толку, но, вероятно, лучше сохранять единообразие имен, если телефоны не всегда выглядят так, как сегодня. - person alex bird; 05.08.2019

Я сделал простую однострочную модификацию imageFromSampleBuffer, чтобы исправить проблему ориентации (см. Мой комментарий в коде в разделе «Я изменил ...»). Надеюсь, это кому-то поможет, потому что я потратил на это слишком много времени.

// Create a UIImage from sample buffer data
- (UIImage *) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer  {
    // Get a CMSampleBuffer's Core Video image buffer for the media data
    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); 
    // Lock the base address of the pixel buffer
    CVPixelBufferLockBaseAddress(imageBuffer, 0); 

    // Get the number of bytes per row for the pixel buffer
    void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer); 

    // Get the number of bytes per row for the pixel buffer
    size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); 
    // Get the pixel buffer width and height
    size_t width = CVPixelBufferGetWidth(imageBuffer); 
    size_t height = CVPixelBufferGetHeight(imageBuffer); 

    // Create a device-dependent RGB color space
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 

    // Create a bitmap graphics context with the sample buffer data
    CGContextRef context1 = CGBitmapContextCreate(baseAddress, width, height, 8, 
                                                 bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);

    // Create a Quartz image from the pixel data in the bitmap graphics context
    CGImageRef quartzImage = CGBitmapContextCreateImage(context1); 
    // Unlock the pixel buffer
    CVPixelBufferUnlockBaseAddress(imageBuffer,0);

    // Free up the context and color space
    CGContextRelease(context1); 
    CGColorSpaceRelease(colorSpace);

    // Create an image object from the Quartz image
    //I modified this line: [UIImage imageWithCGImage:quartzImage]; to the following to correct the orientation:
    UIImage *image =  [UIImage imageWithCGImage:quartzImage scale:1.0 orientation:UIImageOrientationRight]; 

    // Release the Quartz image
    CGImageRelease(quartzImage);

    return (image);
}
person RawMean    schedule 21.07.2012
comment
Спасибо, мне это помогает. обратите внимание на метод, взятый из: stackoverflow.com/questions/8924299 / - person Evgeniy S; 31.03.2014
comment
Ты мужик! Я не мог понять этого, поскольку я не использую AVCaptureConnection ... это помогло. Спасибо! - person Reid; 15.04.2014
comment
@ReidBelton, вы не можете не использовать AVCaptureConnection, он автоматически создается, когда у вас есть вход и выход, вам просто нужно получить его и изменить ориентацию. - person Pochi; 09.10.2014
comment
У меня ошибка CGBitmapContextCreateImage: недопустимый контекст 0x0 - person loretoparisi; 21.09.2015
comment
Разве это не повторно сжимает изображение, снижая его качество? Также EXIF ​​и встроенный эскиз (если есть) будут неправильными. - person Kartick Vaddadi; 12.03.2017
comment
@Gaddafi нет, в этом коде нет сжатия или сжатия - person RawMean; 12.03.2017

Вот правильная последовательность:

AVCaptureVideoDataOutput *videoCaptureOutput = [[AVCaptureVideoDataOutput alloc] init];

if([self.captureSession canAddOutput:self.videoCaptureOutput]){
    [self.captureSession addOutput:self.videoCaptureOutput];
}else{
    NSLog(@"cantAddOutput");
}

// set portrait orientation
AVCaptureConnection *conn = [self.videoCaptureOutput connectionWithMediaType:AVMediaTypeVideo];
[conn setVideoOrientation:AVCaptureVideoOrientationPortrait];
person Rubycon    schedule 09.02.2014
comment
Это сработало для меня. Как показано в этом коде, важно добавить выходные данные в сеанс захвата перед изменением этих свойств. AVCaptureVideoOrientationLandscapeRight с videoMirrored = true дало мне тот же эффект, который вы получаете от использования стандартного приложения камеры с фронтальной камерой. - person spfursich; 10.02.2015
comment
Разве это не просто меняет ориентацию локального объекта conn, а не объекта, принадлежащего self.videoCaptureOutput? - person mylogon; 03.07.2017
comment
@spfursich то, что вы упомянули, очень незаметно, но важно. - person KnowNothing; 12.12.2018

Например:

AVCaptureConnection *captureConnection = <a capture connection>;
if ([captureConnection isVideoOrientationSupported]) {
    captureConnection.videoOrientation = AVCaptureVideoOrientationPortrait;
}

По умолчанию это AVCaptureVideoOrientationLandscapeRight.

См. Также QA1744: установка ориентации видео с помощью AV Foundation.

person ja'    schedule 10.04.2013
comment
у меня это работает, я добавил его в обработчик myAVCaptureConnection. Изображение было повернуто, как и ожидалось, но видео было зеркальным. Я вижу, что isMirrored устарел, поэтому неясно, как исправить материал зеркалирования, потому что новые методы зеркалирования не имеют документации по использованию. - person Jim True; 26.04.2013
comment
@Jim: Поскольку mirrored в AVCaptureVideoPreviewLayer устарел, вы должны использовать вместо этого свойство AVCaptureConnection videoMirrored. Это задокументировано, по крайней мере, в файлах заголовков, где mirrored и его друзья также явно отсылают вас к videoMirrored и подходящим методам подключения захвата. - person ja'; 26.04.2013
comment
похоже, что эта функция недоступна в iOS, к сожалению - person Jim True; 27.04.2013
comment
@ Джим: Нет, чувак! Это также прямо здесь, в онлайн-документации: developer.apple.com/ library / ios / documentation / AVFoundation / ʘ‿ʘ Почему вы решили, что эта функция недоступна для iOS? - person ja'; 01.05.2013

Для тех, кому нужно работать с CIImage и ориентация из буфера неправильная, я применил это исправление.

Это так просто. Кстати, числа 3,1,6,8 отсюда https://developer.apple.com/reference/imageio/kcgimagepropertyorientation

И не спрашивайте меня, почему 3,1,6,8 - правильная комбинация. Я нашел его методом грубой силы. Если вы знаете, почему дайте объяснение в комментарии, пожалуйста ...

- (void)captureOutput:(AVCaptureOutput *)captureOutput
    didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
           fromConnection:(AVCaptureConnection *)connection
{

    // common way to get CIImage

    CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);

    CFDictionaryRef attachments = CMCopyDictionaryOfAttachments(kCFAllocatorDefault, sampleBuffer, kCMAttachmentMode_ShouldPropagate);

    CIImage *ciImage = [[CIImage alloc] initWithCVPixelBuffer:pixelBuffer
                                                      options:(__bridge NSDictionary *)attachments];

    if (attachments) {
       CFRelease(attachments);
    }

    // fixing the orientation of the CIImage

    UIInterfaceOrientation curOrientation = [[UIApplication sharedApplication] statusBarOrientation];

    if (curOrientation == UIInterfaceOrientationLandscapeLeft){
        ciImage = [ciImage imageByApplyingOrientation:3];
    } else if (curOrientation == UIInterfaceOrientationLandscapeRight){
        ciImage = [ciImage imageByApplyingOrientation:1];
    } else if (curOrientation == UIInterfaceOrientationPortrait){
        ciImage = [ciImage imageByApplyingOrientation:6];
    } else if (curOrientation == UIInterfaceOrientationPortraitUpsideDown){
        ciImage = [ciImage imageByApplyingOrientation:8];
    }



    // ....

}
person Marek Manduch    schedule 24.05.2017
comment
Константы, которые вы ищете, определены в kCGImagePropertyorientation ... они взяты из спецификации TIFF / EXIF: knownsystems.be/imaging/tiff/tifftags/orientation.html - person ibisum; 03.10.2017

Если ориентация AVCaptureVideoPreviewLayer правильная, вы можете просто установить ориентацию перед тем, как сделать снимок.

AVCaptureStillImageOutput *stillImageOutput;
AVCaptureVideoPreviewLayer *previewLayer;
NSData *capturedImageData;

AVCaptureConnection *videoConnection = [stillImageOutput connectionWithMediaType:AVMediaTypeVideo];
if ([videoConnection isVideoOrientationSupported]) {
    [videoConnection setVideoOrientation:previewLayer.connection.videoOrientation];
}
[stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
    CFDictionaryRef exifAttachments =
            CMGetAttachment(imageSampleBuffer, kCGImagePropertyExifDictionary, NULL);
    if (exifAttachments) {
        // Do something with the attachments.
    }
    // TODO need to manually add GPS data to the image captured
    capturedImageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
    UIImage *image = [UIImage imageWithData:capturedImageData];
}];

Также важно отметить, что UIImageOrientation и AVCaptureVideoOrientation разные. UIImageOrientationUp относится к ландшафтному режиму, когда регулятор громкости вниз направлен к земле (не вверх, если вы думаете об использовании регуляторов громкости в качестве кнопки спуска затвора).

Таким образом, портретная ориентация с кнопкой питания, направленной в небо (AVCaptureVideoOrientationPortrait), на самом деле равна UIImageOrientationLeft.

person mikeho    schedule 15.06.2015
comment
Это должно быть отмечено как правильный ответ. Вы также можете использовать: connection.videoMirrored = true - person user3344977; 08.04.2016

Проблема с ориентацией связана с фронтальной камерой, поэтому проверьте тип устройства и сгенерируйте новое изображение, это определенно решит проблему с ориентацией:

-(void)capture:(void(^)(UIImage *))handler{

AVCaptureConnection *videoConnection = nil;
for (AVCaptureConnection *connection in self.stillImageOutput.connections)
{
    for (AVCaptureInputPort *port in [connection inputPorts])
    {
        if ([[port mediaType] isEqual:AVMediaTypeVideo] )
        {
            videoConnection = connection;
            break;
        }
    }
    if (videoConnection) { break; }
}

[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error) {

    if (imageSampleBuffer != NULL) {
        NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
        **UIImage *capturedImage = [UIImage imageWithData:imageData];
        if (self.captureDevice == [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo][1]) {
            capturedImage = [[UIImage alloc] initWithCGImage:capturedImage.CGImage scale:1.0f orientation:UIImageOrientationLeftMirrored];
        }**

        handler(capturedImage);
    }
}];
}
person Sandip    schedule 18.10.2014

Прежде всего, в конфигурации вашего видеовыхода поместите следующие строки:

guard let connection = videoOutput.connection(withMediaType: 
AVFoundation.AVMediaTypeVideo) else { return }
guard connection.isVideoOrientationSupported else { return }
guard connection.isVideoMirroringSupported else { return }
connection.videoOrientation = .portrait
connection.isVideoMirrored = position == .front

Затем настройте Target так, чтобы он поддерживал только Portait, сняв отметку с ландшафтных режимов в общей конфигурации.

(Источник)

person Laura Corssac    schedule 21.10.2018

// #1
AVCaptureVideoOrientation newOrientation = AVCaptureVideoOrientationLandscapeRight;
if (@available(iOS 13.0, *)) {
    // #2
    for (AVCaptureConnection *connection in [captureSession connections]) {
        if ([connection isVideoOrientationSupported]) {
            connection.videoOrientation = newOrientation;
            break;
        }
    } // #3
} else if ([previewLayer.connection isVideoOrientationSupported]) {
    previewLayer.connection.videoOrientation = newOrientation;
}

После этого вы сможете правильно использовать свой AVCaptureSession, вы сможете установить ориентацию видео. Вот подробное описание кода выше. Помните, что этот код должен быть выполнен после выполнения [captureSession startRunning]:

  1. Выберите ориентацию, которая вам больше нравится
  2. Для версии ios> = 13.0 вам необходимо получить активное соединение из captureSession. Помните: videoOrientation поддерживает только видеосоединение
  3. Для ios версии ‹13.0 можно использовать подключение от previewLayer

Если ваш viewController не имеет фиксированной ориентации, вы можете установить новое videoOrientation для вашего соединения после изменения ориентации устройства.

person Danilo Raspa    schedule 07.04.2020

Вы можете попробовать это:

private func startLiveVideo() {

    let captureSession = AVCaptureSession()
    captureSession.sessionPreset = .photo
    let captureDevice = AVCaptureDevice.default(for: .video)

    let input = try! AVCaptureDeviceInput(device: captureDevice!)
    let output = AVCaptureVideoDataOutput()
    captureSession.addInput(input)
    captureSession.addOutput(output)

    output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue"))
    output.connection(with: .video)?.videoOrientation = .portrait

    let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
    previewLayer.frame = view.bounds
    view.layer.addSublayer(previewLayer)

    captureSession.startRunning()
}
person zyc    schedule 16.11.2017
comment
Вы можете объяснить, почему это сработает? Потому что, когда я просто пытаюсь это сделать, это не так. - person Koen.; 07.03.2018
comment
Вы вызывали приватную функцию startLiveVideo ()? В чем проблема? - person zyc; 14.03.2018