Finden eines zebraähnlichen Musters im Bild (Erkennung der Mittellinie eines strukturierten Lichtstreifens auf dem Foto)

12

Ich arbeite in einem Projekt, in dem Ränder gegen ein Motiv projiziert werden und ein Foto aufgenommen wird. Die Aufgabe besteht darin, die Mittellinien der Streifen zu finden, die mathematisch die 3D-Schnittkurve zwischen der Streifenebene und der Objektoberfläche darstellen.

Das Foto ist ein PNG (RGB), und frühere Versuche verwendeten Graustufen- und dann Differenzschwellwerte, um eine zebraähnliche Schwarzweißfotografie zu erhalten, aus der sich der Mittelpunkt jeder Pixelspalte jedes Randes leicht ermitteln ließ. Das Problem ist, dass wir durch Schwellenwertbildung und auch durch Ermitteln der mittleren Höhe einer diskreten Pixelspalte einen Präzisionsverlust und eine Quantisierung haben, die überhaupt nicht erwünscht sind.

Mein Eindruck bei Betrachtung der Bilder ist, dass die Mittellinien kontinuierlicher (mehr Punkte) und glatter (nicht quantisiert) sein könnten, wenn sie durch eine statistische Abtastmethode direkt aus dem Bild ohne Schwellenwert (entweder RGB oder Graustufen) erkannt würden (etwas Überschwemmung / iterative Faltung, was auch immer).

Unten ist ein aktuelles Beispielbild:

Bildbeschreibung hier eingeben

Jeder Vorschlag wäre sehr dankbar!

Heltonbiker
quelle
es ist sehr interessant. Aber im Übrigen mache ich einige Nachforschungen mit Farbstreifen, um 3D-Objekte zu erkennen. Durch die Verwendung von Farbstreifen ist es einfach, die Korrespondenz jedes Streifens vom Projektor aus zu ermitteln. Mithilfe der Trigonometrie können die 3D-Informationen berechnet werden. Wie findet man die Entsprechung, wenn die Farbe gleich ist? Ich vermute, in Ihrem Projekt geht es auch um 3D-Rekonstruktion?
@johnyoung: Bitte keine Kommentare als Antworten hinzufügen. Ich bin mir bewusst, dass Sie einen guten Ruf brauchen, bevor Sie einen Kommentar abgeben können, aber bitte unterlassen Sie Ihre derzeitige Vorgehensweise. Ich schlage vor, Ihre eigenen (verwandten) Fragen zu stellen oder die Fragen anderer zu beantworten, um Ihre Wiederholung zu verbessern.
Peter K.
Entschuldigung für eine weitere Frage, anstatt eine Antwort zu geben. Bei der Phasenverschiebungsmethode berechnen wir die Phase für jedes Pixel im projizierten Bild, aber hier ist meine Frage möglicherweise zu albern, aber ich nicht Nein, bitte rufen Sie mich an, um den genauen Grund zu erfahren.
Dies sind verschiedene Methoden. Ich modelliere eine Reihe von geometrischen Ebenen, indem ich eine Reihe von weißen Streifen projiziere (von denen jeder eine "Ebene" im 3D-Raum bildet). Daher muss ich die Mittellinie der Ränder finden, da die Ebenen keine Dicke haben. Sicher, ich könnte eine Phasenverschiebungsanalyse durchführen, aber es gibt ein Problem: Meine Projektion ist binär (schwarze und weiße Streifen wechseln sich ab), die Intensität ändert sich nicht sinusförmig und daher kann ich keine Phasenverschiebung durchführen (und muss es derzeit nicht tun) ).
Heltonbiker

Antworten:

13

