Lichtpunkterkennung

Viele Robotikbastler kommen schnell zum Punkt wo sie einen Kamera auf den Roboter montieren. Doch eigentlich ist dies sinnlos, wenn man  nicht gerade ein ferngesteuertes Auto bauen möchte.

Doch mit so einer Kamera lässt sich viel mehr anfangen als man denkt. In Folge der Aufgabe von MARS, das er einer Linie folgen soll bzw per Laser Entfernung messen soll, habe ich einiges zur Erfahrung bringen können und möchte es hier kurz mit euch teilen.

Die erste Aufgabe bei der Entfernungsmessung ist zunächst überhaupt zu erkennen wohin der Laser leuchtet, also wo sich der Punkt eigentlich befindet. Das kann man dann auch natürlich verwenden um auf den Punkt zuzufahren.

Also fangen wir mal an:

Zunächst stellen wir mal eine kleine Liste auf, wie wir vorgehen werden. Davor legen wir aber die drei Grundelemente eines Programmes fest:

  • Input
  • Processing
  • Output
  • Input sind bei uns die Kamerabilder oder das Kamerabild
  • Processing heißt bei uns den hellsten Punkt auf dem Bild zu finden
  • Output Offset des hellsten Punktes zur Kameramitte

Auf den Input möchte ich nicht darauf eingehen wie man eine Kamera ausließt. Das ist von Programmiersprache zu Programmiersprache unterschiedlich. Wir gehen davon aus, dass wir einen Laser verwenden den wir EIN- oder AUSSCHALTEN können. Steht uns das nicht zur Verfügung kann man andere Filter verwenden um nur den Lichtfleck zu erhalten. Ein Beispiel dafür wäre die Umwandlung ins HSV-System, dazu gibt es aber eine schöne Arbeit im Roboternetz.

Jedoch funktioniert diese Methode auch bei normalen Bildern, auch wenn nur ein Bild mit Licht vorhanden ist, oder sich die zwei Bilder nicht 100% gleichen.

Zurück zu unserem Laser. Unsere Input besteht nun aus zwei Bildern, ein Bild mit Lichtpunkt und ein Bild ohne Lichtpunkt. Ich habe das alles in MATLAB getestet, und ich zeige euch hier die Bilder und den Quellcode:

Man sieht, es sind ziemlich exakt die gleichen Bilder. Wenn wir diese Bilder nun einlesen, erhalten  wir eine 2-Dimensionale Matrix, wobei die Größe den Bildabmessungen entspricht.

Dieses Bild hat eine Abmessung von 160×120 Pixel. Das ergibt also eine Matrix mit 120 Zeilen und 160 Spalten.

Wir lesen also die zwei Bilder in MATLAB ein:

imageNoLight = imread('imgNolight.jpg')
imageLight = imread('imglight.jpg')

Uns stehen also die Variablen imageNoLight und imageLight zur Verfügung, die jeweils das Bild mit bzw. ohne Licht als Matrix beinhalten. Das war unser INPUT

Kommen wir also zum Processing:

Werden zwei Matrizen von einander abgezogen, heißt das praktisch, das jedes einzelne Elemente innerhalb der Matrix vom selben Element der anderen Matrix subtrahiert wird. Z.b.:

(1,2,3,4,) – (4,3,3,4) = (-3,-1,0,0)

Wie wir sehen ergibt sich also die DIFFERENZ der zwei Matrizen, also genau das, was die zwei Matrizen unterscheidet.

Genau das möchten wir nun bei unseren zwei Bilder, Matrizen, erreichen, denn der Lichtpunkt ist genau das was die zwei Bilder unterscheidet. Daher lautet die nächste Operation:

Light = imageLight - imageNoLight

Wichtig hierbei ist, dass man vom Bild MIT Licht das Bild OHNE Licht abzieht und nicht umgekehrt.

Das Resultat ist dieses Bild:

Okay, das ist doch schon einmal sehr gut.

Als nächstes wandeln wir unser Bild in Graustufen um. In dieser Form hat ein Pixel insgesamt drei Farbinfos, im bekannten RGB-Format. Doch so viel Informationen brauchen wir nicht.

