Pregunta

Hola, Estoy intentando tener acceso a datos en bruto de la cámara del iPhone usando AVCaptureSession. Sigo la guía proporcionada por Apple ( enlace aquí ).

Los datos en bruto de la samplebuffer está en formato YUV (Estoy en lo correcto aquí sobre el formato de trama de vídeo en bruto ??), la forma de obtener directamente los datos para la componente Y de los datos en bruto almacenados en la samplebuffer.

¿Fue útil?

Solución

Al configurar el AVCaptureVideoDataOutput que devuelve los marcos de RAW de cámara, se puede ajustar 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 (I utilizado este para hacer coincidir un formato de color para una textura OpenGL ES). Cada píxel en ese formato tiene un byte para el azul, verde, rojo, y alfa, en ese orden. Ir con esto hace que sea fácil de sacar componentes de color, pero lo hace sacrificar un poco el rendimiento al tener que hacer la conversión del espacio de color YUV cámara nativa.

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

Creo que el espacio de color predeterminado utilizado por una instancia AVCaptureVideoDataOutput es la YUV 4: colorspace 0 planar: 2 (excepto en el iPhone 3G, donde es YUV 4: 2: 2 entrelazado). Esto significa que hay dos planos de datos de imagen contenidos dentro de la trama de vídeo, con el plano Y viene primero. Para cada píxel de la imagen resultante, existe 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 devolución de llamada delegado:

- (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 tire el byte de salida que corresponde a la componente Y en ese coordenadas.

muestra FindMyiCone de Apple desde WWDC 2010 (accesibles junto con los videos) muestra cómo para procesar los datos BGRA primas procedentes de cada trama. También creé una aplicación de ejemplo, que se puede descargar el código para aquí , que realiza de color basado en el seguimiento de objetos utilizando el vídeo en directo desde la cámara del iPhone. Ambos muestran cómo procesar los datos de píxeles en bruto, pero ninguno de estos trabajos en el espacio de color YUV.

Otros consejos

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 más bien la dirección base de una estructura de datos adicional. Es probablemente debido a la implementación actual que se obtiene una dirección lo suficientemente cerca al primer plano de manera que se puede ver la imagen. Pero es la razón por la que ha cambiado y tiene basura en la parte superior izquierda. La forma correcta de recibir el primer plano es:

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

Una fila en la imagen podría ser más largo que el ancho de la imagen (debido al redondeo). Es por eso que son funciones separadas para conseguir la anchura y el número de bytes por fila. Usted no tiene este problema en este momento. Pero eso podría cambiar con la próxima versión de iOS. Así que el 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 el código se miserablemente fallar en un iPhone 3G.

Si sólo necesita el canal de luminancia, recomiendo contra el uso de formato BGRA, ya que viene con una sobrecarga de conversión. Manzana sugieren el uso de BGRA si está haciendo las cosas de representación, pero que no lo necesita para extraer la información de luminancia. Como Brad ya se ha mencionado, el formato más eficiente es el formato YUV cámara nativa.

Sin embargo, la extracción de los bytes derecha desde el tampón de muestra es un poco complicado, especialmente en relación con el iPhone 3G con el que está intercalada YUV 422 formato. Así que aquí es mi código, que funciona bien con el iPhone 3G, 3GS, iPod Touch 4 y el 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

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)
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top