Raspberry Video Camera – Teil 17: Exkurs – Wie Computer Farben sehen

Im vorangegangenen Artikel hatte ich gezeigt, wie wir die Raspberry Video Camera durch Farberkennung auslösen können. Dabei analysiert ein Python-Programm die Videobilder auf die typischen Farben der Eichhörnchen hin und generiert dann das Triggersignal. Um die Bildauswertung zu bewerkstelligen, bedienen wir uns einer spezialisierten Software für das maschinelle Sehen. Die nennt sich Computer Vision und in ihrer freien Form OpenCV. Heute möchte ich ein wenig näher darauf eingehen, wie OpenCV Bildimages intern als Zahlen darstellt und was es mit den verschiedenen Farbräumen wie RGB oder HSV auf sich hat. 


Zuerst aber wieder ein Oachkatzl-Video. Mehr davon gibts in meinem YouTube-Kanal.

Mit Farben rechnen

Wie kann das überhaupt gehen, dass ein Programm in Bildern nach Farben sucht? Im vorangegangenen Artikel hat es dazu ein paar Programmzeilen gegeben – richtig verständlich war das, was die bewirken, aber vermutlich noch nicht. Also machen wir heute einen Schritt zurück und schauen uns einmal generell an, wie Computer Vision mit Bildern umgeht.

Dazu braucht es einen Rechner mit Linux, Python3 und OpenCV3, also am besten genau den Raspberry Pi unserer Oachkatzl-Cam. Und wir brauchen ein Bild, das wir laden und analysieren können. Dazu baue ich mir mit Gimp schnell ein sehr kleines Test-Image. Der Screenshot rechts ist sehr stark vergrößert. Das Bild ist lediglich 7 Pixel breit und 4 Pixel hoch. Die Fläche ist in blau gehalten und drei Ecken habe ich farblich hervorgehoben, damit wir die Umsetzung in ein Zahlenformat besser verfolgen können. Das rote Quadrat ist genau ein (1) Pixel breit und hoch, das komplette Bild aus 7×4=28 Pixel wäre in Originalgröße also kaum sichtbar.

Als nächstes werde ich dieses Testbild in OpenCV laden und schauen, wie OpenCV das Image intern darstellt. Mein Bild heißt blau.png und liegt am Raspberry Pi im Unterverzeichnis samples/. Und ich selbst bin wie üblich per SSH als User Pi angemeldet.

Ein Bild mit den Augen eines Computers betrachten

Ein Programm brauchen wir nicht zu schreiben, um das Bild mit den Mitteln von OpenCV zu laden, das können wir in der interaktiven Umgebung von Python machen. Nachfolgend sind die Programmausgaben in normaler Schrift dargestellt und die Eingaben fett. Von Python zurück zum Linuxprompt kommt man übrigens dann mit Strg-D.

$ python3
Python 3.4.2 (default, Oct 19 2014, 13:31:11) 
[GCC 4.9.1] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2
>>> img=cv2.imread('samples/blau.png')
>>> print(img)
[[[  0   0 255]
  [204  52  22]
  [204  52  22]
  [204  52  22]
  [204  52  22]
  [204  52  22]
  [204  52  22]]

 [[204  52  22]
  [204  52  22]
  [204  52  22]
  [204  52  22]
  [204  52  22]
  [204  52  22]
  [204  52  22]]

 [[204  52  22]
  [204  52  22]
  [204  52  22]
  [204  52  22]
  [204  52  22]
  [204  52  22]
  [204  52  22]]

 [[  0   0   0]
  [204  52  22]
  [204  52  22]
  [204  52  22]
  [204  52  22]
  [204  52  22]
  [255 255 255]]]
>>> 

In der ersten Zeile wird Python3 gestartet und liefert einen Eingabeprompt aus drei Größerzeichen. Dann muss zuerst OpenCV importiert werden, damit wir es verwenden können. Wir setzen zwar OpenCV in der Version 3 ein, das Modul heißt aber trotzdem cv2 – keine Ahnung warum.

Jetzt können wir mit cv2.imread() das Bild in die Variable img laden und diese dann mit print() ausgeben. Spätestens hier erkennt man, warum ich das Bild so außerordentlich klein gewählt habe – die Repräsentation im Zahlenformat wäre sonst sehr unübersichtlich. Was sehen wir? Ein mehrdimensionales Array, also mehrere in einander verschachtelte Listen. Wenn wir uns an den eckigen Klammern orientieren und von außen nach innen schauen, dann haben wir zuerst ein Array mit vier Elementen. Jedes der vier besteht wieder aus einem Array mit diesmal 7 Elementen. Und jedes dieser 7 hat drei Zahlen als Inhalt. Die Gestalt des Arrays können wir uns auch anzeigen lassen mit:

>>> img.shape
(4, 7, 3)
>>> 

Die Form meines Testbilds habe ich nicht von ungefähr so gewählt, denn die Zusammenhänge fallen nun direkt ins Auge. Wir haben

  • 4 Bildzeilen mit
  • 7 Bildspalten und jeweils
  • 3 Bytes an Farbinformation

Aber wo liegt der Ursprung der X- und Y-Richtung? Etwa unten links, wie wir es vom Koordinatensystem aus der Schule kennen? Nein, aber suchen wir in der Printausgabe nach dem schwarzen und dem weißen Pixel. Schwarz ist numerisch [0 0 0] also kein Rot-, kein Grün- und kein Blauanteil. Und die Farbe Weiß wird durch [255 255 255] repräsentiert, also volles Rot, volles Grün und volles Blau ergeben bei additiver Farbmischung Weiß.

Damit ist diese Frage auch beantwortet, Schwarz und Weiß befinden sich im vierten Zahlenblock, der folglich der vierten Bildzeile entsprechen muss. Und dort kommt Schwarz als erster Bildpunkt und Weiß ist der letzte. Der Koordinatenursprung liegt also oben links. Von hier aus zählen die Bildzeilen von Null beginnend nach unten und die Bildpunkte innerhalb einer Zeile von Null beginnend nach rechts. Auch wenn das nicht das typische Koordinatensystem ist, das wir aus der Schule kennen, zur Darstellung von Bildern ist das durchaus üblich – The Gimp macht das ganz genauso.

Wenn x für die Horizontale steht und y für die Vertikale, dann wird eine Bildkoordinate mit y vor x notiert, also [y,x]. machen wir die Probe:

>>> print(img[3,0])
[0 0 0]
>>> print(img[3,6])
[255 255 255]
>>>

Tatsächlich, die Bildkoordinate [3,0] liefert den schwarzen Punkt und [3,6] den weißen. Die Zeile wird also vor der Spalte notiert, was ganz logisch ist, wenn man den Arrayaufbau betrachtet. The Gimp macht das allerdings anders, hier kommt Spalte vor Zeile, also [x,y]. In jedem Fall müsste aber die Koordinate [0,0] für den roten Punkt in unserem Testbild stehen.

>>> print(img[0,0])
[  0   0 255]
>>> 

Farbräume

[0,0] ist auf jeden Fall die richtige Koordinate für oben links, aber ist [0 0 255] wirklich die Farbe Rot? Natürlich schon, allerdings nicht im RGB-Farbraum, denn da lautet die Entsprechung für Rot [255 0 0]. Was ist da also los? Ich verrate es gleich, OpenCV verwendet intern nicht das gebräuchliche RGB-Farbmodell, sondern BGR. Keine Ahnung warum.

Bevor Einsprüche kommen, mir ist natürlich schon klar, dass Farbraum und Farbmodell unterschiedliche Bedeutung haben, wenn man es genau nimmt. Das Farbmodell ist die Zuordnung von Zahlenwerten zu Farbörtern, der Farbort ist der Verweis auf einen Punkt im Farbraum und der Farbraum ist ein dreidimensionaler Körper, der aus allen darstellbaren Farben gebildet wird. Diesen ganzen Absatz bitte ich jetzt sofort wieder zu vergessen, so tief wollen wir in die Farbtheorie überhaupt nicht einsteigen und man möge es mir durchgehen lassen, wenn ich zwischen Farbmodell und Farbraum nicht groß unterscheide.

RGB Farbraum

RGB steht für Rot-Grün-Blau und das sind die drei Grundfarben, aus denen sich durch additive Farbmischung alle anderen Farben erzeugen lassen. Die additive Farbmischung arbeitet so, wie das Licht es tut. Blendet man rotes, grünes und blaues Licht übereinander, so erhält man in Summe weißes Licht. Aus Grafikprogrammen kennen wir das und der Farbwürfel stellt dazu den Farbraum dar. Die Werte sind im Normalfall 8 Bit breit, gehen also von 0 bis 255.

RGB color solid cube.pngBy SharkDOwn work, GFDL, Link (Das Bild hab ich mir bei Wikipedia ausgeborgt, deshalb hier die Verlinkung zu Urheber und Lizenz.)

BGR Farbraum

Der BGR Farbraum sieht ganz genauso aus, lediglich die Reihenfolge, in der die Farben notiert werden ist Blau-Grün-Rot. Wir merken uns an dieser Stelle: OpenCV verwendet als Standard BGR und damit wissen wir, welches Byte welchen Farbanteil repräsentiert.

So weit – so einfach, käme jetzt nicht noch ein weiterer Farbraum ins Spiel. Wer sich mein Python-Programm aus dem vorangegangenen Artikel näher angesehen hat, dem ist aufgefallen, dass das Bild nach dem Laden in den HSV-Farbraum überführt wird.

