Как захватывать кадры из Apple iSight, используя Python и PyObjC? - PullRequest
10 голосов
/ 04 февраля 2011

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

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

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

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

В примере сценария, вставленном ниже, предполагаемая операция заключается в том, что после вызова 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's main event loop
    AppHelper.runConsoleEventLoop(installInterrupt=True)

    print "After event loop"


if __name__ == "__main__":
    main()

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

Ответы [ 2 ]

15 голосов
/ 05 февраля 2011

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

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

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

Скрипт ниже будет захватыватьодин кадр

#!/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 голосов
/ 15 сентября 2015

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 подклассифицирован, переопределенные методы будут вызваны с результатом.

...