Kreiserkennung mit ImageJ

Student83

Grünschnabel
Hallo Freunde!

Ich hoffe ihr könnt mir bei folgendem Problem helfen.

Es geht darum, ein beliebiges 8Bit-Grauwert-Bild in ImageJ einzulesen und ohne Hilfe von Filtern folgendes zu tun:
- Erkennen aller GEFÜLLTEN Kreise
- Berechnen der Kreismittelpunkte
- Abstände der Kreismittelpunkte zueinander berechnen

Folgendes habe ich getan:
1.) Das Bild mit einem Threshold-Filter bearbeitet um ein Binärbild mit den Grauwerten 0 und 255 zu erhalten.
2.) Jedes Objektpixel (Grauwert 255), welches alleine im Bild liegt (keine Nachbarn hat) wird um einen Nachbarn vergrößert.
3.) Das Bild invertiert.
4.) Kantendetektion und Speicherung der einzelnen Punkte in einem Polygon. Dieses wiederum gespeichert in einem Vektor.
5.) Einen Hough-Raum (Hough Transformation) von allen gefundenen Objekten erstellt.
6.) Ermitteln der höchsten Hough-Werte und löschen der Nachbarwerte um Doppelung zu vermeiden.

Das Problem ist, dass er nicht die richtigen Hough-Werte berechnet/findet, wie auch immer. Auf jeden Fall heißt es: "Dort wo ein Maxima der Hough-Werte auftaucht, befindet sich der (Kreis)Mittelpunkt. Nur diese stimmen bei mir nicht mit den ausgegebenen Werten überein.

Ich weiß, dass das ganze Programm ganz schön aufwendig ist, aber vllt. hat ja jemand irgendeine Idee oder einen Ansatz.

Vielen Dank und schönen Sonntag Abend!

Hier ein Beispielbild: http://www.wasd.de/circ.tif

Hier der Quellcode (einige Teile sind noch nicht in Funktionen untergliedert, was aber kein Problem beim Verständnis darstellen sollte):
Code:
import ij.*;
import ij.gui.*;
import java.awt.*;
import java.util.Vector;
import ij.plugin.filter.PlugInFilter;
import ij.process.*;
import java.io.*;

public class kontur implements PlugInFilter {
	private Vector<Polygon> polyvec;
	double hough[][][];
	int maxcircles = 20;

	/** Initialisierung in ImageJ */
	public int setup(String arg, ImagePlus imp)	{
		if (arg.equals("about"))
		{ showAbout(); return DONE; }
		// Zugelassen nur für 8-Bit Graubilder
		return DOES_8G + NO_CHANGES;
	}

	/** About Message zu diesem Plug-In */
	void showAbout() {
		IJ.showMessage("Graubildtest",
			"Testprogramm"
		);
	}

