Mehrere Kameras mittels opencv nutzen

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
okliw994
User
Beiträge: 26
Registriert: Dienstag 11. Juni 2019, 09:49

Guten Morgen zusammen,

Ich brauche wirklich eure Hilfe. Ich versuche nur schon seit circa 2 Wochen mehrere Webcams an meinem Raspberry Pi zu betreiben und finde einfach meinen Fehler nicht. Das Programm soll zu Beginn nacheinander Livebilder der einzelnen Kameras erzeugen. Wenn die ESC-Taste gedrückt wird, soll ein Ordner für die Kamera angelegt werden und anschließend soll sich das nächste Livebild erzeugen. Das ganze ist nur der Programmstart zur Ausrichtung der Kameras. Jede Kamera alleine funktioniert ohne Probleme. Sobald ich 2 oder mehr Kameras anschließe stürzt das Programm immer ab. Wenn ich dann die Kameras wieder abziehe und in anderer Reihenfolge einstecke funktioniert es beim nächsten mal meistens.
Ich verwende dafür Python3, opencv, Raspberry Pi3+, mehrere USB-Webcams und einen Hub mit externer Stromversorgung.

Code: Alles auswählen

from __future__ import print_function
import sys, os
from PyQt5 import QtGui
from PyQt5.QtWidgets import*
import PyQt5.QtWidgets as widgets
import PyQt5.uic as uic
from PyQt5.QtGui import QIcon
import cv2 
import threading
import RPi.GPIO as GPIO
import time
import subprocess
import numpy
from PyQt5. QtCore import QThread
import signal

class Initialisierung(QMainWindow):
    def __init__(self):
        super().__init__()
        uic.loadUi('/home/pi/Bildprogramm/Ui - Dateien/KameraInit.ui', self)
        self.setWindowTitle('Kamerainitialisierung')
        self.setWindowIcon(QtGui.QIcon('/home/pi/Bildprogramm/VWLogo.jpeg'))
        self.StartKameraInit.clicked.connect(self.CamInit)
        self.show()
    def CamInit(self):
        global a
        a = self.AnzCam.text()
        
        if a == "":
            IN = Initialisierung()
        
        if a != "0":
            self.Anzeige()
            self.H = Hauptfenster()
            self.close()

            
    def Anzeige(self):
        global a
        a = int(a)
        global pfad
        pfad = []
        q = (['/media/pi/FE21-4590/Bilder/Kamera 1/', '/media/pi/FE21-4590/Bilder/Kamera 2/', '/media/pi/FE21-4590/Bilder/Kamera 3/', '/media/pi/FE21-4590/Bilder/Kamera 4/',' /media/pi/FE21-4590/Bilder/Kamera 5/'])
            
        cam1 = cv2.VideoCapture(0)
        cam1.set(cv2.CAP_PROP_FPS,40)
        cam1.open(0)
        while True:
            ret_val1, img1 = cam1.read()
            cv2.imshow('Livebild Kamera: 1', img1)
            if cv2.waitKey(1) == 27:
                break
            
        cam1.release()
        cv2.destroyWindow('Livebild Kamera: 1')
            
        NeuerOrdner , result = QInputDialog.getText(self,'Ordner anlegen','Bitte Ordner für Kamera 1 anlegen:')
        pfad.append(q[0] + NeuerOrdner + "/")
        os.makedirs(pfad[0])
        time.sleep(1)
        
        if a >= 2:
            cam2 = cv2.VideoCapture(1)
            cam2.set(cv2.CAP_PROP_FPS,40)
            cam2.open(1)
            while(True):
                ret_val2, img2 = cam2.read()
                cv2.imshow('Livebild Kamera: 2', img2)
                if cv2.waitKey(1) == 27:
                    break
                
            cam2.release()
            cv2.destroyAllWindows()
            
            NeuerOrdner , result = QInputDialog.getText(self,'Ordner anlegen','Bitte Ordner für Kamera 2 anlegen:')
            pfad.append(q[1] + NeuerOrdner + "/")
            os.makedirs(pfad[1])
            
            if a >= 3:
                cam3 = cv2.VideoCapture(2)
                cam3.set(cv2.CAP_PROP_FPS,40)
                cam3.open(2)
                while(True):
                    ret_val3, img3 = cam3.read()
                    cv2.imshow('Livebild Kamera: 3', img3)
                    if cv2.waitKey(1) == 27:
                        break
                cam3.release()
                cv2.destroyAllWindows()
            
                NeuerOrdner , result = QInputDialog.getText(self,'Ordner anlegen','Bitte Ordner für Kamera 3 anlegen:')
                pfad.append(q[2] + NeuerOrdner + "/")
                os.makedirs(pfad[2])
                
                if a >= 4:
                    cam4 = cv2.VideoCapture(3)
                    cam4.set(cv2.CAP_PROP_FPS,40)
                    cam4.open(3)
                    while(True):
                        ret_val4, img4 = cam4.read()
                        cv2.imshow('Livebild Kamera: 4', img4)
                        if cv2.waitKey(1) == 27:
                            break
                    cam4.release()
                    cv2.destroyAllWindows()
            
                    NeuerOrdner , result = QInputDialog.getText(self,'Ordner anlegen','Bitte Ordner für Kamera 4 anlegen:')
                    pfad.append(q[3] + NeuerOrdner + "/")
                    os.makedirs(pfad[3])
                    
                    if a >= 5:
                        cam5 = cv2.VideoCapture(4)
                        cam5.set(cv2.CAP_PROP_FPS,40)
                        cam5.open(4)
                        while(True):
                            ret_val5, img5 = cam5.read()
                            cv2.imshow('Livebild Kamera: 5', img5)
                            if cv2.waitKey(1) == 27:
                                break
                        cam5.release()
                        cv2.destroyAllWindows()
            
                        NeuerOrdner , result = QInputDialog.getText(self,'Ordner anlegen','Bitte Ordner für Kamera 5 anlegen:')
                        pfad.append(q[4] + NeuerOrdner + "/")
                        os.makedirs(pfad[4])
           
           
