Localização de padrão zebra na imagem (detecção da linha central da franja de luz estruturada da foto)

12

Estou trabalhando em um projeto em que franjas são projetadas contra um assunto e uma foto é tirada. A tarefa é encontrar as linhas centrais das franjas, que representam, matematicamente, a curva 3D de interseção entre o plano da franja e a superfície do objeto.

A foto é PNG (RGB), e as tentativas anteriores usavam escala de cinza e limiar de diferença para obter uma fotografia em preto e branco, semelhante a uma zebra, a partir da qual era fácil encontrar o ponto médio de cada coluna de pixel de cada franja. O problema é que, limitando e também medindo a altura média de uma coluna discreta de pixels, estamos tendo alguma perda e quantização de precisão, o que não é de todo desejado.

Minha impressão, observando as imagens, é que as linhas de centro poderiam ser mais contínuas (mais pontos) e mais suaves (não quantizadas) se fossem detectadas diretamente a partir da imagem sem limiar (RGB ou escala de cinza), por algum método de varredura estatística (alguma inundação / convolução iterativa, qualquer que seja).

Abaixo está uma imagem de amostra real:

insira a descrição da imagem aqui

Qualquer sugestão seria muito apreciada!

heltonbiker
fonte
é muito interessante. Mas, a propósito, estou fazendo algumas pesquisas usando faixas coloridas para detectar objetos 3D. Como, usando a faixa colorida, é fácil encontrar a correspondência de cada faixa no projetor. Como você encontra a correspondência se a cor é a mesma? Eu acho que seu projeto também é sobre reconstrução 3D?
@johnyoung: Por favor, não adicione comentários como respostas. Sei que você precisa de reputação antes de poder comentar, mas evite seu curso de ação atual. Sugiro fazer suas próprias perguntas (relacionadas) ou responder a perguntas de outras pessoas para aumentar seu representante.
Peter K.
Desculpe por mais uma pergunta, em vez de dar resposta. No método de mudança de fase, calculamos a fase em cada pixel da imagem projetada, mas aqui por que precisamos descobrir a linha central da franja, talvez a minha pergunta seja muito boba, mas eu não não isso, então por favor me diga o motivo exato. U pode apagar a minha pergunta depois de dar resposta
Estes são métodos diferentes. Estou modelando uma série de planos geométricos projetando uma série de listras brancas (cada uma formando um "plano" no espaço 3D). Portanto, preciso encontrar a linha central das franjas, porque os planos não têm espessura. Claro que eu poderia realizar a análise de mudança de fase, mas há um problema: minha projeção é binária (listras preto e branco alternando), a intensidade não varia sinusoidalmente e, portanto, não posso realizar a mudança de fase (e não preciso, atualmente )
heltonbiker

Respostas:

13

Sugiro as seguintes etapas:

  1. Encontre um limite para separar o primeiro plano do segundo plano.
  2. Para cada blob na imagem binária (uma faixa de zebra), para cada x, encontre o centro ponderado (por intensidade de pixel) na ydireção.
  3. Possivelmente, suavize os yvalores, para remover o ruído.
  4. Conecte os (x,y)pontos ajustando algum tipo de curva. Este artigo pode ajudá-lo. Você também pode ajustar um polinômio de alto nível, embora seja pior, na minha opinião.

Aqui está um código do Matlab que mostra as etapas 1,2 e 4. Ignorei a seleção automática de limite. Em vez disso, escolhi o manual th=40:

Estas são as curvas encontradas ao encontrar a média ponderada por coluna: insira a descrição da imagem aqui

Estas são as curvas após o ajuste de um polinômio: insira a descrição da imagem aqui