HSV Farbraum

HSV hat hier nichts mit einem norddeutschen Turnclub zu tun, sondern steht für Hue, Saturation und Value. Was ist das und wozu braucht man sowas?

Die RGB Farbnotation (BGR genauso) ist ein sehr technischer Ausdruck, die Werte zeigen an, wie viel Anteil an rotem, grünem und blauem Licht die jeweilige Farbe durch Mischung ergibt. Mit dem menschlichen Farbempfinden hat das wenig zu tun. HSV geht einen anderen Weg.

  • H = Hue: Es wird ein Farbkreis angenommen, auf dem sich alle Grundfarben befinden. Der beginnt bei Rot und läuft über Gelb, Grün, Blau, Violett wieder nach Rot. Durch diesen einen Wert ist also schon mal die Farbe festgelegt. H steht für Hue, zu Deutsch Farbton und gibt den Winkel im Farbkreis an. Typischerweise passiert das in 360 Grad-Einteilung (aber Obacht: nicht bei OpenCV, dort sind es Werte zwischen 0 und 179, also nur 180 Grad).
  • S = Saturation: Die Sättigung sagt nun aus, wie viel von der Farbe genommen wird, wie satt sie aufgetragen wird. Oder anders ausgedrückt, wie viel bzw.wie wenig Weiß zugemischt wird. Ein hoher Sättigungswert bedeutet einen geringen Weißanteil und eine kräftige Farbe – wir bewegen uns in der Farbscheibe, die innerhalb des Farbkreises liegt, weit außen. Je mehr Weiß zugemischt wird, desto weiter laufen wir in der Farbscheibe nach innen und desto mehr verändert sich der Farbton in Richtung Weiß. Sättigung Null ist immer eine weiße Farbe, da sie außer Weiß keinen Farbanteil mehr enthält.
  • V = Value: Mit Wert ist der Helligkeitswert gemeint. Im Farbraum ist das ein Zylinder, der sich unterhalb der Farbscheibe ausbildet und der den Grad der Abdunkelung angibt. Bei einem hohen Wert befinden wir uns weit oben im Zylinder, der Dunkelanteil ist gering, die Farbe damit hell. Je kleiner der Wert wird, desto mehr nimmt die Helligkeit ab und der Farbton wird dunkler. Liegt der Helligkeitswert schließlich bei Null, so ist die Farbe Schwarz erreicht, weil es keinerlei Helligkeit mehr gibt.

Preisfrage: Wenn die Sättigung bei Null liegt und der Helligkeitswert ebenso, haben wir dann die Farbe Weiß oder Schwarz?

HSV color solid cylinder.pngBy SharkDOwn work, CC BY-SA 3.0, Link (Auch dieses Bild stammt von Wikipedia, deshalb wieder die Verlinkung zu Urheber und Lizenz.)

Antwort: Sättigung 0 und Helligkeitswert 0 ergibt Schwarz, denn ohne Helligkeit (ohne Licht) erscheint auch eine weiße Wand schwarz.

In Farbton, Sättigung und Helligkeit zu denken fällt einem Menschen sicher leichter, als sich Mischergebnisse von drei Einzelfarben vorzustellen. Das ist also schon mal ein Vorteil von HSV – auch wenn einem dieses Modell zu Anfang etwas ungewohnt vorkommt. Warum nimmt man bei Computer Vision nun gerne den HSV-Farbraum, wenn es um die Erkennung von Farben geht? Ich gestehe, dass ich es nicht wirklich mit RGB (oder BGR) ausprobiert habe – irgendwie klappt das vermutlich auch. Aber der augenscheinliche Vorteil von HSV liegt darin, dass man in HSV sehr einfach Bereiche bilden kann. Farbbereiche zum Beispiel, in dem ich mir die Farbwinkel aussuche, zwischen denen das Fell eines rotbraunen Eichhörnchens liegt. Oder Sättigung- und Helligkeitsbereiche. So nehme ich aus einem Orangeton ein wenig Helligkeit heraus und gehe geringfügig mit der Sättigung zurück und ich erhalte ein schönes Braun.

HSV in Zahlen

Zurück in die Konsole und zu Python. Wandeln wir unser BGR-Bild einmal um in HSV:

>>> hsv=cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
>>> hsv.shape
(4, 7, 3)
>>> print(hsv)
[[[  0 255 255]
  [115 228 204]
  [115 228 204]
  [115 228 204]
  [115 228 204]
  [115 228 204]
  [115 228 204]]

 [[115 228 204]
  [115 228 204]
  [115 228 204]
  [115 228 204]
  [115 228 204]
  [115 228 204]
  [115 228 204]]

 [[115 228 204]
  [115 228 204]
  [115 228 204]
  [115 228 204]
  [115 228 204]
  [115 228 204]
  [115 228 204]]

 [[  0   0   0]
  [115 228 204]
  [115 228 204]
  [115 228 204]
  [115 228 204]
  [115 228 204]
  [  0   0 255]]]
