Farbsegmentierung ist ein wichtiger Bestandteil der industriellen Bildverarbeitung. Sie ist beispielsweise Voraussetzung bei der visuellen Erkennung von Bauteilen auf Fließbändern, die hinsichtlich ihrer Ausrichtung oder Produktionsfehlern überprüft werden sollen. Mittels Farbsegmentierung lässt sich ein Bild so aufteilen, dass sich eindeutige Flächen herausbilden. Diese Flächen können dann beispielsweise entweder als Bauteil oder als Fließband klassifiziert werden. Ein wohl bekannterer Einsatzbereich für Farbsegmentierung existiert in der Filmindustrie. Personen die in einem Film durch utopische Landschaften spazieren, bewegen sich beim Dreh lediglich in einer sogenannten Blue- oder Green-Box. Die digitale Bildverarbeitung sorgt später dafür, dass die grünen oder blauen Bereiche vollständig durch die besagte utopische Landschaft ersetzt werden.
Doch wie genau lassen sich Farben in einem Bild als Bauteil, Fließband, Blue-Box oder Schauspieler einstufen? Für einfache Grauwertbilder gilt: hat ein Bildpunkt den Wert 0 ist er schwarz; hat ein Bildpunkt den Wert 255 ist er weiß. Insgesamt ergeben sich somit 256 Helligkeitswerte, sprich eine „Farbtiefe“ von 8 Bit. In der Helligkeitsabstufung liegt auch der einfachste Weg zur Segmentierung. Durch die Definition eines oder mehrerer Schwellwerte lassen sich in einem Bild bestimmte Objekte bestimmen. Hat ein Bauteil beispielsweise einen Helligkeitswert von 128, wäre dies der Schwellwert nach dem im Bild gesucht wird. In der Realität würden natürlich mehrere Schwellwerte bzw. Schwellwertbereiche definiert, da ein solcher Idealfall nicht existiert. Damit optimale Schwellwerte definiert werden können, kann man das Histogramm eines Bildes verwenden.
Das grundlegende Problem von Grauwertbildern ist ihr geringer Informationsgehalt. Die mit 256 Helligkeitswerten dargestellten Bilder werden beispielsweise nicht in der Filmindustrie verwendet. Filme sind farbig – zumindest heutzutage :-) – und bieten eine Vielzahl an Informationen. Ein Farbbild besteht grundsätzlich aus den Farben Rot Grün und Blau. Jede der drei Farben kann dabei wiederum einen Helligkeitswert zwischen 0 und 255 haben. Die Farbsegmentierung profitiert davon, weil sich Objekte mit gleicher Farbe statt nur gleicher Helligkeit definieren lassen. Perfekt ist der RGB Farbraum (Rot Grün Blau) für eine Farbsegmentierung jedoch trotzdem nicht. Auch wenn sich gleiche Farben finden lassen, können sich Störungen durch Schatten oder schlechte Ausleuchtung ergeben. Der Mensch sieht zwar, OK das ist Blau, jedoch unterscheidet sich im RGB Raum ein Blau von einem Schatten bedeckten Blau gravierend.
Um auch diesen Nachteil auszumerzen, kann ein Bild in einen anderen Farbraum transformiert werden. Für eine Farbsegmentierung bietet sich beispielsweise der HSV Farbraum an. Durch die drei Komponenten Farbton (Hue), Sättigung (Saturation) und Helligkeit (Value) kann ein Farbbereich viel präziser definiert werden, als es im RGB-Farbraum der Fall ist. Ein anderer Farbraum, der sich ebenfalls für eine Farbsegmentierung eignet, ist der sogenannte YUV Farbraum. Kommen wir aber nun zum angenehmen Teil. Die programmiertechnische Umsetzung der Farbsegmentierung.
Die Umsetzung soll an einem einfachen Beispiel erklärt werden. Gehen wir also davon aus: wenn ein Farbtonwert eines Bildpunktes im Ausgangsbild in einem bestimmten Bereich liegt, dann soll er im Ausgabebild nicht mehr dargestellt werden. Dies entspricht in etwa der Vorgehensweise wie auch beim Blue- / Greenscreen Verfahren. Definieren lässt sich dieser Bereich wie folgt. Der Farbwert des Bildpunktes liegt zwischen dem gesuchten Farbwert hue abzüglich einer definierten Abweichung hueTh und dem gesuchten Farbwert hue zuzüglich einer definierten Abweichung hueTh. Das Problem dabei ist, dass im HSV Farbraum der kleinste und der größte Farbtonwert gleich ist. Nämlich „Rot“. Deswegen muss für den Toleranzbereich des gesuchten Wertes beachtet werden, dass sobald hue + hueTh größer als 1 oder hue – hueTh kleiner als 0 ist, sich zwei Bereiche ergeben, in denen gesuchte Farbtonwert liegen kann. Die hellen Bereiche in den beiden Bildern zeigen den eingestellten Toleranzbereich.
Wer die kleine Demo ausprobieren möchte, kann sie sich hier herunterladen. Das Programm benötigt die JAVA Laufzeitumgebung.
/** * Die Funktion führt eine Farbsegmentierung anhand von HSV Werten für ein * Quellbild durch. Die nach der Segmentierung verbleibenden Bildbereiche * werden in das Zielbild gezeichnet. * * @param srcImg Quellbild das durchsucht wird * @param dstImg Zielbild in das das Ergebnis gezeichnet wird * @param hue gesuchter Farbwert * @param hueTh Toleranzwert für den Farbwert * @param sat gesuchte Sättigung * @param satTh Toleranzwert für die Sättigung * @param val gesuchte Helligkeit * @param valTh Toleranzwert für die Helligkeit */ public static void segmentImage(BufferedImage srcImg, BufferedImage dstImg, float hue, float hueTh, float sat, float satTh, float val, float valTh) { int imgW = srcImg.getWidth(); int imgH = srcImg.getHeight(); int iRGBA; int[] vRGBA; float[] vHSB = new float[3]; float hMinA, hMinB, hMaxA, hMaxB, sMin, sMax, vMin, vMax; boolean hConA, hConB, sCon, vCon; for (int y = 0; y < imgH; y++) { for (int x = 0; x < imgW; x++) { // Hole RGBA Wert aus Quellbild iRGBA = srcImg.getRGB(x, y); // Teile RGBA Wert in Werte für ROT GRÜN und BLAU auf // vRGBA[0] => ROT Anteil // vRGBA[1] => GRÜN Anteil // vRGBA[2] => BLAU Anteil // vRGBA[3] => ALPHA Anteil (Transparenz) vRGBA = BitShiftRGB(iRGBA); // Berechne aus den RGB Werten die entsprechenden HSV Werte // vHSB[0] => Farbwert // vHSB[1] => Sättigung // vHSB[2] => Helligkeit Color.RGBtoHSB(vRGBA[0], vRGBA[1], vRGBA[2], vHSB); // Bestimme Minimum für Farbwert im Bereich A hMinA = (hue - hueTh < 0.0f) ? 0.0f : (hue - hueTh); // Bestimme Maximum für Farbwert im Bereich A hMaxA = (hue + hueTh > 1.0f) ? 1.0f : (hue + hueTh); // Bestimme Minimum für Farbwert im Bereich B hMinB = (hue - hueTh < 0.0f) ? 1.0f - Math.abs(hue - hueTh) : 0.0f; // Bestimme Maximum für Farbwert im Bereich B hMaxB = (hue + hueTh > 1.0f) ? (hue + hueTh - 1.0f) : 1.0f; // Prüfe ob der Farbwert zwischen den Min Max Werten im Bereich A liegt hConA = ((hMinA <= vHSB[0] && hMaxA >= vHSB[0])); // Prüfe ob der Farbwert zwischen den Min Max Werten im Bereich B liegt hConB = ((hMinB <= vHSB[0] && hMaxB >= vHSB[0]) && (hMinB != 0.0f || hMaxB != 1.0f)); // Bestimme Minimum für die Sättigung sMin = (sat - satTh < 0.0f) ? 0 : sat - satTh; // Bestimme Maximum für die Sättigung sMax = (sat + satTh > 1.0f) ? 1 : sat + satTh; // Prüfe ob die Sättigung zwischen den Min Max Werten liegt sCon = (sMin <= vHSB[1] && sMax >= vHSB[1]); // Bestimme Minumum für die Helligkeit vMin = (val - valTh < 0.0f) ? 0 : val - valTh; // Bestimme Maximum für die Helligkeit vMax = (val + valTh > 1.0f) ? 1 : val + valTh; // Prüfe ob die Helligkeit zwischen den Min Max Werten liegt vCon = (vMin <= vHSB[2] && vMax >= vHSB[2]); // Wenn der Farbwert in den Bereichen A und B liegt UND die // Sättigung im Richtigen Bereich UND die Helligkeit im richtigen // Bereich, dann schreibe in das Zielbild einen leeren Bildpunkt if ((hConA || hConB) && sCon && vCon) { dstImg.setRGB(x, y, new Color(0, 0, 0, 0).getRGB()); } // Ansonsten Zeichne den RGBA Wert aus dem Quellbild in das // Zielbild else { dstImg.setRGB(x, y, iRGBA); } } } }
/** * Die Funtion extrahiert aus einem RGB Integer Wert die Einzelwerte für * Rot, Grün, Blau und Alpha und schreibt sie in ein Array. * * @param rgba RGBA Wert * @return int Array mit den Werten für Rot, Grün, Blau, Alpha */ public static int[] BitShiftRGB(int rgba) { int vRGBA[] = new int[4]; vRGBA[3] = (rgba >> 24) & 0xff; vRGBA[0] = (rgba >> 16) & 0xff; vRGBA[1] = (rgba >> 8) & 0xff; vRGBA[2] = (rgba >> 0) & 0xff; return vRGBA; }