Laplacian of Gaussian

Hallo.

Danke für deine Antwort. Wüsste nicht was ich ohne deine Hilfe machen würde. Leider komme ich schon wieder mit Fragen.

Ich denke du solltest es einfach mal probieren, evtl auch erstmal noch nicht in deinen Algo einbauen sondern erstmal den Kernel den du berechnet hast über das Bild falten und dann bei den Nulldurchgängen im Ergebniss bild die Kanten kennzeichnen, dann siehst dus ja obs auch ohne Normalisierung funktioniert. Evtl. musst du nach der Faltung das Resultatbild selbst noch normalisieren...

Wie darf ich das verstehen, die Kanten expliziet zu kennzeichen?

Und auch zu der Normalisierung hätte ich noch eine Frage. Hab mir in deinem Programm die Normalisierung angeschaut, aber ich weiß nicht wie ich jetzt z.B.: mein Bild normalisiere bzw. den Kernel auf 0. Ich glaub was dieses Fach angeht, stehe ich nur auf der Leitung. Ist mir schon richtig peinlich so dumme Fragen zu stellen.

Also die verrauschten Bild sind alle sehr dumpf oder blaß wenn man das so ausdrücken kann. Die Pixelwerte sind alle negativ.

Ich hoffe ich falle euch nicht allzusehr auf die Nerven.

Danke für alles

Manu

//EDIT: Danke für den Tip mit dem Zettel bzw. ausprobieren. Ist mir einiges klarer geworden.
 
Zuletzt bearbeitet:
Hallo.

Danke für deine Antwort. Wüsste nicht was ich ohne deine Hilfe machen würde. Leider komme ich schon wieder mit Fragen.
Kein Problem, dafür ist ja ein Forum da :)

Wie darf ich das verstehen, die Kanten expliziet zu kennzeichen?
Naja wie gesagt ich habe den LoG noch nicht selbst implementiert, also evtl. brauchst du die Kanten gar nicht zu kennzeichnen... Generell gilt, das überall dort Kanten detektiert wurden bei denen auf der Faltung ein Nulldurchgang (d.h. die Pixelwerte des Bildes wechseln das Vorzeichen => Kante (da an diesen Stellen die 2 Ableitung 0 ist und demzufolge die 1. Ableitung maximal ist => Kante)) ist. D.h. das heißt auf der Linie zwischen stark negativen und stark positiven Werten im Faltungsbild befinden sich die Kanten.

Und auch zu der Normalisierung hätte ich noch eine Frage. Hab mir in deinem Programm die Normalisierung angeschaut, aber ich weiß nicht wie ich jetzt z.B.: mein Bild normalisiere bzw. den Kernel auf 0. Ich glaub was dieses Fach angeht, stehe ich nur auf der Leitung. Ist mir schon richtig peinlich so dumme Fragen zu stellen.
Das Gebiet ist nun auch nicht das einfachste ... Wie man den Kernel auf 0 normalisiert weiß ich ehrlich gesagt momentan auch nicht :). Aber mit Normalisierung meint ich auch das resultierende Bild der Faltung. Prinzipiell bekommt man ja 2 Arten von Werten: einmal sollten relativ hohe negative Werte und ein andermal relativ hohe Positive Werte im Bild vorhanden sein. Das kannst du nun so normalisieren das du dem maximalsten höchsten Wert aller positiven Werte bestimmst und diesen 255 zuordnest und alle andern Positiven Werte werden dann Linear in Relation dazu festgelegt. Dasselbe machst du für die Beträge der negativen Werte. Dann solltest du überall dort wo Kanten auf dem Bild hast eine Art schwarze Rille umhüllt von 2 weißen Wällen sehen. Das ist die Kante. Das ist auch sehr schön zu sehen auf den Beispielbildern der Wikiseite....

Also die verrauschten Bild sind alle sehr dumpf oder blaß wenn man das so ausdrücken kann. Die Pixelwerte sind alle negativ.
Das sollte nicht passieren wenn du die Faltung richtig ausgeführt hast. Es sollte sowohl negative als auch positive Pixelwerte geben.
Ich hoffe ich falle euch nicht allzusehr auf die Nerven.
Nein, ganz und gar nicht.

Gruß,
RedWing
 
Hallo,

ich habe den Gaußfilter heute in einen LoG Detektor umgeschrieben... Dabei hab ich rausgefunden das es essentiell ist das die Summe unterhalb des Kernels 0 ergibt, ansonsten werden die homogenen Flächen nur unzureichend bis gar nicht eleminiert. Realisiert habe ich das indem ich vom Originalkernel die Summe gebildet hab, danach eine average sum die ich anschließend von allen Elementen abgezogen habe. Ist die avg_sum nun negativ wächst der Kernel insgesamt, so das die Summe unterhalb des Kernels 0 werden sollte, andernfalls schrumpft der Kernel...
Du kannst dir ja ImageJ herunterladen und das Plugin ausprobieren:

