Autofocus

#Einleitung

Das automatische Fokussieren einer Kamera, ist ein recht interessanter Vorgang.
Die Mobir M8 Wärmebildkamera unterstützt den Autofokus nur, wenn man die Kamera manuell benutzt. Vom PC aus, gibt es nur Befehle für Motor vor und zurück. Daher hab ich eine Softwareseitigen, einfachen Autofocus zusammengebastelt, welchen ich hier in seiner Funktionsweise beschreiben will.

Mit einer Wärmebildkamera, oder einer S/W (oder Mono) Kamera, lässt sich sowas relativ einfach machen. Schließlich gibt es hier nur eine Intensität, die es auszuwerten gilt. Bei Farbkameras ist das Ganze noch etwas komplexer, da hier auch Farbdifferenzen berücksichtigt werden sollten (geht auch ohne, aber dann schlechter).

Man kann einerseits das ganze Bild im Durchschnitt scharfstellen, oder wie bei den meisten Kameras üblich, die Scharfstellung auf einen Teil des Bildes begrenzen. Es kommt ja schließlich nicht selten vor, dass die betrachteten Objekte in unterschiedlicher Entfernung stehen und daher ist es dem Nutzer zu überlassen, was er davon gern scharfgestellt hätte.
Ich hab mich an meinem Samsung Smartphone orientiert, wo man mit einem Tippen auf den Bildschirm den Bereich festlegt, auf den scharfgestellt werden soll. Bei meinem Programm funktioniert es so ähnlich...

Nachdem der Autofokus aktiviert wurde, wird mit einem Mausklick festgelegt, wo eigentlich der Autofokusbereich ist.
Normalerweise ist er bei mir 30x20 Pixel, zur bessern Darstellung sind die Bilder aber in doppelter Auflösung aufgenommen worden.


Der Autofokusbereich in normaler Darstellung. Meistens wird der Fokusbereich im Hauptbild einfach mit einem Rahmen umrandet, man kann ihn aber auch an der Seite in einem "Fokusfenster" anzeigen.


Bild 1. Hier das Eingangsbild für die Berechnung, was auf die Intensität beschränkt ist. Für Wärme und SW Kameras der Standardzustand.
Für Farbkameras kann dieser Zustand leicht mit der Graustufenumsetzung (Grauwert = 0.6 Rot + 0.4 Grün + 0.2 * Blau) erreicht werden.
Oder jeder Farbkanal wird einzeln so bewertet...


Bild 2. Dieses Bild zeigt die Ergebnisse der Kantenerkennung.
Im Prinzip wird für jeden Pixel nur dargestellt, wie weit sich seine Intensität von seinen Nachbarpixeln unterscheidet.


Hier noch eine binäre nachauswertung des Kantenbildes.
Zuerst wird ein fester Schwellwert definiert. Alles was drüber ist, wird gezählt und als weißer Pixel dargestellt, alles andere beleibt schwarz.
Diese Variante hatte ich zuerst als Eingabe. Allerdings wird es dadurch ungenauer.
Aber man kann sie verwendet, um die Größe von Objekten zu bestimmen. Dafür zählt man nach einer weißen Linie, wie viel schwarze Pixel kommen, bis der nächte weiße erreicht ist.


#Beispielcode