	/** Ausfuehrende Funktion
	 * @param ip Image Processor. Klasse in ImageJ, beinhaltet das Bild und 
	 * zugehörige Metadaten.
	 */
	public void run(ImageProcessor ip)	{

		// Setze Breite, Hoehe und markierte Region
		int w = ip.getWidth();
		int h = ip.getHeight();
		int cirles = 0; // Anzahl gefundener Kreise
		//int table[][][]; // Virtuelle Tabelle fuer Winkelberechnung
		//int angles = 0; // Anzahl der Winkel fuer jeden Radius
		Rectangle roi = ip.getRoi();

		// Erstelle neue Bilder mit selber Groesse und kopiere Pixel von Original

		// Kontur-Bild
		ImagePlus corrected = NewImage.createByteImage("hough image", w, h, 1, NewImage.FILL_BLACK);
		ImageProcessor cor_ip = corrected.getProcessor();

		// Bild zur Bearbeitung von Threshold, Delate und Invertion
		ImagePlus thimg = new ImagePlus("debug image", ip.duplicate());
		ImageProcessor th = thimg.getProcessor();

		// Bild als Vorgabe fuer Kontur auf Debug-Image
		ImagePlus copypix = new ImagePlus("dump image", ip.duplicate());
		ImageProcessor cpix = copypix.getProcessor();

		//Pixel-Array des Eingabebildes
		byte[] pixelsin = (byte[])ip.getPixels();

		//Pixel-Array des Debug-Bildes
		byte[] pixelsth = (byte[])th.getPixels();

		//Pixelarray des neues Bildes
		byte[] pixels = (byte[])cor_ip.getPixels();

		//Pixelarray des Dump-Bildes
		byte[] pix = (byte[])cpix.getPixels();



		// Vorarbeit zur Detektion der Kreise
		threshold(pixelsth, pix, roi, w, h); // Division in S/W
		cpix.copyBits(th, 0, 0, Blitter.COPY); // Bildwerte kopieren
		delate(pixelsth, pix, roi, w, h); // Schwarzwerte verstaerken
		cpix.copyBits(th, 0, 0, Blitter.COPY); // Bildwerte kopieren
		invert(pixelsth, pix, roi, w, h); // Bild invertieren
		contour(pixelsth, pixels, roi, w, h); // Kontur erstellen

		double radiusMax[];
		radiusMax = new double[this.polyvec.size()];
		double len;
		double xpt, ypt;
		double a, b;
		Polygon poly;
		for (int i = 0; i < this.polyvec.size(); i++) {
			poly = (Polygon)this.polyvec.elementAt(i);
			xpt = poly.xpoints[0];
			ypt = poly.ypoints[0];
			for (int j = 1; j < poly.npoints; j++) {
				a = poly.xpoints[j] - xpt;
				b = poly.ypoints[j] - ypt;
				len = Math.sqrt((a * a) + (b * b));
				if (len > radiusMax[i])
				{
					radiusMax[i] = len;
				}
			}
		}

		int depth = 10;
		double schrittweite = 2*Math.PI / depth;
		int r, d;
		double angle ;
		int rsin, rcos;
		hough = new double[w][h][depth];
		int iter;
		for (int i = 0; i < this.polyvec.size(); i++) {
			poly = (Polygon)this.polyvec.elementAt(i);
			for (int j = 0; j < poly.npoints; j++) {
				iter = 0;
				for (double k = 0; k < 2*Math.PI; k+=schrittweite) {
					rcos = (int)Math.round((double)radiusMax[i]/2 * Math.cos(k));
					rsin = (int)Math.round((double)radiusMax[i]/2 * Math.sin(k));

					int xc = poly.xpoints[j] + rcos;
					int yc = poly.ypoints[j] + rsin;
					if ((yc >= 0) & (yc < h) & (xc >= 0) & (xc < w))
					{
						hough[xc][yc][iter]++;
						pixels[xc + yc * w] = (byte)(hough[xc][yc][iter] + 20);
					}
					iter++;
				}
			}
		}

		double cmax;
		double centerpoint[][];
		double xmax = 0;
		double ymax = 0;
		// Binaerbaum in n.tes Polygon mit x an 0 und y an 1
		centerpoint = new double[this.polyvec.size()][2];
		for (int c = 0; c < this.polyvec.size(); c++) {
			cmax = -1;
			for (int k = 0; k < depth; k++) {
				for (int i = 0; i < h; i++) {
					for (int j = 0; j < w; j++) {
						if (hough[i][j][k] > cmax) {
							cmax = hough[i][j][k];
							xmax = i;
							ymax = j;
						}
					}
				}
			}
			centerpoint[ c][0] = xmax;
			centerpoint[ c][1] = ymax;
			clearneighbours(xmax, ymax, radiusMax[ c], w, h, depth);
		}

		// Tabelle
		IJ.write("Anzahl der gefundenen Objekte: " + String.valueOf(this.polyvec.size()));
		for (int i = 0; i < this.polyvec.size(); i++) {
			drawcross(pixels, (int) centerpoint[i][0], (int) centerpoint[i][1], w, h);
			IJ.write("Kreismittelpunkt von Kreis " + (i+1) + ": [x] " + centerpoint[i][0] + " | [y] " + centerpoint[i][1]);
		}

		thimg.show();
		thimg.updateAndDraw();
		corrected.show();
		corrected.updateAndDraw();
	}

	private void drawcross(byte[] pixels, int x, int y, int w, int h) {
		for (int k = -3; k <= 3; k++) {
			pixels[(y + k) * w + x] = (byte)200;
		}
		for (int k = -3; k <= 3; k++)
		{
			pixels[y * w + x + k] = (byte)200;
		}
	}

	private void clearneighbours(double xmax, double ymax, double rmax, int w, int h, int d) {
		int lx, rx;
		int uy, dy;

		lx = (int) (xmax - rmax - 1);
		rx = (int) (xmax + rmax + 1);
		uy = (int) (ymax - rmax - 1);
		dy = (int) (ymax + rmax + 1);

		if (lx < 0) lx = 0;
		if (rx > w) rx = w;
		if (uy < 0) uy = 0;
		if (dy > w) dy = h;

		for (int i = uy; i < dy; i++) {
			for (int j = lx; j < rx; j++) {
				for (int k = 0; k < d; k++) {
					hough[j][i][k] = 0;
				}
			}
		}
	}

