2010-11-03 28 views
9

Xin chào, tôi đang cố truy cập dữ liệu thô từ máy ảnh iphone bằng AVCaptureSession. Tôi làm theo hướng dẫn do Apple cung cấp (link here).Cách lấy thành phần Y từ CMSampleBuffer là kết quả của AVCaptureSession?

Dữ liệu thô từ trình tạo mẫu ở định dạng YUV (Tôi có đúng ở định dạng khung hình thô không?), Cách lấy trực tiếp dữ liệu cho thành phần Y ra khỏi dữ liệu thô được lưu trữ trong trình tạo mẫu.

+1

Cả Brad Larson và Codo đã giúp tôi rất nhiều về vấn đề này. Với sự kết hợp của câu trả lời của họ, cuối cùng tôi có thể đạt được mục tiêu của mình. Cảm ơn bạn rất nhiều, Brad Larson và Codo! – Nihao

Trả lời

20

Khi thiết lập các AVCaptureVideoDataOutput mà trả về khung camera liệu, bạn có thể thiết lập các định dạng của khung sử dụng mã như sau:

[videoOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey:(id)kCVPixelBufferPixelFormatTypeKey]]; 

Trong trường hợp này một định dạng BGRA điểm ảnh được xác định (tôi đã sử dụng này để phù hợp với định dạng màu cho kết cấu OpenGL ES). Mỗi pixel ở định dạng đó có một byte cho màu xanh lam, xanh lục, đỏ và alpha, theo thứ tự đó. Đi với điều này làm cho nó dễ dàng để kéo ra các thành phần màu sắc, nhưng bạn hy sinh một hiệu suất nhỏ bằng cách cần phải thực hiện chuyển đổi từ không gian màu YUV bản địa máy ảnh.

Các không gian màu được hỗ trợ khác là kCVPixelFormatType_420YpCbCr8BiPlanarVideoRangekCVPixelFormatType_420YpCbCr8BiPlanarFullRange trên các thiết bị mới hơn và kCVPixelFormatType_422YpCbCr8 trên iPhone 3G. Hậu tố VideoRange hoặc FullRange chỉ đơn giản cho biết các byte được trả về từ 16 - 235 cho Y và 16 - 240 cho UV hay 0 - 255 đầy đủ cho mỗi thành phần.

Tôi tin rằng không gian màu mặc định được sử dụng bởi một cá thể AVCaptureVideoDataOutput là không gian màu phẳng YUV 4: 2: 0 (ngoại trừ trên iPhone 3G, trong đó YUV 4: 2: 2 xen kẽ). Điều này có nghĩa là có hai mặt phẳng dữ liệu hình ảnh chứa trong khung hình video, với mặt phẳng Y đến trước. Đối với mỗi pixel trong ảnh kết quả của bạn, có một byte cho giá trị Y tại điểm ảnh đó.

Bạn sẽ nhận được ít dữ liệu Y liệu này bằng cách thực hiện một cái gì đó như thế này trong đại biểu gọi lại của bạn:

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection 
{ 
    CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); 
    CVPixelBufferLockBaseAddress(pixelBuffer, 0); 

    unsigned char *rawPixelBase = (unsigned char *)CVPixelBufferGetBaseAddress(pixelBuffer); 

    // Do something with the raw pixels here 

    CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); 
} 

Sau đó bạn có thể tìm ra vị trí trong khung dữ liệu cho mỗi X, Y phối hợp vào hình ảnh và kéo byte ra tương ứng với thành phần Y tại tọa độ đó.

Mẫu FindMyiCone của Apple từ WWDC 2010 (có thể truy cập cùng với video) cho biết cách xử lý dữ liệu BGRA thô từ mỗi khung. Tôi cũng đã tạo một ứng dụng mẫu, bạn có thể tải xuống mã cho here, thực hiện color-based object tracking bằng cách sử dụng video trực tiếp từ máy ảnh của iPhone. Cả hai đều cho thấy cách xử lý dữ liệu pixel thô, nhưng cả hai đều không hoạt động trong không gian màu YUV.

+0

@ brad Larson: Liệu kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange (mặc định của iphone4) và YUV 420 có giống nhau không ?? –

+0

@Asta - Như tôi đã đề cập ở trên, 'kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange' trên iPhone 4 là vùng màu phẳng YUV 4: 2: 0. –

+0

Tôi có thêm một thẻ Question.My chỉ chấp nhận định dạng YUV420, nhưng định dạng 420YpCbCr8BiPlanarVideoRange (biplanar) dữ liệu Y (độ sáng) và dữ liệu CbCr (thông tin màu hoặc màu) nằm trong hai vùng bộ nhớ riêng biệt được gọi là mặt phẳng, Làm thế nào tôi có thể gửi đến codec của tôi? Bất kỳ cách nào để chuyển đổi planar duy nhất? Cho dù tôi phải sử dụng bất kỳ chuyển đổi tách nào –

16

Ngoài câu trả lời của Brad, và mã riêng của bạn, bạn muốn xem xét như sau:

