Znajdowanie wzoru podobnego do zebry na obrazie (Wykrywanie linii frędzlowej światła strukturalnego na zdjęciu)

12

Pracuję w projekcie, w którym frędzle są rzutowane na obiekt i robione jest zdjęcie. Zadanie polega na znalezieniu linii środkowych obrzeży, które matematycznie przedstawiają krzywą 3D przecięcia płaszczyzny obrzeża z powierzchnią przedmiotu.

Zdjęcie jest w formacie PNG (RGB), a poprzednie próby wykorzystywały skalę szarości, a następnie próg różnicy, aby uzyskać czarno-białą „podobną do zebry” fotografię, z której łatwo było znaleźć punkt środkowy każdej kolumny pikselowej każdej grzywki. Problem polega na tym, że poprzez progowanie, a także przez przyjęcie średniej wysokości kolumny z dyskretnymi pikselami, mamy pewną utratę precyzji i kwantyzację, co wcale nie jest pożądane.

Mam wrażenie, patrząc na obrazy, że linie środkowe mogłyby być bardziej ciągłe (więcej punktów) i gładsze (nie skwantyzowane), gdyby zostały wykryte bezpośrednio z obrazu bez progów (RGB lub skali szarości) za pomocą jakiejś statystycznej metody zamiatania (trochę powodzi / iteracyjnego splotu, cokolwiek).

Poniżej znajduje się rzeczywisty przykładowy obraz:

wprowadź opis zdjęcia tutaj

Wszelkie sugestie będą mile widziane!

heltonbiker
źródło
to jest bardzo interesujące. Nawiasem mówiąc, robię badania przy użyciu pasków kolorów w celu wykrycia obiektu 3D. Ponieważ za pomocą kolorowego paska łatwo jest znaleźć korespondencję każdego paska z projektora. Dzięki trygonometrii można obliczyć informacje 3D. Jak znaleźć korespondencję, jeśli kolor jest taki sam? Myślę, że twój projekt dotyczy także rekonstrukcji 3D?
@johnyoung: Nie dodawaj komentarzy jako odpowiedzi. Rozumiem, że potrzebujesz reputacji, zanim będziesz mógł komentować, ale powstrzymaj się od obecnego sposobu działania. Sugeruję zadawanie własnych (powiązanych) pytań lub odpowiadanie na pytania innych osób w celu zwiększenia liczby przedstawicieli.
Peter K.
Przepraszam za jedno pytanie zamiast dać odpowiedź, w metodzie przesunięcia fazowego obliczamy fazę na każdym pikselu w wyświetlanym obrazie, ale tutaj dlaczego musimy znaleźć środkową linię grzywki, może moje pytanie jest zbyt głupie, ale nie nie, więc proszę o podanie dokładnego powodu. Możesz usunąć moje pytanie po udzieleniu odpowiedzi
To są różne metody. Modeluję serię płaszczyzn geometrycznych, projektując serię białych pasków (z których każdy tworzy „płaszczyznę” w przestrzeni 3D). Dlatego muszę znaleźć linię środkową prążków, ponieważ płaszczyzny nie mają grubości. Pewnie, że mógłbym przeprowadzić analizę przesunięcia fazowego, ale jest jeden problem: moja projekcja jest binarna (naprzemiennie czarne i białe paski), intensywność nie zmienia się sinusoidalnie, więc nie mogę przeprowadzić przesunięcia fazowego (i nie muszę tego obecnie ).
heltonbiker

Odpowiedzi:

13

Proponuję następujące kroki:

  1. Znajdź próg oddzielający pierwszy plan od tła.
  2. Dla każdej kropli na obrazie binarnym (jeden pasek zebry) dla każdego xznajdź ważony środek (według intensywności pikseli) w ykierunku.
  3. Możliwe, że wygładzisz ywartości, aby usunąć szum.
  4. Połącz (x,y)punkty, dopasowując jakąś krzywą. Ten artykuł może ci pomóc. Można również dopasować wielomian wysokiego poziomu, choć moim zdaniem jest gorzej.

Oto kod Matlaba, który pokazuje kroki 1,2 i 4. Pominąłem automatyczny wybór progu. Zamiast tego wybrałem manual th=40:

Oto krzywe, które można znaleźć, znajdując średnią ważoną na kolumnę: wprowadź opis zdjęcia tutaj

Oto krzywe po dopasowaniu wielomianu: wprowadź opis zdjęcia tutaj

