OpenCV Gesichtserkennung

Django, Flask, Bottle, WSGI, CGI…
DonJuan
User
Beiträge: 12
Registriert: Donnerstag 15. März 2018, 20:51

Guten Morgen zusammen,

erst einmal zur Ausgangslage: ;-)
Zur Zeit schreibe ich meine Masterarbeit im Bereich Softwareentwicklung zur Gesichtserkennung. Da ich eigentlich Betriebswirtschaftslehre studiere bin ich was die Programmierung anbelangt Anfänger. Aber ich möchte mich mit dem Thema auseinander setzen, weil aufbauend auf diesem Programm eine "Business Intelligence" eingerichtet werden soll, die die Zeitstempel grafisch darstellt.
Wie gesagt, es soll ein einfaches Programm werden um das zu veranschaulichen, was ich innerhalb meiner Masterthesis schreibe.

So sollte es funktionieren:
1. Gesicht wird erkannt
2. Es werden Bilder gemacht und in einem Ordner abgelegt
3. Gesichtserkennung greift darauf zu und schaut ob die Person innerhalb der Ordner vorhanden ist. Wenn ja: Ausgabe Name, wenn nein: Ausgabe unbekannt
4. Später soll alles auf eine Datenbank gelegt werden (Vorname, Name, Zeitstempel)
5. Business Intelligence greift auf Datenbank zu und gibt grafisch die Zeitstempel aus


Mein Vorgehen ist im Moment:
1. Gesichtsdetektion schreiben: Habe ich realisiert
2. Gesichtsspeicherung via Webcam: Dort hänge ich momentan
Es soll der Name eingegeben werden und anhand dieses Namens ein neuer Ordner erstellt werden worin die 200 Trainingsbilder eingepflegt werden.
Dies funktioniert aber nicht. Er hat Probleme in dieser Zeile = cv2.imwrite(path + str(ID) + "." + str(count) + ".jpg")

Fehler ist:
Bitte Name eingeben: Dominik
Traceback (most recent call last):
File "C:/Users/Dominik/Desktop/PythonMasterarbeit/GesichtsspeicherungWebcam.py", line 27, in <module>
cv2.imwrite(path + str(ID) + "." + str(count) + ".jpg")
TypeError: unsupported operand type(s) for +: 'NoneType' and 'str'
[ INFO:0] Initialize OpenCL runtime...

Quellcode:
import cv2
import os

face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier('Haar/haarcascade_eye.xml')

ID = input("Bitte Vorname eingeben: ")
count = 0
cam = cv2.VideoCapture(0)
path = os.makedirs("/" + ID)

while (cam.isOpened()):
ret, img_frame= cam.read()
gray = cv2.cvtColor(img_frame, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.3, 5)

for (x, y, w, h) in faces:
cv2.rectangle(gray, (x, y), (x + w, y + h), (255, 255, 255), 2)
count +=1
cv2.imwrite(path + str(ID) + "." + str(count) + ".jpg")
cv2.imshow("Gesichtsspeicherung",img_frame)
if cv2.waitkey(100) & 0xFF == ord("q"):
break
elif count>200:
break
cam.release()
cv2.destroyAllWindows()

Da ich das erste Mal in einem Forum unterwegs bin hoffe ich, das ich alle Informationen aufgeschrieben habe und ihr mir weiter helfen könnt.

Vielen Dank schon mal!
DonJuan
User
Beiträge: 12
Registriert: Donnerstag 15. März 2018, 20:51

Nur zur Info:
Ich habe es gelöst. Könnten wir diesen Thread trotzdem offen lassen, da ich mit Sicherheit noch fragen haben werde.
Ich sitze jetzt am Trainer.
narpfel
User
Beiträge: 643
Registriert: Freitag 20. Oktober 2017, 16:10

Moin,

in Foren wird es normalerweise gern gesehen, wenn man abschließend seine Lösung veröffentlicht. So können andere, die eventuell das gleiche Problem haben, auch von der Lösung profitieren.