Kể từ khi hình ảnh của bạn có hai máy bay riêng biệt, chức năng CVPixelBufferGetBaseAddress sẽ không trả về địa chỉ cơ sở của máy bay mà địa chỉ cơ sở của cấu trúc dữ liệu bổ sung. Nó có thể là do việc thực hiện hiện tại mà bạn nhận được một địa chỉ đủ gần với mặt phẳng đầu tiên để bạn có thể nhìn thấy hình ảnh. Nhưng đó là lý do nó chuyển dịch và có rác ở phía trên bên trái. Cách chính xác để nhận chiếc máy bay đầu tiên là:

unsigned char *rowBase = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0); 

Hàng trong ảnh có thể dài hơn chiều rộng của hình ảnh (do làm tròn). Đó là lý do tại sao có các hàm riêng biệt để nhận được chiều rộng và số byte cho mỗi hàng. Bạn không có vấn đề này vào lúc này. Nhưng điều đó có thể thay đổi với phiên bản iOS tiếp theo.Vì vậy, mã của bạn phải là:

int bufferHeight = CVPixelBufferGetHeight(pixelBuffer); 
int bufferWidth = CVPixelBufferGetWidth(pixelBuffer); 
int bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0); 
int size = bufferHeight * bytesPerRow ; 

unsigned char *pixel = (unsigned char*)malloc(size); 

unsigned char *rowBase = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0); 
memcpy (pixel, rowBase, size); 

Cũng xin lưu ý rằng mã của bạn sẽ thất bại thảm hại trên iPhone 3G.

+0

Cảm ơn bạn rất nhiều !. Điều đó làm việc cho tôi một cách hoàn hảo: D – Nihao

+0

Nếu đó không phải là CVPixelBufferGetHeightOfPlane? Chỉ tò mò thôi. – akaru

+0

Vì chúng ta biết rằng mặt phẳng Y có cùng số điểm ảnh với hình ảnh nên nó không tạo nên sự khác biệt ở đây. Nhưng nếu chúng ta truy cập mặt phẳng UV có số lượng pixel bị giảm, thì sẽ cần thiết phải sử dụng _CVPixelBufferGetHeightOfPlane_. – Codo

6

Nếu bạn chỉ cần kênh độ sáng, tôi khuyên bạn không nên sử dụng định dạng BGRA, vì nó đi kèm với phí chuyển đổi. Apple khuyên bạn nên sử dụng BGRA nếu bạn đang làm công cụ hiển thị, nhưng bạn không cần nó để trích xuất thông tin độ sáng. Như Brad đã đề cập, định dạng hiệu quả nhất là định dạng YUV gốc máy ảnh.

Tuy nhiên, việc trích xuất các byte phải từ bộ đệm mẫu là một chút khó khăn, đặc biệt là về iPhone 3G với định dạng YUV 422 xen kẽ của nó. Vì vậy, đây là mã của tôi, hoạt động tốt với iPhone 3G, 3GS, iPod Touch 4 và iPhone 4S.

#pragma mark - 
#pragma mark AVCaptureVideoDataOutputSampleBufferDelegate Methods 
#if !(TARGET_IPHONE_SIMULATOR) 
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection; 
{ 
    // get image buffer reference 
    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); 

    // extract needed informations from image buffer 
    CVPixelBufferLockBaseAddress(imageBuffer, 0); 
    size_t bufferSize = CVPixelBufferGetDataSize(imageBuffer); 
    void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer); 
    CGSize resolution = CGSizeMake(CVPixelBufferGetWidth(imageBuffer), CVPixelBufferGetHeight(imageBuffer)); 

    // variables for grayscaleBuffer 
    void *grayscaleBuffer = 0; 
    size_t grayscaleBufferSize = 0; 

    // the pixelFormat differs between iPhone 3G and later models 
    OSType pixelFormat = CVPixelBufferGetPixelFormatType(imageBuffer); 

    if (pixelFormat == '2vuy') { // iPhone 3G 
     // kCVPixelFormatType_422YpCbCr8  = '2vuy',  
     /* Component Y'CbCr 8-bit 4:2:2, ordered Cb Y'0 Cr Y'1 */ 

     // copy every second byte (luminance bytes form Y-channel) to new buffer 
     grayscaleBufferSize = bufferSize/2; 
     grayscaleBuffer = malloc(grayscaleBufferSize); 
     if (grayscaleBuffer == NULL) { 
      NSLog(@"ERROR in %@:%@:%d: couldn't allocate memory for grayscaleBuffer!", NSStringFromClass([self class]), NSStringFromSelector(_cmd), __LINE__); 
      return nil; } 
     memset(grayscaleBuffer, 0, grayscaleBufferSize); 
     void *sourceMemPos = baseAddress + 1; 
     void *destinationMemPos = grayscaleBuffer; 
     void *destinationEnd = grayscaleBuffer + grayscaleBufferSize; 
     while (destinationMemPos <= destinationEnd) { 
      memcpy(destinationMemPos, sourceMemPos, 1); 
      destinationMemPos += 1; 
      sourceMemPos += 2; 
     }  
    } 

    if (pixelFormat == '420v' || pixelFormat == '420f') { 
     // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange = '420v', 
     // kCVPixelFormatType_420YpCbCr8BiPlanarFullRange = '420f', 
     // Bi-Planar Component Y'CbCr 8-bit 4:2:0, video-range (luma=[16,235] chroma=[16,240]). 
     // Bi-Planar Component Y'CbCr 8-bit 4:2:0, full-range (luma=[0,255] chroma=[1,255]). 
     // baseAddress points to a big-endian CVPlanarPixelBufferInfo_YCbCrBiPlanar struct 
     // i.e.: Y-channel in this format is in the first third of the buffer! 
     int bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0); 
     baseAddress = CVPixelBufferGetBaseAddressOfPlane(imageBuffer,0); 
     grayscaleBufferSize = resolution.height * bytesPerRow ; 
     grayscaleBuffer = malloc(grayscaleBufferSize); 
     if (grayscaleBuffer == NULL) { 
      NSLog(@"ERROR in %@:%@:%d: couldn't allocate memory for grayscaleBuffer!", NSStringFromClass([self class]), NSStringFromSelector(_cmd), __LINE__); 
      return nil; } 
     memset(grayscaleBuffer, 0, grayscaleBufferSize); 
     memcpy (grayscaleBuffer, baseAddress, grayscaleBufferSize); 
    } 

    // do whatever you want with the grayscale buffer 
    ... 

    // clean-up 
    free(grayscaleBuffer); 
} 
#endif 
+0