Das ist eine einfache Operation in MATLAB:

grayLight = RGB2GRAY(light)

So erhalten wir folgendes Bild:

So hat jeder Pixel ein wert zwischen 0 und 255, perfekt für uns.

Bisher hat sich der Quelltext in Grenzen gehalten, jetzt kommt allerdings ein wenig Programmierarbeit, aber nichts kompliziertes.

Wir haben nun eine 120*160 Matrix mit Werten zwischen 0-255. 0 ist Schwarz 255 Weiß. Wir wollen nun lokaliseren wo ein Lichtfleck ist. Ein Lichtfleck ist nichts anderes, als eine Anhäufung von Pixeln mit dem Wert 255. Genauergesagt ein runde Anhäufung von Pixeln mit dem Wert 255.

Das heißt, dass wir nun alle Pixel, also alle Werte der Matrix durchlaufen und ihren Wert auslesen.

Das geschieht mit zwei FOR-Schleifen, eine für die Zeilendurchläufe und eine für die Spaltendurchläufe. Dabei wird die Zeile 1 hergenommen und alle Spalten von 1-160 durchgelaufen. Danach folgt Zeile 2 mit allen Spalten dann drei usw usw…

So erhält man die Y/X-Werte eines Pixels. Links Oben wäre daher Zeile 1, Spalte 1, also 1/1, dies würde dann den Wert des Pixels liefern. In unserem Bild ist die Linke Obere Ecke Schwarz, also würde der Wert der Matrix in Zeile 1 Spalte 1 den Wert 0 haben.

In MATLAB sieht das so aus:

for line=1:120,
     for col=1:160,
         currentPixel=grayLight(line,col);

Nun hat man also den Wert des Pixels. Als nächstes schauen wir, ob das vorige Pixel einen höheren Wert hat, wenn ja kommt der nächste Pixel dran mit der Überprüfung, wenn der nächste wieder größer ist, kommt dieser zur Überprüfung und so weiter. Wenn nicht werden die Koordinaten des Pixels gespeichert. Dann kommt der nächste Pixel an die Reihe, wenn ein Pixel gefunden wurde, welches einen größeren Wert hat als der bereits gespeicherte Pixel werden die Koordinaten des helleren Pixels übernommen. Ist der Wert des vorigen Pixels gleich dem aktuellen Pixel, speichern wir, das wir 2 Pixel haben und ihre jeweiligen Koordinaten. Ist jetzt der nächste Pixel größer als der vorige, wird das zuletzt gespeicherten (wir hatten ja zwei) wieder gelöscht und wir haben nun wieder nur mehr einen Pixel mit seinen Koordinaten.

Mit dieser Schleife werden also die hellsten Pixel herausgefiltert.

Das Resultat ist entweder eindeutig: Pixel = 28,56

Der Code zum herausfiltern des hellsten Pixels sieht daher so aus:

%-----------------------------hellsten Pixel ermitteln---------------------
for line=1:y,
     for col=1:x,
         currentPixel=grayLaser(line,col);
         if (currentPixel == Pixel) && (currentPixel > 0)
            k = k +1;
            Pixel = currentPixel;
            lDot = [line,col;lDot];
         end
         if (currentPixel > Pixel)
            k = 1;
            Pixel = currentPixel;
            lDot = [line,col];
         end
     end
end

Oder mehrdeutig in einer Matrix gespeichert, pro Zeile die X/Y Koordinaten des Pixels.

Wenn ich nun die hellsten Pixel habe überprüfen wir, ob es tatsächlich eine Ansammlung von hellen Punkten ist und nicht z.B. ein Fehler im Bild (weißes Rauschen bei Funkkamera).

Das Überprüfen wir indem wir Kreisförmig die Umgebung des Pixels auswerten. Das heißt:

0 0 0 0 0
0 220 220 220 0
0 220 250 220 0
0 220 220 220 0
0 0 0 0 0