Ich schlage folgende Schritte vor:

  1. Suchen Sie eine Schwelle, um den Vordergrund vom Hintergrund zu trennen.
  2. Bestimmen Sie für jeden Fleck im Binärbild (jeweils einen Zebrastreifen) xdas gewichtete Zentrum (nach Pixelintensität) in der yRichtung.
  3. Möglicherweise glätten Sie die yWerte, um Rauschen zu entfernen.
  4. Verbinden Sie die (x,y)Punkte durch Anpassen einer Kurve. Dieser Artikel könnte Ihnen helfen. Sie können auch ein Polynom auf hoher Ebene einsetzen, obwohl es meiner Meinung nach schlimmer ist.

Hier ist ein Matlab-Code, der die Schritte 1, 2 und 4 zeigt. Ich habe die automatische Schwellenwertauswahl übersprungen. Stattdessen habe ich manuell gewählt th=40:

Dies sind die Kurven, die durch Ermitteln des gewichteten Durchschnitts pro Spalte ermittelt werden: Bildbeschreibung hier eingeben

Dies sind die Kurven nach dem Anpassen eines Polynoms: Bildbeschreibung hier eingeben

Hier ist der Code:

function Zebra()
    im = imread('http://i.stack.imgur.com/m0sy7.png');
    im = uint8(mean(im,3));

    th = 40;
    imBinary = im>th;
    imBinary = imclose(imBinary,strel('disk',2));
    % figure;imshow(imBinary);
    labels = logical(imBinary);
    props =regionprops(labels,im,'Image','Area','BoundingBox');

    figure(1);imshow(im .* uint8(imBinary));
    figure(2);imshow(im .* uint8(imBinary));

    for i=1:numel(props)
        %Ignore small ones
        if props(i).Area < 10
            continue
        end
        %Find weighted centroids
        boundingBox = props(i).BoundingBox;
        ul = boundingBox(1:2)+0.5;
        wh = boundingBox(3:4);
        clipped = im( ul(2): (ul(2)+wh(2)-1), ul(1): (ul(1)+wh(1)-1) );
        imClip = double(props(i).Image) .* double(clipped);
        rows = transpose( 1:size(imClip,1) );
        %Weighted calculation
        weightedRows  = sum(bsxfun(@times, imClip, rows),1) ./ sum(imClip,1);
        %Calculate x,y
        x = ( 1:numel(weightedRows) ) + ul(1) - 1;
        y = ( weightedRows ) + ul(2) - 1;
        figure(1);
        hold on;plot(x,y,'b','LineWidth',2);
        try %#ok<TRYNC>
            figure(2);
            [xo,yo] = FitCurveByPolynom(x,y);
            hold on;plot(xo,yo,'g','LineWidth',2);
        end
        linkaxes( cell2mat(get(get(0,'Children'),'Children')) )
    end        
end

function [xo,yo] = FitCurveByPolynom(x,y)
   p = polyfit(x,y,15); 
   yo = polyval(p,x);
   xo = x;
end
Andrey Rubshtein
quelle
Ich fand das sehr interessant. Ich benutze Python, aber trotzdem muss ich die Gründe dafür studieren. Als unabhängiger Kommentar neige ich dazu, keine klassische Bildverarbeitung (direkt auf quantisierten Bildcontainern wie uint8-Arrays) durchzuführen, sondern alles als Float-Arrays in den Speicher zu laden, bevor die Operationen angewendet werden. Ich bin auch überrascht über die Ergebnisse in der unteren Hälfte Ihres Bildes. Blaue Linien verlaufen nicht entlang der erwarteten Mittellinien der Ränder ... (?). Vielen Dank fürs erste, ich bringe ein paar Rückmeldungen, sobald ich ein Ergebnis habe!
Heltonbiker
@ Heltonbiker, überprüfen Sie bitte die aktualisierte Antwort. Sie haben Recht mit Fließkommazahlen. Ich habe sie bei der Umstellung auf verwendet double. Über die Ergebnisse in der unteren Hälfte muss ich prüfen, es könnte ein Software-Fehler sein
Andrey Rubshtein
1
@heltonbiker, fertig. Es war in der Tat ein Fehler im Zusammenhang mit der 1-basierten Indizierung.
Andrey Rubshtein
Hervorragend! Wirklich erstaunlich. Mit dieser Technik und für meine Zwecke wird das Glätten nicht nur nicht benötigt, sondern wäre auch schädlich. Vielen Dank für Ihr Interesse!
Heltonbiker
3

