Original-URL des Artikels: https://www.golem.de/news/intel-galileo-getestet-hardware-die-sich-freut-1401-104066.html    Veröffentlicht: 30.01.2014 12:03    Kurz-URL: https://glm.io/104066

Intel Galileo getestet

Hardware, die sich freut

Obwohl unsere erste Begegnung mit dem Intel Galileo eher unerfreulich verlief, haben wir nicht aufgegeben. Eine Tour de Force durch die Programmierung von Rechnern für Tüftler.

Der Reiz der Galileo-Plattform besteht darin, zwei Welten miteinander zu verbinden und zu integrieren: Linux und Arduino. Mittlerweile ist noch ein weiterer Grund hinzugekommen, dem Intel Galileo eine Chance zu geben: der Intel Edison. Der Kleinstrechner basiert wie der Galileo auf dem SoC Quark.

Wir gehen im Folgenden zwar in erster Linie auf den Intel Galileo ein, die Grundlagen lassen sich aber auch auf andere Kleinstrechner mit Linux übertragen, wie den Raspberry Pi.

Die Aufgabe

Für eine praktische Einführung haben wir uns eine komplexe Aufgabe gestellt: Wir wollen Laika bauen, die freundliche Hundedame fürs Büro. Sie erkennt über eine Kamera ein Gesicht und winkt ihm mit ihrer Rute. Diese wird über einen Servo angesteuert. Eine Aufgabe, die klassische Arduinos mangels Rechenleistung bislang nicht bewältigen.

Das Projekt zerfällt grob in drei Aufgabenbereiche: die Einbindung einer Kamera, die Erkennung eines oder mehrerer Gesichter und die Ansteuerung eines Servos. Unsere Lösung zielt nicht darauf ab, perfekt oder die beste zu sein. Sie soll vor allem funktionieren, und wir wollen dabei lernen.

Die Basis

Statt auf das Standard-Image des Galileo zu setzen, verwenden wir das LSB-Image, dessen Bau und Konfiguration im Blog von Sergey Kiselev beschrieben werden. Damit verzichten wir zwar auf die Arduino-Bibliotheken, zum aktuellen Zeitpunkt ist diese Installation aber deutlich flexibler und weniger fehlerbehaftet. Letzteres ist vor allem deswegen bedeutsam, weil wir Python in Kombination mit OpenCV verwenden wollen. Ein unfreiwilliger Nebeneffekt ist, dass wir recht viel über die hardwarenahe Programmierung lernen, die von den klassischen Arduino-Bibliotheken oft gut gekapselt wird.

Die Kamera

Im Kern gibt es zwei Möglichkeiten, eine Kamera direkt an den Galileo anzuschließen: mittels eines entsprechenden Arduino-Shields oder per USB. Wir nutzen die zweite Variante, denn eine klassische USB-Webcam ist leicht aufzutreiben und preiswert, und sie wird von Linux direkt unterstützt. Damit gestaltet sich auch das Zusammenspiel mit OpenCV einfacher.

Der Weg dahin ist allerdings schwierig, denn der Galileo hat für den Anschluss von USB-Peripherie einen USB-Micro-A-Port. Diese Art von Anschluss ist äußerst selten. Wir mussten einige Kataloge wälzen, um ein Adapterkabel zu finden.

Nachdem die Anschlussfrage gelöst ist, wollen wir einen ersten Capture-Test mit ffmpeg durchführen - und stellen fest, dass das vorinstallierte ffmpeg nicht funktionsfähig ist. Anscheinend setzt das Binary Prozessoreigenschaften voraus, die der Quark-Chip nicht erfüllt. Teilweise lässt sich das Problem durch eine Neukompilierung auf dem Galileo selbst lösen. Damit klappt dann das Speichern eines Bildes von der Kamera.

Gesichtserkennung

