Kamera als Bewegungsmelder

Du hast eine Idee für ein Projekt?
Antworten
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Code: Alles auswählen

#! /usr/bin/env python
# -*- coding: utf-8

import time
from PIL import Image
import numpy as np
import cv2

SENSITIVITY = 7800
UPDATE_INTERVAL = 0.1
FOLDER = "detected"
cam = cv2.VideoCapture(-1)
old_frame = None
if cam.isOpened():
    try:
        while True:
            time.sleep(UPDATE_INTERVAL)
            new_frame = cam.read()[-1]
            if old_frame is not None:
                changes = 0
                if np.array_equal(old_frame, new_frame):
                    raise RuntimeError("cam disconnected")
                for old, new in zip(old_frame, new_frame):
                    changes += str(np.isclose(old, new)).count("False")
                if changes > SENSITIVITY:
                    print "detected"
                    #Image.fromstring("RGB", (int(cam.get(3)), int(cam.get(4))),
                        #old_frame, "raw", "BGR").save(("{0}/{1}.{2}".format(
                        #FOLDER, time.strftime("%d%b%Y_%H_%M_%S", 
                        #time.gmtime()), "png"))) 
            old_frame = new_frame
    except KeyboardInterrupt:
        print "capture stopped"
    except RuntimeError as error:
        print error
    else:
        print "capture finished"
    finally:
        cam.release()
else:
    print "no cam"
Gruß Frank
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@kaytec: da hast Du Dir die umständlichste Art herausgesucht, die Anzahl der nicht-erfüllten Bedingungen zu ermitteln. Wenn jemand über die String-Repräsentation geht, macht er immer etwas nicht richtig.

Code: Alles auswählen

changes = (~np.isclose(old_frame, new_frame)).sum()
Zum Datumsermitteln nutze am besten das datetime-Modul:

Code: Alles auswählen

image.save("{0}/{1:%d%b%Y_%H_%M_%S}.png".format(FOLDER, datetime.datetime.now()))
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Danke Sirius3,

Warum ist datetime besser ? Habe noch PIL entfernt - kann opencv ja auch.

Code: Alles auswählen

#! /usr/bin/env python
# -*- coding: utf-8

import time
import datetime
import numpy as np
import cv2

SENSITIVITY = 100000
UPDATE_INTERVAL = 0.1
FOLDER = "detected"
cam = cv2.VideoCapture(-1)
old_frame = None
if cam.isOpened():
    try:
        while True:
            time.sleep(UPDATE_INTERVAL)
            new_frame = cam.read()[-1]
            if old_frame is not None:
                changes = 0
                if np.array_equal(old_frame, new_frame):
                    raise RuntimeError("cam disconnected")
                for old, new in zip(old_frame, new_frame):
                    changes += np.isclose(old, new).sum()
                if changes < SENSITIVITY:
                    print "detected"
                    #cv2.imwrite("{0}/{1:%d%b%Y_%H_%M_%S}.png".format(FOLDER, 
                        #datetime.datetime.now()), old_frame)
            old_frame = new_frame
    except KeyboardInterrupt:
        print "capture stopped"
    except RuntimeError as error:
        print error
    else:
        print "capture finished"
    finally:
        cam.release()
else:
    print "no cam"
Gruß Frank
BlackJack

@kaytec: `datetime` stellt eine halbwegs vernünftige objektorientierte API für Datums- und Zeitwerte zur Verfügung, die zum Beispiel auch, wie Sirius3 gezeigt hat, mit der `format()`-Methode zusammenarbeitet, statt der Low-Level Funktionen aus dem `time`-Modul was im Grunde nur ein sehr dünner Wrapper über die C-Funktionen aus `time.h` ist.

Ich würde bei so etwas übrigens nicht die lokale Zeit nehmen, denn das gibt Probleme bei der Umstellung zwischen Winter- und Sommerzeit. UTC ist da eindeutig.
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo,

