Cómo convertir un búfer kCVPixelFormatType_420YpCbCr8BiPlanarFullRange a UIImage en iOS

Traté de responder a esto en el hilo original sin embargo, SO no me lo permitió. Es de esperar que alguien con más autoridad pueda combinar esto en la pregunta original.

OK aquí hay una respuesta más completa. Primero, configure la captura:

// Create capture session self.captureSession = [[AVCaptureSession alloc] init]; [self.captureSession setSessionPreset:AVCaptureSessionPresetPhoto]; // Setup capture input self.inputDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; AVCaptureDeviceInput *captureInput = [AVCaptureDeviceInput deviceInputWithDevice:self.inputDevice error:nil]; [self.captureSession addInput:captureInput]; // Setup video processing (capture output) AVCaptureVideoDataOutput *captureOutput = [[AVCaptureVideoDataOutput alloc] init]; // Don't add frames to the queue if frames are already processing captureOutput.alwaysDiscardsLateVideoFrames = YES; // Create a serial queue to handle processing of frames _videoQueue = dispatch_queue_create("cameraQueue", NULL); [captureOutput setSampleBufferDelegate:self queue:_videoQueue]; // Set the video output to store frame in YUV NSString* key = (NSString*)kCVPixelBufferPixelFormatTypeKey; NSNumber* value = [NSNumber numberWithUnsignedInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange]; NSDictionary* videoSettings = [NSDictionary dictionaryWithObject:value forKey:key]; [captureOutput setVideoSettings:videoSettings]; [self.captureSession addOutput:captureOutput]; 