Das Thema Gesichtserkennung ist mit der Bibliothek OpenCV recht leicht. OpenCV umfasst eine Vielzahl von Funktionen zur Bildverarbeitung und -erkennung. Selbst komplexe Aufgaben lassen sich damit häufig über nur wenige Funktionsaufrufe erledigen. Der einzige Nachteil von OpenCV ist ihre Dokumentation: Sie entstammt einem akademischen Umfeld und eine Funktionserklärung kann schon mal aus wenig mehr als einem Verweis auf eine wissenschaftliche Arbeit bestehen. Da aber der Einsatz recht gradlinig ist, reicht es häufig, einfach mit den Funktionsparametern zu spielen, und den Rest beantworten Stackoverflow und Google.

Im Falle der Gesichtserkennung ist nicht einmal das notwendig, der Code besteht aus gerade mal fünf Zeilen:

import cv2 capture = cv2.VideoCapture(0) ret, img = capture.read() cascade = cv2.CascadeClassifier("haarcascade_frontalface_alt2.xml") rects = cascade.detectMultiScale(img, 1.3, 4, \\ cv2.cv.CV_HAAR_SCALE_IMAGE, (20,20))

OpenCV öffnet in der zweiten Zeile die Kameraverbindung und liest in der nachfolgenden Zeile das Kamerabild. In der vierten Zeile wird eine Beschreibungsdatei geladen, am Ende erfolgt die eigentliche Erkennung. Die Positionen der gefundenen Gesichter im Bild befinden sich dann im zurückgegebenen rects-Array. Ist es leer, wurde auch kein Gesicht gefunden.

Die Beschreibungsdatei ist der Dreh- und Angelpunkt der Erkennung. Es handelt sich um eine XML-Datei mit abstrakten, maschinengelernten Beschreibungen (Classifier), wie ein bestimmtes Objekt oder eine Klasse davon aussieht. Wer wie wir nur allgemein Gesichter finden will, kann aus einer Reihe von Beschreibungsdateien auswählen, die von OpenCV mitgeliefert werden.

Wer bestimmte Personen erkennen will, für den liefert OpenCV die notwendigen Werkzeuge mit, um eigene Beschreibungsdateien zu erzeugen.

LEDs ansteuern

Bevor wir uns der Servo-Steuerung zuwenden, legen wir einen Zwischenschritt ein. Unser Programm soll auch drei LEDs ansteuern; zum einen ist das ein praktischer Statusindikator für den monitorlosen Galileo, zum anderen vermittelt es uns die Grundlage, um die Arduino-Pins des Galileo unter Linux mit Python anzusprechen. Praktisch geht es im Folgenden um den Nachbau der Arduino-Funktionen pinMode() und digitalWrite() unter Python.

Die obige Formulierung Arduino-Pins ist eigentlich Unsinn, es handelt sich um normale Ein- und Ausgangspins (General Purpose Input Output, kurz GPIO). Und deshalb werden sie auch problemlos von Linux unterstützt. Gemäß der Linux-Philosophie "Alles ist eine Datei" können diese auch mit Python über normale Dateioperationen gesteuert werden. Ansatzpunkt dafür ist das dateibasierte Interface unter /sys/class/gpio.

Zuerst müssen wir dazu Linux mitteilen, dass wir einen Pin für die Ausgabe nutzen wollen, in diesem Beispiel Pin 4:

f = open("/sys/class/gpio/export", "w"); f.write("28") f.close() f = open("/sys/class/gpio/gpio28/direction", "w") f.write("out") f.close()

Zuerst wird der Pin exportiert, dann die Richtung (direction) gesetzt. Ein Rätsel im Quellcode gilt es noch zu lösen: warum dort 28 als Pin-Nummer steht und nicht 4. Der Grund liegt in der etwas komplexen Anbindung der Pins auf dem Galileo. Die Zuordnung der Arduino-Pin-Nummer zu einer GPIO-Nummer unter Linux geht aus einem IO-Mapping-Dokument hervor. In der oberen Tabelle stehen links unter Arduino IDE ID die Nummern der Arduino-Pins, die GPIO-Pins ergeben sich aus der mittleren Spalte unter GPIO - Linux. Dem Arduino Pin 4 (IO4) ist darin die GPIO-Nummer 28 zugewiesen.

