Problem mit gphoto2, was ich auch nach langer Recherche nicht lösen konnte

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
jochen84
User
Beiträge: 1
Registriert: Donnerstag 14. August 2025, 19:27

Hallo zusammen,

Ich möchte eine simple Software für meine Fotobox schreiben.
Die Software soll einfach einen Live-Stream anzeigen, bei Klick aufs Bild nen Countdown starten, Foto machen, für X Sekunden anzeigen und wieder zurück zum Livestream.
Mir ist bewusst, dass beides gleichzeitig nicht funktioniert, daher soll der Stream ja vor dem capture Befehl stoppen um das Interface der Kamera wieder freizugeben.
Das funktioniert nur leider nicht. Ich bekomme nen Live-Stream, der Countdown zählt runter nach Klick, aber es wird einfach kein Foto gemacht.

Das ist der Output der Konsole:

Code: Alles auswählen

 python3 ./pb5.py 
[ WARN:0@3.678] global ./modules/videoio/src/cap_gstreamer.cpp (2401) handleMessage OpenCV | GStreamer warning: Embedded video playback halted; module typefind reported: Could not determine type of stream.
[ WARN:0@3.678] global ./modules/videoio/src/cap_gstreamer.cpp (1356) open OpenCV | GStreamer warning: unable to start pipeline
[ WARN:0@3.678] global ./modules/videoio/src/cap_gstreamer.cpp (862) isPipelinePlaying OpenCV | GStreamer warning: GStreamer: pipeline have not been created
                                                                               
*** Error ***              
An error occurred in the io-library ('Could not claim the USB device'): Could not claim interface 0 (Device or resource busy). Make sure no other program (gvfs-gphoto2-volume-monitor) or kernel module (such as sdc2xx, stv680, spca50x) is using the device and you have read/write access to the device.
ERROR: Could not capture image.
ERROR: Could not capture.
*** Error (-53: 'Could not claim the USB device') ***       

For debugging messages, please use the --debug option.
Debugging messages may help finding a solution to your problem.
If you intend to send any error or debug messages to the gphoto
developer mailing list <gphoto-devel@lists.sourceforge.net>, please run
gphoto2 as follows:

    env LANG=C gphoto2 --debug --debug-logfile=my-logfile.txt --capture-image-and-download --filename photo.jpg --force-overwrite

Please make sure there is sufficient quoting around the arguments.

[ WARN:0@12.437] global ./modules/videoio/src/cap_gstreamer.cpp (2401) handleMessage OpenCV | GStreamer warning: Embedded video playback halted; module typefind reported: Could not determine type of stream.
[ WARN:0@12.437] global ./modules/videoio/src/cap_gstreamer.cpp (1356) open OpenCV | GStreamer warning: unable to start pipeline
[ WARN:0@12.437] global ./modules/videoio/src/cap_gstreamer.cpp (862) isPipelinePlaying OpenCV | GStreamer warning: GStreamer: pipeline have not been created
^CTraceback (most recent call last):
  File "/home/bpw/diypb/./pb5.py", line 43, in <module>
    ret, frame = cap.read()
                 ^^^^^^^^^^
KeyboardInterrupt

Und das ist mein Code:

Code: Alles auswählen

import cv2
import subprocess
import time
import os

VIRTUAL_DEVICE = "/dev/video10"
WINDOW_NAME = "Fotobox Test"
PHOTO_PATH = "photo.jpg"
COUNTDOWN = 3
USB_WAIT = 2 #delay falls die kamera bissel länger braucht

def start_stream():
    cmd = [
        "bash", "-c",
        f"gphoto2 --stdout --capture-movie | ffmpeg -f mjpeg -i - "
        f"-vcodec rawvideo -pix_fmt bgr24 -f v4l2 {VIRTUAL_DEVICE}"
    ]
    return subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def capture_photo():
    subprocess.run(["gphoto2", "--capture-image-and-download",
                    "--filename", PHOTO_PATH, "--force-overwrite"])