Die Kamera braucht zum Initialisieren Zeit und so habe ich noch einen counter eingefügt und die Zeit auf UTC umgestellt.

Code: Alles auswählen

#! /usr/bin/env python
# -*- coding: utf-8

import time
import datetime
import numpy as np
from itertools import count
import cv2

SENSITIVITY = 150000
INIT_STEPS = 10
UPDATE_INTERVAL = 0.1
FOLDER = "detected"
cam = cv2.VideoCapture(-1)
old_frame = None
counter = 0
init_counter = count()
        
if cam.isOpened():
    try:
        while True:
            time.sleep(UPDATE_INTERVAL)
            new_frame = cam.read()[-1]
            if old_frame is not None:
                changes = 0
                if np.array_equal(old_frame, new_frame):
                    raise RuntimeError("cam disconnected")
                for old, new in zip(old_frame, new_frame):
                    changes += np.isclose(old, new).sum()
                if counter == INIT_STEPS:
                    if changes < SENSITIVITY:
                        print "detected"
                        cv2.imwrite("{0}/{1:%d%b%Y_%H_%M_%S.%f}.png".format(
                            FOLDER, datetime.datetime.utcnow()), new_frame)
                else:
                    counter = init_counter.next()
            old_frame = new_frame
    except KeyboardInterrupt:
        print "capture stopped"
    except RuntimeError as error:
        print error
    else:
        print "capture finished"
    finally:
        cam.release()
else:
    print "no cam"
Gruß Frank
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo,

habe den Vergleich new_frame --> old_frame herausgenommen und lese die Kamera gleich 2-mal ein. Die Fehlermeldung kommt in einigen Fällen - beide Arrays werden in diesem Falle leer sein und somit auch die Bedingung erfüllen.

Code: Alles auswählen

#! /usr/bin/env python
# -*- coding: utf-8

import time
import datetime
import numpy as np
from itertools import count
import cv2

SENSITIVITY = 150000
INIT_STEPS = 10
UPDATE_INTERVAL = 0.5
FOLDER = "detected"
cam = cv2.VideoCapture(-1)
old_frame = None
counter = 0
init_counter = count()
        
if cam.isOpened():
    try:
        while True:
            time.sleep(UPDATE_INTERVAL)
            frame_1 = cam.read()[-1]
            frame_2 = cam.read()[-1]
            changes = 0
            if np.array_equal(frame_1, frame_2):
                raise RuntimeError("cam disconnected")
            for old, new in zip(frame_1, frame_2):
                changes += np.isclose(old, new).sum()
            if counter == INIT_STEPS:
                if changes < SENSITIVITY:
                    print "detected"
                    cv2.imwrite("{0}/{1:%d%b%Y_%H_%M_%S.%f}.png".format(
                        FOLDER, datetime.datetime.utcnow()), frame_2)
            else:
                counter = init_counter.next()
    except KeyboardInterrupt:
        print "capture stopped"
    except RuntimeError as error:
        print error
    else:
        print "capture finished"
    finally:
        cam.release()
else:
    print "no cam"
Gruß Frank
BlackJack

@kaytec: Und warum verwendest Du nicht einfach das Flag das für Fehler zurückgegeben wird, statt das zu ignorieren?
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo BlackJack,
keine Ahnung was eine flag ist - ist eigentlich eine Abwandlung dieses Scriptes --> viewtopic.php?f=1&t=37337&p=286198#p286198 . Hier habe ich mit deiner Hilfe darauf so reagiert.
Gruß Frank
BlackJack

@kaytec: Als Flag bezeichnet man üblicherweise einen Wert der True oder False sein kann.

Ich verstehe den Code nicht wirklich. Warum wird das so gemacht wie es dort gemacht wird? Du vergleichst alle 500ms zwei direkt hintereinander aufgenommene Bilder was zwar für den Test ob die Bilder ”echt” sind ausreicht, aber für das feststellen von Veränderungen über die Zeit keinen Sinn macht.