Diese Tabelle stellt eine 5×5 Matrix dar. Unser Programm hat eindeutig festgestellt, dass das Pixel 3×3 der Hellste ist.  Erinnern wir uns nochmal an das was ich vorhin über ein Lichtpunkt geschrieben habe: Ein Lichtpunkt ist eine kreisförmige Anhäufung von Pixeln mit hohen Werten.

Dieser Fall ist bei uns eingetreten, doch wie können wir das mit dem Programm überprüfen? Sehr einfach:

Wir haben unseren hellsten Pixel, genauer gesagt dessen Koordinaten. Wir müssen nun ganz simpel die benachbarten Pixel auslesen, das tun wir einfach indem wir zunächst die von der Zeile eins abziehen, das versetzt uns in die darüber liegende Zeile. Auf diesem Prinzip lassen sich die insgesamt 4 benachbarten Pixel auslesen.

Man kann das noch verfeinern, indem mach auch die Diagonalen analysiert, jedoch reicht es Kreuzförmig völlig aus.

Programmiertechnisch das zu realisieren ist nicht schwer. Wir nehmen uns insgesamt 4 Schleifen her. Alle 4 Schleifen sind auf der gleichen Ebene und nicht verschachtelt. Nun lassen wir je eine Schleife für eine Richtung durchlaufen. Schematisch für die oberen Nachbarn ungefähr so:

  1. lese oberen Pixelwert aus
  2. ist Pixelwert noch hell? Wenn ja weiter wenn nicht Schleife verlassen (hell muss mit einem Grenzwert definiert werden)
  3. zähler = zähler + 1
  4. gehe zu Punkt 1

Der Zähler enthält beim verlassen der Schleife ein Teil der Diagonale. Zählt man die Zähler von den zwei entgegengesetzten Schleifen zusammen (Oben+Unten bzw Links+Rechts) erhält man die horizontale bzw vertikale Diagonale.

Wenn ich nun mehrere Pixel habe muss ich diese 4 Schleifen gleich der Anzahl an Pixelwerten durchlaufen lassen und den Pixel mit den meisten hellen Nachbarn herausfiltern um den größten Lichtfleck zu ermitteln beziehungsweise den Punkt herausfiltern, der die hellsten Nachbarn hat um den hellsten Lichtfleck zu erhalten.

Und hier der Code dazu:

%------------------------Nachbaranalyse der hellsten Pixel-----------------
for i=1:k

    lneighbour = 255;
    rneighbour = 255;
    tneighbour = 255;
    bneighbour = 255;

    line = lDot(i,1);
    col = lDot(i,2) ;
    ColC = lDot(i,2);
    lineC = lDot(i,1); 

    thr = uint8(Pixel*tolerance);
    diameterH = 0;
    diameterV = 0;
%--------------------------------LINKS-------------------------------------
    while (lneighbour > thr) && (ColC < x)  && (ColC > 1)
        ColC = ColC - 1;
        lneighbour = grayLaser(line,ColC);
        diameterH = diameterH + 1;
    end
%--------------------------------RECHTS------------------------------------
    while (rneighbour  > thr) && (ColC < x)  && (ColC > 1)
        ColC = ColC + 1;
        rneighbour = grayLaser(line,ColC);
        diameterH = diameterH + 1;
    end
%--------------------------------OBEN--------------------------------------
    while (tneighbour > thr) && (lineC < y) && (lineC > 1)
        lineC = lineC - 1;
        tneighbour = grayLaser(lineC,col);
        diameterV = diameterV + 1;
    end
%--------------------------------UNTEN-------------------------------------
    while (bneighbour > thr) && (lineC < y) && (lineC > 1)
        lineC = lineC + 1;
        bneighbour = grayLaser(lineC,col);
        diameterV = diameterV + 1;
    end

    currentdiameter = (diameterH + diameterV) /2;

%--------------Punkt mit den meisten hellen Nachbarn speichern-------------
    if (currentdiameter > Diameter) && (currentdiameter > 0)
        Diameter = currentdiameter;
        mline = line;
        mcol = col;
    end
end

Nun haben wir die Koordinaten des Punktes.

Kommen wir zum Finale: Output