clicked = False
countdown_start = None

def mouse_callback(event, x, y, flags, param):
    global clicked, countdown_start
    if event == cv2.EVENT_LBUTTONDOWN and countdown_start is None:
        clicked = True
        countdown_start = time.time()

cv2.namedWindow(WINDOW_NAME, cv2.WINDOW_NORMAL)
cv2.setWindowProperty(WINDOW_NAME, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
cv2.setMouseCallback(WINDOW_NAME, mouse_callback)

while True:
    stream_proc = start_stream()
    time.sleep(2)  # kurz warten, bis Stream verfügbar
    cap = cv2.VideoCapture(VIRTUAL_DEVICE)

    while True:
        ret, frame = cap.read()
        if not ret:
            continue

        # Countdown Overlay
        if countdown_start is not None:
            elapsed = time.time() - countdown_start
            remaining = COUNTDOWN - int(elapsed)
            h, w = frame.shape[:2]

            if remaining > 0:
                overlay = frame.copy()
                cv2.putText(overlay, str(remaining),
                            (w//2 - 50, h//2),
                            cv2.FONT_HERSHEY_SIMPLEX,
                            4, (0,0,255), 8)
                cv2.imshow(WINDOW_NAME, overlay)
            else:
                # Countdown fertig -> Livestream stoppen
                stream_proc.terminate()
                cap.release()
                time.sleep(USB_WAIT)  # USB-Port freigeben

                # Foto aufnehmen
                capture_photo()

                # Foto anzeigen
                if os.path.exists(PHOTO_PATH):
                    photo = cv2.imread(PHOTO_PATH)
                    cv2.imshow(WINDOW_NAME, photo)
                    cv2.waitKey(5000)

                countdown_start = None
                clicked = False
                break  # verlässt inneren Loop → Livestream neu starten

        else:
            cv2.imshow(WINDOW_NAME, frame)

        key = cv2.waitKey(1) & 0xFF
        if key == ord('q'):
            stream_proc.terminate()
            cap.release()
            cv2.destroyAllWindows()
            exit()
Ich wäre euch sehr dankbar, wenn mir jemand auf die Sprünge helfen könnte, ich bin sicher es ist nur ne Kleinigkeit.
Ich habe die EOS500D. Funktioniert mit diversen anderen Fotobox-Software's auch einwandfrei.

Ergänzung / Was ich bisher probiert hab: "pkill -f gvfs-gphoto2 und pkill -f gvfs-gphoto2-volume-monitor" bevor ich den Script gestartet hab. Leider ohne Erfolg. Er startet ja auch den LiveStream. Das Problem muss irgendwo zwischen Livestream beenden und Foto machen sein.
Benutzeravatar
DeaD_EyE
User
Beiträge: 1254
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

An error occurred in the io-library ('Could not claim the USB device'): Could not claim interface 0 (Device or resource busy). Make sure no other program (gvfs-gphoto2-volume-monitor) or kernel module (such as sdc2xx, stv680, spca50x) is using the device and you have read/write access to the device.
Das Programm gphoto2 kann auf das USB-Gerät nicht zugreifen.
Möglicherweise greift noch ein anderer Prozess (gphoto2 z.B.) auf das Gerät zu.

Von einem Problem mit den Berechtigungen könnte man auch ausgehen, aber das kann man einfach im Terminal als normaler User testen, indem man den Befehl eingibt und schaut, ob das gleiche Problem auftritt.

Jedenfalls scheint es kein Problem mit deinem Programm zu sein.
Du könntest einen Wrapper für gphoto2 nutzen: https://pypi.org/project/gphoto2/
Das würde ich aber erst machen, wenn das Problem mit USB gelöst worden ist.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
__blackjack__
User
Beiträge: 14097
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@jochen84: Anmerkungen zum Quelltext:

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Besonders unübersichtlich ist es wenn man das Hauptprogramm zwischen Funktionsdefinitionen verteilt.

Daraus folgt, dass Funktionen und Methoden alles was sie ausser Konstanten benötigen, als Argument(e) übergeben bekommen. Und ``global`` macht dann auch keinen Sinn mehr, hat auch nix in einem sauberen Programm zu suchen.

Wenn man sich Zustand über Aufrufe hinweg merken muss, kommt man eigentlich im objektorientierte Programmierung (OOP) nicht herum. Also eigene Klassen schreiben. Die API von OpenCV erlaubt da noch einen Zwischenschritt, in dem man zwar eine eigene Klasse schreibt, aber die einfach nur als Verbunddatentyp verwendet, also wie ``struct`` in C, ``RECORD`` in Pascal, oder ``TYPE`` in einigen modernen BASIC-Dialekten.

`clicked` wird nicht wirklich verwendet. Es ist auch redundant, denn das ist immer `False` wenn `countdown_start` den Wert `None` hat, und immer `True` wenn `countdown_start` nicht den Wert `None` hat.

`exit()` gibt es so eigentlich gar nicht. Das sollte man ordentlich aus dem `sys`-Modul importieren, und nicht darauf bauen, dass diese Funktion einfach so existiert.

Aber man sollte sie auch nicht für eine Abkürzung missbrauchen um ein Programm zu beenden. Die ist dazu da um dem aufrufenden Prozess einen Rückgabecode zu übermitteln. Wenn man das nicht macht, dann braucht man auch diese Funktion nicht. Hier würde man sie konkret durch ``break`` ersetzen und das Programm ganz normal am Ende ankommen lassen.

Aufräumen von Ressourcen sollte man möglichst durch Kontextmanager (``with``) oder durch ``finally``-Blöcke sicherstellen.

Die Bash beim Streamen ist unnötig. Man kann die beiden tatsächlich benötigten Prozesse auch direkt starten und deren Ein- und Ausgabe verbinden.

`time.time()` ist nur bedingt zur Zeitmessung geeignet, weil da die Zeit nicht zwingend in eine Richtung laufen muss. `time.monotonic()` ist dafür vorgesehen.

Ob der Dateipfad existiert, würde ich nicht prüfen, sondern einfach den Rückgabewert von `imread()`. Denn es kann ja noch mehr Probleme geben als das der Pfad nicht existiert.

Ist das `USB_WAIT` überhaupt notwendig? Würde es nicht reichen wenn man auf das Ende des Stream-Prozesses wartet?

Ungetestet (und von der Funktion her unverändert):

Code: Alles auswählen

#!/usr/bin/env python3
import subprocess
import time

import cv2

VIRTUAL_DEVICE = "/dev/video10"
WINDOW_NAME = "Fotobox Test"
PHOTO_PATH = "photo.jpg"
COUNTDOWN = 3
USB_WAIT = 2  # Verzögerung in Sekunden falls die Kamera bissel länger braucht.


def start_stream():
    return subprocess.Popen(
        [
            "bash",
            "-c",
            f"gphoto2 --stdout --capture-movie | ffmpeg -f mjpeg -i - "
            f"-vcodec rawvideo -pix_fmt bgr24 -f v4l2 {VIRTUAL_DEVICE}",
        ],
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
    )


def capture_photo():
    subprocess.run(
        [
            "gphoto2",
            "--capture-image-and-download",
            "--filename",
            PHOTO_PATH,
            "--force-overwrite",
        ],
        check=True,
    )


class State:
    def __init__(self):
        self.countdown_start = None


def mouse_callback(event, _x, _y, _flags, userdata):
    if event == cv2.EVENT_LBUTTONDOWN and userdata.countdown_start is None:
        userdata.countdown_start = time.monotonic()


def main():
    state = State()
    cv2.namedWindow(WINDOW_NAME, cv2.WINDOW_NORMAL)
    try:
        cv2.setWindowProperty(
            WINDOW_NAME, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN
        )
        cv2.setMouseCallback(WINDOW_NAME, mouse_callback, state)

        while True:
            with start_stream() as stream_process:
                time.sleep(2)  # kurz warten, bis Stream verfügbar
                video_stream = cv2.VideoCapture(VIRTUAL_DEVICE)
                try:
                    while True:
                        is_ok, frame = video_stream.read()
                        if is_ok:
                            # Countdown Overlay
                            if state.countdown_start is not None:
                                remaining_seconds = COUNTDOWN - int(
                                    time.monotonic() - state.countdown_start
                                )
                                if remaining_seconds > 0:
                                    overlay = frame.copy()  # ???
                                    height, width = frame.shape[:2]
                                    cv2.putText(
                                        overlay,
                                        str(remaining_seconds),
                                        (width // 2 - 50, height // 2),
                                        cv2.FONT_HERSHEY_SIMPLEX,
                                        4,
                                        (0, 0, 255),
                                        8,
                                    )
                                    cv2.imshow(WINDOW_NAME, overlay)
                                else:
                                    video_stream.release()
                                    stream_process.terminate()
                                    stream_process.wait()
                                    time.sleep(USB_WAIT)  # USB-Port freigeben
                                    capture_photo()
                                    photo = cv2.imread(PHOTO_PATH)
                                    if photo:
                                        cv2.imshow(WINDOW_NAME, photo)
                                        cv2.waitKey(5000)

                                    state.countdown_start = None
                                    #
                                    # verlässt inneren Loop → Livestream neu
                                    # starten
                                    #
                                    break

                            else:
                                cv2.imshow(WINDOW_NAME, frame)

                        if cv2.waitKey(1) & 0xFF == ord("q"):
                            break

                finally:
                    video_stream.release()
                    stream_process.terminate()
                    stream_process.wait()
    finally:
        cv2.destroyAllWindows()


if __name__ == "__main__":
    main()
Die Hauptfunktion ist für meinen Geschmack deutlich zu komplex und tief verschachtelt. Der Stream und das `VideoCapture`-Objekt bilden ja eigentlich eine Einheit. Beides wird zusammen gestartet und zusammen gestoppt und aufgeräumt. Das würde ich in ein Objekt kapseln. Auch die GUI könnte man sinnvoll in ein Objekt stecken. Beides auch als Kontextmanager.
“It is easier to change the specification to fit the program than vice versa.” — Alan J. Perlis
Benutzeravatar
__blackjack__
User
Beiträge: 14097
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ups, die Reaktion auf die "q"-Taste müsste natürlich ein ``return`` sein und kein ``break``.

Ich habe mal den `State` zu einem `Countdown`-Objekt ausgebaut, den Prozess und das `VideoCapture`-Objekt in einem `Stream` zusammengefasst, und was da so Prozedural mit dem Fenster gemacht wird, in eine `Window`-Klasse gesteckt. Jeweils als Kontextmanager wo das Sinn macht. Damit wird das Hauptprogramm deutlich aufgeräumter. Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import subprocess
import time

import cv2

VIRTUAL_DEVICE = "/dev/video10"
WINDOW_NAME = "Fotobox Test"
PHOTO_PATH = "photo.jpg"
COUNTDOWN = 3
USB_WAIT = 2  # Verzögerung in Sekunden falls die Kamera bissel länger braucht.


class Stream:
    def __init__(self, device):
        self._stream_process = subprocess.Popen(
            [
                "bash",
                "-c",
                (
                    f"gphoto2 --stdout --capture-movie | ffmpeg -f mjpeg -i -"
                    f" -vcodec rawvideo -pix_fmt bgr24 -f v4l2 {device}"
                ),
            ],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        )
        time.sleep(2)  # kurz warten, bis Stream verfügbar
        self._video_stream = cv2.VideoCapture(device)

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.close()

    def __iter__(self):
        return self

    def __next__(self):
        while True:
            is_ok, frame = self._video_stream.read()
            if is_ok:
                return frame

    def close(self):
        self._video_stream.release()
        self._stream_process.terminate()
        self._stream_process.wait()


def capture_photo():
    subprocess.run(
        [
            "gphoto2",
            "--capture-image-and-download",
            "--filename",
            PHOTO_PATH,
            "--force-overwrite",
        ],
        check=True,
    )
    return cv2.imread(PHOTO_PATH)


class Countdown:
    def __init__(self, duration):
        self._duration = duration
        self._start_timestamp = None

    def is_running(self):
        return self._start_timestamp is not None

    def get_remaining_seconds(self):
        return (
            None
            if self._start_timestamp is None
            else self._duration - (time.monotonic() - self._start_timestamp)
        )

    def start(self):
        self._start_timestamp = time.monotonic()

    def stop(self):
        self._start_timestamp = None


def annotate(frame, text):
    frame = frame.copy()
    height, width = frame.shape[:2]
    cv2.putText(
        frame,
        text,
        (width // 2 - 50, height // 2),
        cv2.FONT_HERSHEY_SIMPLEX,
        4,
        (0, 0, 255),
        8,
    )
    return frame


class Window:
    def __init__(self, countdown, name=WINDOW_NAME):
        self._countdown = countdown
        self._name = name
        cv2.namedWindow(self._name, cv2.WINDOW_NORMAL)
        cv2.setWindowProperty(
            self._name, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN
        )
        cv2.setMouseCallback(self._name, self.on_click)

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.close()

    def on_click(self, event, _x, _y, _flags, _userdata):
        if event == cv2.EVENT_LBUTTONDOWN and not self._countdown.is_running():
            self._countdown.start()

    def show(self, frame):
        cv2.imshow(self._name, frame)

    def close(self):
        cv2.destroyWindow(self._name)

    @staticmethod
    def get_key(timeout=1):
        return chr(cv2.waitKey(timeout) & 0xFF)


def main():
    countdown = Countdown(COUNTDOWN)
    with Window(countdown) as window:
        while True:
            with Stream(VIRTUAL_DEVICE) as stream:
                for frame in stream:
                    if countdown.is_running():
                        remaining_seconds = countdown.get_remaining_seconds()
                        if remaining_seconds > 0:
                            frame = annotate(
                                frame, str(int(remaining_seconds))
                            )
                        else:
                            countdown.stop()
                            stream.close()
                            time.sleep(USB_WAIT)  # USB-Port freigeben
                            photo = capture_photo()
                            if photo:
                                window.show(photo)
                                _ = window.get_key(5000)
                            #
                            # verlässt innere Schleife → Livestream neu starten
                            #
                            break

                    window.show(frame)
                    if window.get_key() == "q":
                        return


if __name__ == "__main__":
    main()
“It is easier to change the specification to fit the program than vice versa.” — Alan J. Perlis
Pedroski55
User
Beiträge: 12
Registriert: Freitag 25. Juli 2025, 00:20

'tschuldige wenn ich dies hier falsch auffasse, aber willst du einfach einen Screenshot machen?

Ganzer Bildschirm:

Code: Alles auswählen

import pyautogui

im = pyautogui.screenshot()
im.save("ss1.jpg")
Oder ROI (Region Of Interest):

Code: Alles auswählen

from PIL import ImageGrab

ss_region = (300, 300, 600, 600)
ss_img = ImageGrab.grab(ss_region)
ss_img.save("ss2.jpg")
Scheint bedeutend einfacher so!
Benutzeravatar
__blackjack__
User
Beiträge: 14097
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pedroski55: Es geht um eine Fotobox: https://de.wikipedia.org/wiki/Fotoautom ... hoto-Booth
“It is easier to change the specification to fit the program than vice versa.” — Alan J. Perlis
Benutzeravatar
DeaD_EyE
User
Beiträge: 1254
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Dann muss er die physische Fotobox zuerst auf den virtuellen Desktop stellen, um davon einen Screenshot zu machen.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Antworten