class Hauptfenster(QMainWindow):
    def __init__(self):
        super().__init__()
        uic.loadUi('/home/pi/Bildprogramm/Ui - Dateien/Bildprogramm.ui', self)
        self.setWindowTitle('Serienbildaufnahme')
        self.setWindowIcon(QtGui.QIcon('/home/pi/Bildprogramm/VWLogo.jpeg'))
        self.show()
        global pfad
        global a
        a = int(a)
        print(pfad[0])
        
        if a >= 2:
            print(pfad[1])
        
            if a >= 3:
                print(pfad[2])
            
if __name__=="__main__":
    app=QApplication(sys.argv)
    app.setStyle('Fusion')
    subprocess.Popen('/usr/bin/matchbox-keyboard')
    IN = Initialisierung()
    sys.exit(app.exec()) 
Immer wenn das Programm abstürzt gibt es folgende Fehlermeldung:
OpenCV Error: Assertion failed (size.width>0 && size.height>0) in imshow, file /home/pi/opencv-3.2.0/modules/highgui/src/window.cpp, line 304
Traceback (most recent call last):
File "/home/pi/Bildprogramm/test ordner nach stream.py", line 50, in CamInit
self.Anzeige()
File "/home/pi/Bildprogramm/test ordner nach stream.py", line 123, in Anzeige
cv2.imshow('Livebild Kamera: 4', img4)
cv2.error: /home/pi/opencv-3.2.0/modules/highgui/src/window.cpp:304: error: (-215) size.width>0 && size.height>0 in function imshow
Backend terminated (returncode: -6)
Fatal Python error: Aborted
Ich hoffe ihr könnt mir helfen und wisst was ich falsch mache... Schon mal danke im voraus!!

Gruß, okliw994
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du machst zwar so einiges falsch mit globalen Variablen, hart-kodierten Pfaden, wiederholung von an sich immer gleichem Code und diverses mehr.

Aber bezueglich deines eigentlichen Problems: nichts. Die Art wie man das in OpenCV macht ist nicht besonders kompliziert und variantenreich. VideoCapture(nummer) ist was man tut, und es ist was du tust. Der Fehler deutet nur darauf hin, dass das gefangene Bild leer ist. Das kannst du abfangen, aber das eigentliche Problem wird bestehen bleiben: der PI kann das halt nicht. Siehe zB hier:

https://www.raspberrypi.org/forums/view ... p?t=102098

Kauf dir einen PI 4 (der kann es vielleicht), oder einen Intel NUC, oder bau dein Setup um so wie in dem verlinkten Artikel.
okliw994
User
Beiträge: 26
Registriert: Dienstag 11. Juni 2019, 09:49

Das mein Programmierstil noch sehr unsauber ist dachte ich mir bereits.. ist mein erstes Programm in Python..

Ich verstehe noch nicht ganz warum der Pi das nicht kann. Ich will ja nacheinander die Kameras aufrufen und nicht alle gleichzeitig. Und warum klappt das mit 3 Kameras nachdem ich x-mal umgestöpselt habe?
Benutzeravatar
__blackjack__
User
Beiträge: 13938
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Also was an dem Programm auf jeden Fall falsch ist, ist das Du das `imshow()` machst, ohne vorher getestet zu haben ob das Lesen des Bildes überhaupt erfolgreich war. Falls nicht bekommst Du nämlich ein Array mit den Dimensionen 0, 0 und genau das führt zu dem Fehler, wie in der Fehlermeldung ja auch deutlich steht.

Ich bin da nicht so zuversichtlich das Du ansonsten alles richtig machst, denn das Programm ist nicht unsauber sondern falsch. Also ich kann nicht sagen ob es tatsächlich etwas ”falsch” macht, aber ich habe versucht den Programmfluss nachzuvollziehen und aus der `Initialisierung.__init__()` , `CamInit()` aufzurufen und da dann wieder ein `Initialisierung`-Objekt zu erstellen ist jenseits von unsauber.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
okliw994
User
Beiträge: 26
Registriert: Dienstag 11. Juni 2019, 09:49

Sorry, hätte ich direkt erwähnen sollen. Der Fehler liegt ganz klar im imshow(). Ich habe mir auch schon die jeweiligen ret_val ausgegeben und diese waren immer bei der Kamera false, bei der das Programm abgebrochen wurde.
Das die Klasse überschrieben wird soll dann geschehen, wenn für a kein Wert in der GUI eingegeben wurde..

Kann mir denn jemand trotz alledem erklären warum die Kameras nach wiederholtem 'Umstöpseln' funktionieren? Und warum der Aufruf mehrerer Kameras nacheinander nicht funktioniert kann?
Ich hänge mich jetzt echt schon seit 2 Wochen an dem Problem auf...
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das unsaubere programmieren hat nichts mit Python zu tun. Auch in Java (das war doch deine andere Sprache?) benutzt keine globalen Variablen und copy und paste Code.

Ich kann dir nur raten dein Programm beiseite zu legen & ein simples, sauberes, rein auf den Test des eigentlichen Problems der mehrfach geöffneten Kameras fokussiertes Programm zu schreiben, welches das Problem klar dokumentiert. Das hilft zu bestimmen ob es sich wirklich um ein Problem von OpenCv und dem pi handelt, oder es an deinem verquasten Code liegt.
okliw994
User
Beiträge: 26
Registriert: Dienstag 11. Juni 2019, 09:49

Ja, aus Java hatte ich meine Grundkenntnisse was OOP, Datentypen, Schleifen etc angeht.. und somit auch z.B. das ich manchmal Klammern bei Schleifen mache...

Ich habe es tatsächlich schon probiert mit diesem Code zu simulieren. Und auch hier zeigt sich das selbe Fehlerbild. Nach mehrmaligem Hin- und Herstecken der Kameras funktioniert es komischer weise... Wenn das nicht funktioniert kommt folgende Fehlermeldung:

Code: Alles auswählen

Unable to stop the stream: Das Argument ist ungültig

Code: Alles auswählen

import cv2 

cam1 = cv2.VideoCapture(0)
while True:
    ret_val1, img1 = cam1.read()
    if ret_val1:
        cv2.imshow('Livebild Kamera:', img1)         
        if cv2.waitKey(1) == 27:
            break
cam1.release()
cv2.destroyAllWindows()

cam2 = cv2.VideoCapture(1)
while(True):
    ret_val2, img2 = cam2.read()
    if ret_val2:
        cv2.imshow('Livebild Kamera:', img2)         
        if cv2.waitKey(1) == 27:
            break