Oto kod:

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
źródło
Uważam to za bardzo interesujące. Używam Pythona, ale i tak będę musiał przestudiować uzasadnienie tego wszystkiego. Jako niezależny komentarz, zwykle nie wykonuję klasycznego przetwarzania obrazu (bezpośrednio na skwantyzowanych pojemnikach obrazu, takich jak tablice uint8), ale zamiast tego ładuję wszystko do pamięci jako tablice zmiennoprzecinkowe przed zastosowaniem operacji. Jestem również zaskoczony wynikami z dolnej połowy twojego obrazu, niebieskie linie nie biegną wzdłuż oczekiwanych linii środkowych na skraju ... (?). Dzięki na teraz, przyniosę informację zwrotną, gdy tylko uzyskam jakiś wynik!
heltonbiker
@heltonbiker, sprawdź zaktualizowaną odpowiedź. Masz rację co do zmiennoprzecinkowego, użyłem go podczas konwersji double. Jeśli chodzi o wyniki w dolnej połowie, muszę sprawdzić, może to być błąd oprogramowania
Andrey Rubshtein
1
@heltonbiker, gotowe. To był rzeczywiście błąd związany z indeksowaniem opartym na 1.
Andrey Rubshtein
Doskonały! Rzeczywiście niesamowite. Dzięki tej technice i dla moich celów wygładzanie nie tylko nie będzie nawet potrzebne, ale także byłoby szkodliwe. Wielkie dzięki za zainteresowanie!
heltonbiker
3

Nie użyłbym obrazu RGB. Kolorowe obrazy są zwykle tworzone przez umieszczenie „filtra Bayera” na matrycy aparatu, co zwykle zmniejsza rozdzielczość, którą można uzyskać.

Jeśli używasz obrazu w skali szarości, myślę, że opisane kroki (binarizacja obrazu zebry, znajdowanie linii środkowej) są dobrym początkiem. Jako ostatni krok zrobiłbym to

  • Weź każdy punkt w linii środkowej, którą znalazłeś
  • weź szare wartości pikseli w linii „zebry” powyżej i poniżej
  • dopasuj parabolę do tych szarych wartości, używając najmniejszych średnich kwadratów
  • wierzchołek tej paraboli jest lepszym oszacowaniem pozycji w linii środkowej
Niki Estner
źródło
Miłe myśli. Planuję użyć paraboli lub splajnu wzdłuż wartości szczytowych każdej kolumny pikseli, ale wciąż zastanawiam się, czy powinienem zbadać kolumnę pikseli, czy zamiast tego „region” piksela wzdłuż linii ... Poczekam jeszcze trochę więcej odpowiedzi. Dzięki za teraz!
heltonbiker
@heltonbiker - jako szybki test użyj tylko zielonego kanału. Na czujniku koloru jest zwykle 2x więcej zielonych pikseli i jest on mniej interpolowany niż czerwony i niebieski
Martin Beckett,
@MartinBeckett Dzięki za zainteresowanie, przeanalizowałem już każdy kanał i rzeczywiście zielony wydaje się być znacznie bardziej rozdzielony niż, powiedzmy, czerwony. Wykreślając wartości intensywności pionowych przekrojów poprzecznych dla każdego kanału, „wzór w paski” wydaje się jednak nie zmieniać tak bardzo między kanałami, a obecnie mieszam je jednakowo po konwersji do skali szarości. Mimo to nadal planuję zbadać najlepszą liniową kombinację między kanałami, aby uzyskać najlepszy kontrast, LUB uzyskać obrazy już w skali szarości. Dzięki jeszcze raz!
heltonbiker
3

Oto jeszcze alternatywne rozwiązanie problemu, modelując pytanie jako „problem optymalizacji ścieżki”. Chociaż jest to bardziej skomplikowane niż proste rozwiązanie do binaryzacji, a następnie dopasowania krzywej, w praktyce jest bardziej niezawodne.

Z bardzo wysokiego poziomu powinniśmy rozważyć ten obraz jako wykres, gdzie

  1. każdy piksel obrazu jest węzłem na tym wykresie

  2. każdy węzeł jest połączony z niektórymi innymi węzłami, znanymi jako sąsiedzi, a ta definicja połączenia jest często nazywana topologią tego wykresu.

  3. każdy węzeł ma wagę (cechę, koszt, energię lub jakkolwiek chcesz to nazwać), odzwierciedlając prawdopodobieństwo, że ten węzeł znajduje się w optymalnej linii centralnej, której szukamy.

Tak długo, jak możemy modelować to prawdopodobieństwo, problem znalezienia „linii środkowych obrzeży” staje się problemem, aby znaleźć lokalne optymalne ścieżki na wykresie , które można skutecznie rozwiązać za pomocą programowania dynamicznego, np. Algorytmu Viterbi.

Oto niektóre zalety przyjęcia tego podejścia:

  1. wszystkie wyniki będą ciągłe (w przeciwieństwie do metody progowej, która może rozbić jedną linię środkową na kawałki)

  2. dużo swobody w tworzeniu takiego wykresu, możesz wybrać różne funkcje i topologię wykresu.

  3. Twoje wyniki są optymalne w sensie optymalizacji ścieżek

  4. Twoje rozwiązanie będzie bardziej odporne na zakłócenia, ponieważ dopóki szum jest równomiernie rozłożony na wszystkie piksele, te optymalne ścieżki pozostają stabilne.