Code:
import ij.*;
import ij.process.*;
import ij.gui.*;
import ij.plugin.*;
import ij.plugin.filter.*;

/**
 * @author redwing
 **/
class LoG {
    private double sigma = 0;
    private double[][] kernel2D;
    private String chosen_condition;
    static private String[] conditions = new String[]{"constant", "mirrored"};

    public LoG(double sigma, String condition) {
	int kernel_size = (int)(2 * Math.ceil(1.5 * sigma)  + 1);
	this.sigma = sigma;
	this.chosen_condition = condition;
	kernel2D = new double[kernel_size][kernel_size];
	fillKernels();
    }

    private void fillKernels() {
	double sqr_sigma = sigma * sigma;
	int half_kernelsize = kernel2D.length / 2;
	double sum = 0, avg_sum = 0;

	for(int y = -half_kernelsize; y <= half_kernelsize; y++) {
	    for(int x = -half_kernelsize; x <= half_kernelsize; x++) {
		kernel2D[y + half_kernelsize]
		    [x + half_kernelsize] = -(1 / (Math.PI *  sqr_sigma * sqr_sigma)) *
		    Math.exp(-(((x * x) + (y * y)) / (2 * sqr_sigma))) * 
		    (1 - ((x * x + y * y) / (2 * sqr_sigma)));
		sum += kernel2D[y + half_kernelsize][x + half_kernelsize];
		System.out.print(kernel2D[y + half_kernelsize][x + half_kernelsize] + " ");
	    }
	    System.out.println();
	}
	//build average sum over all kernel elements
	avg_sum = sum / (kernel2D.length * kernel2D.length);

	// and now substract this sum from the kernel entries so that
	// if the avg_sum is negative the kernel will grow
	// otherwise the kernel will shrink so that the overall sum is
	// become zero
	for (int i = 0; i < kernel2D.length; i++) {
	    for (int j = 0; j < kernel2D[i].length; j++)
		kernel2D[i][j] -= avg_sum ;
	}
    }

    static public String[] getBorderConditions() {
	return conditions;
    }

    public double[][] filter2D(double[][] origin) {
	int half_kernel2Dsize = kernel2D.length / 2;
	int ySize = origin.length;
	int xSize = origin[0].length;
	int maxYIndex = ySize - 1;
	int maxXIndex = xSize - 1;
	int kernel2D_size = kernel2D.length;
	double[][] res = new double[origin.length][origin[0].length];

	if(chosen_condition.equals(conditions[0])) {
	    for(int y = 0; y < ySize; y++){
		int yOffset = y - half_kernel2Dsize;
		for(int x = 0; x < xSize; x++) {
		    int xOffset = x - half_kernel2Dsize;
		    for(int i = 0; i < kernel2D_size; i++) {
			int maxY = Math.min(Math.max(0, yOffset + i), maxYIndex);
			for(int j = 0; j < kernel2D_size; j++)
			    res[y][x] +=  origin[maxY][Math.min(Math.max(0, xOffset + j), maxXIndex)] 
				* kernel2D[i][j];
		    }
		}
	    }
	} else {
	    for(int y = 0; y < ySize; y++) {
		int yOffset = y - half_kernel2Dsize;

		for(int x = 0; x < xSize; x++) {
		    int xOffset = x - half_kernel2Dsize;

		    for(int i = 0; i < kernel2D_size; i++) {
			int yPos = yOffset + i;
			int abs_y_index = (yPos >= ySize)? ySize - (yPos - (maxYIndex))
			    : Math.abs(yPos);

			for(int j = 0; j < kernel2D_size; j++) {
			    int xPos = xOffset + j;
			    int abs_x_index = (xPos >= xSize)? xSize - (xPos - (maxXIndex))
				: Math.abs(xPos);
			    res[y][x] += origin[abs_y_index][abs_x_index] * kernel2D[i][j];
			}
		    }
		}
	    }
	}
	return res;
    }
}

public class LoG_Detector implements PlugInFilter {
    private ImagePlus img = null;

    private double[][] readImg(byte[] pixels) {
	double[][] ret_img = new double[img.getHeight()][img.getWidth()];
	for (int y = 0; y < ret_img.length; y++) {
	    int xpos = y * ret_img[y].length;
	    for (int x = 0; x < ret_img[y].length; x++)
		ret_img[y][x] = pixels[xpos++] & 0xff;
	}
	return ret_img;
    }

    private void writeImg(double[][] img_data, ImageProcessor proc) {
	for (int i = 0; i < img_data.length; i++) {
	    for (int j = 0; j < img_data[i].length; j++) {
		proc.setValue(img_data[i][j]);
		proc.drawPixel(j, i);
	    }
	}
    }