Ich würde das RGB-Bild nicht verwenden. Farbbilder werden in der Regel durch Anbringen eines "Bayer-Filters" am Kamerasensor erstellt, wodurch die erreichbare Auflösung normalerweise verringert wird.

Wenn Sie das Graustufenbild verwenden, sind die von Ihnen beschriebenen Schritte (Binarisieren des "Zebra" -Bilds, Finden der Mittellinie) ein guter Anfang. Als letzten Schritt würde ich

  • Nehmen Sie jeden Punkt in der gefundenen Mittellinie
  • nimm die Grauwerte der Pixel in der "Zebra" -Linie oben und unten
  • Passen Sie eine Parabel an diese Grauwerte an, indem Sie die kleinsten mittleren Quadrate verwenden
  • Die Spitze dieser Parabel ist eine verbesserte Schätzung der Mittellinienposition
Niki Estner
quelle
Nette Gedanken. Ich habe vor, eine Art Parabel oder Spline entlang der Spitzenwerte jeder Pixelspalte zu verwenden, aber ich überlege immer noch, ob ich eine Pixelspalte oder stattdessen einen Pixel- "Bereich" entlang der Linie untersuchen soll ... Ich werde noch etwas warten mehr Antworten. Danke erstmal!
Heltonbiker
@heltonbiker - als schnellen Test nur den grünen Kanal benutzen. Normalerweise hat ein Farbsensor doppelt so viele grüne Pixel und es ist weniger interpoalted als rot und blau
Martin Beckett
@MartinBeckett Vielen Dank für Ihr Interesse, ich habe bereits jeden Kanal analysiert, und in der Tat scheint der grüne viel entschlossener zu sein als der rote. Beim Zeichnen von Intensitätswerten vertikaler Querschnitte für jeden Kanal scheint sich das "Streifenmuster" zwischen den Kanälen nicht so stark zu ändern, und ich mische sie derzeit bei der Konvertierung in Graustufen gleichmäßig. Obwohl ich immer noch vorhabe, die beste lineare Kombination zwischen Kanälen zu untersuchen, um das kontrastreichste Ergebnis zu erzielen, ODER um Bilder zu erfassen, die bereits in Graustufen vorliegen. Danke noch einmal!
Heltonbiker
3

Hier finden Sie noch eine alternative Lösung für Ihr Problem, indem Sie Ihre Frage als 'Pfadoptimierungsproblem' modellieren. Es ist zwar komplizierter als die einfache Lösung zur Binarisierung und anschließenden Kurvenanpassung, in der Praxis jedoch robuster.

Von der sehr hohen Ebene aus sollten wir dieses Bild als Grafik betrachten, wo

  1. Jedes Bildpixel ist ein Knoten in diesem Diagramm

  2. Jeder Knoten ist mit einigen anderen Knoten verbunden, die als Nachbarn bezeichnet werden. Diese Verbindungsdefinition wird häufig als Topologie dieses Diagramms bezeichnet.

  3. Jeder Knoten hat ein Gewicht (Feature, Kosten, Energie oder wie auch immer Sie es nennen möchten), was die Wahrscheinlichkeit widerspiegelt, dass sich dieser Knoten in einer optimalen Mittellinie befindet, nach der wir suchen.

Solange wir diese Wahrscheinlichkeit modellieren können, wird Ihr Problem, die "Mittellinien der Streifen" zu finden, zum Problem, lokale optimale Pfade auf dem Graphen zu finden , was durch dynamische Programmierung, z. B. Viterbi-Algorithmus, effektiv gelöst werden kann.

