Trovare un motivo a forma di zebra nell'immagine (Rilevamento della linea centrale della frangia a luce strutturata dalla foto)


12

Sto lavorando a un progetto in cui le frange sono proiettate su un soggetto e viene scattata una foto. Il compito è quello di trovare le linee centrali delle frange, che rappresentano, matematicamente, la curva 3D dell'intersezione tra il piano della frangia e la superficie del soggetto.

La foto è un PNG (RGB), e i precedenti tentativi hanno usato la scala di grigi e la soglia di differenza per ottenere una fotografia in bianco e nero, simile a una zebra, da cui era facile trovare il punto medio di ogni colonna di pixel di ogni frangia. Il problema è che, soglie e anche prendendo l'altezza media di una colonna di pixel discreti, stiamo avendo una certa perdita di precisione e quantizzazione, che non è affatto desiderata.

La mia impressione, guardando le immagini, è che le linee centrali potrebbero essere più continue (più punti) e più lisce (non quantizzate) se fossero rilevate direttamente dall'immagine senza soglia (RGB o in scala di grigi), con un metodo di scansione statica (qualche alluvione / convoluzione iterativa, qualunque cosa).

Di seguito è riportata un'immagine campione reale:

inserisci qui la descrizione dell'immagine

Qualsiasi suggerimento sarebbe molto apprezzato!


è molto interessante. Ma a proposito, sto facendo delle ricerche usando la striscia colorata per rilevare l'oggetto 3d. Poiché usando la striscia colorata, è facile trovare la corrispondenza di ciascuna striscia dal proiettore. Così, usando la trigonometria, è possibile calcolare le informazioni 3d. Come si trova la corrispondenza se il colore è lo stesso? Immagino che il tuo progetto riguardi anche la ricostruzione 3d?

@johnyoung: per favore non aggiungere commenti come risposte. Mi rendo conto che hai bisogno di reputazione prima di poter commentare, ma per favore astieniti dal tuo attuale corso di azione. Ti suggerisco di porre le tue domande (correlate) o di rispondere alle domande degli altri per aumentare il tuo rappresentante.
Peter K.

Ci scusiamo per un'altra domanda invece di dare una risposta, nel metodo dello spostamento di fase calcoliamo la fase su ciascun pixel nell'immagine proiettata, ma ecco perché dobbiamo scoprire la linea centrale della frangia, potrebbe essere la mia domanda troppo sciocca ma non lo faccio no, quindi per favore fammi sapere il motivo esatto. Puoi eliminare la mia domanda dopo aver dato la risposta

Questi sono metodi diversi. Sto modellando una serie di piani geometrici proiettando una serie di strisce bianche (ognuna formando un "piano" nello spazio 3D). Pertanto, devo trovare la linea centrale delle frange, perché i piani non hanno spessore. Certo, potrei eseguire l'analisi dello sfasamento, ma c'è un problema: la mia proiezione è binaria (si alternano strisce bianche e nere), l'intensità non varia in modo sinusoidale e quindi non posso eseguire lo sfasamento (e non è necessario, al momento ).
Heltonbiker,

Risposte:


13

Suggerisco i seguenti passaggi:

  1. Trova una soglia per separare il primo piano dallo sfondo.
  2. Per ogni blob nell'immagine binaria (una striscia zebra), per ciascuno x, trova il centro ponderato (in base all'intensità dei pixel) in ydirezione.
  3. Forse, leviga i yvalori, per rimuovere il rumore.
  4. Collegare i (x,y)punti inserendo un qualche tipo di curva. Questo articolo potrebbe aiutarti. Puoi anche inserire un polinomio di alto livello, anche se è peggio, secondo me.

Ecco un codice Matlab che mostra i passaggi 1,2 e 4. Ho saltato la selezione automatica della soglia. Invece ho scelto il manuale th=40:

Queste sono le curve che si trovano trovando la media ponderata per colonna: inserisci qui la descrizione dell'immagine

Queste sono le curve dopo aver inserito un polinomio: inserisci qui la descrizione dell'immagine

Ecco il codice:

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

L'ho trovato molto interessante. Uso Python, ma comunque dovrò studiare la logica di tutto questo. Come commento indipendente, tendo a non eseguire l'elaborazione di immagini classiche (direttamente su contenitori di immagini quantizzate come array uint8), ma invece caricare tutto in memoria come array float prima di applicare le operazioni. Inoltre, sono sorpreso dai risultati della metà inferiore dell'immagine, le linee blu non corrono lungo le linee mediane della frangia previste ... (?). Grazie per ora, porterò un feedback non appena avrò un risultato!
heltonbiker,

