Как захватить кадры из Apple iSight с помощью Python и PyObjC?

Я пытаюсь захватить один кадр из камеры Apple iSight, встроенной в Macbook Pro, используя Python (версия 2.7 или 2.6) и PyObjC (версия 2.2).

В качестве отправной точки я использовал qaru.site/info/518138/.... Чтобы убедиться, что это имеет смысл, я перекрестно ссылаюсь на пример Apple MyRecorder, на котором, похоже, основывается. К сожалению, мой script не работает.

Мои большие вопросы:

  • Я правильно инициализирую камеру?
  • Правильно ли я запускаю цикл событий?
  • Была ли какая-либо другая настройка, которую я должен был делать?

В приведенном ниже примере script предполагается, что после вызова startImageCapture() я должен начать печатать сообщения "Получил фрейм..." из CaptureDelegate. Тем не менее, индикатор камеры никогда не включается, и обратный вызов делегата никогда не выполняется.

Кроме того, во время startImageCapture() нет сбоев, все функции утверждают, что они успешны, и он успешно находит устройство iSight. Анализ объекта сеанса в pdb показывает, что он имеет допустимые объекты ввода и вывода, на выходе есть назначенный делегат, устройство не используется другими процессами, а сеанс помечен как запущенный после вызова startRunning().

Здесь код:

#!/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 main event loop
    AppHelper.runConsoleEventLoop(installInterrupt=True)

    print "After event loop"


if __name__ == "__main__":
    main()

Спасибо за любую помощь, которую вы можете предоставить!

Ответ 1

ОК, я провел день, погружаясь в глубины PyObjC и получил его работу.

Для будущей записи причина, по которой код в вопросе не работает: область переменных и сборка мусора. Переменная сеанса была удалена, когда она выпала из области действия, которая произошла до запуска процессора событий. Что-то нужно сделать, чтобы сохранить его, чтобы он не освободился, прежде чем он успеет бежать.

Перемещение всего в класс и создание сеанса переменной класса заставили обратные вызовы начать работать. Кроме того, приведенный ниже код демонстрирует получение данных пикселя кадра в формате растрового изображения и сохранение его с помощью вызовов Cocoa, а также как его копировать обратно в мирное представление Python в виде буфера или строки.

Ниже представлен script один кадр

#!/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 main event loop
        AppHelper.runConsoleEventLoop(installInterrupt=True)

        print "Frame capture completed."

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

Ответ 2

QTKit устарел, а PyObjC - большая зависимость (и, кажется, сложно построить, если вы хотите его в HomeBrew). Плюс PyObjC не было больше AVFoundation, поэтому я создал простое расширение камеры для Python, которое использует AVFoundation для записи видео или привязать изображение, он не требует зависимостей (промежуточные файлы Cython предназначены для того, чтобы избежать необходимости иметь Cython для большинства пользователей).

Его можно было бы создать следующим образом:

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

Затем мы можем использовать его для сделать снимок:

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

Не связан с этим вопросом, но если класс AVFCam является подклассом, переопределенные методы будут вызываться с результатом.