Doppelt belegte Pins

Für die meisten Pins ist die Initialisierung etwas aufwendiger. Hintergrund ist, dass der Galileo über die Pins auch zusätzliche Bussysteme anbieten kann. Deswegen ist es manchmal notwendig, erst den korrekten Modus einzustellen. Auch das erfolgt über GPIO-Einträge.

Soll zum Beispiel Pin 12 genutzt werden, werfen wir zuerst einen Blick in die Mapping-Dokumentation. Für IO12 ergibt sich die GPIO-Nummer 38. Aber in der Spalte Muxed with steht ebenfalls ein Eintrag. Also wird dieser Pin doppelt genutzt. Wir müssen ihn erst in den richtigen Modus versetzen. Dazu gehört der Blick in die zweite Tabelle.

Dort suchen wir den IO12-Eintrag, er befindet sich in der Spalte Mux Selector - 1 und hat die zugehörige GPIO-Nummer 54. Die Spaltenüberschrift Mux Selector - 1 bedeutet, dass in diesem GPIO-Eintrag die Zahl 1 geschrieben werden muss. Ist das erfolgt, kann auch Pin 12 aktiviert und genutzt werden.

f = open("/sys/class/gpio/export", "w"); f.write("54") f.close() f = open("/sys/class/gpio/gpio54/value", "w") f.write("1") f.close() f = open("/sys/class/gpio/export", "w"); f.write("38") f.close() ...

Eines fehlt noch bei der Initialisierung: Um sicherzugehen, dass an den Pins auch tatsächlich ausreichend Strom für LEDs und Ähnliches anliegt, muss für jeden eingesetzten Pin auch noch der Drive korrekt gesetzt werden, hier am Beispiel des Pin 4:

f = open("/sys/class/gpio/gpio28/drive", "w") f.write("strong") f.close()

Die erlaubten Werte für drive sind im Übrigen systemspezifisch. Auf dem Arduino muss der Strom in Milliampere angegeben werden und darf zwischen 2 und 16 liegen.

Was wir bislang beschrieben haben, entspricht der pinMode()-Funktion unter Arduino. Die Funktion digitalWrite() ist leichter nachzubauen:

f = open("/sys/class/gpio/gpio28/value", "w") f.write("1") f.close()

Der geschriebene Wert 1 schaltet den Pin "ein", eine 0 bedeutet "aus".

Ein Zwischenüberblick

Jetzt sind wir in der Lage, ein Skript zu schreiben, das über eine angeschlossene Kamera ein Bild einliest, es auswertet und daraufhin eine LED zum Leuchten bringt:

import cv2 capture = cv2.VideoCapture(0) ret, img = capture.read() cascade = cv2.CascadeClassifier("haarcascade_frontalface_alt2.xml") rects = cascade.detectMultiScale(img, 1.3, 4, \\ cv2.cv.CV_HAAR_SCALE_IMAGE, (20,20)) if not len(rects) == 0 : f = open("/sys/class/gpio/export", "w"); f.write("28") f.close() f = open("/sys/class/gpio/gpio28/direction", "w") f.write("out") f.close() f = open("/sys/class/gpio/gpio28/drive", "w") f.write("strong") f.close() f = open("/sys/class/gpio/gpio28/value", "w") f.write("1") f.close()

Einen Servo steuern

Ein Servo ist ein Stellmotor, dem ein einzustellender Winkel (Servo-Ausschlag) vorgegeben wird, den dieser dann auch unter Belastung hält. Die Ansteuerung erfolgt traditionell über eine Signalleitung, die regelmäßig - in einem wiederkehrenden, 20 Millisekunden langen Zeitfenster - einen elektrischen Impuls übermittelt. Die Länge des Impulses bestimmt den Winkel. Je nach Servo und dessen maximal einstellbarem Winkel, zum Beispiel -90 Grad bis +90 Grad, liegt die Signallänge irgendwo zwischen 0,7 Millisekunde (-90 Grad) bis zu circa 2,5 Millisekunde (+90 Grad).