@heltonbiker, controlla la risposta aggiornata. Hai ragione sul virgola mobile, l'ho usato quando mi sono convertito in double. Per quanto riguarda i risultati nella metà inferiore, devo verificare, potrebbe trattarsi di un bug del software
Andrey Rubshtein,

1
@heltonbiker, fatto. Era in effetti un bug relativo all'indicizzazione basata su 1.
Andrey Rubshtein,

Eccellente! Incredibile, davvero. Con questa tecnica e per i miei scopi, il livellamento non solo non sarà nemmeno necessario, ma sarebbe anche dannoso. Grazie mille per il tuo interesse!
Heltonbiker,

3

Non userei l'immagine RGB. Le immagini a colori sono in genere realizzate inserendo un "filtro Bayer" sul sensore della fotocamera, che di solito riduce la risoluzione che è possibile ottenere.

Se usi l'immagine in scala di grigi, penso che i passaggi che hai descritto (binarizza l'immagine "zebra", trova la linea mediana) siano un buon inizio. Come ultimo passo, lo farei

  • Prendi ogni punto nella linea mediana che hai trovato
  • prendi i valori di grigio dei pixel nella riga "zebra" sopra e sotto
  • adatta una parabola a questi valori di grigio usando i minimi quadrati
  • l'apice di questa parabola è una stima migliorata della posizione mediana

Bei pensieri. Ho intenzione di usare una sorta di parabola o spline lungo i valori di picco di ogni colonna di pixel, ma mi chiedo ancora se dovrei esaminare una colonna di pixel o invece una "regione" di pixel lungo la linea ... Aspetterò ancora più risposte. Grazie per ora!
heltonbiker,

@heltonbiker - come test rapido usa solo il canale verde. Normalmente ci sono 2 volte più pixel verdi su un sensore di colore ed è meno interpoaltato del rosso e del blu
Martin Beckett,

@MartinBeckett Grazie per il tuo interesse, ho già analizzato ogni canale, e in effetti quello verde sembra essere molto più risolto di, diciamo, quello rosso. Tracciando i valori di intensità delle sezioni trasversali verticali per ciascun canale, tuttavia, il "motivo a strisce" non sembra cambiare molto tra i canali e attualmente li sto mescolando allo stesso modo al momento della conversione in scala di grigi. Anche se, ho ancora intenzione di studiare la migliore combinazione lineare tra i canali per ottenere il miglior risultato di contrasto, O di acquisire immagini già in scala di grigi. Grazie ancora!
heltonbiker,

3

Ecco ancora una soluzione alternativa al tuo problema modellando la tua domanda come un "problema di ottimizzazione del percorso". Sebbene sia più complicato della semplice soluzione di binarizzazione e adattamento della curva, in pratica è più robusto.

Da un livello molto alto, dovremmo considerare questa immagine come un grafico, dove

  1. ogni pixel dell'immagine è un nodo su questo grafico

  2. ciascun nodo è collegato ad alcuni altri nodi, noti come vicini, e questa definizione di connessione viene spesso definita come la topologia di questo grafico.

  3. ogni nodo ha un peso (caratteristica, costo, energia o come vuoi chiamarlo), riflettendo la probabilità che questo nodo si trovi in ​​una linea centrale ottimale che stiamo cercando.

Finché possiamo modellare questa probabilità, allora il tuo problema di trovare "le linee centrali delle frange" diventa il problema di trovare percorsi ottimali locali sul grafico , che possono essere risolti efficacemente con la programmazione dinamica, ad esempio l'algoritmo di Viterbi.

Ecco alcuni pro dell'adozione di questo approccio:

  1. tutti i risultati saranno continui (a differenza del metodo della soglia che potrebbe spezzare una linea centrale in pezzi)

  2. molte libertà per costruire un tale grafico, è possibile selezionare diverse funzionalità e topologia del grafico.

  3. i tuoi risultati sono ottimali nel senso di ottimizzazioni del percorso

  4. la tua soluzione sarà più robusta contro il rumore, perché finché il rumore è equamente distribuito tra tutti i pixel, quei percorsi ottimali rimangono stabili.