OK ahora la implementación para el delegado / callback:

 - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { // Create autorelease pool because we are not in the main_queue @autoreleasepool { CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); //Lock the imagebuffer CVPixelBufferLockBaseAddress(imageBuffer,0); // Get information about the image uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddress(imageBuffer); // size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); size_t width = CVPixelBufferGetWidth(imageBuffer); size_t height = CVPixelBufferGetHeight(imageBuffer); size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); CVPlanarPixelBufferInfo_YCbCrBiPlanar *bufferInfo = (CVPlanarPixelBufferInfo_YCbCrBiPlanar *)baseAddress; // This just moved the pointer past the offset baseAddress = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0); // convert the image _prefImageView.image = [self makeUIImage:baseAddress bufferInfo:bufferInfo width:width height:height bytesPerRow:bytesPerRow]; // Update the display with the captunetworking image for DEBUG purposes dispatch_async(dispatch_get_main_queue(), ^{ [_myMainView.yUVImage setImage:_prefImageView.image]; }); } 

y finalmente aquí está el método para convertir de YUV a un UIImage

 - (UIImage *)makeUIImage:(uint8_t *)inBaseAddress bufferInfo:(CVPlanarPixelBufferInfo_YCbCrBiPlanar *)inBufferInfo width:(size_t)inWidth height:(size_t)inHeight bytesPerRow:(size_t)inBytesPerRow { NSUInteger yPitch = EndianU32_BtoN(inBufferInfo->componentInfoY.rowBytes); uint8_t *rgbBuffer = (uint8_t *)malloc(inWidth * inHeight * 4); uint8_t *yBuffer = (uint8_t *)inBaseAddress; uint8_t val; int bytesPerPixel = 4; // for each byte in the input buffer, fill in the output buffer with four bytes // the first byte is the Alpha channel, then the next three contain the same // value of the input buffer for(int y = 0; y < inHeight*inWidth; y++) { val = yBuffer[y]; // Alpha channel rgbBuffer[(y*bytesPerPixel)] = 0xff; // next three bytes same as input rgbBuffer[(y*bytesPerPixel)+1] = rgbBuffer[(y*bytesPerPixel)+2] = rgbBuffer[y*bytesPerPixel+3] = val; } // Create a device-dependent RGB color space CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(rgbBuffer, yPitch, inHeight, 8, yPitch*bytesPerPixel, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedLast); CGImageRef quartzImage = CGBitmapContextCreateImage(context); CGContextRelease(context); CGColorSpaceRelease(colorSpace); UIImage *image = [UIImage imageWithCGImage:quartzImage]; CGImageRelease(quartzImage); free(rgbBuffer); return image; } 

También necesitarás #import "Endian.h"

Tenga en count que la llamada a CGBitmapContextCreate es mucho más complicada de lo que esperaba. No soy muy experta en el procesamiento de video, pero esta llamada me dejó perpleja por un time. Luego, cuando finalmente funcionó, era como magia.

Información de background: la versión de @ Michaelg solo tiene acceso al búfer y, de modo que solo obtiene luminosidad y no color. También tiene un error de desbordamiento de búfer si el tono en los búferes y la cantidad de píxeles no coinciden (relleno de bytes al final de una línea por cualquier motivo). El background de lo que está ocurriendo aquí es que se trata de un formatting de image plana que asigna un byte por píxel para la luminancia y 2 bytes por cada 4 píxeles para la información de color. En lugar de almacenarse continuamente en la memory, estos se almacenan como "planos", donde el plano de luminancia o Y tiene su propio bloque de memory y el plano de color o CbCr también tiene su propio bloque de memory. El plano CbCr consta de 1/4 el número de muestras (media altura y ancho) del plano Y y cada píxel en el plano CbCr corresponde a un bloque 2×2 en el plano Y. Con suerte este background ayuda.

editar: Tanto su versión como mi versión anterior tenían el potencial de anular los búferes y no funcionarían si las filas en el búfer de imágenes tienen bytes de relleno al final de cada fila. Además, mi búfer de plano cbcr no se creó con el desplazamiento correcto. Para hacer esto correctamente, siempre debe usar las funciones básicas de video, como CVPixelBufferGetWidthOfPlane y CVPixelBufferGetBaseAddressOfPlane. Esto asegurará que está interpretando correctamente el búfer y funcionará independientemente de si el búfer tiene un encabezado y si arruina el puntero matemático. Debería usar también los tamaños de fila de las funciones de Apple y la dirección base de la memory intermedia desde sus funciones. Estos están documentados en: https://developer.apple.com/library/prerelease/ios/documentation/QuartzCore/Reference/CVPixelBufferRef/index.html Tenga en count que si bien esta versión aquí utiliza las funciones de Apple y usa el encabezado lo mejor es usar solo las funciones de Apple. Puedo actualizar esto en el futuro para no usar el encabezado en absoluto.

Esto convertirá un kcvpixelformattype_420ypcbcr8biplanarfullrange búfer de almacenamiento intermedio en un UIImage que luego puede usar.

Primero, configure la captura:

 // Create capture session self.captureSession = [[AVCaptureSession alloc] init]; [self.captureSession setSessionPreset:AVCaptureSessionPresetPhoto]; // Setup capture input self.inputDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; AVCaptureDeviceInput *captureInput = [AVCaptureDeviceInput deviceInputWithDevice:self.inputDevice error:nil]; [self.captureSession addInput:captureInput]; // Setup video processing (capture output) AVCaptureVideoDataOutput *captureOutput = [[AVCaptureVideoDataOutput alloc] init]; // Don't add frames to the queue if frames are already processing captureOutput.alwaysDiscardsLateVideoFrames = YES; // Create a serial queue to handle processing of frames _videoQueue = dispatch_queue_create("cameraQueue", NULL); [captureOutput setSampleBufferDelegate:self queue:_videoQueue]; // Set the video output to store frame in YUV NSString* key = (NSString*)kCVPixelBufferPixelFormatTypeKey; NSNumber* value = [NSNumber numberWithUnsignedInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange]; NSDictionary* videoSettings = [NSDictionary dictionaryWithObject:value forKey:key]; [captureOutput setVideoSettings:videoSettings]; [self.captureSession addOutput:captureOutput]; 

OK ahora la implementación para el delegado / callback:

 - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { // Create autorelease pool because we are not in the main_queue @autoreleasepool { CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); //Lock the imagebuffer CVPixelBufferLockBaseAddress(imageBuffer,0); // Get information about the image uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddress(imageBuffer); // size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); size_t width = CVPixelBufferGetWidth(imageBuffer); size_t height = CVPixelBufferGetHeight(imageBuffer); size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); CVPlanarPixelBufferInfo_YCbCrBiPlanar *bufferInfo = (CVPlanarPixelBufferInfo_YCbCrBiPlanar *)baseAddress; //get the cbrbuffer base address uint8_t* cbrBuff = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1); // This just moved the pointer past the offset baseAddress = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0); // convert the image _prefImageView.image = [self makeUIImage:baseAddress cBCrBuffer:cbrBuff bufferInfo:bufferInfo width:width height:height bytesPerRow:bytesPerRow]; // Update the display with the captunetworking image for DEBUG purposes dispatch_async(dispatch_get_main_queue(), ^{ [_myMainView.yUVImage setImage:_prefImageView.image]; }); } 

y finalmente aquí está el método para convertir de YUV a un UIImage

 - (UIImage *)makeUIImage:(uint8_t *)inBaseAddress cBCrBuffer:(uint8_t*)cbCrBuffer bufferInfo:(CVPlanarPixelBufferInfo_YCbCrBiPlanar *)inBufferInfo width:(size_t)inWidth height:(size_t)inHeight bytesPerRow:(size_t)inBytesPerRow { NSUInteger yPitch = EndianU32_BtoN(inBufferInfo->componentInfoY.rowBytes); NSUInteger cbCrOffset = EndianU32_BtoN(inBufferInfo->componentInfoCbCr.offset); uint8_t *rgbBuffer = (uint8_t *)malloc(inWidth * inHeight * 4); NSUInteger cbCrPitch = EndianU32_BtoN(inBufferInfo->componentInfoCbCr.rowBytes); uint8_t *yBuffer = (uint8_t *)inBaseAddress; //uint8_t *cbCrBuffer = inBaseAddress + cbCrOffset; uint8_t val; int bytesPerPixel = 4; for(int y = 0; y < inHeight; y++) { uint8_t *rgbBufferLine = &rgbBuffer[y * inWidth * bytesPerPixel]; uint8_t *yBufferLine = &yBuffer[y * yPitch]; uint8_t *cbCrBufferLine = &cbCrBuffer[(y >> 1) * cbCrPitch]; for(int x = 0; x < inWidth; x++) { int16_t y = yBufferLine[x]; int16_t cb = cbCrBufferLine[x & ~1] - 128; int16_t cr = cbCrBufferLine[x | 1] - 128; uint8_t *rgbOutput = &rgbBufferLine[x*bytesPerPixel]; int16_t r = (int16_t)roundf( y + cr * 1.4 ); int16_t g = (int16_t)roundf( y + cb * -0.343 + cr * -0.711 ); int16_t b = (int16_t)roundf( y + cb * 1.765); //ABGR rgbOutput[0] = 0xff; rgbOutput[1] = clamp(b); rgbOutput[2] = clamp(g); rgbOutput[3] = clamp(r); } } // Create a device-dependent RGB color space CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); NSLog(@"ypitch:%lu inHeight:%zu bytesPerPixel:%d",(unsigned long)yPitch,inHeight,bytesPerPixel); NSLog(@"cbcrPitch:%lu",cbCrPitch); CGContextRef context = CGBitmapContextCreate(rgbBuffer, inWidth, inHeight, 8, inWidth*bytesPerPixel, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedLast); CGImageRef quartzImage = CGBitmapContextCreateImage(context); CGContextRelease(context); CGColorSpaceRelease(colorSpace); UIImage *image = [UIImage imageWithCGImage:quartzImage]; CGImageRelease(quartzImage); free(rgbBuffer); return image; } 

También necesitará #import "Endian.h" y la definición #define clamp(a) (a>255?255:(a<0?0:a));

Tenga en count que la llamada a CGBitmapContextCreate es mucho más complicada de lo que esperaba. No soy muy experta en el procesamiento de video, pero esta llamada me dejó perpleja por un time. Luego, cuando finalmente funcionó, era como magia.