    private double[][] normalizeImg(double[][] origin) {
	double minimum;
	double[][] res = new double[origin.length][origin[0].length];
	double min = origin[0][0], max = origin[0][0];

	// find minimum (biggest negative value and maximum (biggest positive value)
	for (int i = 0; i < origin.length; i++)
	    for (int j = 0; j < origin[i].length; j++) {
		if (origin[i][j] < 0) {
		    if (origin[i][j] < min)
			min = origin[i][j];
		} else {
		    if (origin[i][j] > max)
			max = origin[i][j];
		}
	    }

	// normalize the negative values and the positive values of the image
	for (int i = 0; i < res.length; i++) {
	    for (int j = 0; j < res[i].length; j++) {
		if (origin[i][j] < 0)
		    res[i][j] = (255 * Math.abs(origin[i][j])) / Math.abs(min);
		else
		    res[i][j] = (255 * origin[i][j]) / max;
	    }
	}
	return res;

    }

    private double[][] doFilter(double sigma, String condition, double[][] origin) {
	double[][] res 		= null;
	LoG l = new LoG(sigma, condition);
	long millis_before 	= System.currentTimeMillis();

	res = l.filter2D(origin);
	System.out.println((System.currentTimeMillis() - millis_before) / (double)1000);
	return normalizeImg(res);
    }

    private GenericDialog buildAndShowDialog() {
	final GenericDialog diag = new GenericDialog("Gauss filter options");

	diag.addNumericField("Sigma: ", 1, 0);
	diag.addChoice("Border condition:", 
		LoG.getBorderConditions(), 
		LoG.getBorderConditions()[0]);
	diag.showDialog();
	if(diag.wasCanceled())
	    return null;
	return diag;
    }

    public void run(ImageProcessor ip) {
	GenericDialog diag = buildAndShowDialog();
	if(diag == null)
	    return;
	double sigma = diag.getNextNumber();
	String condition = diag.getNextChoice();
	double[][] image = null;

	image = readImg((byte[])ip.getPixels());
	ImagePlus newImg = IJ.createImage("filtered with border condition " + 
		condition, 
		"8-bit", 
		img.getWidth(), 
		img.getHeight(), 0);

	writeImg(doFilter(sigma, condition, image), newImg.getProcessor());
	newImg.show();
    }

    public int setup(String arg, ImagePlus img) {
	this.img = img;
	return DOES_8G;
    }
}
Das ist sicher nicht die Speicherschonenste Lösung, aber sie funktioniert :)

Eingangsbild:
http://people.fh-landshut.de/~skreyer/Pfeil_SW2.jpg

normalisiertes Filterergebniss für Sigma = 2:
http://people.fh-landshut.de/~skreyer/Pfeil_Kanten.jpg

Man sieht beim letzten Bild diese schwarzen Rillen zwischen den weißen Linien, das sind die eigtl. Kanten...

HTH,
RedWing

P.S. Irgendwie haben die Java Tags meine Tabs auf 2er Ebene nicht dargestellt, deshalb gibts den Code leider nur in simplen Code Tags
 
Hallo RedWing.

Ich kann nur danke danke danke sagen für deine Hilfe. Entschuldige bitte den Aufwand den du wegen mir hattest. Ich werde mir das jetzt in Ruhe anschaun und hoffe ich verstehe es auch. Und dann natürlich versuchen es umzusetzen.

Ich trau mich ja gar nicht mehr wirklich fragen, aber dürfte ich bei Verständnisproblemen mich nochmal melden?

Danke für alles

Manu
 
Hallo RedWing.

Ich kann nur danke danke danke sagen für deine Hilfe. Entschuldige bitte den Aufwand den du wegen mir hattest.
Naja hat mich ja auch zum großen Teil selbst interessiert ...
Ich trau mich ja gar nicht mehr wirklich fragen, aber dürfte ich bei Verständnisproblemen mich nochmal melden?

Nur nicht so schüchtern, schließlich kocht unsereins ja auch nur mit Wasser :)
Ansonsten, nat. kannst du fragen falls du was nicht verstanden hast, hatte das nur beim letzten Post vergessen zu erwähnen.

Gruß,
RedWing
 
Hallo RedWing.

Trotzdem nochmal danke. Ich hoffe ich kann mich irgendwie revanchieren.

Ganz hundert 100% bin ich noch nicht durchgestiegen. Also das mit die Rillen glaub ich ist mir klar. Dies muss mit den Nulldurchgängen zusammenhängen, wenn ich mir den Graphen jetzt richtig vorgestellt habe.

Auch diese normale Normalisierung des Bildes ist mir auch soweit verständlich. Da ja im Ergebnis negative Werte drinnen sind, wird der Wertebereich des Bild mittels Schlussrechnung umgewandelt, also so das er zwischen 0 und 255 liegt.