Außerdem sollte Quelltext in Codebox-Tags („Code auswählen“ direkt über dem Editor-Textfeld) gesetzt werden, damit die Einrückung erhalten bleibt. Das macht es allen Beteiligten leichter, den Code zu lesen.

Zum Code: Du könntest dir mal Stringformatierung sowie die Module `os.path` und `pathlib` angucken. Damit kann man das Zusammenbauen des Dateinamens sehr viel eleganter ausdrücken.

Ansonsten noch viel Erfolg bei deiner Masterarbeit! :wink:
DonJuan
User
Beiträge: 12
Registriert: Donnerstag 15. März 2018, 20:51

Hallo noch einmal,

tut mir leid für die späte Antwort. Ich hatte viele Abstimmungen mit meinem Professor.
Ich werde am Ende mein Ergebnis hier ins Forum stellen, damit alle etwas davon haben.
Das Pfad-Problem habe ich genau wie Sie es gesagt haben gelöst.

Nun zu meinem nächsten Problem. Wie oben erwähnt hat sich ein wenig die Aufgabenstellung geändert.
Nun soll eine Datenbank angelegt werden, auf welche die Gesichtserkennung zugreift und den Namen heraus zieht, wenn das Gesicht erkannt wurde. Später sollen noch Emotionen erkannt werden innerhalb des erkannten Gesichts und diese in die gleiche Datenbank in eine andere Tabelle geschrieben werden.

Nun erstmal meine "Datenbank-Datei", in dem die Datenbank erstellt wird um die Spaltennamen erwähnt zu haben:

Code: Alles auswählen

Tabelle1 = """
DROP TABLE IF EXISTs Benutzer;
CREATE TABLE Benutzer(
           ID integer primary key,
           Vorname text,
           Nachname text,
           Geschlecht text,
           Geburtsdatum text
);
"""
Danach werden die Daten via Input abgefragt und abgespeichert.
Es werden Bilder gemacht diese gespeichert und trainiert.
Bis dahin funktioniert auch alles.

Und nun zu meinem eigentlichen Problem:
Hier soll das Gesicht erkannt werden. Dies ist auch der Fall. Er geht in die if Bedingung rein, aber scheitert dann ohne Fehler beim Auslesen der Datenbank.

Code: Alles auswählen

