2011-11-09 7 views
23

Tôi đang gặp một số sự cố khi tải UIIMage từ CVPixelBuffer. Đây là những gì tôi đang cố gắng:Làm cách nào để biến CVPixelBuffer thành UIImage?

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]; 

heightwidth đều thiết lập đúng với độ phân giải của máy ảnh. image được tạo nhưng tôi có vẻ là màu đen (hoặc có thể trong suốt?). Tôi hoàn toàn không thể hiểu được vấn đề ở đâu. Có những câu chuyện mới trên trang chủ.

+0

Bạn chắc chắn muốn có một CIImage ở giữa, ví dụ: bởi vì bạn sẽ ném một số CIFilters trung gian vào, hoặc nó sẽ được chấp nhận chỉ để đi CGBitmapContextCreate -> UIImage? – Tommy

+0

Bây giờ, tôi chỉ muốn hiển thị nó trong một cái nhìn và xem những gì tôi đang xử lý. Xuống đường, tôi muốn chơi với các điểm ảnh. – mahboudz

Trả lời

36

Trước tiên, mọi thứ rõ ràng không liên quan trực tiếp đến câu hỏi của bạn: AVCaptureVideoPreviewLayer là cách rẻ nhất để video từ một trong hai máy ảnh thành chế độ xem độc lập nếu đó là nơi dữ liệu đến và bạn không kế hoạch ngay lập tức sửa đổi nó. Bạn không cần phải làm gì để đẩy bản thân, lớp xem trước được kết nối trực tiếp với AVCaptureSession và tự cập nhật.

Tôi phải thừa nhận thiếu sự tự tin về câu hỏi trung tâm. Có sự khác biệt về ngữ nghĩa giữa một hình ảnh CIImage và hai loại hình ảnh khác - một CIImage là công thức cho hình ảnh và không nhất thiết phải được hỗ trợ bởi pixel. Nó có thể là một cái gì đó giống như "lấy các điểm ảnh từ đây, biến đổi như thế này, áp dụng bộ lọc này, biến đổi như thế này, hợp nhất với hình ảnh khác này, áp dụng bộ lọc này". Hệ thống không biết nội dung CIImage trông như thế nào cho đến khi bạn chọn hiển thị. Nó cũng không vốn đã biết các giới hạn thích hợp để rasterise nó.

UIImage purports chỉ để bọc CIImage. Nó không chuyển đổi nó thành pixel. Có lẽ UIImageView nên đạt được điều đó, nhưng nếu như vậy thì tôi dường như không thể tìm thấy nơi bạn muốn cung cấp hình chữ nhật đầu ra thích hợp.

Tôi đã thành công chỉ né tránh xung quanh vấn đề này với:

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); 

Với đưa ra một cơ hội rõ ràng để xác định hình chữ nhật đầu ra. Tôi chắc chắn có một lộ trình mà không cần sử dụng CGImage làm người trung gian, vì vậy, đừng cho rằng giải pháp này là phương pháp hay nhất.

+0

Cảm ơn, tôi sẽ thử. Lý do mà previewLayer không hữu ích là tôi cần độ phân giải cao hơn. Và lý do tôi đi với CIIImage thay vì một jpegreprententation là để xem liệu nén jpeg có thêm các tạo tác quan trọng không. Tôi có thể, trên thực tế, chọn để ở lại với jpeg nếu hiện vật là tối thiểu. – mahboudz

+0

Nó hoạt động. Cảm ơn! – mahboudz

11

Một cách khác để nhận được UIImage. Thực hiện ~ 10 lần nhanh hơn, ít nhất là trong trường hợp của tôi:

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(); 
+0

Bạn nên sử dụng một con trỏ tăng dần, điều đó sẽ giúp bạn tăng tốc độ cực nhanh cũng như – jjxtra

+6

Bạn sẽ cần phải chèn một cuộc gọi đến CVPixelBufferLockBaseAddress trước khi gọi tới CVPixelBufferGetBaseAddress và gọi CVPixelBufferUnlockBaseAddress sau khi sao chép dữ liệu. Ngoài ra, bạn có thể muốn xem xét sử dụng CVPixelBufferGetDataSize và memcpy() để thực hiện một bản sao khối dữ liệu duy nhất. –

8

Trừ khi dữ liệu hình ảnh của bạn là trong một số định dạng khác nhau mà đòi hỏi phải có sự gian lận hoặc chuyển đổi - tôi muốn giới thiệu không incrementing bất cứ điều gì ... chỉ smack các dữ liệu vào của bạn vùng nhớ ngữ cảnh với memcpy như sau:

//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... 
+0

Tôi đã tăng ~ 50% fps trên các thiết bị cũ hơn so với đường dẫn CGImageCreate. Cảm ơn! –

+2

Hãy cẩn thận mặc dù vì thường có đệm byte ở cuối hàng trong CVPixelBuffer. I E. CVPixelBufferGetBytesPerRow có thể nhiều hơn bạn mong đợi. Sau đó, đầu ra hình ảnh được sao chép của bạn sẽ trông hoàn toàn nghiêng. – Baxissimo

3

Các phương pháp trước đây đã dẫn tôi bị rò rỉ dữ liệu Raster CG. Phương pháp chuyển đổi này không bị rò rỉ cho tôi:

@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); 
    } 
0

Hãy thử cái này trong Swift.

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

Lưu ý: Đây chỉ làm việc cho bộ đệm pixel RGB, không phải cho màu xám.