2011-02-03 20 views
10

Estoy tratando de capturar un solo fotograma de la cámara Apple iSight integrada en un Macbook Pro usando Python (versión 2.7 o 2.6) y el PyObjC (versión 2.2).Cómo capturar marcos de Apple iSight usando Python y PyObjC?

Como punto de partida, utilicé la pregunta this old StackOverflow. Para verificar que tenga sentido, hice una referencia cruzada contra el ejemplo Apple's MyRecorder que parece estar basado en. Desafortunadamente, mi script no funciona.

Mis grandes preguntas son:

  • Am I inicializar la cámara correctamente?
  • ¿Estoy iniciando correctamente el ciclo de eventos?
  • ¿Había alguna otra configuración que se suponía que debía hacer?

En el script de ejemplo pegado a continuación, la operación prevista es que después de llamar a startImageCapture(), debería comenzar a imprimir los mensajes "Obtuve un fotograma ..." de CaptureDelegate. Sin embargo, la luz de la cámara nunca se enciende y la devolución de llamada del delegado nunca se ejecuta.

Además, no hay fallas durante startImageCapture(), todas las funciones pretenden tener éxito y encuentra con éxito el dispositivo iSight. El análisis del objeto de sesión en pdb muestra que tiene objetos de entrada y salida válidos, la salida tiene un delegado asignado, el dispositivo no está siendo utilizado por otro proceso y la sesión se marca como ejecutada después de llamar a startRunning().

Aquí está el código:

#!/usr/bin/env python2.7 

import sys 
import os 
import time 
import objc 
import QTKit 
import AppKit 
from Foundation import NSObject 
from Foundation import NSTimer 
from PyObjCTools import AppHelper 
objc.setVerbose(True) 

class CaptureDelegate(NSObject): 
    def captureOutput_didOutputVideoFrame_withSampleBuffer_fromConnection_(self, captureOutput, 
                      videoFrame, sampleBuffer, 
                      connection): 
     # This should get called for every captured frame 
     print "Got a frame: %s" % videoFrame 

class QuitClass(NSObject): 
    def quitMainLoop_(self, aTimer): 
     # Just stop the main loop. 
     print "Quitting main loop." 
     AppHelper.stopEventLoop() 


def startImageCapture(): 
    error = None 

    # Create a QT Capture session 
    session = QTKit.QTCaptureSession.alloc().init() 

    # Find iSight device and open it 
    dev = QTKit.QTCaptureDevice.defaultInputDeviceWithMediaType_(QTKit.QTMediaTypeVideo) 
    print "Device: %s" % dev 
    if not dev.open_(error): 
     print "Couldn't open capture device." 
     return 

    # Create an input instance with the device we found and add to session 
    input = QTKit.QTCaptureDeviceInput.alloc().initWithDevice_(dev) 
    if not session.addInput_error_(input, error): 
     print "Couldn't add input device." 
     return 

    # Create an output instance with a delegate for callbacks and add to session 
    output = QTKit.QTCaptureDecompressedVideoOutput.alloc().init() 
    delegate = CaptureDelegate.alloc().init() 
    output.setDelegate_(delegate) 
    if not session.addOutput_error_(output, error): 
     print "Failed to add output delegate." 
     return 

    # Start the capture 
    print "Initiating capture..." 
    session.startRunning() 


def main(): 
    # Open camera and start capturing frames 
    startImageCapture() 

    # Setup a timer to quit in 10 seconds (hack for now) 
    quitInst = QuitClass.alloc().init() 
    NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(10.0, 
                      quitInst, 
                      'quitMainLoop:', 
                      None, 
                      False) 
    # Start Cocoa's main event loop 
    AppHelper.runConsoleEventLoop(installInterrupt=True) 

    print "After event loop" 


if __name__ == "__main__": 
    main() 

Gracias por cualquier ayuda que puede proporcionar!

Respuesta

15

OK, pasé un día buceando en las profundidades de PyObjC y lo puse en funcionamiento.

Para el registro futuro, el motivo por el que el código en la pregunta no funcionó: alcance variable y recolección de basura. La variable de la sesión se eliminó cuando cayó fuera del alcance, lo que ocurrió antes de que se ejecutara el procesador de eventos. Se debe hacer algo para retenerlo para que no se libere antes de que tenga tiempo de funcionar.

Moviendo todo a una clase y haciendo sesión una variable de clase hizo que las devoluciones de llamada comenzaran a funcionar. Además, el siguiente código demuestra cómo obtener los datos de píxeles del fotograma en formato de mapa de bits y guardarlos mediante llamadas Cocoa, y también cómo copiarlos en la vista mundial de Python como un búfer o cadena.

El siguiente script capturará una sola trama

#!/usr/bin/env python2.7 
# 
# camera.py -- by Trevor Bentley (02/04/2011) 
# 
# This work is licensed under a Creative Commons Attribution 3.0 Unported License. 
# 
# Run from the command line on an Apple laptop running OS X 10.6, this script will 
# take a single frame capture using the built-in iSight camera and save it to disk 
# using three methods. 
# 

import sys 
import os 
import time 
import objc 
import QTKit 
from AppKit import * 
from Foundation import NSObject 
from Foundation import NSTimer 
from PyObjCTools import AppHelper 

