Как превратить CVPixelBuffer в UIImage?
У меня возникли некоторые проблемы с получением UIIMage от CVPixelBuffer. Вот что я пытаюсь сделать:
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(imageDataSampleBuffer);
CFDictionaryRef attachments = CMCopyDictionaryOfAttachments(kCFAllocatorDefault, imageDataSampleBuffer, kCMAttachmentMode_ShouldPropagate);
CIImage *ciImage = [[CIImage alloc] initWithCVPixelBuffer:pixelBuffer options:(NSDictionary *)attachments];
if (attachments)
CFRelease(attachments);
size_t width = CVPixelBufferGetWidth(pixelBuffer);
size_t height = CVPixelBufferGetHeight(pixelBuffer);
if (width && height) { // test to make sure we have valid dimensions
UIImage *image = [[UIImage alloc] initWithCIImage:ciImage];
UIImageView *lv = [[UIImageView alloc] initWithFrame:self.view.frame];
lv.contentMode = UIViewContentModeScaleAspectFill;
self.lockedView = lv;
[lv release];
self.lockedView.image = image;
[image release];
}
[ciImage release];
height
и width
оба правильно настроены на разрешение камеры. image
создается, но мне кажется, что он черный (или, может быть, прозрачный?). Я не совсем понимаю, в чем проблема. Любые идеи будут оценены по достоинству.
5 ответов:
Прежде всего очевидные вещи, которые не имеют прямого отношения к вашему вопросу:
Я должен признать, что мне не хватает уверенности в главном вопросе. Существует семантическая разница между aAVCaptureVideoPreviewLayer
- это самый дешевый способ передать видео с любой из камер в независимое представление, если это то, откуда поступают данные, и у вас нет немедленных планов по их изменению. Вам не нужно ничего делать самостоятельно, слой предварительного просмотра напрямую связан сAVCaptureSession
и обновляет себя.CIImage
и два других типа изображений - ACIImage
является рецептом для изображения и не обязательно подкрепляется пикселями. Это может быть что-то вроде "возьмите пиксели отсюда, преобразуйте вот так, примените этот фильтр, преобразуйте вот так, объедините с этим другим изображением, примените этот фильтр". Система не знает, как выглядитCIImage
, пока вы не решите отрисовать его. Он также по своей сути не знает соответствующих границ, в которых его можно растеризировать.
UIImage
имеет целью просто обернутьCIImage
. Оно не преобразует его в пиксели. ПредположительноUIImageView
должен достичь этого, но если это так, то я не могу найти, где бы вы предоставили соответствующий выходной прямоугольник.У меня был успех, просто уклоняясь от проблемы с:
CIImage *ciImage = [CIImage imageWithCVPixelBuffer:pixelBuffer]; CIContext *temporaryContext = [CIContext contextWithOptions:nil]; CGImageRef videoImage = [temporaryContext createCGImage:ciImage fromRect:CGRectMake(0, 0, CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer))]; UIImage *uiImage = [UIImage imageWithCGImage:videoImage]; CGImageRelease(videoImage);
With дает очевидную возможность указать выходной прямоугольник. Я уверен, что есть маршрут без использования
CGImage
в качестве посредника, поэтому, пожалуйста, не думайте, что это решение является лучшей практикой.
Еще один способ получить UIImage. Выполняет ~10 раз быстрее, по крайней мере в моем случае:
int w = CVPixelBufferGetWidth(pixelBuffer); int h = CVPixelBufferGetHeight(pixelBuffer); int r = CVPixelBufferGetBytesPerRow(pixelBuffer); int bytesPerPixel = r/w; unsigned char *buffer = CVPixelBufferGetBaseAddress(pixelBuffer); UIGraphicsBeginImageContext(CGSizeMake(w, h)); CGContextRef c = UIGraphicsGetCurrentContext(); unsigned char* data = CGBitmapContextGetData(c); if (data != NULL) { int maxY = h; for(int y = 0; y<maxY; y++) { for(int x = 0; x<w; x++) { int offset = bytesPerPixel*((w*y)+x); data[offset] = buffer[offset]; // R data[offset+1] = buffer[offset+1]; // G data[offset+2] = buffer[offset+2]; // B data[offset+3] = buffer[offset+3]; // A } } } UIImage *img = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext();
Если ваши данные изображения не находятся в каком - то другом формате, который требует swizzle или преобразования-я бы не рекомендовал увеличивать что-либо... просто поместите данные в область контекстной памяти с помощью memcpy, как в:
//not here... unsigned char *buffer = CVPixelBufferGetBaseAddress(pixelBuffer); UIGraphicsBeginImageContext(CGSizeMake(w, h)); CGContextRef c = UIGraphicsGetCurrentContext(); void *ctxData = CGBitmapContextGetData(c); // MUST READ-WRITE LOCK THE PIXEL BUFFER!!!! CVPixelBufferLockBaseAddress(pixelBuffer, 0); void *pxData = CVPixelBufferGetBaseAddress(pixelBuffer); memcpy(ctxData, pxData, 4 * w * h); CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); ... and so on...
Попробуйте это в Swift.
extension UIImage { public convenience init?(pixelBuffer: CVPixelBuffer) { var cgImage: CGImage? VTCreateCGImageFromCVPixelBuffer(pixelBuffer, nil, &cgImage) if let cgImage = cgImage { self.init(cgImage: cgImage) } else { return nil } } }
Примечание: это работает только для буферов пикселей RGB, а не для оттенков серого.
Предыдущие способы не привели меня к у ЗБ растровых утечки данных. Этот метод преобразования не утек для меня:
@autoreleasepool { CGImageRef cgImage = NULL; OSStatus res = CreateCGImageFromCVPixelBuffer(pixelBuffer,&cgImage); if (res == noErr){ UIImage *image= [UIImage imageWithCGImage:cgImage scale:1.0 orientation:UIImageOrientationUp]; } CGImageRelease(cgImage); } static OSStatus CreateCGImageFromCVPixelBuffer(CVPixelBufferRef pixelBuffer, CGImageRef *imageOut) { OSStatus err = noErr; OSType sourcePixelFormat; size_t width, height, sourceRowBytes; void *sourceBaseAddr = NULL; CGBitmapInfo bitmapInfo; CGColorSpaceRef colorspace = NULL; CGDataProviderRef provider = NULL; CGImageRef image = NULL; sourcePixelFormat = CVPixelBufferGetPixelFormatType( pixelBuffer ); if ( kCVPixelFormatType_32ARGB == sourcePixelFormat ) bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipFirst; else if ( kCVPixelFormatType_32BGRA == sourcePixelFormat ) bitmapInfo = kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst; else return -95014; // only uncompressed pixel formats sourceRowBytes = CVPixelBufferGetBytesPerRow( pixelBuffer ); width = CVPixelBufferGetWidth( pixelBuffer ); height = CVPixelBufferGetHeight( pixelBuffer ); CVPixelBufferLockBaseAddress( pixelBuffer, 0 ); sourceBaseAddr = CVPixelBufferGetBaseAddress( pixelBuffer ); colorspace = CGColorSpaceCreateDeviceRGB(); CVPixelBufferRetain( pixelBuffer ); provider = CGDataProviderCreateWithData( (void *)pixelBuffer, sourceBaseAddr, sourceRowBytes * height, ReleaseCVPixelBuffer); image = CGImageCreate(width, height, 8, 32, sourceRowBytes, colorspace, bitmapInfo, provider, NULL, true, kCGRenderingIntentDefault); if ( err && image ) { CGImageRelease( image ); image = NULL; } if ( provider ) CGDataProviderRelease( provider ); if ( colorspace ) CGColorSpaceRelease( colorspace ); *imageOut = image; return err; } static void ReleaseCVPixelBuffer(void *pixel, const void *data, size_t size) { CVPixelBufferRef pixelBuffer = (CVPixelBufferRef)pixel; CVPixelBufferUnlockBaseAddress( pixelBuffer, 0 ); CVPixelBufferRelease( pixelBuffer ); }