	private void threshold(byte[] pixelsth, byte[] pix, Rectangle roi, int w, int h)
	{
		// Grauwerte, welche nicht weiss sind, muessen schwarz gemacht werden
		int offset, pos;
		int gw; // aktueller Grauwert an Position pos
		for (int i = roi.y; i < roi.y + roi.height; i++) {
			offset = i * w;
			for (int j = roi.x; j < roi.x + roi.width; j++) {
				pos = offset + j;
				gw = pix[pos];
				gw = gw & 0xff; // Verschiebung nach rechts

				if (gw < 200) gw = 0;
				else gw = 255;
				pixelsth[pos] = (byte) gw;
			}
		}
	}

	private void delate(byte[]pixelsth, byte[] pix, Rectangle roi, int w, int h) {
		for (int i=roi.y+1; i<roi.y+roi.height-1; i++) {
			for (int j=roi.x+1; j<roi.x+roi.width-1; j++) {
				int pix1 = pix[(i-1)*w+(j-1)];
				pix1 = (pix1&0x0000ff);
				int pix2 = pix[(i-1)*w+j];
				pix2 = (pix2&0x0000ff);
				int pix3 = pix[(i-1)*w+(j+1)];
				pix3 = (pix3&0x0000ff);
				int pix4 = pix[i*w+(j-1)];
				pix4 = (pix4&0x0000ff);
				int pix5 = pix[i*w+j];
				pix5 = (pix5&0x0000ff);
				int pix6 = pix[i*w+(j+1)];
				pix6 = (pix6&0x0000ff);
				int pix7 = pix[(i+1)*w+(j-1)];
				pix7 = (pix7&0x0000ff);
				int pix8 = pix[(i+1)*w+j];
				pix8 = (pix8&0x0000ff);
				int pix9 = pix[(i+1)*w+(j+1)];
				pix9 = (pix9&0x0000ff);
				if(pix5 == 255 && (pix1==0||pix2==0||pix3==0||pix4==0||pix6==0||pix7==0||pix8==0||pix9==0)) pixelsth[i*w+j] = (byte) 0;
			}
		}
	}
	
	private void invert(byte[] pixelsth, byte[] pix, Rectangle roi, int w, int h) {
		for (int i=roi.y; i<roi.y+roi.height; i++) {
			int offset =i*w; 
			for (int j=roi.x; j<roi.x+roi.width; j++) {
				int pos = offset+j;
				pixelsth[pos] = (byte)(255-pix[pos]);
			}
		}  
	}

	private Vector<Polygon> contour(byte[] pixelsth, byte[] pixels, Rectangle roi, int w, int h) {
		// Erstelle neuen Vektor vom Typ Polygon
		polyvec = new Vector<Polygon>();
		for (int i = roi.y + 1; i < roi.y + roi.height - 1; i++) {
			for (int j = roi.x + 1; j < roi.x + roi.width - 1; j++)	{
				if (this.checkcontains(j, i)) {
					//pixels[i*w+j] = (byte) 100; //Innere Pixel grau setzen
				} else {
					int pix1 = pixelsth[i * w + j];
					pix1 = (pix1 & 0x0000ff);
					if (pix1 == 255) {
						createObject(pixelsth, pixels, j, i, w);
					}
				}
			}
		}
		return polyvec;
	}

	/** Erstellt ein Polygon mit Hilfe der detektierten Kontur
	 * @param Neues Bild
	 * @param Threshold-Bild
	 * @param x-Koordinate, wo begonnen wird
	 * @param y-Koordinate, wo begonnen wird
	 * @param Breite des Bereiches
	 */
	private void createObject(byte[] pixelsth, byte[] pixels, int x, int y, int w) {
		int xcoo = x;
		int ycoo = y;
		int holdx = x;
		int holdy = y;
		int pixvalue;
		int direction = 1;
		int rightcount = 0;
		Polygon polygon = new Polygon();

		do {
			switch (direction) {
				case 1: ycoo -= 1; break; //up
				case 2: xcoo += 1; break; //right
				case 3: ycoo += 1; break; //down
				case 4: xcoo -= 1; break; //left
			}
			pixvalue = (int)pixelsth[ycoo * w + xcoo];
			pixvalue = (pixvalue & 0xff); // Verschiebung

			if (pixvalue == 255) {  // Punkt Hindergrund oder Objektpixel?
				if (xcoo == holdx && ycoo == holdy) ; // Punkt bereits abgearbeitet?
				else {
					pixels[ycoo * w + xcoo] = (byte) 155; // Aendere Grauwert von neuem Punkt
					holdx = xcoo;
					holdy = ycoo;
					polygon.addPoint(xcoo, ycoo);
				}
				direction -= 1; // gehe eins nach links
				rightcount = 0;
			} else {
				direction += 1; // gehe eins nach rechts
				rightcount += 1;
			}
			if (rightcount == 4) { // wenn 4 mal nach rechts, wieder nach links gehen
				direction -= 2;
				rightcount = 0;
			}
			if (direction == 5) direction = 1;
			if (direction == 0) direction = 4;
		} while (ycoo != y || xcoo != x);  // Ende, wenn Endpunkt = Startpunkt
		this.polyvec.addElement(polygon);
	}