In der Theorie würden wir also in einer Schleife alle 20 Millisekunden einen Pin für 1 bis 2 Millisekunden "an"-schalten, um den Servo zu steuern. Auf einem Multitasking-Betriebssystem wie Linux sind solche kleinen Zeiteinheiten aber nur unzuverlässig einzuhalten.

Pulsweitenmodulation

Also müssen wir uns anderweitig behelfen. Als Arduino-kompatible Plattform unterstützt der Galileo auch die Generierung von PWM-Signalen an bestimmten Pins. PWM steht für Pulsweitenmodulation und wird benutzt, um analoge Werte über die an sich nur digital (Strom an/aus) arbeitenden Pins zu übertragen.

Bei PWM werden die digitalen Pins innerhalb eines festen Zeitrahmens (die Periode) in sehr kurzen Abständen an- und ausgeschaltet. Als Zeichnung sieht das Ganze aus wie eine Wellenfolge von Bergen und Tälern. Für einen angeschlossenen Baustein entsteht dadurch der Eindruck, nur einen Bruchteil des Stromes zu erhalten.

Ein Beispiel: Ein Baustein erwartet an seinem Eingangspin einen Wert, der zwischen 0 und 255 liegen kann, wobei der Wert zum übertragenen Strom korreliert: 0 bedeutet kein Strom, 255 bedeutet maximalen Strom über eine Dauer X. Wir wollen den Wert 127 übertragen, müssen also den "halben" Strom übertragen. Mit PWM können wir genau dies erreichen. In der Zeit X wird unser Ausgangspin abwechselnd an- und abgeschaltet. Zum Beispiel: In einem Zehntel der Zeit wird der Strom angeschaltet und im zweiten Zehntel wieder abgeschaltet, im dritten wieder an, dann wieder aus usw. Gerechnet auf die Zeit X kommt dabei nur die Hälfte des Stroms an - die Hälfte im Vergleich zu einem ständig geschalteten Strom.

Hardware statt Software

Auf dem Galileo ist der Chip Cypress CY8C9540A I/O Expander für die Umsetzung der PWM-Funktion mitverantwortlich. Wir können ihn steuern und dabei einfach einen Wert übergeben, der zwischen 0 und 255 liegen darf und als PWM-Signal ausgegeben werden soll.

Wie uns das hilft? Die Servo-Elektronik hat eine für uns erfreuliche Eigenschaft: Innerhalb des oben genannten 20-Millisekunden-Zeitfensters interessiert sie sich nur für den ersten Impuls, der sie erreicht, weitere Impulse ignoriert sie. Moderne Servos ignorieren dieses Schema völlig und akzeptieren einfach jeden Impuls, der eine sinnvolle Länge hat und dem eine signifikante Pause folgt. Um sicherzugehen, orientieren wir uns jedoch am 20-Millisekunden-Schema.

Wir müssen den Chip also überreden, als Zeitspanne X 20 Millisekunden anzusetzen und ungefähr ausrechnen, wie die Werte 0 bis 255 in "Berge und Täler" umgesetzt werden. Die "Berge" und die zugehörigen Werte, die einer Zeitspanne von 1 bis 2 Millisekunden entsprechen, benutzen wir dann zur Servo-Ansteuerung.

I2C - die letzte Abkürzung, versprochen!

Der I/O-Chip kann über den I2C-Bus angesprochen und gesteuert werden. Auch für dieses System bietet Linux entsprechende dateibasierte Unterstützung über die /sys/class/i2c-dev- und /dev/i2c-*-Interfaces. An dieser Stelle wollen wir uns aber Unterstützung holen: Die i2c-tools enthalten auch ein Python-Modul für I2C und den ähnlichen SMBus. Es lässt sich problemlos auf dem Galileo installieren und ermöglicht auch das Ansprechen des Buses über die Kommandozeile.