Xin chào, cảm ơn bạn đã trả lời, tôi đang đối mặt với cùng một vấn đề. Một điều là tôi cũng muốn các thành phần Cr và Cb và tôi không chắc chắn làm thế nào để có được điều đó. Tôi đang cố gắng để làm cho một máy dò da và tôi cần những giá trị quá như tôi đã tìm thấy trên SO trong bài viết khác. Tôi đã làm điều đó bằng cách sử dụng định dạng BGRA và chuyển đổi sau đó vào YCbCr nhưng tôi muốn tránh bước chuyển đổi đó nếu có thể để tăng FPS. Đó là lý do tại sao tôi muốn nhận các giá trị Y Cb và Cr riêng lẻ cho mỗi pixel trong hình ảnh. Bất kỳ ý tưởng? – George

+0

Bạn đã tìm ra thứ tự byte cho tín hiệu thành phần như thế nào? Tài liệu tôi tìm thấy từ Microsoft có nó được liệt kê là Y0CrY1Cb. – Pescolly

+0

Tôi tìm thấy gợi ý trong tệp tiêu đề của Apple. Tôi xin lỗi, nhưng tôi không thể nói cho bạn biết tập tin tiêu đề nào nữa. – Tafkadasoh

1

Đây chỉ đơn giản là đỉnh cao của công việc khó khăn của mọi người, ở trên và trên các chủ đề khác, được chuyển đổi thành nhanh cho bất kỳ ai thấy hữu ích.

func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) { 
    if let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) { 
     CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags.readOnly) 

     let pixelFormatType = CVPixelBufferGetPixelFormatType(pixelBuffer) 
     if pixelFormatType == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange 
      || pixelFormatType == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange { 

      let bufferHeight = CVPixelBufferGetHeight(pixelBuffer) 
      let bufferWidth = CVPixelBufferGetWidth(pixelBuffer) 

      let lumaBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0) 
      let size = bufferHeight * lumaBytesPerRow 
      let lumaBaseAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0) 
      let lumaByteBuffer = unsafeBitCast(lumaBaseAddress, to:UnsafeMutablePointer<UInt8>.self) 

      let releaseDataCallback: CGDataProviderReleaseDataCallback = { (info: UnsafeMutableRawPointer?, data: UnsafeRawPointer, size: Int) ->() in 
       // https://developer.apple.com/reference/coregraphics/cgdataproviderreleasedatacallback 
       // N.B. 'CGDataProviderRelease' is unavailable: Core Foundation objects are automatically memory managed 
       return 
      } 

      if let dataProvider = CGDataProvider(dataInfo: nil, data: lumaByteBuffer, size: size, releaseData: releaseDataCallback) { 
       let colorSpace = CGColorSpaceCreateDeviceGray() 
       let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.noneSkipFirst.rawValue) 

       let cgImage = CGImage(width: bufferWidth, height: bufferHeight, bitsPerComponent: 8, bitsPerPixel: 8, bytesPerRow: lumaBytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo, provider: dataProvider, decode: nil, shouldInterpolate: false, intent: CGColorRenderingIntent.defaultIntent) 

       let greyscaleImage = UIImage(cgImage: cgImage!) 
       // do what you want with the greyscale image. 
      } 
     } 

     CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags.readOnly) 
    } 
} 
Các vấn đề liên quan