Hier sind einige Vorteile dieses Ansatzes:

  1. Alle Ihre Ergebnisse sind kontinuierlich (im Gegensatz zu der Schwellenwertmethode, bei der eine Mittellinie in Stücke gebrochen werden kann).

  2. Um ein solches Diagramm zu erstellen, haben Sie viele Freiheiten. Sie können verschiedene Features und Diagrammtopologien auswählen.

  3. Ihre Ergebnisse sind optimal im Sinne von Pfadoptimierungen

  4. Ihre Lösung ist robuster gegen Rauschen, da diese optimalen Pfade stabil bleiben, solange das Rauschen auf alle Pixel gleichmäßig verteilt ist.

Hier ist eine kurze Demonstration der obigen Idee. Da ich keine Vorkenntnisse verwende, um anzugeben, welche Start- und Endknoten möglich sind, decodiere ich einfach jeden möglichen Startknoten. Entschlüsselte Viterbi-Pfade

Für die Fuzzy-Endungen liegt es daran, dass wir für jeden möglichen Endknoten nach optimalen Wegen suchen. Infolgedessen ist der hervorgehobene Pfad für einige Knoten, die sich in dunklen Bereichen befinden, immer noch der lokal optimale Pfad.

Für den unscharfen Pfad können Sie ihn entweder glätten, nachdem Sie ihn gefunden haben, oder einige geglättete Features anstelle der Rohintensität verwenden.

Es ist möglich, Teilpfade wiederherzustellen, indem Start- und Endknoten geändert werden.

Es wird nicht schwierig sein, diese unerwünschten lokalen optimalen Pfade zu beschneiden. Da wir nach der Viterbi-Dekodierung die Wahrscheinlichkeit haben, dass alle Pfade vorhanden sind, und Sie möglicherweise verschiedene Vorkenntnisse verwenden (z. B. müssen wir für diejenigen, die dieselbe Quelle verwenden, nur einen optimalen Pfad angeben).

Weitere Informationen finden Sie auf dem Papier.

 Wu, Y.; Zha, S.; Cao, H.; Liu, D., & Natarajan, P.  (2014, February). A Markov Chain Line Segmentation Method for Text Recognition. In IS&T/SPIE 26th Annual Symposium on Electronic Imaging (DRR), pp. 90210C-90210C.

Hier ist ein kurzer Python-Code, mit dem das obige Diagramm erstellt wird.


import cv2
import numpy as np
from matplotlib import pyplot
# define your image path
image_path = ;
# read in an image
img = cv2.imread( image_path, 0 );
rgb = cv2.imread( image_path, -1 );