Das Python-Modul erfordert unter moderneren Linux-Installationen eine kleinere Änderung. Im Verzeichnis py-smbus des Installationsarchivs muss in der Datei smbusmodule.c in der Zeile 158 eine Konstante geändert werden: Statt I2C_SLAVE ist I2C_SLAVE_FORCE erforderlich. Erst dann sollte es kompiliert und installiert werden.

Der Begriff "Bus" deutet auf ein komplexes Protokoll, wenn nicht gar schwierige Sprache hin, die wir beherrschen müssen. Das Gegenteil ist der Fall, die Kommunikation mit dem I/O-Chip besteht lediglich darin, diversen Registern einen bestimmten Wert zuzuweisen. Das sieht mit dem Python-SMBus-Modul der IC2-Tools wie folgt aus:

from smbus import SMBus bus = SMBus(0) bus.write_byte_data(0x20, 0x29, 0x04)

Die Initialisierung SMBus(0) verweist auf das /dev/i2c-0-Device - auf dem Galileo gibt es nur diesen einen I2C-Master. Der Schreibvorgang write_byte_data() schreibt den Wert 0x04 in das Register 0x29. Der erste Parameter 0x20 verweist auf den I/O-Chip - dessen Adresse ist auf dem Galileo fest codiert.

Datenblatt-Deutung

Was macht nun diese Angabe? Ein Blick auf Seite 13 des Datenblattes des Chips löst das Rätsel: Es setzt die PWM Clock Source (Taktvorgabe) auf die programmierbare 367,6-Hz-Einstellung. Standardmäßig liegt diese bei 32 kHz, das heißt, die Periode des PWM-Signals dauert ganze 31,25 Mikrosekunden. Das ist viel zu kurz für uns. 367,6 Hz hingegen entsprechen 2,72 Millisekunden. Das ist schon einmal in der richtigen Größenordnung, aber unter Umständen immer noch zu kurz. Zum Glück ist dieser Wert laut Datenblatt aber programmable. Das heißt, durch Angabe eines Teilers können wir eine kleinere Frequenz erzielen und damit eine längere Periode.

Naheliegenderweise wäre 9 ein guter Teiler: Damit kämen wir auf 40,8 Hz, circa 24 Millisekunden. Das entspricht fast unserem notwendigen 20-Millisekunden-Raster. Wie wir später sehen werden, führt dieser Wert aber zu einer relativ groben Auflösung für die Servo-Ansteuerung. Deswegen entscheiden wir uns für einen Teiler von 2. Das sind 183,8 Hz, circa 5,4 Millisekunden. Das ist lang genug, um einen bis zu 2,5 Millisekunden langen Impuls plus Pause zu generieren, und 5,4 selbst ist ein halbwegs vernünftiger Teiler von 20 Millisekunden, wir bleiben also im Raster. Zur Erinnerung: Die Servo-Elektronik interessiert nur das erste Signal im 20-Millisekunden-Raster, die übrigen drei Signale, die wir hier innerhalb dieser 20 Millisekunden generieren, interessiert sie nicht.

Der Teiler wird ebenfalls über den I2C-Bus gesetzt:

bus.write_byte_data(0x20, 0x2C, 0x02)

Kommen wir zur eigentlichen Länge des Impulses, also zur Länge des zu generierenden "Berges". Dessen Wert setzen wir in Register 2b:

bus.write_byte_data(0x20, 0x2B, value)

Doch welche Werte sind für value zulässig beziehungsweise überhaupt nützlich?

Da nur ein Byte gesetzt werden kann, sind wir limitiert auf den Wertebereich 0 bis 255. Und dies entspricht natürlich nicht direkt einer Zeitangabe. Vielmehr müssen wir ausrechnen, welche Zeitspanne zum Beispiel dem Wert 100 entspricht. Alternativ könnten wir auch sämtliche Werte von 0 bis 255 durchtesten und schauen, wie der Servo reagiert (und ihn im Extremfall zerstören).

Zielgerichtet probieren