Aqui está o código:

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
fonte
Eu achei isso muito interessante. Eu uso o Python, mas de qualquer maneira eu vou ter que estudar a lógica de tudo isso. Como um comentário independente, costumo não executar o processamento clássico de imagens (diretamente em contêineres quantizados de imagens, como matrizes uint8), mas, em vez disso, carrego tudo na memória como matrizes flutuantes antes de aplicar as operações. Além disso, estou surpreso com os resultados da metade inferior da sua imagem, as linhas azuis não estão sendo executadas ao longo das linhas médias esperadas ... (?). Obrigado por agora, trarei alguns comentários assim que obtiver algum resultado!
heltonbiker
@ heltonbiker, verifique a resposta atualizada. Você está certo sobre o ponto flutuante, eu o usei quando me converti em double. Sobre os resultados na metade inferior, eu preciso verificar, pode ser um bug de software
Andrey Rubshtein
1
@heltonbiker, pronto. Na verdade, era um bug relacionado à indexação baseada em 1.
21912 Andrey Rubshtein
Excelente! Incrível, de fato. Com essa técnica e para meus propósitos, a suavização não só não será necessária, mas também seria prejudicial. Muito obrigado pelo seu interesse!
heltonbiker
3

Eu não usaria a imagem RGB. As imagens coloridas geralmente são criadas colocando um "Filtro Bayer" no sensor da câmera, o que geralmente reduz a resolução que você pode obter.

Se você usar a imagem em escala de cinza, acho que os passos que você descreveu (binarizar a imagem "zebra", encontrar a linha média) são um bom começo. Como passo final, eu gostaria

  • Tome cada ponto na linha média que você encontrou
  • pegue os valores em cinza dos pixels na linha "zebra" acima e abaixo
  • ajuste uma parábola a esses valores de cinza usando mínimos quadrados médios
  • o ápice dessa parábola é uma estimativa melhorada da posição da linha média
Niki Estner
fonte
Belos pensamentos. Eu pretendo usar algum tipo de parábola ou spline ao longo dos valores de pico de cada coluna de pixel, mas ainda estou pensando se devo examinar uma coluna de pixel ou, em vez disso, uma "região" de pixel ao longo da linha ... Vou esperar um pouco mais por mais respostas. Obrigado por ora!
21812 heltonbiker
@ heltonbiker - como teste rápido, use apenas o canal verde. Há normalmente 2x tantos pixels verdes em um sensor de cor e é menos interpoalted de vermelho e azul
Martin Beckett
@MartinBeckett Obrigado pelo seu interesse, eu já analisei cada canal e, de fato, o canal verde parece estar muito mais resolvido do que o canal vermelho. No entanto, plotando valores de intensidade de seções transversais verticais para cada canal, o "padrão de faixa" não parece mudar muito entre os canais, e atualmente os estou misturando igualmente na conversão em escala de cinza. Mesmo assim, ainda pretendo estudar a melhor combinação linear entre canais para obter o melhor resultado de contraste, OU adquirir imagens já em escala de cinza. Obrigado novamente!
heltonbiker
3

Aqui está ainda uma solução alternativa para o seu problema, modelando sua pergunta como um 'problema de otimização de caminho'. Embora seja mais complicado do que a solução simples de montagem de binarização e curva, é mais robusta na prática.

Do nível muito alto, devemos considerar esta imagem como um gráfico, onde

  1. cada pixel da imagem é um nó neste gráfico

  2. cada nó está conectado a outros nós, conhecidos como vizinhos, e essa definição de conexão é frequentemente referida como a topologia deste gráfico.

  3. cada nó tem um peso (recurso, custo, energia ou o que você quiser chamar), refletindo a probabilidade de que esse nó esteja em uma linha central ideal que estamos procurando.

Desde que possamos modelar essa probabilidade, seu problema de encontrar 'as linhas centrais das franjas' passa a ser o problema para encontrar caminhos ótimos locais no gráfico , que podem ser efetivamente resolvidos pela programação dinâmica, por exemplo, o algoritmo Viterbi.

Aqui estão alguns profissionais da adoção dessa abordagem:

  1. todos os seus resultados serão contínuos (diferente do método do limite que pode quebrar uma linha central em pedaços)

  2. muitas liberdades para construir esse gráfico, você pode selecionar diferentes recursos e topologia de gráfico.

  3. seus resultados são ótimos no sentido de otimizações de caminho

  4. sua solução será mais robusta contra o ruído, porque, enquanto o ruído estiver igualmente distribuído entre todos os pixels, esses caminhos ideais permanecerão estáveis.