>>> 

Mit cvtColor führen wir den Farbraumwechsel durch, COLOR_BGR2HSV ist dabei eine cv2-Konstante, die bestimmt, welche Formate gewandelt werden (und davon gibt es in OpenCV eine ganze Menge). Die Datenstruktur ändert sich durch die Umwandlung nicht, der Shape-Befehl zeigt auch für das HSV-Bild (4,7,3) an. Was sich aber ändert sind die Farbwerte. Lediglich Schwarz bleibt [0 0 0], was uns nicht verwundert, denn null Helligkeit ergibt zwangsweise Schwarz. Weiß dagegen braucht die volle Helligkeit bei null Sättigung, also [0 0 255]. Und die Farbe Rot besteht aus dem Winkel 0 am Farbkreis, voller Sättigung und voller Helligkeit, denn es ist als Grundfarbe direkt oben außen am Farbkreis positioniert. Die vielen blauen Pixel lassen sich dann so interpretieren: 115 ist die Position auf dem Farbkreis, die Sättigung ist ziemlich stark, wenn auch nicht voll und die Farbe ist leicht mit Schwarz abgedunkelt. Ich habe also kein reines Blau verwendet.

Halten wir fest: Die Arraystruktur ist bei BGR und HSV gleich – die Farbwerte sind es aber nicht. Wir müssen uns immer genau im Klaren darüber sein, was wir verwenden. Eine Fehlermeldung werden wir vermutlich nicht erhalten, wenn wir den falschen Farbraum verwenden, aber ein falsches Ergebnis.

Was die möglichen Zahlenwerte angeht, so verwendet OpenCV für die Sättigung und die Helligkeit Zahlen zwischen 0 und 255. Der Farbwinkel wäre für einen Vollkreis ja 360 Grad. Die Zahl 360 passt aber nicht in ein Byte, deshalb halbiert OpenCV den Hue-Wert, der damit zwischen 0 und 179 liegen muss.

Schauen wir vergleichsweise auf The Gimp. Dieses freie Grafikprogramm hat einen sehr schönen Farbwähler, mit dem gleichzeitig RGB- und HSV-Werte mit 6 Schiebereglern verändert werden können. Das Ergebnis wird dann farblich und numerisch für RGB und HSV angezeigt. Sehr schön um ein wenig mit HSV zu spielen. Die Hoffnung drängt sich sogar auf, wir könnten damit die Eichhörnchenfarben mischen und die zugehörigen Zahlenwerte abschreiben. Leider eine Hoffnung, die bitter enttäuscht wird. The Gimp verwendet nämlich für den Farbwert 360 Grade und für Sättigung und Helligkeit Zahlen von 0 bis 100 und ist somit nicht kompatibel zu OpenCV.

Wie kommen wir dann zu den Farben für unsere Eichhörnchen? Das Zauberwort heißt Histogramm.

Das Histogramm kennen viele vielleicht von der Digitalkamera. Normalerweise zeigt es die Helligkeitsverteilung im Bild und man kann daraus ablesen, ob es über- unter- oder normalbelichtet ist.

Ein Histogramm ist aber auf alle Werte eines Bildes anwendbar, also zum Beispiel auf den Rot-Kanal eines RGB-Bilds oder auf den Hue-Kanal bei HSV. Im Beispiel sehen wir das Hue-Histogramm des Eichhörnchenbilds. Auf der X-Achse sind dabei die Farbwerte von 0 bis 179 aufgetragen und jeder Ausschlag der Kurve nach oben steht für die Anzahl von Pixeln im Bild, die genau diese Farbe haben. (Hier ist es nicht genau die Anzahl, die Kurve wurde normalisiert, so dass die höchste Spitze den Wert 100 hat. Die Normalisierung braucht man, wenn man mehrere Kurven vergleichen will.)

An sich ist aus diesem Histogramm nicht allzu viel herauslesbar, das liegt aber daran, dass im Bild zwar ein Eichhörnchen vorhanden ist, die Kiefer im Hintergrund mit ihren Grün- und Brauntönen aber weit mehr Fläche auf das Bild bringt. Ich habe aber im Histogramm mal eingezeichnet, wo wir denn Eichhörnchenfarben annehmen dürfen. Interessant wird es jetzt, wenn wir Histogramme von Eichhörnchen ohne Hintergrund bilden und zwar für Hue, Saturation und Value. Und genau darum geht es im nächsten Artikel.

 


Weitere Artikel in dieser Kategorie:

 

Schreiben Sie einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.