In diesem Blogeintrag setzen wir die Objekterkennung mit dem im vorherigen Blogeintrag ausgewählten SSD Model um. Für die Umsetzung benötigen wir das Caffeemodell und die Modellstruktur des MobileNet SSD, die Bibliothek OpenCV, sowie verschiedene Bibliotheken für die Bild- und Videoverarbeitung

Dockerfile

Damit die Objekterkennung in einem eigenen Service startet ist es nötig folgendes Dockerfile zu erstellen.

# Fehlende Dependencies für Raspi4 images daher raspbi 3
FROM balenalib/raspberrypi3-debian:stretch-20190612

# Install dependencies
RUN apt-get update && \
  apt-get install -yq \
    python3 \
    python3-dev \
    python3-pip \
    python3-setuptools \
    gstreamer-1.0 \
    v4l-utils \
    libopus-dev \
    libvpx-dev \
    libsrtp2-dev \
    libopencv-dev \
    libatlas3-base \
    libatlas-base-dev \
    libjasper-dev \
    libilmbase12 \
    libopenexr22 \
    libavformat-dev \
    libswscale-dev \
    libqtgui4 \
    libqt4-test \
    libavdevice-dev \
    libavfilter-dev \
    libavcodec-dev \
    libgtk-3-dev \
  && apt-get clean && rm -rf /var/lib/apt/lists/*

WORKDIR /usr/src/app
# Enable the v4l2 driver for the Raspberry Pi camera
RUN printf "bcm2835-v4l2\n" >> /etc/modules

RUN pip3 install --upgrade pip

COPY requirements.txt .
RUN pip3 install --no-cache-dir -r requirements.txt

COPY app.conf ./
COPY ./src ./
COPY ./model ./

CMD ["python3", "app.py"]

Die Installation der OpenCV Bibliothek ist entweder durch die Nutzung des Package Installers pip oder durch die eigene Kompilierung auf dem Endgerät möglich. Da das Kompilieren von OpenCV auf einem Raspberry Pi bis zu einer Stunde dauern kann verwenden wir die Installation über pip. Die pip Installation von OpenCV bringt jedoch ebenfalls eigene Nachteile mit sich. Durch die pip Installation ist es nicht mehr möglich selbstständig die Bibliotheken zur Bild- und Videoverarbeitung festzulegen. Manche der von der pip Installation festgelegten Bibliotheken wie beispielsweise libjasper-dev sind nicht kompatibel zu den Balena Images des Raspberry Pi 4. Daher ist es nötig ein älteres Base-Image des Raspberry Pi 3 zu nutzen. Anschließend werden in der Dockerfile die nötigen Bibliotheken und Python 3 installiert. Nach der Installation der Bibliotheken wird das Workingdirectory gesetzt und der v4l2-Treiber aktiviert sowie die nötigen Python Bibliotheken aus der requirements.txt installiert. Abschließend werden die Konfigurationsdatei, der Quellcode und das SSD Model Kopiert und die app.py ausgeführt.

App.py

if __name__ == "__main__":
    configfile = "app.conf"
    checkDeviceReadiness()

    if len(sys.argv) > 1:
        configfile = sys.argv[1]
    camera_device = CameraDevice()
    app = App(configfile, camera_device)
    app.main()

Wie im obenliegenden Codeblock einsehbar wird bei der Ausführung des app.py codes zuerst überprüft ob das Skript eigenständig ausgeführt wird oder von einem anderen Skript importiert wird. Falls das Skript eigenständig ausgeführt wird ruft es die Methode checkDeviceReadiness()auf. Die Methode überprüft, ob ein Videogerät erkannt wird und ob das Betriebssystem Linux ist. Wenn das der Fall ist wird ausgegeben, dass das Videogerät bereit ist. Ansonsten versucht die Methode den v4l2 Treiber erneut zu laden und beendet das Programm.

def checkDeviceReadiness():
    if not os.path.exists('/dev/video0') and platform.system() == 'Linux':
        print('Video device is not ready')
        print('Trying to load bcm2835-v4l2 driver...')
        os.system('bash -c "modprobe bcm2835-v4l2"')
        time.sleep(1)
        sys.exit()
    else:
        print('Video device is ready')

Nach der Überprüfung, ob das Videogerät bereit ist wird ein CameraDevice erstellt. Das erstellte CameraDevice Objekt wird mir der Methode __init__mithilfe der OpenCV Bibliothek initalisiert und eine Aufnahme mit einem Buffer von einem Frame begonnen.

def __init__(self):
        self.cap = cv2.VideoCapture(0)
        self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
        ret, frame = self.cap.read()
        if not ret:
            print('Failed to open default camera. Exiting...')
            sys.exit()
        self.cap.set(3, 640)
        self.cap.set(4, 480)

Daraufhin wird die App ausgeführt. In der __init__-Methode der App werden zuerst der Logger konfiguriert, eine Verbindung zur Redis-Datenbank aufgebaut und das SSD Modell geladen. Für das Laden des Modells wird die load-Methode der eigenen Klasse CaffeModelLoader genutzt. In dieser Methode wird die OpenCV readNetFromCaffe-Methode verwendet um das Modell zu laden.

class CaffeModelLoader:	
    @staticmethod
    def load(proto, model):
    	net = cv2.dnn.readNetFromCaffe(proto, model)
    	return net

Nachdem die App initialisiert ist wird die Hauptmethode ausgeführt. In dieser Methode wird in einer Endlosschleife überprüft, ob das Alarmsystem scharf gestellt ist und der Bewegungsmelder eine Bewegung erkannt hat. Falls dies Zutrifft wird eine Messung durchgeführt und anschließend in der Redis-Datenbank gespeichert.

while True:
                if self._is_alert_system_active() and self._is_movement_detected():
                    measurement = self._perform_measurement()
                    self._save_measurement(measurement)

                interval_seconds = self._read_measurement_interval()
                time.sleep(interval_seconds)

Um die Messung durchzuführen wird eine FrameProcessor Objekt initialisiert, dieser ist für die Konvertierung der Bilddateien zu BinaryLanguageObjects (BLOB) zuständig und in verbindung mit dem geladenen Modell verwendet um ein SSD Objekt zu initialisieren. Nach der Initialisierung der beiden Objekte wird der letzte Frame der PiCam als JPEG-Bild abgespeichert mit der save_jpeg_frame-Methode. Das gespeicherte Bild wird in die detect-Methode der SSD Klasse als Parameter mitgegeben. In der detect-Methode wird das mitgegebene Bild in ein BLOB konvertiert und daraufhin in das Neurale Netz gegeben. Die erkannten Objekte werden als Tensor in der variable obj_data gespeichert.

def detect(self, frame):
        blob = self.proc.get_blob(frame)
        self.net.setInput(blob)
        detections = self.net.forward()
    	# detected object count
        k = detections.shape[2]
        obj_data = []
        for i in np.arange(0, k):
            obj = detections[0, 0, i, :]
            obj_data.append(obj)
        
        return obj_data

Da ein Tensor eine komplizierte Datenstruktur aufweist werden diese durch die get_object-Methode vereinfacht. In der get_objects-Methode werden die erkannten Objekte danach gefiltert, ob sie als Mensch erkannt wurden und die mindest Erkennungssicherheit überschreiten. Falls dies zutrifft werden diese Objekte in einem Array zurückgegeben.

Der ganze Code für die Umsetzung ist auf GitHub veröffentlicht.

Sicherheitskamera featuring Machine Learning – Umsetzung der Objekterkennung

Schreibe einen Kommentar