UIImage, созданный из CMSampleBufferRef, не отображается в UIImageView? - PullRequest
23 голосов
/ 22 июля 2010

Я пытаюсь отобразить UIImage в режиме реального времени с камеры, и кажется, что мой UIImageView не отображает изображение должным образом. Это метод, который AVCaptureVideoDataOutputSampleBufferDelegate должен реализовать

- (void)captureOutput:(AVCaptureOutput *)captureOutput 
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer 
       fromConnection:(AVCaptureConnection *)connection
{ 
    // Create a UIImage from the sample buffer data
    UIImage *theImage = [self imageFromSampleBuffer:sampleBuffer];
//    NSLog(@"Got an image! %f %f", theImage.size.width, theImage.size.height);
//    NSLog(@"The image view is %@", imageView);
//    UIImage *theImage = [[UIImage alloc] initWithData:[NSData 
//        dataWithContentsOfURL:[NSURL 
//        URLWithString:@"http://farm4.static.flickr.com/3092/2915896504_a88b69c9de.jpg"]]];
    [self.session stopRunning];
    [imageView setImage: theImage];
}

Чтобы избавиться от простых проблем:

  • Использование UIImagePickerController не является опцией (в конце концов мы действительно будем делать что-то с изображением)
  • Я знаю, что вызывается обработчик (сделаны вызовы NSLog, и я вижу вывод)
  • Я знаю, что объявления IBOutlet настроены правильно. Если я использую приведенный выше код для загрузки произвольного изображения из Интернета вместо простой отправки setImage:theImage в imageView, изображение будет загружено правильно (и второй вызов NSLog сообщает об отличном от nil объекте).
  • По крайней мере, в базовой степени, изображение, которое я получаю с imageFromSampleBuffer:, в порядке, поскольку NSLog сообщает, что его размер равен 360x480, что является ожидаемым размером.

Код, который я использую, - это недавно опубликованный фрагмент AVFoundation от Apple, доступный здесь .

В частности, я использую код, который устанавливает объект AVCaptureSession и друзей (о которых я очень мало понимаю) и создает объект UIImage из буферов Core Video (это метод imageFromSampleBuffer).

Наконец, я могу заставить приложение аварийно завершить работу, если я попытаюсь отправить drawInRect: в простой подкласс UIView с UIImage, возвращаемым imageFromSamplerBuffer, в то время как оно не падает, если я использую UIImage из URL, как указано выше. Вот трассировка стека отладчика внутри сбоя (я получаю сигнал EXC_BAD_ACCESS):

#0  0x34a977ee in decode_swap ()
#1  0x34a8f80e in decode_data ()
#2  0x34a8f674 in img_decode_read ()
#3  0x34a8a76e in img_interpolate_read ()
#4  0x34a63b46 in img_data_lock ()
#5  0x34a62302 in CGSImageDataLock ()
#6  0x351ab812 in ripc_AcquireImage ()
#7  0x351a8f28 in ripc_DrawImage ()
#8  0x34a620f6 in CGContextDelegateDrawImage ()
#9  0x34a61fb4 in CGContextDrawImage ()
#10 0x321fd0d0 in -[UIImage drawInRect:blendMode:alpha:] ()
#11 0x321fcc38 in -[UIImage drawInRect:] ()

РЕДАКТИРОВАТЬ: Вот еще некоторая информация о UIImage, возвращаемом этим битом кода.

Используя описанный метод здесь , я могу добраться до пикселей и распечатать их, и они на первый взгляд выглядят нормально (например, каждое значение в альфа-канале равно 255). Однако, что-то немного не так с размерами буфера. Изображение, которое я получаю с Flickr с этого URL, имеет размер 375x500, и его [pixelData length] дает мне 750000 = 375*500*4, что является ожидаемым значением. Однако пиксельные данные изображения, возвращаемые из imageFromSampleBuffer:, имеют размер 691208 = 360*480*4 + 8, поэтому в данных пикселей есть 8 дополнительных байтов. CVPixelBufferGetDataSize само возвращает это значение off-by-8. На мгновение я подумал, что это может быть связано с размещением буферов в совмещенных позициях в памяти, но 691200 кратно 256, так что это тоже не объясняется. Это несоответствие размеров - единственное различие, которое я могу сказать между двумя UIImages, и оно может быть причиной проблемы. Тем не менее, нет никаких причин выделять дополнительные памяти для буфера, что должно вызвать нарушение EXC_BAD_ACCESS.

