UIImage, созданный из CMSampleBufferRef, не отображается в UIImageView?
Я пытаюсь отобразить 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 сообщает о не ноль объекте). - По крайней мере, в основном, изображение, которое я получаю от
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 ответов
У меня была такая же проблема... но я нашел этот старый пост, и его метод создания 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;
}
Оперативный захват видеокадров теперь хорошо объясняется техническими вопросами и ответами Apple QA1702:
Также важно установить правильный формат вывода. У меня была проблема с захватом изображения при использовании настроек формата по умолчанию. Так должно быть:
[videoDataOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA] forKey:(NSString*)kCVPixelBufferPixelFormatTypeKey]];
Бен Лульер хорошо написал, как это сделать.
Я использую его пример приложения в качестве отправной точки, и он работает для меня. Наряду с заменой imageFromSamplerBuffer
функция с чем-то, что создает CGImageRef с CGBitmapContextCreate, он использует основную очередь отправки (через dispatch_get_main_queue()
) при настройке выходного буфера выборочного делегата. Это не лучшее решение, потому что ему нужна последовательная очередь, и, насколько я понимаю, основная очередь не является последовательной очередью. Так что, хотя вы не гарантированно получите кадры в правильном порядке, мне кажется, это пока работает:)
Другая вещь, на которую стоит обратить внимание, это то, обновляете ли вы 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);
}];
}