Die Schleife mit dem `zip()` erscheint mir überflüssig. `np.isclose()` und die `sum()`-Methode funktionieren nicht nur bei eindimensionalen Arrays.

Nach `INIT_STEPS` Durchläufen läuft die Endlosschleife soweit ich das sehe sinnlos weiter? Im Grunde wird da nur noch auf das abziehen der Kamera gewartet, beziehungsweise auf irgendeine andere Möglichkeit das zweimal Bilder abfragen zu gleichen Werten führt.

Das Konstrukt ist für meinen Geschmack zu verworren. Ich würde das sinnvoll auf Funktionen aufteilen. Beispielsweise eine Generatorfunktion die ausschliesslich Bilder von der Kamera liest und gegebenfalls eine Ausnahme auslöst, falls ein Problem aufgetreten ist.
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo BlackJack,
ich vergleiche die Frames und bei einer Bewegung (man läuft vor die Kamera/hält die Hand davor etc.) ändern sich die Farbwerte der einzelnen Pixel und die dies wird auch so augelöst. Die INIT_STEPS habe ich eingefügt, da die Kamera einige Zeit braucht, damit sie sich "eingependelt" hat, danach soll die Schleife auch weiterlaufen und immer wieder auf Bewegungen reagieren und ein Bild speichern. Habe es mit drei verschiedenen Kameras probiert und mit jeder hat es auch so geklappt ( "JA, nur bei mir" - "Kann man nicht ...."). Nur habe ich die Dokumentation nicht richtig gelesen, denn cam.read gibt True/False zurück beim abziehen der Kamera bzw. fehlendem Frame. Ich habe bei dem Script die Zeilen:

Code: Alles auswählen

if np.array_equal(frame_1, frame_2):
                raise RuntimeError("cam disconnected")
gelöscht und diese gehören natürlich dazu.


Gruß Frank
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo BlackJack,

Die Verwendung von "np,isclose" mit einer Schleife stammt von meiner "unprofessionellen" Umwandlung in einen string. Dort lieferte "np.isclose" ohne Schleife zu wenig Ergebnisse - glaube daher kommt die Schleife !? Es geht auch ohne diese und liefert genug "Empfindlichkeit".
Auszug Doku opencv:
The methods/functions combine VideoCapture::grab() and VideoCapture::retrieve() in one call. This is the most convenient method for reading video files or capturing data from decode and return the just grabbed frame. If no frames has been grabbed (camera has been disconnected, or there are no more frames in video file), the methods return false and the functions return NULL pointer.

So müsste doch "connect, frame_1 = cam.read()" beim Abziehen der Kamera ein False erzeugen - oder? Macht es nicht - es bleibt weiter True ?

Gruß Frank
BlackJack

@kaytec: Ja es sollte wenn kein Bild gelesen werden konnte `False` zurückgegeben werden. Ist das nicht der Fall, würde ich von einem Fehler in CV oder der Python-Anbindung ausgehen. Ich habe hier zum Testen nur die im Laptop im Displayrahmen eingebaute Kamera — die lässt sich zu Testzwecken leider nicht mal eben so im laufenden Betrieb abziehen. ;-)
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo BlackJack,

falls du möchtest kannst du es so probieren. Auch hier werden die Bewegungen bei gleichzeitigem Einlesen erkannt.

Code: Alles auswählen

#! /usr/bin/env python
# -*- coding: utf-8

import time
import cv2

cam = cv2.VideoCapture(-1)
        
while True:
    time.sleep(0.1)
    frame = cv2.threshold(cv2.absdiff(
            cv2.cvtColor(cam.read()[1], 
            cv2.COLOR_BGR2GRAY), 
            cv2.cvtColor(cam.read()[1], 
            cv2.COLOR_BGR2GRAY)), 20, 255, 
            cv2.THRESH_BINARY)[1]
    cv2.imshow("Motion", frame)
    
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
cam.release()
cv2.destroyAllWindows()
Gruß Frank
Antworten