Oto krótka prezentacja powyższego pomysłu. Ponieważ nie używam żadnej wcześniejszej wiedzy do określania możliwych węzłów początkowych i końcowych, po prostu dekoduję wrt każdy możliwy węzeł początkowy. Dekodowane ścieżki Viterbi

W przypadku rozmytych zakończeń jest to spowodowane tym, że szukamy optymalnych ścieżek dla każdego możliwego węzła końcowego. W rezultacie, chociaż dla niektórych węzłów znajdujących się w ciemnych obszarach, podświetlona ścieżka jest nadal lokalnie optymalna.

W przypadku rozmytej ścieżki można ją wygładzić po jej znalezieniu lub użyć wygładzonych funkcji zamiast surowej intensywności.

Możliwe jest przywrócenie ścieżek częściowych poprzez zmianę węzłów początkowych i końcowych.

Przycinanie tych niepożądanych lokalnych ścieżek optymalnych nie będzie trudne. Ponieważ mamy prawdopodobieństwo wszystkich ścieżek po dekodowaniu viterbi i możesz skorzystać z różnych wcześniejszych informacji (np. Widzimy, że prawdą jest, że potrzebujemy tylko jednej optymalnej ścieżki dla osób współużytkujących to samo źródło).

Aby uzyskać więcej informacji, możesz odwołać się do artykułu.

 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.

Oto krótki fragment kodu python używanego do wykonania powyższego wykresu.


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 ) );
pułapka
źródło
To bardzo interesujące podejście. Przyznaję, że temat „wykresów” był dla mnie niejasny do niedawna, kiedy (w tym samym projekcie) mogłem rozwiązać inny problem tylko za pomocą wykresów. Po „zrozumieniu” zdałem sobie sprawę, jak potężne mogą być te algorytmy najkrótszych ścieżek. Twój pomysł jest bardzo interesujący i nie jest niemożliwe, że dokonałbym ponownej realizacji tego pomysłu, jeśli mam taką potrzebę / możliwość. Dziękuję Ci bardzo.
heltonbiker
Jeśli chodzi o twoje obecne wyniki, z mojego doświadczenia prawdopodobnie lepiej byłoby najpierw wygładzić obraz filtrem gaussa i / lub mediany, zanim zbudujesz wykres. To dałoby znacznie gładsze (i bardziej poprawne) linie. Ponadto jedną z możliwych sztuczek jest rozszerzenie sąsiedztwa, aby umożliwić „bezpośrednie przeskakiwanie” przez dwa lub więcej pikseli (do określonego limitu, powiedzmy 8 lub 10 pikseli). Oczywiście należy wybrać odpowiednią funkcję kosztów, ale myślę, że łatwo ją dostroić.
heltonbiker
O tak. Po prostu wybrałem coś pod ręką, na pewno możesz użyć innych funkcji topologii i energii. W rzeczywistości ramy te można także wyszkolić. W szczególności zaczynasz od surowej intensywności, dekodujesz w celu uzyskania optymalnych ścieżek, podnosisz tylko te optymalne węzły z dużymi poufnościami i w ten sposób otrzymujesz „etykietowane dane”. Dzięki tej niewielkiej części automatycznie oznakowanych danych możesz nauczyć się wielu przydatnych rzeczy.
pułapka
3

Pomyślałem, że powinienem opublikować swoją odpowiedź, ponieważ różni się ona nieco od innych podejść. Próbowałem tego w Matlabie.

  • zsumuj wszystkie kanały i utwórz obraz, aby wszystkie kanały miały jednakową wagę
  • wykonać morfologiczne zamknięcie i filtrowanie Gaussa na tym obrazie
  • dla każdej kolumny wynikowego obrazu znajdź lokalne maksima i utwórz obraz
  • znajdź połączone elementy tego obrazu

Jedną wadą, którą widzę tutaj, jest to, że to podejście nie będzie działać dobrze w przypadku niektórych orientacji pasków. W takim przypadku musimy poprawić jego orientację i zastosować tę procedurę.

Oto kod Matlab:

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);

Na przykład, jeśli weźmiesz środkową kolumnę obrazu, jego profil powinien wyglądać następująco: (na niebiesko jest profil. Na zielono są lokalne maksima) środkowy profil i lokalne maksima

Obraz zawierający lokalne maksima dla wszystkich kolumn wygląda następująco: wprowadź opis zdjęcia tutaj

Oto połączone komponenty (chociaż niektóre paski są zepsute, większość z nich ma ciągły region):

wprowadź opis zdjęcia tutaj

dhanushka
źródło
To właśnie robimy teraz, z jedyną różnicą, jak znaleźć lokalne maksima dla każdej kolumny pikseli: używamy interpolacji parabolicznej, aby znaleźć dokładny wierzchołek paraboli przechodzącej przez piksel o maksymalnej wartości oraz jego górnych i dolnych sąsiadów . Dzięki temu wynik może znajdować się „między” pikselami, co lepiej reprezentuje subtelną gładkość linii. Dzięki za odpowiedź!
heltonbiker