cam2.release()
cv2.destroyAllWindows()

cam3 = cv2.VideoCapture(2)
while(True):
    ret_val3, img3 = cam3.read()
    if ret_val3:
        cv2.imshow('Livebild Kamera:', img3)         
        if cv2.waitKey(1) == 27:
            break
cam3.release()
cv2.destroyAllWindows()
Benutzeravatar
__blackjack__
User
Beiträge: 13938
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@okliw994: Es wird keine Klasse überschrieben. Du hast da eine sehr undurchsichtige Art eine einfache ``while``-Schleife zu schreiben, nämlich als Rekursion von `__init__()`-Methoden. Das würde ich als Programmfehler ansehen, denn man kann nicht endlos den Aufrufstapel vollmüllen ohne dass das Programm irgendwann in eine Ausnahme deswegen läuft. Auch wenn das hier alleine nicht der Grund werden dürfte, sollte man so etwas gar nicht erst anfangen. Zumal das wie gesagt viel schwerer nachzuvollziehen ist, als direkt eine Schleife zu schreiben an der der Leser leicht sieht, dass das wiederholt wird. Beziehungsweise muss da doch gar nichts wiederholt werden. Man muss doch nur am Anfang prüfen ob der Benutzer die notwendige Eingabe gemacht hat. Falls nicht, weist man ihn darauf hin, falls doch macht man mit der Initialisierung weiter. Es gibt keinen Grund dafür noch mal komplett ein neues Fenster zu erstellen.

Zum Testprogramm: Da sind Codewiederholungen die ganz offensichtlich ganz einfach durch eine Schleife ersetzt werden können. Selbst ohne Schleife macht es keinen Sinn da irgendwelche Namen durchzunummerieren, warum tust Du so etwas? Auch so eine Sache die man gar nicht erst *anfangen* sollte.

Code: Alles auswählen

import cv2 


def main():
    for camera_number in range(3):
        camera = cv2.VideoCapture(camera_number)
        try:
            while True:
                is_ok, image = camera.read()
                if is_ok:
                    cv2.imshow('Livebild Kamera:', image)         
                    if cv2.waitKey(1) == 27:
                        break
        finally:
            camera.release()
        cv2.destroyAllWindows()


if __name__ == '__main__':
    main()
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du musst WIRKLICH mal anfangen ueber die Verwendung von Funktionen nachzudenken. Permanentes copy und paste sind kein Weg vorwaerts. Selbst __blackjacks__'s Loesung abstrahiert IMHO noch an der falschen Stelle, ich haette "kamera auf, darstellen, schliessen" in eine Funktion gepackt, und das dann beliebig oft hintereinander aufgerufen. Aber es ist natuerlich schon mal um Groessenordnungen besser.

Aber sei's drum, das ist ja nur gutes Programmieren.

Wenn der Code so wie gezeigt das Problem nicht loest, und das entfernen/anstoepseln der Kameras daran wieder etwas verbessert, dann klingt das nach meinem dafuehrhalten danach, als ob entweder Linux selbst oder die OpenCV beim schliessen der Geraete nicht das richtige macht.

Unter der Haube scheint es sich um diesen Aufruf zu handeln: https://www.linuxtv.org/downloads/legac ... 1re61.html , kommend von https://github.com/opencv/opencv/blob/1 ... .cpp#L1924

Es ist ziemlich schwierig, da was systematisch besseres/anderes zu machen. Dinge die mir einfallen die ich mal ausprobieren wuerde:

- statt nur release aufzurufen, und zu hoffen, dass das dann sofort klappt, noch ein bisschen weiter den mainloop treiben. Die zitierte man-page sagt, dass es nicht wirklich eine Garantie fuer "genau jetzt" gibt. Gegebenenfalls muss man also ein paar Sekunden(bruchteile) warten, bis sich da alles zurecht geruckelt hat.
- alternative APIs anschauen. Sowohl Qt Multimedia hat ja ein backend, als auch zB gstreamer etc. Wenn du damit erfolgreich mehrere Geraete nacheinander angesprochen hast, dann musst du halt dessen Bilder in die OpenCV zur weiterverarbeitung eintueten. Oder per Qt darstellen. Was auch immer du da schlussendlich mit machst.
Antworten