Aqui está uma breve demonstração da idéia acima. Como não uso nenhum conhecimento prévio para especificar quais são os possíveis nós iniciais e finais, simplesmente decodifico wrt todos os nós iniciais possíveis. Caminhos decodificados de Viterbi

Para as terminações difusas, isso é causado pelo fato de estarmos procurando caminhos ideais para todos os nós finais possíveis. Como resultado, embora para alguns nós localizados em áreas escuras, o caminho destacado ainda seja o ideal local.

Para o caminho difuso, você pode suavizá-lo após encontrá-lo ou usar alguns recursos suavizados em vez da intensidade bruta.

É possível restaurar caminhos parciais alterando os nós inicial e final.

Não será difícil remover esses caminhos ótimos locais indesejados. Como temos a probabilidade de todos os caminhos após a decodificação de viterbi, e você pode usar vários conhecimentos anteriores (por exemplo, vemos que é verdade que precisamos apenas de um caminho ideal para aqueles que compartilham a mesma fonte).

Para mais detalhes, consulte o 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.

Aqui está um pequeno pedaço de código python usando para fazer o gráfico acima.


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 ) );
armadilha
fonte
Esta é uma abordagem muito interessante. Confesso que o tópico "gráficos" ficou obscuro para mim até recentemente, quando (nesse mesmo projeto) eu só conseguia resolver outro problema usando gráficos. Depois que eu "entendi", percebi o quão poderosos esses algoritmos de caminhos mais curtos podem ser. Sua ideia é muito interessante e não é impossível que eu a reimplementasse se tivesse a necessidade / oportunidade. Muito obrigado.
heltonbiker
Quanto aos resultados atuais, pela minha experiência, provavelmente seria melhor suavizar a imagem primeiro com filtro gaussiano e / ou mediano, antes de criar o gráfico. Isso daria linhas muito mais suaves (e mais corretas). Além disso, um truque possível é expandir a vizinhança para permitir o "salto direto" acima de dois ou mais pixels (até um determinado limite, digamos, 8 ou 10 pixels). É claro que uma função de custo adequada deve ser escolhida, mas acho que é fácil de ajustar.
heltonbiker
Ai sim. Simplesmente escolhi algo em mãos, você pode definitivamente usar outras funções de topologia e energia. Na verdade, essa estrutura também é treinável. Em particular, você começa com a intensidade bruta, decodifica para caminhos ideais, apenas escolhe esses nós ideais com alta confidencialidade e, dessa forma, obtém 'dados rotulados'. Com essa pequena parte dos dados rotulados automaticamente, você pode aprender vários tipos de coisas úteis.
armadilha
3

Pensei que deveria postar minha resposta, pois é um pouco diferente de outras abordagens. Eu tentei isso no Matlab.

  • soma todos os canais e cria uma imagem, para que todos os canais tenham o mesmo peso
  • executar fechamento morfológico e filtragem Gaussiana nesta imagem
  • para cada coluna da imagem resultante, encontre o máximo local e construa uma imagem
  • encontre os componentes conectados desta imagem

Uma desvantagem que vejo aqui é que essa abordagem não terá bom desempenho em algumas orientações das faixas. Nesse caso, temos que corrigir sua orientação e aplicar este procedimento.

Aqui está o código do 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);

Por exemplo, se você usar a coluna do meio da imagem, seu perfil deverá ficar assim: (em azul é o perfil. Em verde são os máximos locais) perfil médio e máximos locais

E a imagem que contém os máximos locais para todas as colunas fica assim: insira a descrição da imagem aqui

Aqui estão os componentes conectados (embora algumas faixas estejam quebradas, a maioria delas obtém uma região contínua):

insira a descrição da imagem aqui

dhanushka
fonte
Na verdade, é isso que estamos fazendo agora, com a única diferença de como encontrar máximos locais para cada coluna de pixel: usamos uma interpolação parabólica para encontrar o vértice exato da parábola que passa pelo pixel com valor máximo e seus vizinhos superiores e inferiores . Isso permite que o resultado seja "entre" pixels, o que representa melhor a suavidade sutil das linhas. Obrigado pela sua resposta!
heltonbiker