2012-11-02 7 views
8

Ich versuchte, dies im ursprünglichen Thread zu beantworten, aber SO würde mich nicht lassen. Hoffentlich kann jemand mit mehr Autorität dies in die ursprüngliche Frage einbringen.Wie konvertiert man einen kCVPixelFormatType_420YpCbCr8BiPlanarFullRange Puffer zu UIImage in iOS

OK hier ist eine vollständigere Antwort. Zuerst Setup die Erfassung:

// 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 jetzt die Implementierung für die Delegierten/Rückruf:

- (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 captured image for DEBUG purposes 
    dispatch_async(dispatch_get_main_queue(), ^{ 
     [_myMainView.yUVImage setImage:_prefImageView.image]; 
    });   
} 

und schließlich hier ist die Methode von YUV auf ein UIImage zu konvertieren

- (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; 
} 

Sie müssen auch #import "Endian.h"

Beachten Sie, dass der Aufruf von CGBitmapContextCrea te ist viel schwieriger, als ich erwartet habe. Ich bin nicht sehr versiert auf Videoverarbeitung überhaupt, aber dieser Anruf hat mich für eine Weile ratlos. Dann, als es endlich funktionierte, war es wie Magie.

+0

Ich habe gerade meine letzten zwei Tage damit verbracht, einen UIImage in einem Puffer zu schreiben, um ein Video hinzuzufügen. Ich verstehe deine Aufregung! –

+0

@NicolasManzini Funktioniert diese Lösung für Sie? Ich bekomme immer ': copy_read_only: vm_copy failed: status 1.' Sieht im Zusammenhang mit http://stackoverflow.com/questions/3367849/cgbitmapcontextcreateimage-vm-copy-failed-iphone-sdk –

+0

Überprüfen Sie die Größe Ihrer Bitmap Kontext vielleicht. aber ich habe es andersherum gemacht CGContextDrawImage (...) –

Antwort

2

Hintergrundinfo: @ Michaelgs Version greift nur auf den Y-Puffer zu, so dass Sie nur Luminanz und nicht Farbe erhalten. Es hat auch einen Buffer-Overrun-Bug, wenn die Tonhöhe in den Puffern und die Anzahl der Pixel nicht übereinstimmen (Füll-Bytes am Ende einer Zeile, aus welchem ​​Grund auch immer). Der Hintergrund zu dem, was hier passiert, ist, dass dies ein planares Bildformat ist, das ein Byte pro Pixel für die Luminanz und 2 Bytes pro 4 Pixel für die Farbinformation zuweist. Diese werden nicht ständig im Speicher gespeichert, sondern als "Ebenen" gespeichert, wobei die Y- oder Luminanzebene einen eigenen Speicherblock hat und die CbCr- oder Farbebene auch einen eigenen Speicherblock hat. Die CbCr-Ebene besteht aus 1/4 der Anzahl der Abtastungen (halbe Höhe und Breite) der Y-Ebene und jedes Pixel in der CbCr-Ebene entspricht einem 2x2-Block in der Y-Ebene. Hoffentlich hilft dieser Hintergrund.

edit: Sowohl seine Version als auch meine alte Version hatten das Potenzial, Puffer zu überlaufen und würde nicht funktionieren, wenn die Zeilen im Bildpuffer Padding Bytes am Ende jeder Zeile haben. Außerdem wurde mein cbcr-Ebenenpuffer nicht mit dem korrekten Offset erstellt. Um dies richtig zu machen, sollten Sie immer die Kern-Video-Funktionen wie CVPixelBufferGetWidthOfPlane und CVPixelBufferGetBaseAddressOfPlane verwenden. Dies stellt sicher, dass Sie den Puffer richtig interpretieren, und es wird funktionieren, unabhängig davon, ob der Puffer einen Header hat und ob Sie die Zeigermathematik vermasseln. Sie sollten die Zeilengrößen von Apples Funktionen und die Pufferbasisadresse auch von ihren Funktionen verwenden. Diese sind dokumentiert unter: https://developer.apple.com/library/prerelease/ios/documentation/QuartzCore/Reference/CVPixelBufferRef/index.html Beachten Sie, dass während diese Version hier einige Funktionen von Apple und einige Verwendung der Kopfzeile nutzt, es am besten ist, nur Apples Funktionen zu verwenden. Ich kann das in Zukunft aktualisieren, um den Header überhaupt nicht zu verwenden.

Dies wird einen kcvpixelformattype_420ypcbcr8biplanarfullrange Puffer Puffer in eine UIImage konvertieren, die Sie dann verwenden können.

Zuerst Setup die Erfassung:

// 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 jetzt die Implementierung für die Delegierten/Rückruf:

- (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 captured image for DEBUG purposes 
    dispatch_async(dispatch_get_main_queue(), ^{ 
     [_myMainView.yUVImage setImage:_prefImageView.image]; 
    });   
} 

und schließlich hier ist die Methode von YUV auf ein UIImage zu konvertieren

- (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; 
} 

Sie müssen auch #import "Endian.h" und die definieren #define clamp(a) (a>255?255:(a<0?0:a));

Beachten Sie, dass der Aufruf von CGBitmapContextCreate viel komplizierter ist, als ich erwartet habe. Ich bin nicht sehr versiert auf Videoverarbeitung überhaupt, aber dieser Anruf hat mich für eine Weile ratlos. Dann, als es endlich funktionierte, war es wie Magie.

+0

Dieser Code funktioniert nicht, wenn Sie 'videoOrientation' in' AVCaptureConnection' ändern. Überprüfen Sie [diese Antwort] (http://stackoverflow.com/a/31553521/16) für weitere Informationen. –