2010-07-22 11 views
22

Estoy tratando de mostrar un UIImage en tiempo real proveniente de la cámara, y parece que mi UIImageView no muestra la imagen correctamente. Este es el método que un AVCaptureVideoDataOutputSampleBufferDelegate tiene que implementarUIImage creado a partir de CMSampleBufferRef no se muestra en UIImageView?

- (void)captureOutput:(AVCaptureOutput *)captureOutput 
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer 
     fromConnection:(AVCaptureConnection *)connection 
{ 
    // Create a UIImage from the sample buffer data 
    UIImage *theImage = [self imageFromSampleBuffer:sampleBuffer]; 
// NSLog(@"Got an image! %f %f", theImage.size.width, theImage.size.height); 
// NSLog(@"The image view is %@", imageView); 
// UIImage *theImage = [[UIImage alloc] initWithData:[NSData 
//  dataWithContentsOfURL:[NSURL 
//  URLWithString:@"http://farm4.static.flickr.com/3092/2915896504_a88b69c9de.jpg"]]]; 
    [self.session stopRunning]; 
    [imageView setImage: theImage]; 
} 

Para obtener los problemas fáciles fuera del camino:

  • Usando UIImagePickerController no es una opción (con el tiempo vamos a realmente hacer cosas con la imagen)
  • Sé que se está llamando al controlador (las llamadas NSLog están hechas, y veo la salida)
  • Sé que tengo las declaraciones IBOutlet configuradas correctamente. Si uso el código comentado de arriba para cargar una imagen arbitraria desde la web en lugar de simplemente enviar setImage:theImage a la imageView, la imagen se carga correctamente (y la segunda llamada a NSLog informa un objeto que no es nada).
  • Al menos en una medida básica, la imagen que recibo de imageFromSampleBuffer: está muy bien, ya que NSLog informa del tamaño que ser 360x480, que es el tamaño lo que esperaba.

El código que estoy usando es el publicado recientemente AVFoundation fragmento de Apple disponibles here.

En particular, ese es el código que uso que configura el objeto y amigos AVCaptureSession (del cual entiendo muy poco), y crea el objeto UIImage de los almacenamientos intermedios de Core Video (ese es el método imageFromSampleBuffer).

Por último, puedo conseguir la aplicación se bloquee si intento enviar drawInRect: a una subclase UIView llano con el UIImage devuelto por imageFromSamplerBuffer, mientras que no se cuelga si uso un UIImage desde una dirección URL que el anterior. Aquí está el seguimiento de la pila desde el depurador en el interior del accidente (me da una señal EXC_BAD_ACCESS):

#0 0x34a977ee in decode_swap() 
#1 0x34a8f80e in decode_data() 
#2 0x34a8f674 in img_decode_read() 
#3 0x34a8a76e in img_interpolate_read() 
#4 0x34a63b46 in img_data_lock() 
#5 0x34a62302 in CGSImageDataLock() 
#6 0x351ab812 in ripc_AcquireImage() 
#7 0x351a8f28 in ripc_DrawImage() 
#8 0x34a620f6 in CGContextDelegateDrawImage() 
#9 0x34a61fb4 in CGContextDrawImage() 
#10 0x321fd0d0 in -[UIImage drawInRect:blendMode:alpha:]() 
#11 0x321fcc38 in -[UIImage drawInRect:]() 

EDIT: Aquí hay más información sobre el UIImage ser devuelto por ese trozo de código.

Usando el método descrito here, puedo llegar a los píxeles e imprimirlos, y se ven bien a primera vista (cada valor en el canal alfa es 255, por ejemplo). Sin embargo, hay algo levemente apagado con los tamaños de buffer. La imagen que obtengo de Flickr de esa URL es 375x500, y su [pixelData length] me da 750000 = 375*500*4, que es el valor esperado. Sin embargo, los datos de píxeles de la imagen devuelta desde imageFromSampleBuffer: tienen el tamaño 691208 = 360*480*4 + 8, por lo que hay 8 bytes adicionales en los datos de píxeles. CVPixelBufferGetDataSize en sí mismo devuelve este valor off-by-8. Pensé por un momento que podría deberse a la asignación de almacenamientos intermedios en posiciones alineadas en la memoria, pero 691200 es un múltiplo de 256, por lo que tampoco lo explica. Esta discrepancia de tamaño es la única diferencia que puedo decir entre los dos UIImages, y podría estar causando el problema. Aún así, no hay ninguna razón para asignar memoria adicional para que el almacenamiento intermedio cause una violación EXC_BAD_ACCESS.

Muchas gracias por cualquier ayuda, y quiero saber si necesita más información.

+0

Tengo el mismo problema. Lo que es irritante es que fue la propia Apple que nos proporcionó la función personalizada "imageFromSampleBuffer". –

+0