Ok zur Normalisierung der Maske auf 0. Kann nur sagen einfach genial. Auf diesen Rechenwäge wäre ich nie gekommen. Ist mir jetzt total klar. Warum diese 0 sein muss, damit die homogenen Flächen elemeniert werden, versteh ich nicht. Aber wie du schon gesagst hast, einfach ausprobieren. Werde mir jetzt dann gleich nochmal ein paar Masken einfallen lassen.

Nur nicht so schüchtern, schließlich kocht unsereins ja auch nur mit Wasser :)

(Bzgl. unsicher --> Dies sind meine ersten Forumserfahrungen und ich bin etwas unsicher wieviel und wieoft man Fragen kann, bis man anderen Personen auf die Nerven geht.)

Beim Testen deines Plugins mit ImageJ (geniales Programm) ist noch eine Frage aufgekommen. Wenn ich die Noramlisierung des Bildes auskommentiere, warum sind hier die Nulldurchgänge nicht sichtbar, also keine Rillen vorhanden. Müssten diese hier nicht auch erkennbar sein oder sind die Nulldurchgänge nur aufgrund eine Vorzeichenwechsels erkennbar und im Bild selber gar nicht.

Danke danke nochmal für alles

Manu
 
Also das mit die Rillen glaub ich ist mir klar. Dies muss mit den Nulldurchgängen zusammenhängen, wenn ich mir den Graphen jetzt richtig vorgestellt habe.
Ja also es ist so wenn ich mich nicht komplett irre. Wenn du den LoG über das Bild faltest, dann erhälst du im wesentlichen die 2. Ableitung des Bildes in X und in Y Richtung. Wo die 2te Ableitung eines Signals bzw einer Funktion Nulldurchgänge besitzt, muss die 1. Ableitung desselbigen Signals ein Maximum besitzen => der Anstieg des Signals an der Stelle muss hoch sein => Abrupter Helligkeitsübergang im Bild => Kante

Auch diese normale Normalisierung des Bildes ist mir auch soweit verständlich. Da ja im Ergebnis negative Werte drinnen sind, wird der Wertebereich des Bild mittels Schlussrechnung umgewandelt, also so das er zwischen 0 und 255 liegt.
Ja, das gilt aber genauso auch für die Positiven Werte. Am Ende der Normalisierung hab ich eine LoG Faltung die nur noch Werte zwischen 0 und 255 besitzt.
Ok zur Normalisierung der Maske auf 0. Kann nur sagen einfach genial. Auf diesen Rechenwäge wäre ich nie gekommen. Ist mir jetzt total klar. Warum diese 0 sein muss, damit die homogenen Flächen elemeniert werden, versteh ich nicht. Aber wie du schon gesagst hast, einfach ausprobieren. Werde mir jetzt dann gleich nochmal ein paar Masken einfallen lassen.
Genau, wenn du dir das nochmal per Hand anhand eines kleinen Signals und eines kleinen LoG Filters (3x3) überlegst sollte das klar werden...
Ansonsten auf die Frage nach dem wie ist es einfach so das der Mittel- bzw Erwartungswert aller Kernelkoeffizienten gebildet wird und dann das Kernelgebirge (wenn man sich die Matrix einfach als Gebirge vorstellt) dann um diesen Mittelwert verschoben wird, so das das neue verschobene Gebirge den Mittelwert 0 besitzt.

Beim Testen deines Plugins mit ImageJ (geniales Programm) ist noch eine Frage aufgekommen. Wenn ich die Noramlisierung des Bildes auskommentiere, warum sind hier die Nulldurchgänge nicht sichtbar, also keine Rillen vorhanden. Müssten diese hier nicht auch erkennbar sein oder sind die Nulldurchgänge nur aufgrund eine Vorzeichenwechsels erkennbar und im Bild selber gar nicht.
Da das Ausgabebild ein 8-bit Bild ist, nehme ich an das die negativen Werte einfach abgeschnitten werden und somit sieht man sie auch nicht da sie die gleichen Werte wie auch die Rillen bzw die homogenen Flächen besitzen... Ohne die Normalisierung wird wohl der gewünschte Effekt (die Kantendetektierung) zwar im Originalfaltungsergebniss vorhanden sein, aber auf dem 8-bit Ausgabebild nicht sichtbar sein...

Gruß,
RedWing
 
Hallo RedWing.

Danke nochmal für die Erklärungen. Ich glaub ich hab es jetzt endlich verstanden :-D Hab zwar lange gebraucht, aber endlich ist mir ein licht aufgegangen.
Danke euch allen besonders RedWing. Ohne dich hätte ich es niemals geschafft.

So werd mich dann mal meiner nächsten Aufgabe stellen. Hoffentlich fällt mir diese leichter, nicht das ich euch wieder belästigen muss.

Danke für alles

Manu
 
Zurück