Bilderkennung mit OpenCV und MobileNet auf dem Raspberry Pi
Mit Electreeks in die Welt der künstlichen Intelligenz (KI) einsteigen. Heute bekommst du einen kleinen Exkurs in die Bilderkennung und Objekterkennung mit OpenCV und MobileNet auf dem Raspberry Pi.
Das sind die Unterlagen mit dem Bildmaterial, dem Skript, sowie den vortrainierten Objektklassen, welche wir im Tutorial einsetzen werden:
Versionsinformation
Getestet mit:
Python 3.9.0 (Öffne Terminal -> Python3 – öffnet latest build mit Versionsinformation)
OpenCV 4.4.0 (import cv2 -> cv2.__version__)
Vorbereitung
Um starten zu können, benötigst du folgende Dinge:
- Einen Raspberry Pi 3b oder neuer
- Ein geeignetes Netzteil
- Eine Mirco SD Karte mit mindestens 8 GB Speicherkapazität
- Einen Bildschirm (und wenn nötig, einen HDMI Adapter)
- Eine USB Tastatur und Maus
Die Micro-SD Karte sollte mit einem aktuellen Raspbian OS bespielt sein (Stretch oder Buster – Programmiert wurde mit Stretch).
Du hast noch kein Betriebssystem installiert? Hier erfährst du, wie es in wenigen Schritten erledigt ist.
Nachdem du die Komponenten angeschlossen hast, und das Betriebssystem bereit ist, empfiehlt sich im nächsten Schritt empfiehlt ein Update mit anschließendem Neustart. So haben wir sicher alles auf dem neuesten Stand ist. Dafür wird eine Internetverbindung vorausgesetzt. Öffne das LXTerminal und gib folgenden Befehl ein:
sudo apt-get update
Prozedur
Erstelle als erstes unter Verwendung der Dateiendung “.py” eine Python-Datei und speichere diese in deinem Home Verzeichnis ab. In unserem Verzeichnis ist das die “main.py”. Zur Umsetzung der Bilderkennung, nutzen wir diverse OpenSource Anwendungen.
MobileNet
MobileNet ist ein Objekterkennungsmodell speziell für Geräte mit geringerer Rechenleistung. Die Erkennung verläuft über eine mathematische Methode namens Konvolution (Convolution), oder auch Faltung genannt. Dadurch wird eine feinkörnige Analyse des Bildes auf mehreren Ebenen gewährleistet, wodurch einzelne Merkmale auf dem Bild ausgewertet werden können. Entscheidend ist am Ende die Wahrscheinlichkeit der Existenz eines Objekts innerhalb der vortrainierten Klassen, welche unten aufgeführt werden.
Bildquelle: https://www.researchgate.net/publication/324584455/figure/fig7/AS:616614458310664@1524023751976/MobileNet-SSD-AF-architecture-we-use-MobileNet-as-the-feature-extractor-network-and-SSD.png [21.01.2020]
OpenCV
OpenCV nutzen wir für die prozessuale Abwicklung der Bilderkennung. Wir laden damit die Klassen (Bibliothek der zu erkennenden Objekte), verarbeiten das betreffende Bild und geben die erkannten Objekte (Klassennummer) mit den jeweiligen Positionen als eingezeichnetes Rechteck zurück.
Zur Nutzung ist ein Modul namens OpenCV notwendig. Mit folgendem Befehl in der Konsole LXTerminal, kannst es installieren:
pip install opencv-python
Sollte es auf diesem Weg nicht funktionieren, versuch folgenden längeren Befehl:
sudo apt install libatlas3-base libwebp6 libtiff5 libjasper1 libilmbase12 libopenexr22 libilmbase12 libgstreamer1.0-0 libavcodec57 libavformat57 libavutil55 libswscale4 libqtgui4 libqt4-test libqtcore4 sudo pip3 install opencv-python
Coding
Kommen wir nun zur eigentlichen Programmierung mit Python und dem Raspberry Pi.
Klassen definieren
Das Modell ist für 90 Klassen von Objekten wie Couch, Toaster, Mikrowelle, Pizza und Elefant etc. ausgebildet und getestet. ObjectsNames ist ein Dictionary, das aus einem Namen von 90 Klassen mit einer ID besteht.
Wir laden gleich eine Datei, welche ein vortrainierte Klassen von Objekten enthält. Das Modell ist für 90 Klassen wie Couch, Toaster, Mikrowelle, Pizza und Elefant etc. trainiert und getestet.
“ObjectsNames” ist ein Dictionary, das den namenlosen Klassen (0-90), das entsprechende Objekt zuordnet.
# Pretrained classes in the model ObjectsNames = {0: 'background', 1: 'person', 2: 'bicycle', 3: 'car', 4: 'motorcycle', 5: 'airplane', 6: 'bus', 7: 'train', 8: 'truck', 9: 'boat', 10: 'traffic light', 11: 'fire hydrant', 13: 'stop sign', 14: 'parking meter', 15: 'bench', 16: 'bird', 17: 'cat', 18: 'dog', 19: 'horse', 20: 'sheep', 21: 'cow', 22: 'elephant', 23: 'bear', 24: 'zebra', 25: 'giraffe', 27: 'backpack', 28: 'umbrella', 31: 'handbag', 32: 'tie', 33: 'suitcase', 34: 'frisbee', 35: 'skis', 36: 'snowboard', 37: 'sports ball', 38: 'kite', 39: 'baseball bat', 40: 'baseball glove', 41: 'skateboard', 42: 'surfboard', 43: 'tennis racket', 44: 'bottle', 46: 'wine glass', 47: 'cup', 48: 'fork', 49: 'knife', 50: 'spoon', 51: 'bowl', 52: 'banana', 53: 'apple', 54: 'sandwich', 55: 'orange', 56: 'broccoli', 57: 'carrot', 58: 'hot dog', 59: 'pizza', 60: 'donut', 61: 'cake', 62: 'chair', 63: 'couch', 64: 'potted plant', 65: 'bed', 67: 'dining table', 70: 'toilet', 72: 'tv', 73: 'laptop', 74: 'mouse', 75: 'remote', 76: 'keyboard', 77: 'cell phone', 78: 'microwave', 79: 'oven', 80: 'toaster', 81: 'sink', 82: 'refrigerator', 84: 'book', 85: 'clock', 86: 'vase', 87: 'scissors', 88: 'teddy bear', 89: 'hair drier', 90: 'toothbrush'}
Vortrainiertes Modell laden
Für die Objekterkennung wird ein vortrainiertes MobileNet-Modell zusammen mit seinen Gewichten verwendet. Es wird mit dem OpenCV DNN-Tool (DNN für deep neural network) mit Tensorflow geladen. OpenCV ist eine Open-Source-Bibliothek für maschinelles Lernen und verfügt über umfangreiche Befehle zur Gesichts-, Objekterkennung uvm. Dazu müssen wir folgende Datei einlesen:
# Loading model model = cv2.dnn.readNetFromTensorflow('models/frozen_inference_graph.pb', 'models/ssd_mobilenet_v2_coco_2018_03_29.pbtxt')
Diese Dateien, welche im obigen Befehl geladen werden solltest du vorher auf deinem PC abgespeichert haben. Sofern du die Dateien oben noch nicht gedownloadet hast, hast du hier noch einmal den Downloadlink:
Stell sicher, dass dich die Dateien relativ zu deinem Python-Skript, in einem Unterordner namens “models” befinden.
Bild einfügen
Das vortrainierte Modell kann an Videostreams oder Bildern angewandt werden. In diesem Tutorial nehmen wir Bilder als Input. Das Bild wird mit OpenCV gelesen. Dann werden Bildhöhe und -breite zur weiteren Verarbeitung in Variablen gespeichert.
Das ist das Bild, mit welchem wir den Versuch durchführen werden.
#Loading image image = cv2.imread("dogndapple.jpg") #Getting height and shape of image image_height, image_width, _ = image.shape
Verarbeiten des Bildes
Das Bild wird jetzt mit folgendem Kommando bearbeitet, damit es nachfolgend ordnungsgemäß auf das Erkennungsmodell angewandt werden kann:
blob=cv2.dnn.blobFromImage(image, size=(299, 299), swapRB=True) print("First Blob: {}".format(blob.shape))
Der Befehl erzeugt ein 4-dimensionales Objekt (Blob) aus dem Bild in angepasster Größe und vertauschten Blau- und Rotkanälen (wird für die Verarbeitung in OpenCV benötigt, da dieses mit dem BGR statt dem RGB Schema arbeitet).
Klassenname aus ID erzeugen
“id_Object_class” ist eine Funktion, die die vorhergesagte Klassen-ID und das Wörterbuch von Objekten als Eingabe nimmt und dann mit den Schlüssel-IDs des Wörterbuchs vergleicht. Diese Funktion gibt den Namen der Klasse zurück, die mit der vorhergesagten ID verknüpft ist.
def id_Object_class(class_id, classes): for key_id, class_name in classes.items(): if class_id == key_id: #compared class id with model predicted id return class_name
Schwellenwert festlegen
detection[1] stellt die Klassen-ID dar, die vom Modell vorhergesagt wird, während detection[2] die Wahrscheinlichkeit für das vorliegen der entsprechenden Klasse ist. Der Schwellenwert ist eingestellt bei größer 0,35. Das bedeutet, dass für alle IDs, deren Wahrscheinlichkeit größer als der eingestellte Schwellenwert ist, “id_Object_class” übergeben wird, um den entsprechenden Klassennamen zu identifizieren. Je höher der Schwellwert, desto sicherer handelt es sich um das erkannte Objekt.
Zeichnen der Rechtecke
Zur Visualisierung der beschrifteten Klasse im Bild werden für jede Klasse begrenzte Kästchen gezeichnet. Diese Rechtecke erfordern eine definierte Breite und Höhe sowie die x- und y-Koordinaten.
Der nächste Schritt ist die Skalierung der Box entsprechend der Bildhöhe und -breite. Zum Zeichnen wird der Befehl “cv2.rectangle()” verwendet. Es nimmt die x- und y-Werte der Box zusammen mit der Rechteckbreite und -höhe als Argument. Die Dicke des Rechtecks wird auf 1 gesetzt, während (23, 230, 210) seine Farbe als RGB zeigt.
Die Funktion “cv2.putText()” fügt der Box den Klassennamen hinzu. Der Objektname wird als Eingabe an diese Funktion übergeben. Schriftart und Größe des Textes können ebenfalls angepasst werden.
output = model.forward() #print(output[0,0,:,:]) #print(output) for detection in output[0, 0, :, :]: confidence = detection[2] #confidence for occuring a class #print(detection[2]) if confidence > .35: #threshold class_id = detection[1] #print(detection[1]) class_name=id_Object_class(class_id,ObjectsNames) #calling id_Object_class function print(str(str(class_id) + " " + str(detection[2]) + " " + class_name)) box_x = detection[3] * image_width box_y = detection[4] * image_height box_width = detection[5] * image_width box_height = detection[6] * image_height cv2.rectangle(image, (int(box_x), int(box_y)), (int(box_width), int(box_height)), (23, 230, 210), thickness=1) #drawing triangle cv2.putText(image,class_name ,(int(box_x), int(box_y+.05*image_height)), cv2.FONT_HERSHEY_SIMPLEX,(.005*image_width),(0, 0, 255)) #putting text
Ausgabe
Das Bild kann mit der Funktion “imshow()” wie folgt angezeigt werden.
cv2.imshow('image', image) # cv2.imwrite("image_box_text.jpg",image) cv2.waitKey(0) cv2.destroyAllWindows()
Hier siehst du die Ausgabe des Bildes mit den erkannten Objekten. Darunter die Klassen-ID in der ersten Spalte, die Wahrscheinlichkeit in der zweiten Spalte und der Klassenname in der dritten Spalte.
Der Code im Gesamten
Zur Übersicht kommt hier noch einmal der gesamte Code.
import cv2 #Pretrained classes in the model ObjectsNames = {0: 'background', 1: 'person', 2: 'bicycle', 3: 'car', 4: 'motorcycle', 5: 'airplane', 6: 'bus', 7: 'train', 8: 'truck', 9: 'boat', 10: 'traffic light', 11: 'fire hydrant', 13: 'stop sign', 14: 'parking meter', 15: 'bench', 16: 'bird', 17: 'cat', 18: 'dog', 19: 'horse', 20: 'sheep', 21: 'cow', 22: 'elephant', 23: 'bear', 24: 'zebra', 25: 'giraffe', 27: 'backpack', 28: 'umbrella', 31: 'handbag', 32: 'tie', 33: 'suitcase', 34: 'frisbee', 35: 'skis', 36: 'snowboard', 37: 'sports ball', 38: 'kite', 39: 'baseball bat', 40: 'baseball glove', 41: 'skateboard', 42: 'surfboard', 43: 'tennis racket', 44: 'bottle', 46: 'wine glass', 47: 'cup', 48: 'fork', 49: 'knife', 50: 'spoon', 51: 'bowl', 52: 'banana', 53: 'apple', 54: 'sandwich', 55: 'orange', 56: 'broccoli', 57: 'carrot', 58: 'hot dog', 59: 'pizza', 60: 'donut', 61: 'cake', 62: 'chair', 63: 'couch', 64: 'potted plant', 65: 'bed', 67: 'dining table', 70: 'toilet', 72: 'tv', 73: 'laptop', 74: 'mouse', 75: 'remote', 76: 'keyboard', 77: 'cell phone', 78: 'microwave', 79: 'oven', 80: 'toaster', 81: 'sink', 82: 'refrigerator', 84: 'book', 85: 'clock', 86: 'vase', 87: 'scissors', 88: 'teddy bear', 89: 'hair drier', 90: 'toothbrush'} #Loading model model = cv2.dnn.readNetFromTensorflow('models/frozen_inference_graph.pb', 'models/ssd_mobilenet_v2_coco_2018_03_29.pbtxt') #Loading image image = cv2.imread("dogndapple.jpg") #Getting height and shape of image image_height, image_width, _ = image.shape blob=cv2.dnn.blobFromImage(image, size=(299, 299), swapRB=True) print("First Blob: {}".format(blob.shape)) #set blob as input to model model.setInput(cv2.dnn.blobFromImage(image, size=(299, 299), swapRB=True)) def id_Object_class(class_id, classes): for key_id, class_name in classes.items(): if class_id == key_id: return class_name output = model.forward() #print(output[0,0,:,:]) #print(output) for detection in output[0, 0, :, :]: confidence = detection[2] #confidence for occuring a class #print(detection[2]) if confidence > .35: #threshold class_id = detection[1] #print(detection[1]) class_name=id_Object_class(class_id,ObjectsNames) #calling id_Object_class function print(str(str(class_id) + " " + str(detection[2]) + " " + class_name)) box_x = detection[3] * image_width box_y = detection[4] * image_height box_width = detection[5] * image_width box_height = detection[6] * image_height cv2.rectangle(image, (int(box_x), int(box_y)), (int(box_width), int(box_height)), (23, 230, 210), thickness=1) #drawing rectangle cv2.putText(image,class_name ,(int(box_x), int(box_y+.05*image_height)), cv2.FONT_HERSHEY_SIMPLEX,(.005*image_width),(0, 0, 255)) #text cv2.imshow('image', image) # cv2.imwrite("image_box_text.jpg",image) cv2.waitKey(0) cv2.destroyAllWindows()
Wenn du jetzt ein eigenes Bild verwenden möchtest, musst du nichts weiter tun, als die betreffende Datei unter dem Ordner “models” zu speichern und an der Stelle “image = cv2.imread(“DeinBild.jpg”)” einzufügen. Beachte aber, dass das Skript nur Objekte innerhalb der vortrainierten Klasse erkennen kann.
Schluss
Dieses Tutorial diente nur als ein kleiner Ausblick in die Objekterkennung. Wir sind im Detail bewusst nicht auf jedes Detail eingegangen, da es den Rahmen in diesem Eintrag sprengen würde. Wir hoffen, es hat dir dennoch gefallen.
Ihr dürft euch zukünftig auf noch mehr Einträge über Bilderkennung, Machine Learning und Neuronalen Netzen freuen.
Teil uns doch in den Kommentaren mit, was du dir als nächtes wünschst.
Wir freuen uns!
Herzliche Gratulation zu diesem ausgezeichneten Tutorial !
Es hat praktisch auf Anhieb funktioniert und brachte erstaunliche Resultate.