2010-11-03 8 views
9

Hola, estoy tratando de acceder a los datos brutos de la cámara iphone usando AVCaptureSession. Sigo la guía proporcionada por Apple (link here).¿Cómo obtener el componente Y de CMSampleBuffer como resultado de AVCaptureSession?

Los datos brutos del samplebuffer están en formato YUV (¿Estoy correcto aquí sobre el formato de marco de video sin formato?), Cómo obtener directamente los datos para el componente Y de los datos sin procesar almacenados en el buffer de muestreo.

+1

Ambos Brad Larson y Codo me ayudaron mucho en este problema. Con la combinación de sus respuestas, finalmente pude alcanzar mi objetivo. ¡Muchas gracias, Brad Larson y Codo! – Nihao

Respuesta

20

Al configurar el AVCaptureVideoDataOutput que devuelve las imágenes de cámara primas, se puede establecer el formato de las tramas utilizando un código como el siguiente:

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

En este caso se especifica un formato de píxel BGRA (He utilizado este para hacer coincidir un formato de color para una textura OpenGL ES). Cada píxel en ese formato tiene un byte para azul, verde, rojo y alfa, en ese orden. Seguir con esto hace que sea fácil extraer los componentes de color, pero se sacrifica un poco el rendimiento al necesitar hacer la conversión del espacio de color YUV nativo de la cámara.

Otros espacios de color compatibles son kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange y kCVPixelFormatType_420YpCbCr8BiPlanarFullRange en dispositivos más nuevos y kCVPixelFormatType_422YpCbCr8 en el iPhone 3G. El sufijo VideoRange o FullRange simplemente indica si los bytes se devuelven entre 16 - 235 para Y y 16 - 240 para UV o 0 - 255 para cada componente.

Creo que el espacio de color predeterminado utilizado por una instancia de AVCaptureVideoDataOutput es el espacio de color plano YUV 4: 2: 0 (excepto en el iPhone 3G, donde está intercalado YUV 4: 2: 2). Esto significa que hay dos planos de datos de imagen contenidos dentro del cuadro de video, con el plano Y como el primero. Por cada píxel en la imagen resultante, hay un byte para el valor Y en ese píxel.

puede conseguir en estos datos Y primas mediante la aplicación de algo como esto en su delegado de devolución de llamada:

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

A continuación, podría averiguar la ubicación de los datos de la trama para cada coordenada x, y en la imagen y tira del byte que corresponde al componente Y en esa coordenada.

El ejemplo FindMyiCone de Apple de WWDC 2010 (accesible junto con los videos) muestra cómo procesar los datos brutos de BGRA de cada fotograma. También creé una aplicación de muestra, que puede descargar el código para here, que realiza color-based object tracking usando el video en vivo de la cámara del iPhone. Ambos muestran cómo procesar los datos de píxeles en bruto, pero ninguno de estos funciona en el espacio de color YUV.

+0

@ brad Larson: si kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange (por defecto de iphone4) y YUV 420 son iguales? –

+0

@Asta - Como menciono anteriormente, 'kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange' en el iPhone 4 es un espacio de color planar YUV 4: 2: 0. –

+0

tengo una Pregunta más.Mi códec solo acepta el formato YUV420, pero 420YpCbCr8BiPlanarVideoRange (biplanar) formatea los datos Y (luminancia) y los datos CbCr (información cromática o de color) están en dos áreas de memoria separadas llamadas planos, ¿Cómo puedo enviar a mi códec? ¿Alguna forma de convertir planar simple? Si tengo que usar cualquier conversión SPL –

16

Además de la respuesta de Brad y su propio código, que desea tener en cuenta lo siguiente:

Debido a que su imagen tiene dos planos separados, la función CVPixelBufferGetBaseAddress no devolverá la dirección base del plano sino la dirección base de una estructura de datos adicional. Es probable que debido a la implementación actual obtenga una dirección lo suficientemente cerca del primer plano para que pueda ver la imagen. Pero es la razón por la que se desplazó y tiene basura en la parte superior izquierda. La forma correcta para recibir el primer plano es:

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

una fila de la imagen puede ser más largo que el ancho de la imagen (debido al redondeo). Es por eso que hay funciones separadas para obtener el ancho y el número de bytes por fila. No tienes este problema en este momento. Pero eso podría cambiar con la próxima versión de iOS.Por lo tanto, su código debe ser:

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

Tenga en cuenta también que su código fallará miserablemente en un iPhone 3G.

+0

Muchas gracias ¡mucho!. Eso me funciona a la perfección: D – Nihao

+0

¿No debería ser CVPixelBufferGetHeightOfPlane? Sólo curioso. – akaru

+0

Dado que sabemos que el plano Y tiene el mismo número de píxeles que la imagen, aquí no debería marcar la diferencia. Pero si accedemos al plano UV que tiene un número reducido de píxeles, entonces sería esencial usar _CVPixelBufferGetHeightOfPlane_. – Codo

6

Si solo necesita el canal de luminancia, recomiendo no usar el formato BGRA, ya que viene con una sobrecarga de conversión. Apple sugiere usar BGRA si está haciendo renderizado, pero no lo necesita para extraer la información de luminancia. Como Brad ya mencionó, el formato más eficiente es el formato YUV nativo de la cámara.

Sin embargo, extraer los bytes correctos del buffer de muestra es un poco complicado, especialmente con respecto al iPhone 3G con su formato intercalado YUV 422. Así que aquí está mi código, que funciona bien con el iPhone 3G, 3GS, iPod Touch 4 y 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

Hola, gracias por la respuesta, estoy enfrentando el mismo problema. Una cosa es que también quiero los componentes Cr y Cb y no estoy seguro de cómo conseguirlos. Estoy tratando de hacer un detector de piel y también necesito esos valores, ya que he encontrado en SO en otra publicación. Ya lo hice usando el formato BGRA y la conversión después de eso en YCbCr, pero quiero evitar ese paso de conversión si es posible para aumentar el FPS. Es por eso que quiero obtener valores Y Cb y Cr individuales para cada píxel en la imagen. ¿Algunas ideas? – George

+0

¿Cómo calculó el orden de bytes para la señal componente? El documento que encontré de Microsoft lo tiene listado como Y0CrY1Cb. – Pescolly

+0

Encontré una pista en un archivo de encabezado de Apple. Lo siento, pero no puedo decir qué archivo de encabezado era. – Tafkadasoh

1

Esto es simplemente la culminación de todos los demás es un trabajo duro, por encima y en otros temas, se convirtió al veloz 3 para cualquier persona que le resulta útil.

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) 
    } 
} 
Cuestiones relacionadas