Ecco una breve dimostrazione dell'idea sopra. Dato che non utilizzo alcuna conoscenza precedente per specificare quali sono possibili nodi di inizio e di fine, decodifico semplicemente ogni nodo iniziale possibile. Percorsi di Viterbi decodificati

Per i finali fuzzy, è causato dal fatto che stiamo cercando percorsi ottimali per ogni possibile nodo finale. Di conseguenza, sebbene per alcuni nodi situati in aree scure, il percorso evidenziato sia comunque quello ottimale locale.

Per il percorso sfocato, è possibile attenuarlo dopo averlo trovato o utilizzare alcune funzioni uniformi anziché l'intensità non elaborata.

È possibile ripristinare percorsi parziali modificando i nodi iniziale e finale.

Non sarà difficile potare questi percorsi ottimali locali indesiderati. Perché abbiamo le probabilità di tutti i percorsi dopo la decodifica del viterbi e puoi usare varie conoscenze precedenti (ad esempio, vediamo che è vero che abbiamo bisogno di un solo percorso ottimale per coloro che condividono la stessa fonte.)

Per maggiori dettagli, puoi fare riferimento al documento.

 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.

Ecco un breve pezzo di codice Python che usa per creare il grafico sopra.


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

Questo è un approccio molto interessante. Confesso che l'argomento dei "grafici" mi è stato oscuro fino a poco tempo fa quando (nello stesso progetto) ho potuto risolvere un altro problema solo usando i grafici. Dopo averlo "capito", mi sono reso conto di quanto possano essere potenti questi algoritmi dei percorsi più brevi. La tua idea è molto interessante e non è impossibile reimplementare per questa se ho bisogno / opportunità. Grazie mille.
Heltonbiker,

Per quanto riguarda i tuoi attuali risultati, dalla mia esperienza sarebbe probabilmente meglio levigare l'immagine prima con filtro gaussiano e / o mediano, prima di costruire il grafico. Ciò darebbe linee molto più fluide (e più corrette). Inoltre, un possibile trucco è quello di espandere il vicinato per consentire il "salto diretto" su due o più pixel (fino a un determinato limite, ad esempio 8 o 10 pixel). Naturalmente dovrebbe essere scelta una funzione di costo adeguata, ma penso che sia facile da regolare.
Heltonbiker,

Oh si. Ho semplicemente scelto qualcosa a portata di mano, puoi sicuramente utilizzare altre funzioni di topologia ed energia. In realtà, questo framework è anche addestrabile. In particolare, inizi con l'intensità grezza, decodifica per percorsi ottimali, raccogli solo quei nodi ottimali con confidenze elevate e in questo modo ottieni "dati etichettati". Con questa piccola parte di dati etichettati automaticamente puoi imparare molti tipi di cose utili.
Trappola

3

Ho pensato di pubblicare la mia risposta in quanto è leggermente diverso dagli altri approcci. L'ho provato in Matlab.

  • sommare tutti i canali e creare un'immagine, in modo che tutti i canali siano equamente ponderati
  • eseguire la chiusura morfologica e il filtro gaussiano su questa immagine
  • per ogni colonna dell'immagine risultante, trova i massimi locali e costruisci un'immagine
  • trova i componenti collegati di questa immagine

Uno svantaggio che vedo qui è che questo approccio non funzionerà bene per alcuni orientamenti delle strisce. In tal caso, dobbiamo correggere il suo orientamento e applicare questa procedura.

Ecco il codice 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);

Ad esempio, se prendi la colonna centrale dell'immagine, il suo profilo dovrebbe apparire così: (in blu è il profilo. In verde sono i massimi locali) profilo medio e massimi locali

E l'immagine contenente i massimi locali per tutte le colonne è simile alla seguente: inserisci qui la descrizione dell'immagine

Ecco i componenti collegati (anche se alcune strisce sono rotte, la maggior parte di esse ha una regione continua):

inserisci qui la descrizione dell'immagine


Questo è ciò che stiamo facendo ora, con la sola differenza di come trovare i massimi locali per ogni colonna di pixel: usiamo un'interpolazione parabolica per trovare il vertice esatto della parabola che passa attraverso il pixel con il massimo valore e i suoi vicini superiore e inferiore . Ciò consente che il risultato sia "tra" i pixel, il che rappresenta meglio la sottile levigatezza delle linee. Grazie per la tua risposta!
Heltonbiker,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.