Farbhistogramme helfen bei der Beurteilung, welche Farben wie häufig in einem Bild vertreten sind. Vereinfacht ausgedrückt zumindest. Eindimensionale Histogramme haben wir bei der Farbkalibrierung des Python-Kamera-Triggerprogramms bereits verwendet. Für die drei HSV-Farbkanäle Hue, Saturation und Value, hatten wir zu diesem Zweck drei Histogramme generiert und daraus die Grenzen der Eichhörnchenfarben abgelesen. Nun stelle ich das 2D-Histogramm vor, mit dem wir zwei Kanäle gleichzeitig berechnen und visualisieren können – typischerweise Hue und Saturation.
Diesmal gibt es kein Oachkatzl-Video sondern eine Zeitrafferaufnahme des Bildhintergrunds mit dem zugehörigen Hue-Saturation-Histogramm. Alle Eichhörnchen-Videos sind natürlich weiterhin in meinem YouTube-Kanal.
Zur Wahrung deiner Privatsphäre wird erst eine Verbindung zu YouTube hergestelt, wenn du den Abspielbutton betätigst.
Ein- und zweidimensionale Histogramme
Um eine automatische Farberkennung eine Videokamera triggern lassen zu können, müssen wir die Farbe(n) unseres Zielobjekts kennen. Hier im Beispiel links ist das Zielobjekt ein rotbraunes Eichhörnchen. Das wurde auf dem Bild grob freigestellt, also vom Bildhintergrund befreit. Der Hintergrund ist nun transparent, deshalb scheint die weiße Blogseite durch. Zahlenmäßig hat The Gimp, mit dem ich die Freistellung vorgenommen habe, den Hintergrund als schwarz kodiert. Per Farbhistogramm können wir nun die Farbverteilung aus dem Bild herauslesen. Das eindimensionale Histogramm hat auf der X-Achse den jeweiligen Farbkanal aufgetragen und auf der Y-Achse die zugehörige Anzahl der im Bild vorhandenen Pixel. Für die drei Farbkanäle Hue, Saturation und Value sind das also drei Histogramme.
Wenn wir nun beschließen würden, den Value-Wert, der für die Helligkeit des jeweiligen Bildpunktes steht, zu vernachlässigen, dann hätten wir es nur noch mit zwei Farbkanälen, nämlich Hue und Saturation zu tun. Das können wir auch ruhig machen, denn die Helligkeit unserer Objekte (Eichhörnchen) nimmt auf der Skala einen relativ breiten Bereich ein, so dass der Value-Kanal am allerwenigsten zu einer treffsicheren Erkennung beiträgt.
Zwei Farbkanäle, also Hue (Farbton) und Saturation (Sättigung), können wir dann leicht zu einem zweidimensionalen Histogramm vereinigen. Hue tragen wir auf der X-Achse auf, Saturation auf der Y-Achse und die Pixelanzahl für das jeweilige Hue-Saturation-Paar wird durch eine Falschfarbe repräsentiert. Falschfarbe deshalb, weil die Farbe nichts mit der realen Bildfarbe zu tun hat, sondern nur für einen Zahlenwert steht. Die Zuordnung kann man der nebenstehenden Farbskala entnehmen. Blau steht hier für Null und Braun für den Maximalwert 100.
Vergleichen wir das 2D-Histogramm des rotbraunen Eichhörnchens mit den beiden Einzel-Histogrammen für Hue und Saturation (nur jeweils die roten Kurven), dann eröffnet sich der Zusammenhang. Die rote Kurve schlägt für den Farbwert (Hue) nur ganz links bei niedrigen Zahlenwerten aus. Entsprechend befindet sich der Farbbalken auch im 2D-Histogramm ziemlich links. Die Sättigungskurve der roten Linie ist relativ breit und hat ihr Maximum in etwa in der Mitte der Skala. Und genauso ist es, wenn wir das auf die Y-Achse des 2D-Histogramms übertragen: Das Maximum liegt mit dem braunen Kästchen in der Mitte – nach oben und unten fallen die Werte gegen blau (null) ab.
Die Darstellung als 2D-Histogramm ist erst einmal ungewohnt und sie wirkt auch weniger genau, als die beiden einzelnen 1D-Histogramme. Das hat nicht nur mit der Falschfarbendarstellung zu tun, sondern auch mit der Tatsache, dass ich die Auflösung radikal verringert habe. Bei den 1D-Histogrammen haben wir auf der X-Achse eine Auflösung von 180 Skalenteilen (Bins) für Hue und 256 für Saturation. Beim 2D-Histogramm sind es jeweils nur 30 Bins. Daher kommt die grobe Pixeligkeit auf der blauen Fläche, die aber durchaus gewollt ist. 30 Skalenteile (Bins) in beide Richtungen reichen durchaus aus, um einen qualitativen Eindruck von der Farbverteilung zu erhalten. 180 x 256 Bins wären zwar zahlenmäßig exakter, der optische Eindruck aber eher schlierenhaft und verwaschen. 30 Bins – das ist erst mal eine völlig willkürliche Wahl meinerseits, wenn wir später mit 2D-Histogrammen rechnen wollen, dann gilt es einen Kompromiss zu finden zwischen Auflösung und Datenumfang. Denn klar ist, dass sich mit einer Matrix aus 30 x 30 Werten einfacher und schneller rechnen lässt als mit einer in der Größe von 180 x 256.
Das 2D-Histogramm hat in meinen Augen gegenüber den 2 (oder 3) Einzelhistogrammen folgende Vorteile:
- Es fasst zwei Grafiken zu einer einzigen zusammen
- Es ermöglicht einen schnellen qualitativen Überblick
- Auch rechnerisch repräsentiert ein einziges Histogramm (ein Python-Objekt) zwei Farbkanäle
2D-Histogramm im Video
Und damit ist nun klar, was in dem Video oben passiert. Der Tag von Sonnenaufgang bis Sonnenuntergang wird in ca. eineinhalb Minuten durchlaufen und die verschiedenen Beleuchtungssituationen zeigen ihre Auswirkungen direkt im Histogramm. Der Regen gegen 20 Uhr zum Beispiel verbreitert das Farbspektrum und fährt gleichzeitig die Sättigung im Bild zurück. Und auch in dunkler Nacht scheint es noch Farben zu geben – hier Hue 8 bei Saturation 5 – nur Helligkeit ist natürlich keine mehr vorhanden.
2D Histogramm berechnen und anzeigen
import cv2
import numpy as np
import matplotlib
matplotlib.use('Agg')
from matplotlib import pyplot as plt
fileIn = "samples/r33.png" # image file name
fileOut = "hist2d.png" # histogram file name
img = cv2.imread(fileIn,1) # load image
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # convert to HSV
hist = cv2.calcHist([hsv],[0,1],None,[30,30],[0,180,0,256]) # calc hist satu + hue
hist[0,0]=0 # delete bachground
cv2.normalize(hist,hist,0,100,cv2.NORM_MINMAX) # normalize to 100
plt.imshow(hist.T, interpolation = "nearest", origin = "lower") # plot histogram
plt.title('Hue - Saturation - Histogram')
plt.xlabel('Hue')
plt.ylabel('Saturation')
plt.colorbar(ticks=(0,20,40,60,80,100))
plt.savefig(fileOut) # save histogram
plt.close()
print(fileIn, 'processed')
Das kleine Python-Programm bietet nicht viel Neues. Eine Image-Datei wird in OpenCV geladen und in den HSV-Farbraum konvertiert, so dass wir die Farbkanäle Hue, Saturation und Value haben. Dann wird das Histogramm erzeugt mit:
hist = cv2.calcHist([hsv],[0,1],None,[30,30],[0,180,0,256])
Diesen Befehl hatten wir auch schon bei der Farbkalibrierung verwendet, allerdings drei mal und jeweils nur für einen Farbkanal. Hier kommt nun der zweite Farbkanal hinzu und damit die zweite Dimension. Bei den Kanälen wird 0 und 1 angegeben, jeweils 30 Bins für beide und als Ranges 0-180 für Kanal 0 (Hue) und 0-256 für Kanal 1 (Saturation).
Da es sich bei diesem Beispielbild um ein freigestelltes Eichhörnchen handelt, müssen wir den Hintergrund zahlenmäßig eliminieren, sonst hätte dessen Farbe im Histogramm den absoluten Spitzenwert. Im Beispielbild ist der Hintergrund transparent und zahlenmäßig schwarz, deshalb wird die Histogrammposition [0,0] auf Null gesetzt.
Und danach erfolgt noch eine Normalisierung auf den Wert 100. Das ist zum Beispiel sehr wichtig im Video (ganz oben), damit die Farbskala rechts für alle Einzelbilder konstant bleibt.
Die Ausgabe eines 2D-Histogramms mit Pyplot erfolgt allerdings auf eine andere Weise als gewohnt. Das 1D-Histogramm hatten wir mit plt.plot
erzeugt, nun verwenden wir:
plt.imshow(hist.T, interpolation = "nearest", origin = "lower")
plt.imshow
plottet das Histogramm-Array als Image mit X- und Y-Achse.hist.T
verweist auf das Histogramm-Array. Das.T
verdreht dabei das Array, so dass Hue auf der X-Achse zu liegen kommt und Saturation auf der Y-Achse.imshow
würde das nämlich sonst genau andersherum machen.interpolation = "nearest"
sorgt dafür, dass klare Rechtecke dargestellt und keine Farbverläufe interpoliert werden.- Und
origin = "lower"
verlegt den Ursprung des Diagramms nach unten links. Der würde per Standard oben links liegen.
Neben Titel und Achsenbeschriftungen fügen wir vor dem Speichern noch rechts den Farbbalken hinzu mit plt.colorbar
. Den Parameter ticks
kann man auch weglassen, die Vorgabe sorgt aber dafür, dass mehrere Histogramme (wie im Video) eine einheitliche Beschriftung erhalten.
Warum dann nicht gleich ein 3D-Histogramm?
Wenn ein 2D-Histogramm besser ist, als 1D, dann liegt die Frage nahe, warum man nicht gleich ein 3D-Histogramm erzeugen sollte, um dann den Value-Kanal noch mit an Bord zu haben. Abgesehen davon, dass der Value-Kanal entbehrlich ist, wie ich oben schon erwähnt habe, gibt es ein paar Gründe und Hindernisse, die gegen das 3D-Histogram sprechen:
- Zur Darstellung bräuchten wir die dritte Dimension und damit ein räumliches Bild. Und das sollte dreh- und schwenkbar sein um es von allen Seiten betrachten zu können. Mit einem einfachen Image geht das nicht.
- Pyplot hat auch keine 3D-Darstellungsfunktion.
- Zum Rechnen wäre ein 3D-Histogramm reizvoll, aber es würde ein wesentlich höheres Datenvolumen beanspruchen und zur Verarbeitung mehr Rechnerzeit.
- Angesehen davon funktioniert
cv2.calcBackProject
(siehe nächster Artikel) mit drei Dimensionen nicht. Zumindest ist es mir nicht gelungen und was man im Internet so findet, anderen auch nicht.
Wofür braucht man das?
Meine Artikelserie zur Farberkennung als Kameraauslöser ist noch nicht zu Ende. In den folgenden Beiträgen werde ich bevorzugt das 2D-Histogramm verwenden um Farbverteilungen zu veranschaulichen.Und das 2D-Histogramm wird zusammen mit der Histogramm-Rück-Projektion das nächste Mittel zur Farberkennung werden.
Diejenigen, die selbst programmieren wollen und meine Programme als Anregung verwenden, die haben mit den 2D-Histogrammen ein Werkzeug zur Hand, um schnell mal das zu visualisieren, was das Programm errechnet. Ich empfehle, die Pyplot-Ausgabe aus obigem Programmcode während der Entwicklungs- und Testphase in eigene Programme einzubauen. In der Produktivphase kann man sie dann auskommentieren um Verarbeitungszeit zu sparen.
Weitere Artikel in dieser Kategorie:
- Raspberry Video Camera – Teil 1: Oachkatzl-Cam
- Raspberry Video Camera – Teil 2: Komponenten & Konzepte
- Raspberry Video Camera – Teil 3: Raspberry Pi Kamera Modul V2.1
- Raspberry Video Camera – Teil 4: Aufnahmeauslöser
- Raspberry Video Camera – Teil 5: Passiver Infrarot Bewegungssensor
- Raspberry Video Camera – Teil 6: Stromversorgung
- Raspberry Video Camera – Teil 7: Spannungsregler 5V
- Raspberry Video Camera – Teil 8: Montage Modell 850
- Raspberry Video Camera – Teil 9: Montage Kamera Modul
- Raspberry Video Camera – Teil 10: SW Installation Betriebssystem und Module
- Raspberry Video Camera – Teil 11: SW Python für die Kamera
- Raspberry Video Camera – Teil 12: SW Trigger per Bewegungssensor
- Raspberry Video Camera – Teil 13: SW Autostart und Überwachung
- Raspberry Video Camera – Teil 14: SW Installation Computer Vision (OpenCV 3.2)
- Raspberry Video Camera – Teil 15: SW Einzelbilder exportieren für die Farberkennung
- Raspberry Video Camera – Teil 16: SW Trigger per Farberkennung
- Raspberry Video Camera – Teil 17: Exkurs – Wie Computer Farben sehen
- Raspberry Video Camera – Teil 18: SW Farbkalibrierung
- Raspberry Video Camera – Teil 19: SW Kombinationstrigger
- Raspberry Video Camera – Teil 21: Konzept einer selbstlernenden Farberkennung
- Raspberry Video Camera – Teil 22: SW selbstlernende Farberkennung in Python
- Raspberry Video Camera – Teil 23: Verbesserung durch ROI und Aufnahmezeitbegrenzung
- Raspberry Video Camera – Teil 24: Anpassung von Programmparametern
- Raspberry Video Camera – Teil 25: Zweite Kamera
- Raspberry Video Camera – Teil 26: Optimierungen gegen Frame Drops
- Raspberry Video Camera – Teil 27: Was kostet der Spaß?
- Raspberry Video Camera – Teil 28: Kamera Modell 200 mit Raspberry Pi Zero
- Raspberry Video Camera – Teil 29: Stromversorgung für Kamera Modell 200
- Raspberry Video Camera – Teil 30: Software für Kamera Modell 200
Geniale Idee, nun sogar 2D-Histogramme zu verwenden.