# some feature to reflect how likely a node is in an optimal path
img = cv2.equalizeHist( img ); # equalization
img = img - img.mean(); # substract DC
img_pmax = img.max(); # get brightest intensity
img_nmin = img.min(); # get darkest intensity
# express our preknowledge
img[ img > 0 ] *= +1.0  / img_pmax; 
img[ img = 1 :
    prev_idx = vt_path[ -1 ].astype('int');
    vt_path.append( path_buffer[ prev_idx, time ] );
    time -= 1;
vt_path.reverse();    
vt_path = np.asarray( vt_path ).T;

# plot found optimal paths for every 7 of them
pyplot.imshow( rgb, 'jet' ),
for row in range( 0, h, 7 ) :
    pyplot.hold(True), pyplot.plot( vt_path[row,:], c=np.random.rand(3,1), lw = 2 );
pyplot.xlim( ( 0, w ) );
pyplot.ylim( ( h, 0 ) );
Falle
quelle
Dies ist ein sehr interessanter Ansatz. Ich gestehe, das Thema "Graphen" war mir bis vor kurzem verborgen, als ich (bei demselben Projekt) nur ein anderes Problem mit Graphen lösen konnte. Nachdem ich es verstanden hatte, wurde mir klar, wie leistungsfähig diese Algorithmen für kürzeste Wege sein können. Ihre Idee ist sehr interessant und es ist nicht unmöglich, dass ich sie erneut implementieren würde, wenn ich die Notwendigkeit / Gelegenheit dazu hätte. Vielen Dank.
Heltonbiker
Was Ihre aktuellen Ergebnisse betrifft, ist es meiner Erfahrung nach wahrscheinlich besser, das Bild zuerst mit Gauß- und / oder Medianfilter zu glätten, bevor Sie das Diagramm erstellen. Dies würde viel glattere (und korrektere) Linien ergeben. Ein möglicher Trick besteht auch darin, die Nachbarschaft zu erweitern, um ein "direktes Springen" über zwei oder mehr Pixel zu ermöglichen (bis zu einer bestimmten Grenze, beispielsweise 8 oder 10 Pixel). Natürlich sollte eine geeignete Kostenfunktion gewählt werden, aber ich denke, es ist einfach zu stimmen.
Heltonbiker
Oh ja. Ich habe einfach etwas zur Hand genommen, Sie können definitiv andere Topologie- und Energiefunktionen verwenden. Eigentlich ist dieses Framework auch trainierbar. Insbesondere beginnen Sie mit der Rohintensität, decodieren für optimale Pfade, nehmen nur die optimalen Knoten mit hoher Vertraulichkeit auf und erhalten auf diese Weise "beschriftete Daten". Mit diesem kleinen Teil der automatisch gekennzeichneten Daten können Sie viele nützliche Dinge lernen.
Fallstricke
3

Ich dachte, ich sollte meine Antwort posten, da sie sich ein bisschen von anderen Ansätzen unterscheidet. Ich habe es in Matlab versucht.

  • summieren Sie alle Kanäle und erstellen Sie ein Bild, sodass alle Kanäle gleich gewichtet werden
  • Morphologisches Schließen und Gaußsche Filterung für dieses Bild durchführen
  • Finden Sie für jede Spalte des resultierenden Bildes die lokalen Maxima und konstruieren Sie ein Bild
  • Finden Sie die verbundenen Komponenten dieses Bildes

Ein Nachteil, den ich hier sehe, ist, dass dieser Ansatz für einige Ausrichtungen der Streifen nicht gut funktioniert. In diesem Fall müssen wir die Ausrichtung korrigieren und dieses Verfahren anwenden.

Hier ist der Matlab-Code:

im = imread('m0sy7.png');
imsum = sum(im, 3); % sum all channels
h = fspecial('gaussian', 3);
im2 = imclose(imsum, ones(3)); % close
im2 = imfilter(im2, h); % smooth
% for each column, find regional max
mx = zeros(size(im2));
for c = 1:size(im2, 2)
    mx(:, c) = imregionalmax(im2(:, c));
end
% find connected components
ccomp = bwlabel(mx);

Wenn Sie zum Beispiel die mittlere Spalte des Bildes nehmen, sollte das Profil so aussehen: (in Blau ist das Profil. In Grün sind die lokalen Maxima) mittleres Profil und lokale Maxima

Und das Bild mit den lokalen Maxima für alle Spalten sieht folgendermaßen aus: Bildbeschreibung hier eingeben

Hier sind die verbundenen Komponenten (obwohl einige Streifen gebrochen sind, erhalten die meisten von ihnen einen durchgehenden Bereich):

Bildbeschreibung hier eingeben

dhanushka
quelle
Dies ist eigentlich das, was wir jetzt tun, mit dem einzigen Unterschied, wie lokale Maxima für jede Pixelspalte ermittelt werden: Wir verwenden eine parabolische Interpolation, um den genauen Scheitelpunkt der Parabel zu ermitteln, die durch das Pixel mit dem Maximalwert und seinen oberen und unteren Nachbarn verläuft . Dies ermöglicht, dass das Ergebnis "zwischen" Pixeln liegt, was die subtile Glätte der Linien besser darstellt. Danke für deine Antwort!
Heltonbiker