while True:
    ret, img_frame = cam.read()
    gray = cv2.cvtColor(img_frame, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray, 1.2, 5)

    for (x, y, w, h) in faces:
        cv2.rectangle(img_frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
        ID, conf = LBPH.predict(gray[y:y+h, x:x+w])

        if conf < 50:
            continue
            FRecs = c.execute("SELECT * FROM Benutzer WHERE ID = (?);", (ID,))
            Benutzer = FRecs.fetchall()
            cv2.putText(img_frame, Benutzer[0] + " " + Benutzer[1], (x + 2, y + h - 5), font, 0, 4, (0, 255, 0), 1)

        #if FRecs.rowcount == 0:
            #print("Ein Gesicht wurde gefunden ist aber nicht in der Datenbank")
            #continue


        else:
            cv2.putText(img_frame, 'Nicht bekannt', (x + 2, y + h - 5), font, 1, (0, 0, 255), 1)
Ich hoffe mir kann irgendjemand helfen. Ich habe wirklich versucht den Fehler zu finden, gegoogelt etc., aber nichts gefunden. :( :( :(
Ich bin wirklich am verzweifeln. Bin schon kurz davor aufzugeben. Sitze da jetzt schon seit drei Wochen dran. :( :( :(
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@DonJuan: das Geburtsdatum sollte vom Typ Datum sein, das ist kein Text. Du sagst ja explizit, dass Du nichts machen willst (`continue`)m wenn conf < 50 ist. `continue` sollte man so weit wie möglich vermeiden, weil es den Programmfluß undurchsichtig macht. Du willst wahrscheinlich `fetchone` benutzen, statt `fetchall`. `c` ist ein schlechter Name, weil man nicht weiß, ist das nun ein Cursor oder eine Connection. Sollte ein Cursor sein, auf dem Du dann auch fetchone aufrufst, der Rückgabewert von `execute` ist eine SQLite-Spezialität, die den Umstieg auf eine andere Datenbank nur unnötig verkompliziert. Gib bei SELECT die Felder immer explizit an, das ist robuster gegen Datenbankänderungen. Statt Strings mit + zusammenzustückeln, nimm format.
DonJuan
User
Beiträge: 12
Registriert: Donnerstag 15. März 2018, 20:51

Vielen Dank erstmal für die schnelle Antwort.
Ich habe es soweit angepasst und vereinfacht. Sieht nun so aus:

Code: Alles auswählen

        if conf < 50:
            FRecs = cur.execute("SELECT * FROM Benutzer WHERE ID = (?);", (ID,))
            Benutzer = FRecs.fetchone()

            #cv2.putText(img_frame, Benutzer[0] + Benutzer[1], (x + 2, y + h - 5), font, 0, 4, (0, 255, 0), 1)
            cv2.putText(img_frame, Benutzer[1], (x + 2, y + h - 5), font, 0, 4, (0, 255, 0), 1)
Jetzt bekomme ich leider diesen Fehler:
cv2.putText(img_frame, Benutzer[1], (x + 2, y + h - 5), font, 0, 4, (0, 255, 0), 1)
TypeError: an integer is required (got type tuple)

Ich weiß zwar was es bedeutet, aber ich finde einfach nicht den Ursprung des Ganzen. Bzw habe ich eine Ahnung, aber weiß nicht wie ich es löse.
Ich vermute ich iteriere durch int Werte, aber will string ausgeben und das funktioniert nicht... Ist nur meine Vermutung.... wie gesagt bin absoluter Anfänger.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Statt die Funktion aufzurufen, Druck dir mal die Argumente aus. Dann kann man das mal abgleichen.
DonJuan
User
Beiträge: 12
Registriert: Donnerstag 15. März 2018, 20:51

Code: Alles auswählen

        if conf < 50:
            FRecs = cur.execute("SELECT * FROM Benutzer WHERE ID = (?);", (ID,))

            Benutzer = FRecs.fetchone()
            print(Benutzer[1])
Es funktioniert. Er gibt den Vornamen aus.

Also scheitert es an der Funktion, oder?

Code: Alles auswählen

cv2.putText(img_frame, Benutzer[1], (x + 2, y + h - 5), font, 0, 4, (0, 255, 0), 1)
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ist nicht worum ich gebeten habe. Ich will alle Argumente zu putText sehen. DA passiert doch der Fehler.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@DonJuan: allein, wenn man die beiden putText-Aufrufe vergleicht, merkt man doch sofort, dass da unterschiedliche Anzahl an Parametern übergeben werden.
DonJuan
User
Beiträge: 12
Registriert: Donnerstag 15. März 2018, 20:51

Wow, super, vielen Dank!!!

Leider stehe ich nun vor dem nächsten Problem.

Code: Alles auswählen

            for (sx, sy, sw, sh) in smiles:
                cv2.rectangle(roi_color, (sx, sy), (sx + sw, sy + sh), (0, 0, 255), 1)

                unix = time.time()
                date = datetime.datetime.fromtimestamp(unix).strftime('%Y-%m-%d %H:%M:%S')

                SDec = cur.execute("SELECT Zeitstempel FROM Emotion WHERE ID = (?) ORDER BY Zeitstempel DESC;", (ID,))
                result = SDec.fetchone()
                print(result)


                if result < result - 300:
                    cur.execute(
                        "INSERT INTO Emotion (ID, Vorname, Nachname, Geschlecht, Geburtsdatum, Emotion, Zeitstempel) VALUES (?,?,?,?,?,?,?)",
                        (Benutzer[0], Benutzer[1], Benutzer[2], Benutzer[3], Benutzer[4], 'Smile', date))

                    conn.commit()
Die if Bedingung macht mir Probleme. Er soll aus der Datenbank den Zeitstempel auslesen und überprüfen, ob 5 Minuten vergangen sind. Leider finde ich in Google immer nur Möglichkeiten mit time.sleep(300). Das bringt mir aber nichts, da das Programm ja weiterlaufen soll.

Bei allen anderen Möglichkeiten die ich bisher ausprobiert habe (wie oben der dümmste Ausdruck mit result -300) gibt er mir natürlich zurück, das es unterschiedliche Typen sind.

Kann mir hier eventuell noch einmal jemand helfen. Sorry für die vielen Fragen. Vermutlich aus eurer Sicht auch richtig doofe :roll:
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

fetchone gibt dir immer ein Tupel zurueck. Auch wenn du nur einen Wert erwartest. Darum macht man dann

Code: Alles auswählen

result, = SDec.fetchone()
Und fuer die Zukunft: poste konkrete Fehlermeldungen, nicht paraphrasierungen. Last but not least: result < result - 300 ist natuerlich unter keinen Umstaenden wahr.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@DonJuan: ich verstehe nicht wo Du wie warten willst und was das mit sleep zu tun haben soll.

Die Datenbank kennt automatisch Zeitstempel und der Treiber wandelt das in Datetime-Objekte um. Da von Hand was rumzuformatieren ist falsch.

Solche bedingten INSERTS macht man üblicherweise auch direkt in SQL, bei SQLite also:

Code: Alles auswählen

c.execute("INSERT INTO Emotion (ID, Vorname, Nachname, Geschlecht, Geburtsdatum, Emotion, Zeitstempel) "
    "SELECT ?,?,?,?,?,?,datetime('NOW') WHERE NOT EXISTS ("
    "SELECT * FROM Emotion WHERE ID=? AND Zeitstempel >= datetime('NOW', '-5 minutes'))",
    [Benutzer[0], Benutzer[1], Benutzer[2], Benutzer[3], Benutzer[4], 'Smile', Benutzer[0]])
Jetzt hast Du noch die erste Normierung von Datenbanktabellen mißachtet. Da Vorname, Nachname, Geschlecht und Geburtsdatum sich nicht mit der Emotion ändern und Du diese Daten schon in der Benutzer-Tabelle hast, brauchst Du sie nicht nochmal zu speichern, und die Emotionstabelle verkleinert sich zu ID, Emotion, Zeitstempel:

Code: Alles auswählen

c.execute("INSERT INTO Emotion (ID, Emotion, Zeitstempel) "
    "SELECT ?,?,datetime('NOW') WHERE NOT EXISTS ("
    "SELECT * FROM Emotion WHERE ID=? AND Zeitstempel >= datetime('NOW', '-5 minutes'))",
    [Benutzer[0], 'Smile', Benutzer[0]])
DonJuan
User
Beiträge: 12
Registriert: Donnerstag 15. März 2018, 20:51

Hallo zusammen,

verzeiht mir die verspätete Rückmeldung.
Ich habe mich zuerst mit dem theoretischen Teil meiner Thesis beschäftigen müssen und bin nun zum Programmieren zurück gekehrt.
Das Programm steht soweit. Ich werde es gerne mit Abgabe meiner Thesis hier posten, um anderen evtl. weiterhelfen zu können.

Aber wie gesagt, mein Programm steht nur fast.
Ich habe folgendes Problem:

Code: Alles auswählen

import cv2
import os
import time
import datetime
import shutil



if not os.path.exists('./Personen'):
    os.makedirs('./Personen')

vname = input("Bitte Vorname eingeben: ")
nname = input("Bitte Nachname eingeben: ")
name = vname + " " + nname


if not os.path.exists('./Personen' + '/' + name):
    os.makedirs('./Personen' + '/' + name)


face_cascade = cv2.CascadeClassifier('Cascades/haarcascade_frontalface_default.xml')
cam = cv2.VideoCapture(0)
counter = 0


Basispfad = os.path.dirname(os.path.abspath(__file__))
Personenpfad = os.path.join(Basispfad, "Personen")
Namenpfad = os.path.join(Personenpfad, name)


ts = time.time()
st = datetime.datetime.fromtimestamp(ts).strftime('%d-%m-%Y_%H-%M-%S')



while counter < 5:
    ret, img_frame = cam.read()
    gray = cv2.cvtColor(img_frame,cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray, 1.3, 5)

    for (x,y,w,h) in faces:
        roi_gray = gray[y:y + h, x:x + w]
        roi_color = img_frame[y:y + h, x:x + w]
        cv2.rectangle(gray, (x, y), (x + w, y + h), (255, 0, 0), 2)
        file_name =  name + " " + str(st)
        
        cv2.imwrite("Personen/" + str(file_name) + " -c " + str(counter)+ ".jpg", roi_gray)#Diese Zeile funktioniert, jedoch natürlich im falschen Ordner
        cv2.imwrite(Namenpfad + str(file_name) + " -c " + str(counter)+ ".jpg", roi_gray) # Diese Zeile funktioniert nicht, es kann nur am Pfad liegen, da ich alles andere getestet habe. Ich hoffe jemand kann mir helfen

        cv2.waitKey(300)
        cv2.imshow('Gesichtsspeicherung',gray)


        counter += 1
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

print ('Gesichtsspeicherung abgeschlossen')
Ich habe mein Problem im Text markiert. Letztendlich soll durch Input ein Ordner erstellt werden in welchen abgespeichert wird.
Habe wirklich schon viel ausprobiert, aber irgendwie scheitere ich immer am Pfad. Meine zweite und nicht sehr schlanke Lösung war, die Dateien anschließend einfach zu verschieben, aber auch damit habe ich Probleme.

Code: Alles auswählen

for subdir, dirs, files in os.walk(Personenpfad):
    for file in files:
        if str(file).startswith(name):
            print(file)
            src = Personenpfad + file
            os.rename(src, Namenpfad)
        else:
            print("Keine Dateien vorhanden")
Ich hoffe Ihr könnt mir weiterhelfen...

Achso Fehler 1:
Er speichert einfach keine Dateien

Fehler 2:
Er findet die Datei nicht um sie zu verschieben

Danke
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Du hast da einen komischen Mix aus Pfaden relativ zum aktuellen Arbeitsverzeichnis und relativ zum Programm, die mal mit ``+`` und '/' und mal mit `os.path.join()` zusammengesetzt werden. Bei ersterem entscheide Dich für das eine oder das andere, und Pfade immer mit `os.path.join()` zusammensetzen.

Das `str()` und ``+`` wo keine Pfadteile verbunden werden, solltest Du durch die `format()`-Methode ersetzen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ergänzend zu den richtigen Anmerkung von BlackJack: nimm zB Personenpfad. Den hast du doch schon. Und den nicht zu nehmen erklärt auch deinen Fehler.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@DonJuan: Dein Skript ist schwer zu lesen, weil Du verschiedene logische Teile vermischst, Pfad anlegen, Video-Capture, Counter. Der Counter wird erst direkt vor der while-Schleife gebraucht, sollte also erst dort definiert werden.

Es gibt in Python eine Namenskonvention, dass alle Variablen klein_mit_unterstrich geschrieben werden. Abkürzungen sollten vermieden werden. vname -> vorname. Was soll st oder ts bedeuten? Beim Lesen muß man da raten.

Zeiten formatiert man am besten in der Form "%Y-%m-%d_%H-%M-%S", dann lassen sie sich einfach lexikalisch sortieren.

Dass Cascades relativ zum Programmcode liegt (was Du nicht erzwingst) ist für mich noch logisch, die Daten, die Du schreibst, erwartet man aber relativ zum Arbeitsverzeichnis.

os.makedirs kennt ein exist_ok-Argument, das ist sicherer, als zu prüfen, ob das Verzeichnis existiert. Das erste makedirs ist überflüssig, weil es mit dem zweiten automatisch erzeugt wird.

Die meisten der str-Aufrufe sind überflüssig, weil es sowieso schon Strings sind. Besser ist es aber, die Dateinamen per format zu erzeugen:

Code: Alles auswählen

current_time = datetime.datetime.now()
...
        file_name =  os.path.join(Namenpfad, "{} {:%Y-%m-%d_%H-%M-%S} - c {}.jpg".format(name, current_time, counter))
DonJuan
User
Beiträge: 12
Registriert: Donnerstag 15. März 2018, 20:51

Vielen Dank erstmal für die Hilfe. Ich habe es versucht umzusetzen. Wie gesagt ich bin Neuling in diesem Gebiet.
Es funktioniert wieder alles bis auf das er die Datei nicht abspeichert. Ich habe mir mit print() ausgeben lassen:
...\Personen\Max Mustermann\Max Mustermann 2018-08-25_11-40-03 - c 4.jpg

Aber im Ordner ist keine Datei.

Code: Alles auswählen

import cv2
import os
import time
import datetime

vname = input("Bitte Vorname eingeben: ")
nname = input("Bitte Nachname eingeben: ")
name = vname + " " + nname


if not os.path.exists('./Personen/' + name):
    os.makedirs('./Personen/' + name)


face_cascade = cv2.CascadeClassifier('Cascades/haarcascade_frontalface_default.xml')
cam = cv2.VideoCapture(0)



Basispfad = os.path.dirname(os.path.abspath(__file__))
Personenpfad = os.path.join(Basispfad, "Personen")
Namenpfad = os.path.join(Personenpfad, name)


current_time = datetime.datetime.now()

counter = 0

while counter < 5:
    ret, img_frame = cam.read()
    gray = cv2.cvtColor(img_frame,cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray, 1.3, 5)

    for (x,y,w,h) in faces:
        roi_gray = gray[y:y + h, x:x + w]
        roi_color = img_frame[y:y + h, x:x + w]
        cv2.rectangle(gray, (x, y), (x + w, y + h), (255, 0, 0), 2)
        file_name =  os.path.join(Namenpfad, "{} {:%Y-%m-%d_%H-%M-%S} - c {}.jpg".format(name, current_time, counter))
        print(file_name)

        cv2.imwrite(file_name + ".jpg", roi_gray)

        cv2.waitKey(300)
        cv2.imshow('Gesichtsspeicherung',gray)

        counter += 1
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

print ('Gesichtsspeicherung abgeschlossen')

cam.release()
cv2.destroyAllWindows()
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Schau Dir mal an, wie Du an welchen Stellen auf Dateien zugreifst, oder Verzeichnisse erzeugst. Ein Warnhinweis sollte sein, dass Du an verschiedenen Stellen fast gleiche Verzeichnisse zusammenbaust.
DonJuan
User
Beiträge: 12
Registriert: Donnerstag 15. März 2018, 20:51

Ich verstehe momentan nicht woran es liegen könnte...

ich erstelle den Pfad:

Code: Alles auswählen

Namenpfad = os.path.join(Personenpfad, name)
Ausgabe:
...\Personen\Max Mustermann

Dann speichere ich innerhalb dieses Pfads das Bild unter dem Namen: file_name ab, oder habe ich da einen Denkfehler?

Code: Alles auswählen

cv2.imwrite(file_name, roi_gray)
Es tut mir leid das ich da so nach hake, aber ich sitze schon seit heute morgen um 05:00 Uhr an diesem Problem :|
Antworten