class NSImageTest(NSObject): 
    def init(self): 
     self = super(NSImageTest, self).init() 
     if self is None: 
      return None 

     self.session = None 
     self.running = True 

     return self 

    def captureOutput_didOutputVideoFrame_withSampleBuffer_fromConnection_(self, captureOutput, 
                      videoFrame, sampleBuffer, 
                      connection): 
     self.session.stopRunning() # I just want one frame 

     # Get a bitmap representation of the frame using CoreImage and Cocoa calls 
     ciimage = CIImage.imageWithCVImageBuffer_(videoFrame) 
     rep = NSCIImageRep.imageRepWithCIImage_(ciimage) 
     bitrep = NSBitmapImageRep.alloc().initWithCIImage_(ciimage) 
     bitdata = bitrep.representationUsingType_properties_(NSBMPFileType, objc.NULL) 

     # Save image to disk using Cocoa 
     t0 = time.time() 
     bitdata.writeToFile_atomically_("grab.bmp", False) 
     t1 = time.time() 
     print "Cocoa saved in %.5f seconds" % (t1-t0) 

     # Save a read-only buffer of image to disk using Python 
     t0 = time.time() 
     bitbuf = bitdata.bytes() 
     f = open("python.bmp", "w") 
     f.write(bitbuf) 
     f.close() 
     t1 = time.time() 
     print "Python saved buffer in %.5f seconds" % (t1-t0) 

     # Save a string-copy of the buffer to disk using Python 
     t0 = time.time() 
     bitbufstr = str(bitbuf) 
     f = open("python2.bmp", "w") 
     f.write(bitbufstr) 
     f.close() 
     t1 = time.time() 
     print "Python saved string in %.5f seconds" % (t1-t0) 

     # Will exit on next execution of quitMainLoop_() 
     self.running = False 

    def quitMainLoop_(self, aTimer): 
     # Stop the main loop after one frame is captured. Call rapidly from timer. 
     if not self.running: 
      AppHelper.stopEventLoop() 

    def startImageCapture(self, aTimer): 
     error = None 
     print "Finding camera" 

     # Create a QT Capture session 
     self.session = QTKit.QTCaptureSession.alloc().init() 

     # Find iSight device and open it 
     dev = QTKit.QTCaptureDevice.defaultInputDeviceWithMediaType_(QTKit.QTMediaTypeVideo) 
     print "Device: %s" % dev 
     if not dev.open_(error): 
      print "Couldn't open capture device." 
      return 

     # Create an input instance with the device we found and add to session 
     input = QTKit.QTCaptureDeviceInput.alloc().initWithDevice_(dev) 
     if not self.session.addInput_error_(input, error): 
      print "Couldn't add input device." 
      return 

     # Create an output instance with a delegate for callbacks and add to session 
     output = QTKit.QTCaptureDecompressedVideoOutput.alloc().init() 
     output.setDelegate_(self) 
     if not self.session.addOutput_error_(output, error): 
      print "Failed to add output delegate." 
      return 

     # Start the capture 
     print "Initiating capture..." 
     self.session.startRunning() 


    def main(self): 
     # Callback that quits after a frame is captured 
     NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(0.1, 
                       self, 
                       'quitMainLoop:', 
                       None, 
                       True) 

     # Turn on the camera and start the capture 
     self.startImageCapture(None) 

     # Start Cocoa's main event loop 
     AppHelper.runConsoleEventLoop(installInterrupt=True) 

     print "Frame capture completed." 

if __name__ == "__main__": 
    test = NSImageTest.alloc().init() 
    test.main() 
+0

Cool man, por cierto, debe marcar su propio aceptar como 'aceptado' :) –

+0

Este script funciona muy bien, pero falla al escribir el archivo de bytes. Deberías cambiar la apertura ('nombre de archivo', 'w') para abrir ('nombre de archivo', 'wb') para abrir un archivo en modo byte, luego funciona. – andli

0

QTKit es obsoleto y PyObjC es una gran dependencia (y parece ser difícil de construir si lo desea en Homebrew). Además, PyObjC no tenía la mayoría de AVFoundation, así que creé a simple camera extension for Python que usa AVFoundation para grabar un video o tomar una foto, no requiere dependencias (los archivos intermedios de Cython se comprometen a evitar la necesidad de tener Cython para la mayoría de los usuarios).

Debería ser posible construir de esta manera:

pip install -e git+https://github.com/dashesy/pyavfcam.git 

entonces podemos utilizar para take a picture:

import pyavfcam 

# Open the default video source 
cam = pyavfcam.AVFCam(sinks='image') 
frame = cam.snap_picture('test.jpg') # frame is a memory buffer np.asarray(frame) can retrieve 

No relacionado con esta pregunta, pero si la clase es sub AVFCam clasificados, los métodos reemplazados se invocarán con el resultado.

+0

La instalación de pip falla para mí: '' instalación de pip -e git + https: //github.com/dashesy/pyavfcam.git --editable = git + https: //github.com/dashesy/pyavfcam. git no es el formato correcto; debe tener # huevo = paquete' – DanHickstein

Cuestiones relacionadas