2012-11-02 28 views
8

Tôi đã cố gắng trả lời điều này trong chuỗi ban đầu nhưng SO không cho phép tôi. Hy vọng rằng một người có thẩm quyền hơn có thể hợp nhất điều này vào câu hỏi ban đầu.Làm thế nào để chuyển đổi bộ đệm kCVPixelFormatType_420YpCbCr8BiPlanarFullRange sang UIImage trong iOS

OK đây là câu trả lời hoàn chỉnh hơn. Thứ nhất, thiết lập chụp:

// 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 bây giờ thực hiện cho các đại biểu/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]; 
    });   
} 

và cuối cùng ở đây là phương pháp để chuyển đổi từ YUV tới một 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; 
} 

Bạn cũng cần phải #import "Endian.h"

Lưu ý rằng cuộc gọi đến CGBitmapContextCrea te là phức tạp hơn nhiều mà tôi mong đợi. Tôi không hiểu lắm về xử lý video, tuy nhiên, cuộc gọi này đã làm tôi bối rối một lúc. Sau đó, khi nó cuối cùng đã làm việc nó giống như ma thuật.

+0

Tôi vừa dành hai ngày cuối cùng cố gắng viết một UIImage trong bộ đệm để thêm vào video Tôi hiểu sự phấn khích của bạn! –

+0

@NicolasManzini Giải pháp này có phù hợp với bạn không? Tôi đang nhận được ': copy_read_only: vm_copy không thành công: trạng thái 1. 'Có vẻ liên quan đến http://stackoverflow.com/questions/3367849/cgbitmapcontextcreateimage-vm-copy-failed-iphone-sdk –

+0

kiểm tra kích thước của bitmap của bạn bối cảnh có thể. nhưng tôi đã làm theo cách khác xung quanh CGContextDrawImage (...) –

Trả lời

2

Thông tin cơ bản: Phiên bản @ Michaelg chỉ truy cập bộ đệm y nên bạn chỉ nhận được độ sáng và không phải màu. Nó cũng có lỗi tràn bộ đệm nếu độ cao trong vùng đệm và số pixel không khớp (đệm byte ở cuối dòng vì bất kỳ lý do gì). Bối cảnh về những gì đang xảy ra ở đây là đây là định dạng hình ảnh phẳng, phân bổ một byte cho mỗi pixel cho độ sáng và 2 byte cho 4 pixel cho thông tin màu. Thay vì được lưu trữ liên tục trong bộ nhớ, chúng được lưu trữ dưới dạng "mặt phẳng" trong đó mặt phẳng Y hoặc độ sáng có khối bộ nhớ riêng và mặt phẳng màu hoặc CbCr cũng có khối bộ nhớ riêng. Mặt phẳng CbCr gồm 1/4 số mẫu (nửa chiều cao và chiều rộng) của mặt phẳng Y và mỗi điểm ảnh trong mặt phẳng CbCr tương ứng với khối 2x2 trong mặt phẳng Y. Hy vọng rằng nền tảng này sẽ giúp.

chỉnh sửa: Cả phiên bản và phiên bản cũ của tôi đều có khả năng tràn bộ đệm và sẽ không hoạt động nếu các hàng trong bộ đệm hình ảnh có byte đệm ở cuối mỗi hàng. Hơn nữa bộ đệm mặt phẳng cbcr của tôi không được tạo với độ lệch chính xác. Để làm điều này một cách chính xác, bạn nên luôn sử dụng các chức năng video lõi như CVPixelBufferGetWidthOfPlane và CVPixelBufferGetBaseAddressOfPlane. Điều này sẽ đảm bảo rằng bạn đang diễn giải chính xác bộ đệm và nó sẽ làm việc bất kể bộ đệm có một tiêu đề và liệu bạn có vặn lên phép toán con trỏ hay không. Bạn nên sử dụng các kích thước hàng từ các chức năng của Apple và địa chỉ cơ sở đệm từ các chức năng của chúng. Đây là những tài liệu tại: https://developer.apple.com/library/prerelease/ios/documentation/QuartzCore/Reference/CVPixelBufferRef/index.html Lưu ý rằng trong khi phiên bản này ở đây làm cho một số sử dụng các chức năng của Apple và một số sử dụng tiêu đề tốt nhất là chỉ sử dụng các chức năng của Apple. Tôi có thể cập nhật điều này trong tương lai để không sử dụng tiêu đề nào cả.

Điều này sẽ chuyển đổi bộ đệm đệm kcvpixelformattype_420ypcbcr8biplanarfullrange thành UIImage mà bạn có thể sử dụng.

Thứ nhất, thiết lập chụp:

// 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 bây giờ thực hiện cho các đại biểu/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]; 
    });   
} 

và cuối cùng ở đây là phương pháp để chuyển đổi từ YUV tới một 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ạn cũng cần phải #import "Endian.h" và xác định #define clamp(a) (a>255?255:(a<0?0:a));

Lưu ý rằng cuộc gọi đến CGBitmapContextCreate phức tạp hơn nhiều so với tôi mong đợi. Tôi không hiểu lắm về xử lý video, tuy nhiên, cuộc gọi này đã làm tôi bối rối một lúc. Sau đó, khi nó cuối cùng đã làm việc nó giống như ma thuật.

+0

Mã này không hoạt động nếu bạn thay đổi 'videoOrientation' trong' AVCaptureConnection'. Kiểm tra [câu trả lời này] (http://stackoverflow.com/a/31553521/16) để biết thêm thông tin. –

Các vấn đề liên quan