Как превратить 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
, но я, кажется, черный (или, может быть, прозрачный?). Я не могу понять, где проблема. Любые идеи будут оценены.
Ответы
Ответ 1
Прежде всего очевидный материал, который напрямую не связан с вашим вопросом: AVCaptureVideoPreviewLayer
- самый дешевый способ передачи видео с любой из камер на независимое представление, если это происходит, и вы нет немедленных планов по его изменению. Вам не нужно нажимать на себя, слой предварительного просмотра напрямую подключается к AVCaptureSession
и сам обновляет.
Я должен признать, что не доверяю центральному вопросу. Там семантическая разница между CIImage
и двумя другими типами изображений - a CIImage
является рецептом изображения и не обязательно поддерживается пикселями. Это может быть что-то вроде "взять пиксели отсюда, преобразовать вот так, применить этот фильтр, преобразовать, как это, объединить с этим другим изображением, применить этот фильтр". Система не знает, как выглядит 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);
С дает очевидную возможность указать выходной прямоугольник. Я уверен, что существует маршрут без использования CGImage
в качестве посредника, поэтому, пожалуйста, не думайте, что это решение является наилучшей практикой.
Ответ 2
Попробуйте это в Swift.
Swift 4.2:
import VideoToolbox
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
}
}
}
Свифт 5:
import VideoToolbox
extension UIImage {
public convenience init?(pixelBuffer: CVPixelBuffer) {
var cgImage: CGImage?
VTCreateCGImageFromCVPixelBuffer(pixelBuffer, options: nil, imageOut: &cgImage)
if let cgImage = cgImage {
self.init(cgImage: cgImage)
} else {
return nil
}
}
}
Примечание. Это работает только для пиксельных буферов RGB, но не для градаций серого.
Ответ 3
Другой способ получить 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();
Ответ 4
Если ваши данные изображения находятся в каком-то другом формате, который требует swizzle или conversion - я бы рекомендовал ничего не увеличивать... просто нанесите данные в область контекстной памяти с помощью 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...
Ответ 5
Предыдущие методы привели меня к утечке данных CG Raster Data. Этот метод конверсии не утечка для меня:
@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 );
}
Ответ 6
Современное решение будет
let image = UIImage(ciImage: CIImage(cvPixelBuffer: YOUR_BUFFER))