Aún más extraño, el contenido de la imagen en sí es correcto. Estoy empujando los píxeles brutos a través de un socket a una máquina diferente y son exactamente los cuadros de video capturados desde la cámara en el formato correcto. –

Respuesta

40

que tenían el mismo problema ... pero yo encontramos este antiguo puesto, y su método de creación funciona el CGImageRef!

http://forum.unity3d.com/viewtopic.php?p=300819

He aquí una muestra de trabajo:

app has a member UIImage theImage; 

// Delegate routine that is called when a sample buffer was written 
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection 
{ 
     //... just an example of how to get an image out of this ... 

    CGImageRef cgImage = [self imageFromSampleBuffer:sampleBuffer]; 
    theImage.image =  [UIImage imageWithCGImage: cgImage ]; 
    CGImageRelease(cgImage); 
} 

- (CGImageRef) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer // Create a CGImageRef from sample buffer data 
{ 
    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); 
    CVPixelBufferLockBaseAddress(imageBuffer,0);  // Lock the image buffer 

    uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0); // Get information of the image 
    size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); 
    size_t width = CVPixelBufferGetWidth(imageBuffer); 
    size_t height = CVPixelBufferGetHeight(imageBuffer); 
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 

    CGContextRef newContext = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst); 
    CGImageRef newImage = CGBitmapContextCreateImage(newContext); 
    CGContextRelease(newContext); 

    CGColorSpaceRelease(colorSpace); 
    CVPixelBufferUnlockBaseAddress(imageBuffer,0); 
    /* CVBufferRelease(imageBuffer); */ // do not call this! 

    return newImage; 
} 
+0

Bueno, intentaré esto tan pronto como termine otra cosa, y me pondré en contacto contigo. –

+1

Por lo tanto, soy un completo idiota cuando se trata de la API de iOS, y no sé cómo insertar los píxeles en la CGImage real. Esto parece lograrse mediante la llamada 'memcpy (mImageData, ...)', pero mImageData en realidad no está declarada en ningún lugar en ese fragmento. ¿Sabes cómo puedo acceder a dicho puntero con la API de iOS? –

+0

Ah, sí, ni siquiera necesito eso: el CGImageRef ya se había creado. Gracias por eso. ¿Te importaría editar tu respuesta para agregar ese código en línea para referencia futura en StackOverflow? Te daré la recompensa en cualquier caso, pero creo que será de gran ayuda. –

5

Ben Loulier tiene una good write up sobre cómo hacer esto.

Estoy usando his example app como punto de partida, y está trabajando para mí. Además de reemplazar la función imageFromSamplerBuffer con algo que crea CGImageRef con CGBitmapContextCreate, está utilizando la cola de distribución principal (a través de dispatch_get_main_queue()) al configurar el delegado del buffer de muestra de salida. Esta no es la mejor solución porque necesita una cola en serie, y según tengo entendido, la cola principal no es una cola en serie. Así, mientras que no está garantizado para conseguir las tramas en el orden correcto parece que funciona para mí hasta ahora :)

+0

Esto funciona, incluso sin la cola diferente. Ahora estoy sospechoso de la llamada de Apple a' CGDataProviderCreateWithData' en su fragmento de código, la única diferencia restante en el código. –

9

la captura en vivo de fotogramas de vídeo está bien explicado por técnicos de Apple Q & Un QA1702:

https://developer.apple.com/library/ios/#qa/qa1702/_index.html

+1

Este es un gran ejemplo. Sin embargo, tomar unos dos minutos construir la imagen en un iPad3, muy extraño. –

+2

OK, soy un mentiroso. performSelectorOnMainThread para configurando el UIImageView proporcionado cerca del resultado instantáneo. –

7

También es importante configurar el formato de salida correcto. Tuve un problema con la captura de imágenes cuando usé configuraciones de formato predeterminadas. Debería ser:

[videoDataOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA] forKey:(NSString*)kCVPixelBufferPixelFormatTypeKey]]; 
1

Otra cosa a tener en cuenta es si en realidad estás actualizar su UIImageView en el hilo principal: si no es así, lo más probable es que no reflejará ningún cambio.

captureOutput:didOutputSampleBuffer:fromConnection método delegado a menudo se llama en una cadena de fondo. Entonces desea hacer esto:

- (void)captureOutput:(AVCaptureOutput *)captureOutput 
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer 
     fromConnection:(AVCaptureConnection *)connection 
{ 
    CFRetain(sampleBuffer); 

    [[NSOperationQueue mainQueue] addOperationWithBlock:^{ 

     //Now we're definitely on the main thread, so update the imageView: 
     UIImage *capturedImage = [self imageFromSampleBuffer:sampleBuffer]; 

     //Display the image currently being captured: 
     imageView.image = capturedImage; 

     CFRelease(sampleBuffer); 
    }]; 
} 
Cuestiones relacionadas