	/** Ueberprueft, ob Punkt bereits in Polygon liegt oder nicht.
	 * @param x x-Koordinate des Punktes
	 * @param y y-Koordinate des Punktes
	 * @return True, wenn der Punkt im Polygon liegt, sonst False
	 */
	public boolean checkcontains(int x, int y) {
		for (int i = 0; i < this.polyvec.size(); i++) {
			Polygon poly = (Polygon)this.polyvec.elementAt(i);
			if (poly.contains(x, y)) return true;

			for (int j = 0; j < poly.npoints; j++) {
				if (x == poly.xpoints[j] && y == poly.ypoints[j]) return true;
			}
		}
		return false;
	}
}
 
Vielen Dank zeja! Leider hilft mir das nicht, denn ich kenne die Seite bereits.

Der Unterschied von meinem Programm zur normalen Hough Transformation für Kreise ist, dass ich keine Look Up Tabelle (auch Akkumulator-Tabelle genannt) benutze, da ich bereits den Radius aller meiner Objekte mittels der Kantenerkennung berechnet habe.
Der Hough Raum sieht in meinem Programm ja sogar schon perfekt aus und die entstandenen Kreise überlagern sich genau am Kreismittelpunkt. Jedoch ist dort das Maxima in meiner Hough Tabelle nicht am Größten, was es nach meiner Programmierung und auch grafischen Ausgabe eigentlich sein müsste.
 
So, hab die perfekte Lösung gefunden und bin jetzt schlauer. Für alle die, die es interessiert:

Das Ganze lässt sich um einiges einfacher realisieren.

Vorarbeit: Anstatt der Nachbarschaftsüberprüfung (s.o. Punkt 2): vor der Threshold-Filtererung einfach einen Gaussion-Filter anwenden.

1.) Konturerkennung (Speicherung der Kontur in Vektor mit Polygonen)
2.) Berechnung des Kreismittelpunktes mit Hilfe der Berechnung des
Schwerpunktes eines Kreises, da Kreisschwerpunkt = Kreismittelpunkt.
3.) Strecke von Kreismittelpunkt zu Polygonenpunkt berechnen und Länge mit allen
anderen Polygonenpunkten vergleichen (wenn gleich, ist Polygon ein Kreis).
4.) Alle Punkte im Polygon auf Grauwert überprüfen, ob Kreis auch gefüllt ist.
5.) Strecke von allen Kreismittelpunkten zueinander berechnen.

Das war's dann auch schon. Nix mit Hough-Transformation!

Schönen Tag noch!
 
Hmm ist das dann nicht langsamer als ne Hough-Transformation?

Wobei bei ImageJ kommts auf Geschwindigkeit eh nicht mehr an ;)
 
Also Geschwindigkeit spielt schon einen entscheidenden Punkt. Auch noch bei ImageJ. :)

Auf jeden Fall, sofern die Konturerkennung algorithmisch gut programmiert ist und es ausschließlich um die Erkennung von Kreisen geht, ist die, hier letzte, Lösung sehr schnell.
Sofern es sich allerdings um andere Objekte wie Ellipsen o.ä. handelt, welche man erkennen soll, ist die Hough-Transformation mit Sicherheit die bessere Wahl.
 

Anhänge

  • circ.jpg
    circ.jpg
    2,2 KB · Aufrufe: 2.818
  • circ_edge.jpg
    circ_edge.jpg
    3 KB · Aufrufe: 2.824
Vielen Dank Thomas für deine Mühe, aber meine Edge Detection funktioniert mehr als nur genial. Es lag halt nicht an der Detektierung. Mein Fehler war einfach die Verwendung des Delate-Filters, welcher bei solch einem Bild gar nicht benötigt wird (falsche Überlegung).

Trotzdem nochmal danke an euch beide!

Das Thema ist dann nun abgehakt. ;-)
 

Neue Beiträge

Zurück