2012-11-02 17 views
8

Próbowałem odpowiedzieć na to w oryginalnym wątku, jednak SO nie dałoby mi. Mam nadzieję, że ktoś z większą władzą może połączyć to z pierwotnym pytaniem.Jak przekonwertować bufor kCVPixelFormatType_420YpCbCr8BiPlanarFullRange na UIImage w iOS

OK tutaj jest bardziej kompletna odpowiedź. Po pierwsze, ustawienia przechwytywania:

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

i wreszcie tutaj jest metoda przekonwertować z YUV do 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; 
} 

Konieczne będzie również #import "Endian.h"

Należy pamiętać, że połączenie z CGBitmapContextCrea Te są o wiele trudniejsze, niż się spodziewałem. Nie jestem bardzo doświadczony w przetwarzaniu wideo, jednak ta rozmowa przez chwilę mnie zaskakiwała. Potem, kiedy w końcu zadziałało, było jak magia.

+0

Właśnie spędziłem ostatnie dwa dni próbując napisać UIImage w buforze, aby dodać do filmu Rozumiem twoje podniecenie! –

+0

@NicolasManzini Czy to rozwiązanie działa? Otrzymuję ': copy_read_only: vm_copy nie powiodło się: status 1." Wygląda związane z http://stackoverflow.com/questions/3367849/cgbitmapcontextcreateimage-vm-copy-failed-iphone-sdk –

+0

sprawdź rozmiar swojej bitmapy może kontekst. ale robiłem na odwrót CGContextDrawImage (...) –

Odpowiedz

2

Informacje dodatkowe: Wersja @ Michaelg ma tylko dostęp do bufora y, dzięki czemu uzyskujesz luminancję, a nie kolor. Ma również błąd przekroczenia bufora, jeśli wysokość buforów i liczba pikseli nie pasują do siebie (dopełnianie bajtów na końcu linii z jakiegokolwiek powodu). Podstawą tego, co się tu dzieje, jest to, że jest to płaski format obrazu, który przydziela 1 bajt na piksel dla luminancji i 2 bajty na 4 piksele dla informacji o kolorze. Zamiast zapisywania w pamięci w sposób ciągły, są one przechowywane jako "płaszczyzny", w których płaszczyzna Y lub luminancja ma swój własny blok pamięci, a CbCr lub płaszczyzna koloru ma również własny blok pamięci. Płaszczyzna CbCr składa się z 1/4 liczby próbek (połowa wysokości i szerokości) płaszczyzny Y, a każdy piksel w płaszczyźnie CbCr odpowiada blokowi 2x2 na płaszczyźnie Y. Mam nadzieję, że to tło pomaga.

edytuj: zarówno jego wersja, jak i moja stara wersja, miały potencjał do przekroczenia buforów i nie działałyby, gdyby wiersze w buforze obrazu zawierały dopełnienie bajtów na końcu każdego wiersza. Ponadto mój bufor płaszczyzny bufora nie został utworzony z poprawnym przesunięciem. Aby to zrobić poprawnie, zawsze powinieneś używać podstawowych funkcji wideo, takich jak CVPixelBufferGetWidthOfPlane i CVPixelBufferGetBaseAddressOfPlane. Zapewni to poprawną interpretację bufora i będzie działać bez względu na to, czy bufor ma nagłówek i czy pominiesz wskaźnik matematyki. Powinieneś także użyć rozmiarów wierszy z funkcji Apple i adresu bazy bufora z ich funkcji. Są one udokumentowane pod adresem: https://developer.apple.com/library/prerelease/ios/documentation/QuartzCore/Reference/CVPixelBufferRef/index.html Należy zauważyć, że chociaż ta wersja tutaj wykorzystuje niektóre funkcje Apple i niektóre wykorzystanie nagłówka, najlepiej jest używać tylko funkcji Apple. Mogę to zaktualizować w przyszłości, aby w ogóle nie używać nagłówka.

Spowoduje to przekształcenie bufora bufora kcvpixelformattype_420ypcbcr8biplanarfullrange na UIImage, którego można następnie użyć.

pierwsze, konfiguracja przechwytywania:

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

i wreszcie tutaj jest metoda przekonwertować z YUV do 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; 
} 

Będziesz także potrzebować #import "Endian.h" i zdefiniować #define clamp(a) (a>255?255:(a<0?0:a));

Należy zauważyć, że wywołanie funkcji CGBitmapContextCreate jest znacznie trudniejsze, niż się spodziewałem. Nie jestem bardzo doświadczony w przetwarzaniu wideo, jednak ta rozmowa przez chwilę mnie zaskakiwała. Potem, kiedy w końcu zadziałało, było jak magia.

+0

Ten kod nie działa, jeśli zmienisz "videoOrientation" w "AVCaptureConnection". Sprawdź [tę odpowiedź] (http://stackoverflow.com/a/31553521/16), aby uzyskać więcej informacji. –