[OpenCV] Kantenerkennung



  • Moin,

    ich habe folgendes Setup:
    Gummistreifen liegen auf einem Förderband. Am Anfang und am Ende des Streifens wird ein Bild aufgenommen (eine Kamera schaut von oben, die andere von unten). Die Kanten sind nicht geade sondern schräg durch Produkt geschnitten.

    Die Bilder werden gefiltert und spaltenweise aufsummiert. Das Maximum / die Maxima ist / sind die Kante/n. Über die Kante wird der Abstand zum Rand bestimmt und daraus die Produktlänge.

    Ich habe einige Funktionen mit einem vertikalen Sobel (signed/unsigned) Marke Eigenbau "geerbt", doch liefert dieser nicht zuverlässig die gewünschten Ergebnisse.

    Nun kam die Idee auf: Das geht doch auch über OpenCV...

    Da ich bisher noch nicht mit OpenCV gearbeitet habe habe ich mir Beispiele gesucht und mich daran ausprobiert, aber so richtig zufrieden bin ich mit dem Ergebnis nicht...

    Es ist leider nicht möglich ein Filtersetup für jedes Produkt anzulegen (das ist in der übergeordneten Software nicht vorgesehen und eine Änderung würde nach Aussage zu viel Zeit kosten)

    Die "Bilder" sind in Graustufen aufgenommen und wurden binär in eine Datei abgespeichert (die Endung *.img hat historische Gründe...)

    Ich habe mal ein paar Rohdaten (2 Bilder a 1024 x 1280 in einer Datei) auf Droppox geladen:
    https://www.dropbox.com/sh/w0yad02fr7elgqb/AADZit-TIBM7Bwf_xG3ZfqG3a?dl=0

    #include "opencv2/highgui/highgui.hpp"
    #include "opencv2/imgproc/imgproc.hpp"
    #include <Windows.h>
    
    using namespace cv;
    using namespace std;
    
    int main(void) {
    	uchar *data = new uchar[1024 * 1280];
    
    	// Daten aus Datei lesen
    	readDataFromFile(data);	
    
    	// in OpenCV Format packen
    	Mat original = Mat(height, width, CV_8U);
    	original.data = data;
    
    	// Bild aufhellen/Kontrast verstärken
    	Mat contrast;
    	double blk = 0.;
    	double wht = 70.;
    	int tmp;
    	for (int i = 0; i < 1024 * 1280; i++) {
    		tmp = int(abs(((((double) original.data[i] - blk) / (blk - wht)) * 255.)) + 0.5);
    		contrast.data[i] = tmp < 256 ? (uchar) tmp : 255;
    	}
    
    	// Normalisieren
    	Mat normalized;
    	equalizeHist(contrast, normalized);
    
    	// Rauschen filtern
    	Mat smoothed;
    	GaussianBlur(normalized, smoothed, Size(13, 13), 0);
    
    	// Kantenerkennung
    	Mat scharr, abs_scharr;
    	Scharr(smoothed, scharr, CV_32F, 1, 0);
    	convertScaleAbs(scharr, abs_scharr);
    
    	// Schwarz/Weiß konvertierung
    	Mat bw;
    	adaptiveThreshold(~abs_scharr, bw, 255, CV_ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 9, -2);
    	bitwise_not(bw, bw);
    
    	// Schwarzanteile verstärken (Strukturen aus Bild entfernen)
    	Mat verticalStructure_e = getStructuringElement(MORPH_RECT, Size(1, 9));
    	erode(bw, bw, verticalStructure_e, Point(-1, -1));
    
    	// Weißanteile verstärken (verbliebene Strukturen verstärken)
    	Mat verticalStructure_d = getStructuringElement(MORPH_RECT, Size(1, 1));
    	dilate(bw, bw, verticalStructure_d, Point(-1, -1));
    
    	Mat horizontalStructure_e = getStructuringElement(MORPH_RECT, Size(4, 1));
    	erode(bw, bw, horizontalStructure_e, Point(-1, -1));
    
    	Mat horizontalStructure_d = getStructuringElement(MORPH_RECT, Size(1, 1));
    	dilate(bw, bw, horizontalStructure_d, Point(-1, -1));
    
    	// Schließen von Lücken
    	Mat closed;
    	Mat element = getStructuringElement(MORPH_RECT, Size(1, 10));
    	morphologyEx(bw, closed, MORPH_CLOSE, element);
    
    	waitKey(0);
    	return 0;
    }
    

    Die imshow() Aufrufe habe ich im Code weggelassen, zumal die Zwischenergebnisse im "Normalbetrieb" nicht interessieren.

    Im Prinzip bekomme ich damit ganz brauchbare Ergenisse. Doch gerade im Bild 1 (Kamera von oben) ist die 2. Kante (Produktinnenkante) kaum erkennbar.

    Über Anregungen und Tipps, was man besser/anders machen kann wäre ich dankbar.



  • Dieser Thread wurde von Moderator/in SeppJ aus dem Forum C++ (alle ISO-Standards) in das Forum Rund um die Programmierung verschoben.

    Im Zweifelsfall bitte auch folgende Hinweise beachten:
    C/C++ Forum :: FAQ - Sonstiges :: Wohin mit meiner Frage?

    Dieses Posting wurde automatisch erzeugt.



  • kannst du mal ein paar Bilder in einem üblichen Format reinstellen (z.B. BMP oder PNG) sowie die readDataFromFile() Funktion zur Verfügung stellen.
    Möglicherweise könnte ich helfen, hab aber keine Lust mir eine Import Routine für irgendein Spezialformat erst selbst bauen zu müssen.



  • Das ist der Code für die Einlesung der Datei

    void readDataFromFile(uchar *data) {
    
    	int bild = 1;
    	HANDLE fileHandle;
    	unsigned long iTotal = 1024 * 1280;
    	unsigned long iRead;
    
    	//	Datei öffnen
    	fileHandle = CreateFile(L"D:\\temp\\test2.img", GENERIC_READ, (DWORD) 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, (HANDLE) NULL);
    
    	//	Datei auslesen
    	if(bild != 1){
    		ReadFile(fileHandle, data, iTotal, &iRead, NULL);
    	}
    	ReadFile(fileHandle, data, iTotal, &iRead, NULL);
    	//	Datei schließen
    	CloseHandle(fileHandle);
    }
    

    Ist nicht sonderlich komfortabel ich weiß, aber es war auch nur als Testprogramm gedacht.

    Edit: Je nachdem welches "Bild" man betrachten will, muss das 2. ReadFile auskommentiert werden. Jetzt muss nur noch die Variable bild auf 1 oder 2 gesetzt werden

    Bilder habe ich nur im img Format (als Rohdaten) vorliegen, da kein Header erzeugt wird.



  • Hallo,

    ganz klar ist mir bei den Bildern nicht, was das sein soll. Somit weiß ich auch nicht, welche Kanten du finden willst. Ich zeige dir einen möglichen Weg, den du wenn nötig auch auf die anderen Kanten anwenden kannst.

    Angenommen, du möchtest die Kante zwischen der linken, texturierten Fläche und der rechten, eher dunklen und gleichmäßigen Fläche finden, kannst du das in etwa so tun:

    1. Kontrast verbessern mit Histogramm Streckung
    2. Standardabweichung als Texturmaß verwenden, d.h. je Pixel wird die Standardabweichung in einem n*n großen Gebiet um diesen Pixel berechnet, und wird schließlich wieder als neuer Pixelwert gesetzt
    3. vertikale Kanten finden (z.B. mit Prewitt)
    4. wir nehmen die stärksten Kanten, dazu kann man z.B. Mittelwert und Standardabweichung des gesamten Bilds nehmen und dann z.B. Mittelwert+Standardabweichung als Schwellwert verwenden
    5. mittels Hough Transformation findet man schließlich die stärksten Linien. Vorteil: Man hat die Linie gleich in Parameterform, also Start und Endpunkt.
    **
    C++ Code damit du deine img Dateien mal in ein vernünftiges Format konvertieren kannst:**

    void toPNG(const cv::Mat& img)
    {
    	cv::imwrite("dump.png", img);
    }
    

    Matlab Code (die Funktionen existieren auch in OpenCV, sodass dieser Code leicht nach OpenCV portiert werden kann), mit dem ich das ausprobiert habe, Ergebnis siehe unten:

    function doIt()
    
        % Bild einlesen
        I=imread('dump.png');
    
        % Histogram strecken
        I=histeq(I);
    
        % Strukturgröße ist etwa im Bereich ~20px, wir nehmen 25x25
        % und bestimmen in diesem Bereich jeweils die Standardabweichung
        Ivar=stdfilt(I,ones([25,25]));
        imagesc(Ivar);
        pause;
    
        % Kanten (vertikal) finden
        h=fspecial('prewitt')';
        Iedges=imfilter(Ivar,h,'replicate');
        imagesc(Iedges);               
        pause;
    
        % wir nehmen die stärksten Kanten
        m=mean(Iedges(:));
        s=std(Iedges(:));
        Ibw=Iedges>2*(m+s); % Mittelwert + 2*Standardabweichung
        imshow(Ibw);
        pause;
    
        % Linienerkennung mittels Hough
        [H,T,R] = hough(Ibw);
        P = houghpeaks(H,5,'Threshold',0.9*max(H(:)));    
        lines = houghlines(Ibw,T,R,P,'FillGap',5,'MinLength',7);
    
        % gefundene, stärkste Linien zeichnen
        imshow(I); hold on; 
        for k = 1:length(lines)
           plot([lines(k).point1(1) lines(k).point2(1)],[lines(k).point1(2) lines(k).point2(2)],'LineWidth',5)
    
        end   
        pause;
    
    end
    

    Hier die Ergebnisbilder der oben genannten Schritte 2-5:
    http://postimg.org/image/mqztptyyh/



  • Moin,

    danke für die Mühe, die du dir mit mir gibst.

    Es handelt sich um Gummistreifen, die auf eine bestimmte Länge geschnitten werden. Die Schnittkante ist leider nicht gerade sondern diagonal, daher auch die jeweils 2 Kanten.
    bei der texturierten Fläche handelt es sich übrigends um das Förderband.

    Mist "imwrite" hatte ich nicht auf dem Zettel...

    Ich habe die Rohdaten als Bilder abgepeichert:
    https://www.dropbox.com/sh/kq8yf2baan2fpes/AAA2zPsq2lBdp3MKXP53HX40a?dl=0

    Einen Satz habe ich aufgehellt und die Kanten eingezeichnet:

    oben
    unten

    Die roten bzw. die grünen Kanten gehören jeweils zusammen.

    Könnte ich mich auf die grünen Kanten beschränken wäre alles OK, aber ich brauche leider auch die roten bzw. hautsächlich die roten. Grade im Bild "oben" ist diese sehr schwer zu finden, auch wenn sie im Aufgehellten Bild mit dem Auge recht gut auszumachen ist.



  • Hallo,

    hast du ein a priori Wissen über die zu erwartende Lage der zweiten Kante in Bezug auf die erste Kante?
    Auf den Bildern die ich gesehen habe gibt es immer die "Hauptkante", und parallel dazu, in einem gewissen Abstand, findet sich nun die zweite Kante.

    Du könntest also die Wahrscheinlichkeit, die du vergibst dass die zweite Kante mit Versatz x auftritt als Wahrscheinlichkeitsdichte modellieren, siehe das verlinkte Bild: http://postimg.org/image/ablod5j9j/
    Dabei bedeutet schwarz=unwahrscheinlich und weiß=sehr wahrscheinlich.

    Das heißt, du berechnest das Kantenbild wie oben beschrieben, erkennst die Hauptkante und dann legst du an der Stelle deiner Hauptkante das Bild mit den Wahrscheinlichkeiten mittig darüber (also so dass der schwarze Streifen die Hauptkante überdeckt) und multiplizierst nun pixelweise den Pixelwert des Wahrscheinlichkeitsbildes mit dem Pixelwert des Kantenbilds.
    Es verbleiben also diejenigen Pixel, die Kanten darstellen und an passender Stelle relativ zur Hauptkante liegen.

    Mit diesem Bild kannst du nun weitermachen und wie gewohnt mittels Hough Transformation die Linien erkennen.



  • Also was ich weiß, ist die Dicke (Mittelwert über den gesamten Streifen) des Materials.

    Wenn nun der Schnittwikel immer der gleiche ist (was ich erfragen bzw. austesten muss) dann sollte ich über a/tan(alpha) mit a = Materialdicke, alph = Schnittwinkel den Ort zu berechen, Wo die andere Kante zu erwarten ist bzw. wo sich die andere Kante befinden muss.

    Damit sollte theoretisch das a priori Wissen vorhanden sein.



  • das klingt ja schon mal gut.
    Es muss ja auch nicht exakt bekannt sein wo die Kante zu liegen kommt, du kannst die Wahrscheinlichkeitsdichte so anpassen, dass es am besten der Realität entspricht.
    Wenn du noch dazu weißt, auf welcher Seite die zweite Kante auftritt (also IMMER links oder IMMER rechts), dann kannst du oben gezeigtes Bild sogar noch halbieren und suchst dann eben nur auf einer Seite nach der zweiten Kante.
    Je mehr a priori Informationen du hast, desto robuster wird das Verfahren.



  • Also der Übergang Band/Gummi sollten eigendlich immer der Übergang sein, den wir ohne Probleme finden sollten. Somit liegt die 2. Kante grundsätzlich rechts von der 1. Kante.

    Das Unterbodenbild ist zur Zeit eher nachhrangig zu betrachten, da hier Überlegungen laufen, die untere Kamera nach oben zu holen. Hier Gäbe es dann eh nur den Übergang Unendlichkeit (Rollen/Boden) bzw. Band / Gummi.

    Wenn wir dann diese eine Kante (sicher) finden und diese durch einen entsprechenden Auslöser auch noch an der nahezu gleichen Stelle im Bild liegt, könnte man noch eine weitere Prognose anstellen:

    Wenn wir wissen, wo in dem einen Kamerabild (ex Unterbodenkamera) die Kante liegt, können wir den Abstand zur Bezugsaußenkante des Bildes ermitteln. Hat man(n) dann die Solllänge und den Abstand der Bezugskanten, sollte man eine Prognose aufstellen können, in was für einem Rahmen (Prognostizierte Kantenposition +/- Akzeptanzbereich) sich die 1. Kante im Bild der Oberbodenkamera befinden sollte.

    Nun ist, glaube ich so langsam der Zeitpunkt gekommen, an dem ich an die Umsetzung gehen sollte/könnte.


Anmelden zum Antworten