Uns interessiert nun der Offset, also der Abstand zum Bildmittelpunkt. Das sollte keine Probleme darstellen. Wie nehmen einfach Bildbreite / 2 und Bildlänge /2. Diese Werte ziehen wir von unserem Punkt ab und erhalten den Offset. Je nach Quadrant erhalten wir positive Werte bzw negative Werte.

Hier der Output:

%-------------------Lichtfleck gefunden -> Kreuz zeichnen------------------
if (Diameter > 0)

    lightDott = [mline,mcol] ;
    grayLaser(mline,mcol) = 0;

    grayLaser(mline+1,mcol) = 0;
    grayLaser(mline-1,mcol) = 0;
    grayLaser(mline,mcol+1) = 0;
    grayLaser(mline,mcol-1) = 0;

end

%--------------------------------------------------------------------------
%--------------------------------output------------------------------------
%--------------------------------------------------------------------------

lightest = Pixel;
AvrDiameter = Diameter;
X = mline;
Y = mcol;
offset = lightDott - imgMidPoint;
img = grayLaser;
HDiameter = diameterH;
VDiameter = diameterV;
%--------------------------------------------------------------------------
%----------------------------- Q.E.D --------------------------------------
%--------------------------------------------------------------------------

Wir haben nun einen Parameter mit dem wir bestimmen können ob wir den hellsten Lichtfleck suchen oder ob wir den größten Lichtfleck suchen.

Hier ein Bild, welches das verdeutlicht:

 

Im linken Bild lautete der Parameter für den Grenzwert 98% vom hellsten Lichtfleck für Nachbarn, Links 50%.

Dem entsprechend sind auch die Daten verschieden:

X größte Fleck hellster Fleck
durchschnittlicher Durchmesser: 56 Pixel 5 Pixel
horizontaler Durchmesser: 17 Pixel 4 Pixel
vertikaler Durchmesser: 15 Pixel 6 Pixel
X-Koordinate: 77 47
Y-Koordinate: 297 43
hellste Pixel: 247 247
offset: 17,132 -13,-117
% von hellsten Pixel: 50 98

Um zu verdeutlichen, was der Toleranzwert bewirkt, habe ich eine Zeile eingefügt, welches die untersuchten Pixel bei der Nachbaranalyse einfärbt. Man sieht eindeutig, wenn der Toleranzbereich hoch ist, also sagen wir, es werden alle Pixel untersucht, die einen Wert 20% vom hellsten Pixel haben, dann werden viel mehr untersucht, was auch logisch ist.

Ihr könnt die gesamte Funktion als Matlab-Datei herunterladen:

>>DOWNLOAD<<

Um zu beweisen, dass die Erkennung auch ohne Zweitbild funktioniert hier noch ein Bild:

 

 

Das lässt sich auch auf den Sternenhimmel anwenden:

 

 

 

Die Funktion heißt: lightDot(imgLight,imNoLight,threshold,onePics)

Hier die HELP von der Funktion:

function [lightest,AvrDiameter,HDiameter,VDiameter,X,Y,_
offset,img ] = lightDot( imageLight, imageNoLight,tol)

>> help lightDot
 Funktion zum erfassen des hellsten und Lichtflecks
 INPUT:
 imageLight: Bild MIT Lichtpunkt
 imageNoLight: Bild OHNE Lichtpunkt
 tol: Toleranz bei Nachbarschaftsanalyse 0-100 (Prozent)
 onePic: 0 angeben wenn nur ein Bild vorhanden ist, ansonsten 1
 OUTPUT:
 lightest: das hellste Pixel
 AvrDiameter: durchschnittlicher Durchmesser
 HDiameter: horizontaler Durchmesser
 VDiameter: vertikaler Durchmesser
 X: X-Koordinate des Lichtflecks
 Y: Y-Koordinate des Lichtflecks
 offset: Abstand vom Bildmitterpunkt in Pixel
 img: das analysierte Bild mit eingezeichneten Lichtfleck
  1. Noch keine Kommentare vorhanden.

  1. Noch keine TrackBacks.

*