Gestenerkennung in Python (ohne Backend)
Das Ziel ist es eine Handerkennung in Python zu implementieren, welche es ermöglicht Gesten auszuwerten und entsprechende Steuerungsbefehle an den Arduino (Steuerung des Roboterarms) zu schicken.
Achtung: Ein Raspberry Pi4 hat nicht genügend Rechenleistung, weswegen dort eine Differenzierung zwischen Client und Server gebraucht wird, wobei der Server mehr Rechenleistung hat und die Webcam auswertet. Diese Implementierung der Gestensteuerung setzt einen leistungsstärkeren SBC (= Single Board Computer) oder einen Laptop (in unserem Fall: Macbook Air M2 mit 16GB Ram) vorraus zur Ausführung und Auswertung der Webcam für die Gestensteuerung. In dieser Dokumentation finden Sie auch eine Gestenerkennung, welche zur Auswertung auf ein Backend (Server) mittels Internet zugreift.
Vorraussetzung: Pythoninstallation + Funktionales "pip install" (empfohlen: Visual Studio Code)
Vorbereitung
1. Bibliotheken
Zum Auslesen der Webcam wird die Pythonbibliothek OpenCV verwendet.
Für die Handerkennung wird die Bibliothek Mediapipe verwendet.
Für die entsprechenden Berechnungen zu den von Mediapipe ausgelesenen Punkten der Hand wird Numpy verwendet.
Um die ein zeitliches Intervall festzulegen um nicht zu viele, kurze Befehle an den Arduino zu schicken wird die Bibliothek time verwendet.
Zur Kommunikation mit dem Arduino wird die Bibliothek pySerial verwendet.
pip install opencv-pythonpip install mediapipeTime ist Teil der Python Standard Bibliothek und muss nicht installiert werden.
pip install numpypip install pySerial2. Projektdatei
Erstelle nun in einem Ordner eine Projektdatei, worin du deinen Pythoncode schreibst. Diese könnte zum Beispiel "handtracking" heißen. Wichtig ist das die Datei die Dateiendung .py besitzt, damit der Computer weiß, das er mit einer Pythondatei handtiert, welche auch mittels Pythoninterpret ausgeführt werden kann.
Optional:
Wenn Visual Studio Code verwendet wird, sollte sichergestellt sein das die Extensions "Python", "Python Debugger" und "Pylance" installiert sind (siehe: Installation - Visual Studio Code).
Daraufhin kann oben links über den Reiter "Datei" ein Submenu aufgerufen werden in welchem man ein Ordner öffnen kann. Klicke auf den Knopf "Ordner öffnen" und suche wähle deinen Ordner aus, wo du deine .py Datei gespeichert hast.
Nun kann seitlich links im Explorer entsprechende .py-Datei mittels Doppelklick geöffnet werden. Ist dies geschehen kann nun programmiert werden.
Programmierung
1. Import der Bibliotheken
Zu Beginn jeden Pythonprogramms muss man dem Programm sagen welche Bibliotheken, also vorgefertigte Programmschnipsel von anderen, man verwendet. In unserem Fall sind das die bereits mittels pip-install heruntergeladenen Bibliotheken opencv, mediapipe, numpy, time und pySerial. Diese werden wiefolgt importiert:
Bei den Bibliotheken mediapipe und numpy steht nachdem import noch ein as mit welchem gesagt wird das man die Bibliotheken im Programm mit dem nach dem as stehenden Namen aufrufen möchte . Das sorgt dafür das wir nicht immer mediapipe ausschreiben müssen, sondern einfach mp zum Aufruf einer Funktion aus der Bibliothek verwenden können.
2. Deklarierung und Initialisierung der Variablen
Als werden die für das Programm benötigten Variablen deklariert und initialisiert. Zum einen werden verschiedene Variablen für den Zugriff auf mediapipes Handtracking benötigt, damit nicht immer ewig lange Befehle sondern ein kurzer Variablenname, der auf den Befehl verweist, verwendet werden kann. Außerdem legen wir zugleich eine Variable für die angeschlossene Webcam an, von welcher wir das Bild auslesen und später mit mediapipes Handtracking auswerten wollen, sowie eine Variable für die serielle Kommunikation zwischen Raspi und Arduino.
Danach legen wir einige Variablen mit Defaultwerten fest, in welchen wir die ausgewerteten Daten abspeichern werden.
Daraufhin werden noch 2 weitere Variablen festgelegt, welche Grenzwerte in Form von Zahlen zur Auswertung speichern. Zum ob die Mindestdistanz bei einer Bewegung überschritten wurde, sonst zählt es nicht als Bewegung um zu präventieren das kleine Wackler oder Zuckungen zu zu drastischen Bewegungen führen. Zum anderen den Minimalabstand zwischen Zeigefingerspitze und Daumenspitze , welcher aussagt ab wann die beiden Finger als aufeinanderliegend und somit geschlossen gelten.
3. Auslesen und Auswerten
Als erstes legen wir eine Schleife an, welche solange läuft wie die Webcam erfolgreich ausgelesen werden kann. Mit jedem Durchlauf der Schleife wird der aktuelle Frame in der Webcam ausgelesen und in die Variable "frame" gespeichert.
Das aktuell ausgelesene Webcambild ist mir BGR (=Blue Green Red) kodiert und muss für mediapipe in RGB (=Red Green Blue) umgewandelt werden. Damit wir die Variable "erkennung" festlegen können, welche den Befehl beinhaltet das mediapipe den jeweiligen frame, in RGB-Kodierung, gespeichert als "rgb_frame", verarbeiten soll.
Daraufhin wird eine If-Bedingung aufgestellt die lautet: wenn mediapipe die typischen Merkmale einer Hand erkennt und Sie somit tracken könnte ist nachfolgender If-Bedinung erfüllt und es werden die von Mediapipe übergebenen Werte ausgewertet.
Dann wird eine For-Schleife genutzt, welche über jede erkannte Hand (wobei nur 1 Hand maximal erkannt werden darf) geht und entsprechend mit jedem Durchlauf die Zählervariable "hand_punkte" um 1 inkrementiert.
In Zeile 2 des Programmausschnitts wird gesagt das beim ausgegebenen Frame (später als POP-Up Fenster) die erkannten Schlüsselpunkte markiert werden sollen (deswegen .draw_landmarks [=".male_schlüsselpunkte"]).
In Zeile 4 wird die Variable "handgelenk_cords" angelegt, über welche man die X-, Y- und Z-Koordinate der Hand abrufen kann.
Zur Erklärung: Mediapipe legt quasi ein unsichtbares Koordinatensystem auf das aufgenommene Bild der Webcam, wodurch eine Position der Hand erkannt und entsprechende Positionsänderungen bestimmt werden können.
In Zeile 5 wird ein Array mit numPy erstellt ("aktuelle_pos") wie die X, Y und Z-Koordinaten des Handgelenks hineingespeichert werden.
In Zeile 7 & 8 werden zwei Variablen zugewiesen über welche man, wie in Zeile 4, die Koordinaten des Daumens und des Zeigefingers abrufen kann.
In Zeile 10 bis 12 wird mittels numPys linalg Funktion die Distanz zwischen den festgestellen Punkten von Daumen und Zeigefinger im Dreidimensionalen Raum errechnet.
Wenn die eben errechente Distanz zwischen Daumen und Zeigefinger kleiner als der Wert in der Variable "fingerabstand_geschlossen" ist, wird die Boolean "geschlossen" auf True umgesetzt, da die Hand dann für das Programm als geschlossen gelten soll.
Wenn die Schleife mindestens einmal durchgelaufen ist, ist die Variable alte_pos nicht mit dem definierten nichts zugewiesen, sondern mit der vorherigen Position der Hand, weswegen dann die If-Verzweigung ausgeführt wird, da die Bedingung "is not None" zutreffend ist.
In der Verzweigung wird die Variable "bewegung" ausgerechnet, welche ein Array ist, da die einzelnen Index von dem Array aktuelle_pos mit denen vom dem Array alte_pos subtrahiert werden.
Daraufhin "zeichnet" numPy eine unsichtbare Gerade mit den gegebenen Werten aus dem neuem Array "bewegung". Aus dieser errechneten Gerade kann abgeleitet werden was für eine Bewegung stattgefunden hat und ob die gesamte Strecke der neuen Bewegung, in diesem errechneten Frame im Verhältnis zum vorher ausgerechneten Frame, so groß ist das diese größer als die Mindestbewegung ist. Wenn das zutrifft wird der Boolean "aktive_bewegung" der Wert True zugewiesen, andernfalls wird der Wert False zugewiesen.
In den nachfolgenden Zeilen des Programms wird die Handbewegung in verschiedene Richtungen sowie der Zustand der Hand (ob geschlossen oder offen) ausgewertet und entsprechend kodiert.
Zunächst wird überprüft ob sich die Hand horizontal bewegt hat, wenn ja ob der Wert in Index 0 vom Array "bewegung" größer null ist. Wenn ja kann man daraus ableiten, da vorher das Array "bewegung" durch die Subtraktion von der alten und aktuellen Position errechnet wurde, ob sich die Hand nach links (trifft zu: 2) oder rechts (trifft zu: 1) bewegt hat. Falls sie sich nicht bewegt hat wird der Variable lr, in welche die Bewegung kodiert wird, mit dem Wert 0 zugewiesen.
Gleiches passiert für die Variable ou, welche die vertikale Bewegung beinhaltet. Wenn der y-Wert, gespeichert in Index 1 vom Array "bewegung", größer null ist wird der Variable der Wert 2 zugewiesen, also bewegt sich die Hand nach oben. Wenn der Wert kleiner 0 ist, wird der Wert 1 zugewiesen, da sich die Hand nach unten bewegt. Falls keine Bewegung vorliegt wird ein Defaultwert von 0 zugewiesen.
Das gleiche Prinzip ist auch auf die Variable vh, welche die Bewegung im Raum (nach vorne und hinten) beinhaltet. Wenn die Bewegung, gespeichert im Index 2 von Array "bewegung", größer 0 ist bewegt sich die Hand nach vorne (vh = 2), wenn sie (die Bewegung) kleiner 0 ist, bewegt sich die Hand nach hinten (vh = 1). Falls sich die Hand nicht bewegt wird der Defaultwert 0 zugewiesen.
Zuletzt wird überprüft ob die Boolean "geschlossen" True gesetzt ist, also die Hand geschlossen ist. Wenn ja wird dem Integer "geschlossenint" der Wert 1 zugewiesen, sonst der Wert 2.
Alle eben kodierten Werte werden nun in ein bytearray gespeichert, damit sie mittels pySerial an den Arduino über die seriale Schnittstelle geschickt werden können.
Daraufhin wird die aktuelle Zeit erfasst, und geschaut ob die Mindestzeit bis zum nächsten Senden (Subtraktion von den Variablen "aktuelle_zeit" und "letzte_zeit") unterschritten wurde. Falls ja wird mittels pySerial, über die zu Beginn dafür initialisierte Variable, zum abkürzen des Befehls, "serialcom" und der Funktion "write" das bytearray "daten" in die seriale Schnittsstelle zwischen Arduino und Computer geschrieben.
Daraufhin werden nochmal zur Überprüfung am Computer alle Daten in die Pythonkonsole geschrieben.
Als letztes wird die Variable "letzte_zeit" mit der Variable "aktuelle_zeit" neu initialisiert, da in dieser immer der Zeitpunkt der letzten Sendung, zur vorhin erwähnten Errechnung der Bedindung, gespeichert sein soll.
Gleiches gilt für die Variable "alte_pos", weswegen diese nun neu zugewiesen wird mit den Werten der Variable "aktuelle_pos".
Dieser Codeschnipsel zeigt das aktuelle Videoframe mit den erkannten Handlandmarken in einem Fenster namens "Handtracking" an. Wenn die Taste 'q' gedrückt wird, wird die Schleife beendet. Anschließend werden die Kamera und die serielle Verbindung freigegeben, alle geöffneten Fenster geschlossen und die Handverfolgung beendet.
4. Ausführung
Verbinde den Arduino Uno mit dem jeweiligen Gerät auf dem der Pythoncode, lokal, ausgeführt werden soll.
Finden Sie mit dem untenstehenden Kommand den Port, an welchen der Arduino angeschlossen ist und ändern Sie den Programmcode auf diesen (Variable "serialcom", erster übergebener Wert). Standardmäßig ist der Port "COM3" eingestellt.
Führen Sie den Pythoncode aus. Stellen Sie sicher das der Steuerungsmodus auf "Gestensteurung" beim Arduino Uno umgestellt ist. Beim fertigen Projekt kann dies mittels des Druckknopfes geschehen.
Last updated