Aber versuchen wir, Zeit zu sparen sowie sicherzugehen und stecken wenigstens einen ungefähren Wertebereich ab. Dazu setzen wir eine simple Verhältnisgleichung an: Wir wissen, dass bei einem Wert von 255 durchgängig Strom anliegt für 5,4 Millisekunden, weiterhin sollte bei circa 0,7 Millisekunden der Servo bei circa -90 Grad stehen.

Also rechnen wir: (255 * 0,7) / 5,4 = 33. Übergeben wir also den Wert 33, sollte der Servo ganz nach links ausschlagen. Der Ausschlag ganz nach rechts sollte bei einer Zeit von um die 2,0 Millisekunden erfolgen: (255 * 2,0) / 5,4 = 94. Damit haben wir einen potenziellen Wertebereich von 33 bis 94.

Die Nullstellung des Servos (+-0 Grad) sollte in der Mitte bei (94 - 33) / 2 + 33 = rund 63 liegen. Folgende Zeile müsste den Servo auf 0 Grad stellen:

bus.write_byte_data(0x20, 0x2B, 63)

Oder wir lassen den Servo in einer Schleife von links nach rechts laufen:

for i in range(33, 94) : bus.write_byte_data(0x20, 0x2B, i) time.sleep(0.1)

Die Pause ist notwendig, weil ein zu schneller Wechsel des Wertes zum "Verschlucken" von Werten führen kann. Im praktischen Test haben wir festgestellt, dass unser Servo mit etwas kürzeren Zeiten arbeitet. Da hat sich ein Wertebereich von 29 bis 89 als sinnvoll erwiesen. Wie oben angesprochen, sind die Werte vom Servotyp abhängig.

Nach diesen Rechnungen erklärt sich auch, warum eine Periodenlänge von 24 Millisekunden/40,8 Hz eher unpraktisch ist: Nutzen wir die obigen Verhältnisgleichungen und setzen wir als untere Grenze wieder 0,7 und als obere 2,0 Millisekunden an, erhalten wir einen Wertebereich von 7 bis 21. Also gerade mal 21 - 7 = 14 mögliche Servo-Positionen, im Gegensatz zu 5,4 Millisekunden/183.8 Hz mit seinen 94 - 33 = 61 möglichen Servo-Positionen.

Der letzte Schritt

Nach all der Rechnerei fehlt uns nunmehr nur noch ein kleiner Schritt: Wir müssen den PWM-Modus für einen Pin aktivieren. Das können wir über Zugriffe in /sys/class/pwm erledigen, fast analog zur Aktivierung des Pins zur Nutzung für GPIO.

Zuerst gilt es, den Pin zu exportieren - auch hier ist die Nummer des Pins der Galileo-Dokumentation zu entnehmen. Dem Arduino-Pin 9 ist der PWM-Pin 1 zugeordnet:

f = open("/sys/class/pwm/pwmchip0/export", "w"); f.write("1") f.close()

Und dann wird er aktiviert:

f = open('/sys/class/pwm/pwmchip0/pwm1/enable', "w") f.write('1') f.close()

Die potenzielle Doppelbelegung von Pins gilt es auch hier zu beachten. Hinzu kommt, dass mit diesen Zeilen lediglich der PWM-Modus für den Pin angeschaltet wird, die eigentliche Aktivierung als Ausgangspin muss im Vorfeld über die GPIO-Schnittstelle erfolgen - also unserem pinMode()-Imitat von oben.

PWM-Modus für sonstige Fälle

Der Vollständigkeit halber wollen wir hier noch erwähnen, wie der PWM-Modus "normal" benutzt werden kann, zum Beispiel, um eine LED zu dimmen oder einen Motor über einen Motortreiber zu steuern - also Fälle, in denen die Frequenz der PWM uns nicht weiter interessiert. Dann reicht auch der Zugriff über /sys/class/pwmchip0/-Einträge aus.

Die eigentliche Konfiguration erfolgt über die Einträge period und duty_cycle. Beide erwarten Angaben in Nanosekunden. Der period-Wert entspricht der im PWM-Abschnitt angesprochenen Periode. Der duty_cycle definiert die Impulsdauer, also die Dauer, in der insgesamt tatsächlich Strom während einer Periode fließt. Folgendes Beispiel würde eine LED nur zu 50 Prozent mit Strom versorgen:

f = open('/sys/class/pwm/pwmchip0/pwm1/period', "w") f.write('1000000') f.close() f = open('/sys/class/pwm/pwmchip0/pwm1/enable', "w") f.write('1') f.close() f = open('/sys/class/pwm/pwmchip0/pwm1/duty_cycle', "w") f.write('500000') f.close()

Die Prozentzahl von 50 Prozent ergibt sich aus dem Verhältnis von duty_cycle zu period. Der duty_cycle-Wert von 500000 sind 50 Prozent des period-Wertes von 1000000.

Das finale Skript

Jetzt haben wir alles notwendige Wissen und die Zutaten tatsächlich zusammen, um ein vollständiges Skript zu erstellen. Es kann von Golem.de heruntergeladen werden. Alle bislang erläuterten Bruchstücke sind in entsprechenden Funktionen untergebracht.

Erklärungsbedürftig am Skript ist wohl nur der Programmstart:

initPins() start_new_thread(loadRecognition, ()) start_new_thread(initCam, ()) while True : analyse()

Zuerst werden die Pins initialisiert, dann jeweils in einem eigenen Thread die Beschreibungsdatei für die Gesichtserkennung geladen und die Kamera aktiviert.

Die beiden Threads sollen in erster Linie den Programmstart beschleunigen. Das Laden der gut 1 MByte großen Beschreibungsdatei dauert einen wahrnehmbaren Moment, genauso wie die Initialisierung der Kamera. Beide benötigen dabei nicht unbedingt CPU-Ressourcen, sondern warten auf I/O-Operationen - deswegen können beide auch problemlos quasi-parallel warten.

Während sich der Lade-Thread nach dem Laden auch beendet, läuft der Kamera-Thread bis zum Programmende weiter. Er holt dann kontinuierlich die Bilder von der Kamera über OpenCV. Genauso läuft auch die eigentliche Analyse des Bildes über analyse() ununterbrochen. Auf den ersten Blick liegt es nahe, ein Bild zu holen, es auszuwerten und erst dann das nächste zu holen, praktisch hat das aber unschöne Konsequenzen.

Die Trennung hat folgenden Hintergrund: OpenCV puffert die Bilder, die es von der Kamera empfängt. Erhalten wir nun ein Bild, verarbeiten es und steuern im Erfolgsfall den Servo an, dann sind einige Sekunden vergangen. Das nächste Bild, das wir von OpenCV abholen, ist in dem Moment also schon veraltet.

Wenn die beiden Vorgänge "Bild holen" und "Bild auswerten" in getrennten Threads laufen, werten wir hingegen fast immer ein aktuelles Bild aus. Denn der Kamera-Thread leert ständig den Bilder-Puffer von OpenCV, die analyse()-Funktion sieht stets nur das zuletzt geholte Bild.

Wir haben eine Tour de Force versprochen, und wir hoffen, die Leser haben dabei genauso viel gelernt wie wir. Das wird nicht das letzte Hardwareprojekt dieser Art sein. Im Gegenteil, wir fühlen uns inzwischen mutig genug, weitere Ideen anzugehen.  (am)


Verwandte Artikel:
Optane SSD 800p: Intel bringt 3D Xpoint in die Mittelklasse   
(08.03.2018, https://glm.io/133229 )
ARM-SoC-Hersteller: Qualcomm darf NXP übernehmen   
(19.01.2018, https://glm.io/132261 )
Intel: Edison-Module und Arduino-Board werden eingestellt   
(28.04.2017, https://glm.io/127567 )
Raumfahrt: Europäer experimentieren mit wiederverwendbaren Raketen   
(02.02.2018, https://glm.io/132246 )
Prozessor: Modder startet Coffee Lake auf H110-Board   
(06.03.2018, https://glm.io/133178 )

© 1997–2019 Golem.de, https://www.golem.de/