Большое спасибо за любую помощь, и дайте мне знать, если вам нужна дополнительная информация.

Ответы [ 5 ]

40 голосов
/ 26 июля 2010

У меня была такая же проблема ... но я нашел этот старый пост, и его метод создания CGImageRef работает!

http://forum.unity3d.com/viewtopic.php?p=300819

Вот рабочий пример:

app has a member UIImage theImage;

// Delegate routine that is called when a sample buffer was written
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer  fromConnection:(AVCaptureConnection *)connection
{
        //... just an example of how to get an image out of this ...

    CGImageRef cgImage = [self imageFromSampleBuffer:sampleBuffer];
    theImage.image =     [UIImage imageWithCGImage: cgImage ];
    CGImageRelease( cgImage );
}

- (CGImageRef) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer // Create a CGImageRef from sample buffer data
{
    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); 
    CVPixelBufferLockBaseAddress(imageBuffer,0);        // Lock the image buffer 

    uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);   // Get information of the image 
    size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); 
    size_t width = CVPixelBufferGetWidth(imageBuffer); 
    size_t height = CVPixelBufferGetHeight(imageBuffer); 
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 

    CGContextRef newContext = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst); 
    CGImageRef newImage = CGBitmapContextCreateImage(newContext); 
    CGContextRelease(newContext); 

    CGColorSpaceRelease(colorSpace); 
    CVPixelBufferUnlockBaseAddress(imageBuffer,0); 
    /* CVBufferRelease(imageBuffer); */  // do not call this!

    return newImage;
}
10 голосов
/ 02 февраля 2012

Оперативный захват видеокадров теперь хорошо объясняется техническими вопросами и ответами Apple QA1702:

https://developer.apple.com/library/ios/#qa/qa1702/_index.html

7 голосов
/ 19 октября 2012

Также важно установить правильный формат вывода. У меня была проблема с захватом изображения при использовании настроек формата по умолчанию. Должно быть:

[videoDataOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA] forKey:(NSString*)kCVPixelBufferPixelFormatTypeKey]];
5 голосов
/ 27 июля 2010

У Бен Лулье есть хорошая статья о том, как это сделать.

Я использую его пример приложения в качестве отправной точки, и он работает для меня. Наряду с заменой функции imageFromSamplerBuffer чем-то, что создает CGImageRef с CGBitmapContextCreate, он использует основную очередь отправки (через dispatch_get_main_queue()) при настройке делегата буфера выходного образца. Это не лучшее решение, потому что ему нужна последовательная очередь, и, насколько я понимаю, основная очередь не является последовательной очередью. Поэтому, хотя вы не гарантированно получите кадры в правильном порядке, мне кажется, что пока это работает:)

1 голос
/ 27 марта 2015

Еще одна вещь, на которую стоит обратить внимание, - действительно ли вы обновляете свой UIImageView в главном потоке: если это не так, скорее всего, он не отразит никаких изменений.

captureOutput:didOutputSampleBuffer:fromConnection метод делегата часто вызывается в фоновом потоке.Итак, вы хотите сделать это:

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

    [[NSOperationQueue mainQueue] addOperationWithBlock:^{

        //Now we're definitely on the main thread, so update the imageView:
        UIImage *capturedImage = [self imageFromSampleBuffer:sampleBuffer];

        //Display the image currently being captured:
        imageView.image = capturedImage;

        CFRelease(sampleBuffer);
    }];
}
...