Hier der Code (C#) für die Kantenberechnung

int[,] dataset = new int[30,20];

for (int x = 0; x<30; x++) {
    for (int y = 0; y<20; y++) {
        //Datenerfassung: 43.5°C -> 435
        dataset[x,y] = (int)(tempData[autofoc.X-15+x,autofoc.Y-10+y]*10);
        if (dataset[x,y]>max) { max = dataset[x,y]; }
        if (dataset[x,y]<min) { min = dataset[x,y]; }
    }
}
//dataset als Bild -> siehe Bild 1
autofoc_temp += max-min;
for (int x = 1; x<30; x++) {
    for (int y = 1; y<20; y++) {
        int data=0;
        int val = dataset[x,y]-dataset[x-1,y];//Kante horizontal
        //differenzen aller Kanten zusammen addieren
        if (val<0) { data+=0-val; } else { data+=val; }
        val=dataset[x,y]-dataset[x,y-1];//Kante vertikal
        if (val<0) { data+=0-val; } else { data+=val; }
        val=dataset[x,y]-dataset[x-1,y-1];//Kante diagonal
        if (val<0) { data+=0-val; } else { data+=val; }
        //data als Bild -> siehe Bild 2
        autofoc_cnt += val;
        //byte grenze
        if (data > 255) { data = 255; }
        if (data < 0) { data = 0; }
        C.red = (byte)data; C.green = (byte)data; C.blue = (byte)data;
        bmp_Source.SetPixel(x,y,C);
    }
}
Autofocus count

Wenn die Kanten entsprechend ausgewertet sind, erhält man den Focuswert (hier autofoc_cnt genannt). Je besser Focussiert ist, desto stärker sind die Differenzen der benachbarten Pixel.
Der obere Graph zeigt die Messwerte von autofoc_cnt bei einem kompletten Scandurchlauf (betrachtet wird ein ca. 1m entferntes Objekt).
Der untere Graph zeigt autofoc_temp. Dieser Messwert gibt die Differenz zwischen niedrigster und höchster Temperatur im Autofokusbereich an.
Einen Verlauf wie beim oberen habe ich erwartet, warum der untere nicht fast genauso ausschaut...
Ich kann es mir nur so erklären, dass höhere Objekttemperaturen, die eigentlich außerhalb des Messbereichs waren (z.B. direkt neben dem Rand) nun durch die Brechung in der Linse plötzlich mit erfasst wurden.

Autofocus Temp

Wenn die Focusintensität erst mal berechnet ist, muss nun noch die Bewegung der Linse entsprechend gesteuert werden. Unten ist zu sehen, wie ich das ganze bei meiner Mobir M8 umgesetzt hab.
Zuerst wird in den Fernfokusbereich so lange gefahren, bis sich die Werte oft genug verschlechtert haben, oder eine gewisse Gesamtmenge erreicht ist. Wenn sich die Werte verschlechtern, ist es ein Zeichen dafür, dass die Linse in die falsche Richtung bewegt wird. Zusätzlich wird eine gewisse Gesamtmenge bewertet. Denn wenn der Focus ganz am Ende oder Anfang ist, verschlechtern sich die Werte nicht mehr, da die Linse sich nicht mehr verschiebt.
Nun wird die Linse so lange in den Nahfokusbereich gefahren, bis sich auch hier die Werte oft genug verschlechtert haben, oder die Gesamtmenge erreicht ist.
Nach diesen 2 Durchläufen ist einerseits das Ziel bekannt (der höchste Messwert) und andererseits die Richtung (da zuletzt in den Nahbereich fokussiert wurde, muss man nun in den Fernbereich weitersteuern). Nun fährt die Linse wieder in Richtung Fernfokusbereich und es wird jedes Mal kontrolliert, ob der Messwert innerhalb einer gewissen Toleranz zum Maximum liegt. Ab hier wird bei Verschlechterung der Messwerte die Fahrrichtung geändert. Nach ein bisschen hin und her, ist der Fokuspunkt meistens gefunden.
Wichtig ist hier noch die Einstellung der Toleranz zum Maximum. Ist sie zu klein, dann fährt die Linse ewig hin und her, um den Punkt zu treffen (oder ein Zähler beendet nach x Fehlversuchen schon vorher). Ist sie zu groß, dann gilt ein unscharfes Bild als "Fokussiert".

Es gibt bestimmt auch bessere Wege und dieser Code müsste für jede Kamera etwas angepasst werden. Aber die Grundstruktur dürfte erkennbar sein. Immerhin werden hier nur ein Bildsignal und 2 Linsenbewegungen (nah/fern) gebraucht. Auf eine Positionsangabe oder ähnliches, kann verzichtet werden.

//Mitteln (durchschnitt aus 2 Messwerten)
if (autofoc_lastcnt != 0) { //beim ersten durchlauf nicht halbieren
    autofoc_cnt = autofoc_cnt / 2;
    autofoc_temp = autofoc_temp / 2;
}
//beste Messwerte erfassen
if (autofoc_cnt_max<autofoc_cnt) { autofoc_cnt_max = autofoc_cnt; }
if (autofoc_temp_max<autofoc_temp) { autofoc_temp_max = autofoc_temp; }

if (autofoc_far) { //nur Fern #############################
    if (autofoc_temp>autofoc_temp_last||autofoc_cnt>autofoc_lastcnt) {
        label_Info.Text = "Focus: weiter weg...";
        m8.Move("FAR");
        autofoc_movecnt++;
    } else {
        autofoc_errcnt++;
    }
    if (autofoc_errcnt>5||autofoc_movecnt>40) {
        //genug Fehler -> weiter in die andere Richtung
        autofoc_movecnt = 0; autofoc_errcnt = 0; 
        autofoc_far = false; autofoc_near = true;
    }
} else if (autofoc_near) { //nur Nah ###########################
    if (autofoc_temp>autofoc_temp_last||autofoc_cnt>autofoc_lastcnt) {
        label_Info.Text = "Focus: zur Kamera...";        
        m8.Move("NEAR");
        autofoc_movecnt++;
    if (autofoc_errcnt>0) { autofoc_errcnt--; }
    } else {
        autofoc_errcnt++;
    }
    if (autofoc_errcnt>5||autofoc_movecnt>40) {
        //genug Fehler -> weiter in die andere Richtung
        autofoc_movecnt = 0; autofoc_errcnt = 0; 
        autofoc_near = false;
    }
} else {//Nah und Fern ####################################
    if (autofoc_cnt>autofoc_cnt_max-autofoc_toleranz) {
        //Messwert nah genug am besten bisher gemessenen dran -> PASS
        do_autofoc = false;
        label_Info.Text = "Autofocus PASS...";
        timer_infolabel.Enabled = true;
        return;
    }
    if (autofoc_errcnt>30) {
        //zu viele Fehlversuche, Autofocus fehlgeschlagen
        do_autofoc = false;
        label_Info.Text = "Autofocus FAIL...";
        autofoc_movecnt = 0; autofoc_errcnt = 0;
        return;
    }
    if (autofoc_cnt>autofoc_cnt_max-autofoc_toleranz) {
        //Focuswert kleiner als der Letzte -> Richtungswechsel
        autofoc_switch=!autofoc_switch;
    }
    autofoc_errcnt++;
    if (autofoc_switch) {
        label_Info.Text = "Focus: zur Kamera...";
        m8.Move("NEAR");
    } else {
        label_Info.Text = "Focus: weiter weg...";
        m8.Move("FAR");
    }
    //Motorbewegung abwarten
    thread.Sleep(100);
}
autofoc_lastcnt = autofoc_cnt; 
autofoc_temp_last = autofoc_temp;

Die Messwerte unterliegen leider nicht ganz unwesentlichen Schwankungen, weshalb autofoc_cnt und autofoc_temp auch leicht gemittelt werden (AVR = 2). Das wird einfach dadurch erreicht, dass während der Berechnung die neuen Werte zu den alten hinzugefügt werden. Dann werden sie vor dem Auswerten durch 2 geteilt (außer beim ersten Durchlauf).
Da jede neue Messung ihre Ungenauigkeiten mitbringt, ist es nicht von Vorteil, beim ersten verringern gleich abzubrechen. Deshalb ist der Fehlerzähler autofoc_errcnt relativ klein, aber eben auch nicht zu klein.

Inzwischen funktioniert der Autofocus recht passabel. Es klappt leider nicht immer (trotz gutem Kontrast). Aber wenn für die Berechnungen mehr gemittelt werden würde, wäre es zwar genauer, aber eben auch um einiges langsamer (Messwerte zum Mitteln, sind nur bedingt zur Berechnung nützlich). Es gilt die Balance zwischen Geschwindigkeit und Qualität zu waren...

© by joe-c, 2023 - 2024. All Rights Reserved. Built with Typemill.