Encontrar un patrón similar a una cebra en la imagen (Detección de la línea central franja de luz estructurada de la foto)

12

Estoy trabajando en un proyecto donde se proyectan flecos contra un sujeto y se toma una foto. La tarea es encontrar las líneas centrales de las franjas, que representan, matemáticamente, la curva 3D de intersección entre el plano marginal y la superficie del sujeto.

La foto es PNG (RGB), y los intentos anteriores utilizaron escala de grises y luego umbral de diferencia para obtener una fotografía en blanco y negro, "similar a una cebra", desde la cual fue fácil encontrar el punto medio de cada columna de píxeles de cada franja. El problema es que, al establecer un umbral y también al tomar la altura media de una columna de píxeles discreta, estamos teniendo algo de pérdida de precisión y cuantización, lo que no se desea en absoluto.

Mi impresión, al mirar las imágenes, es que las líneas centrales podrían ser más continuas (más puntos) y más suaves (no cuantificadas) si se detectaran directamente desde la imagen sin umbral (RGB o escala de grises), mediante algún método de barrido estadístico (alguna inundación / convolución iterativa, lo que sea).

A continuación se muestra una imagen de muestra real:

ingrese la descripción de la imagen aquí

Cualquier sugerencia sería muy apreciada!

heltonbiker
fuente
es muy interesante. Pero, por cierto, estoy investigando con franjas de color para detectar objetos en 3D. Debido al uso de la franja de color, es fácil encontrar la correspondencia de cada franja del proyector. Por lo tanto, utilizando la trigonometría, se puede calcular la información en 3D. ¿Cómo encuentras la correspondencia si el color es el mismo? ¿Supongo que tu proyecto también se trata de la reconstrucción en 3D?
@johnyoung: Por favor no agregue comentarios como respuestas. Me doy cuenta de que necesitas reputación antes de poder comentar, pero por favor abstente de tu curso de acción actual. Sugiero hacer sus propias preguntas (relacionadas) o responder las preguntas de otros para aumentar su reputación.
Peter K.
Perdón por una pregunta más en lugar de dar una respuesta, en el método de cambio de fase calculamos la fase en cada píxel en la imagen proyectada, pero aquí por qué necesitamos encontrar la línea central de la franja, podría ser mi pregunta demasiado tonta, pero no lo hago no eso, así que por favor dime la razón exacta. Puede eliminar mi pregunta después de responder
Estos son diferentes métodos. Estoy modelando una serie de planos geométricos proyectando una serie de rayas blancas (cada una formando un "plano" en el espacio 3D). Por lo tanto, necesito encontrar la línea central de las franjas, porque los planos no tienen grosor. Claro que podría realizar un análisis de cambio de fase, pero hay un problema: mi proyección es binaria (rayas blancas y negras alternadas), la intensidad no varía sinusoidalmente, por lo que no puedo realizar el cambio de fase (y no es necesario, actualmente )
heltonbiker

Respuestas:

13

Sugiero los siguientes pasos:

  1. Encuentre un umbral para separar el primer plano del fondo.
  2. Para cada blob en la imagen binaria (una franja de cebra), para cada uno x, encuentre el centro ponderado (por intensidad de píxel) en ydirección.
  3. Posiblemente, suavice los yvalores para eliminar el ruido.
  4. Conecte los (x,y)puntos ajustando algún tipo de curva. Este artículo puede ayudarte. También puede colocar un polinomio de alto nivel, aunque en mi opinión es peor.

Aquí hay un código de Matlab que muestra los pasos 1,2 y 4. Me salteé la selección automática de umbral. En su lugar, elegí el manual th=40:

Estas son las curvas que se encuentran al encontrar el promedio ponderado por columna: ingrese la descripción de la imagen aquí

Estas son las curvas después de ajustar un polinomio: ingrese la descripción de la imagen aquí

Aquí está el 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
fuente
Esto me pareció muy interesante. Uso Python, pero de todos modos tendré que estudiar la lógica de todo esto. Como comentario independiente, tiendo a no realizar el procesamiento clásico de imágenes (directamente en contenedores de imágenes cuantificadas, como las matrices uint8), sino que en lugar de cargar todo en la memoria como matrices flotantes antes de aplicar las operaciones. Además, estoy sorprendido con los resultados de la mitad inferior de su imagen, las líneas azules no se ejecutan a lo largo de las líneas medias marginales esperadas ... (?). Gracias por ahora, ¡traeré algunos comentarios tan pronto como obtenga algún resultado!
heltonbiker
@heltonbiker, comprueba la respuesta actualizada. Tienes razón sobre el punto flotante, lo usé cuando me convertí double. Sobre los resultados en la mitad inferior, necesito verificar, podría ser un error de software
Andrey Rubshtein
1
@heltonbiker, hecho. De hecho, fue un error relacionado con la indexación basada en 1.
Andrey Rubshtein
Excelente! Increíble, de hecho. Con esta técnica, y para mis propósitos, el alisado no solo ni siquiera será necesario, sino que también sería dañino. ¡Muchas gracias por tu interés!
heltonbiker
3

No usaría la imagen RGB. Las imágenes en color generalmente se realizan colocando un "Filtro Bayer" en el sensor de la cámara, lo que generalmente reduce la resolución que puede lograr.

Si usa la imagen en escala de grises, creo que los pasos que describió (binarizar la imagen de "cebra", encontrar la línea media) son un buen comienzo. Como paso final, lo haría

  • Toma cada punto en la línea media que encontraste
  • tomar los valores grises de los píxeles en la línea "cebra" arriba y abajo
  • ajustar una parábola a estos valores grises usando mínimos cuadrados medios
  • El vértice de esta parábola es una estimación mejorada de la posición de la línea media.
Niki Estner
fuente
Pensamientos agradables. Planeo usar algún tipo de parábola o spline a lo largo de los valores máximos de cada columna de píxeles, pero todavía me pregunto si debería examinar una columna de píxeles o una "región" de píxeles a lo largo de la línea ... Voy a esperar un poco más Más respuestas. Gracias por ahora
heltonbiker
@heltonbiker: como prueba rápida, use solo el canal verde. Normalmente hay 2 veces más píxeles verdes en un sensor de color y es menos interpolado que el rojo y el azul
Martin Beckett
@ MartininBeckett Gracias por su interés, ya he analizado cada canal y, de hecho, el verde parece estar mucho más resuelto que, por ejemplo, el rojo. Sin embargo, al trazar los valores de intensidad de las secciones transversales verticales para cada canal, el "patrón de bandas" no parece cambiar tanto entre canales, y actualmente los estoy mezclando por igual al convertirlos a escala de grises. Sin embargo, todavía planeo estudiar la mejor combinación lineal entre canales para obtener el mejor resultado de contraste, O para adquirir imágenes que ya están en escala de grises. ¡Gracias de nuevo!
heltonbiker
3

Aquí hay una solución alternativa a su problema modelando su pregunta como un 'problema de optimización de ruta'. Aunque es más complicado que la solución simple de binarización y luego ajuste de curva, es más robusto en la práctica.

Desde el nivel más alto, debemos considerar esta imagen como un gráfico, donde

  1. cada píxel de la imagen es un nodo en este gráfico

  2. cada nodo está conectado a otros nodos, conocidos como vecinos, y esta definición de conexión a menudo se refiere a la topología de este gráfico.

  3. cada nodo tiene un peso (característica, costo, energía o como quiera llamarlo), lo que refleja la probabilidad de que este nodo se encuentre en una línea central óptima que estamos buscando.

Mientras podamos modelar esta probabilidad, entonces su problema de encontrar 'las líneas centrales de las franjas' se convierte en el problema de encontrar rutas óptimas locales en el gráfico , que pueden resolverse efectivamente mediante programación dinámica, por ejemplo, el algoritmo de Viterbi.

Aquí hay algunas ventajas de adoptar este enfoque:

  1. todos sus resultados serán continuos (a diferencia del método de umbral que podría dividir una línea central en pedazos)

  2. muchas libertades para construir un gráfico de este tipo, puede seleccionar diferentes características y la topología del gráfico.

  3. sus resultados son óptimos en el sentido de optimizaciones de ruta

  4. su solución será más robusta contra el ruido, porque mientras el ruido se distribuya por igual entre todos los píxeles, esas rutas óptimas permanecerán estables.

Aquí hay una breve demostración de la idea anterior. Como no utilizo ningún conocimiento previo para especificar qué son posibles nodos iniciales y finales, simplemente decodifico wrt cada posible nodo inicial. Decodificado Viterbi Paths

Para las terminaciones difusas, se debe al hecho de que estamos buscando rutas óptimas para todos los nodos finales posibles. Como resultado, aunque para algunos nodos ubicados en áreas oscuras, la ruta resaltada sigue siendo la óptima local.

Para el camino difuso, puede suavizarlo después de encontrarlo o usar algunas características suavizadas en lugar de intensidad bruta.

Es posible restaurar rutas parciales cambiando nodos iniciales y finales.

No será difícil podar estas rutas óptimas locales no deseadas. Debido a que tenemos las probabilidades de todas las rutas después de la decodificación de viterbi, y puede usar varios conocimientos previos (por ejemplo, vemos que es cierto que solo necesitamos una ruta óptima para aquellos que comparten la misma fuente).

Para más detalles, puede consultar el 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.

Aquí hay un pequeño fragmento de código de Python que se usa para hacer el gráfico anterior.


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 ) );
trampa
fuente
Este es un enfoque muy interesante. Confieso que el tema de "gráficos" me ha sido oscuro hasta hace poco cuando (en este mismo proyecto) solo podía resolver otro problema usando gráficos. Después de "entenderlo", me di cuenta de lo poderosos que pueden ser estos algoritmos de rutas más cortas. Su idea es muy interesante y no es imposible que la vuelva a implementar si tengo la necesidad / oportunidad. Muchas gracias.
heltonbiker
En cuanto a sus resultados actuales, según mi experiencia, probablemente sería mejor suavizar la imagen primero con un filtro gaussiano y / o mediano, antes de construir el gráfico. Esto daría líneas mucho más suaves (y más correctas). Además, un posible truco es expandir el vecindario para permitir el "salto directo" sobre dos o más píxeles (hasta un límite dado, por ejemplo, 8 o 10 píxeles). Por supuesto, se debe elegir una función de costo adecuada, pero creo que es fácil de ajustar.
heltonbiker
Oh si. Simplemente elegí algo a mano, definitivamente puedes usar otras funciones de topología y energía. En realidad, este marco también es entrenable. En particular, comienza con la intensidad bruta, decodifica para rutas óptimas, solo recoge aquellos nodos óptimos con altas confidencias y de esta manera obtiene 'datos etiquetados'. Con esta pequeña parte de datos etiquetados automáticamente, puede aprender muchos tipos de cosas útiles.
trampa
3

Pensé que debería publicar mi respuesta, ya que es un poco diferente de otros enfoques. Intenté esto en Matlab.

  • suma todos los canales y crea una imagen, de modo que todos los canales se ponderan por igual
  • realizar el cierre morfológico y el filtrado gaussiano en esta imagen
  • para cada columna de la imagen resultante, encuentre los máximos locales y construya una imagen
  • encuentra los componentes conectados de esta imagen

Una desventaja que veo aquí es que este enfoque no funcionará bien para algunas orientaciones de las franjas. En ese caso tenemos que corregir su orientación y aplicar este procedimiento.

Aquí está el código de 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 ejemplo, si toma la columna central de la imagen, su perfil debería verse así: (en azul está el perfil. En verde están los máximos locales) perfil medio y máximos locales

Y la imagen que contiene los máximos locales para todas las columnas se ve así: ingrese la descripción de la imagen aquí

Aquí están los componentes conectados (aunque algunas franjas están rotas, la mayoría de ellas obtienen una región continua):

ingrese la descripción de la imagen aquí

dhanushka
fuente
Esto es realmente lo que estamos haciendo ahora, con la única diferencia de cómo encontrar máximos locales para cada columna de píxeles: utilizamos una interpolación parabólica para encontrar el vértice exacto de la parábola que pasa a través del píxel con el valor máximo y sus vecinos superior e inferior. . Esto permite que el resultado sea "entre" píxeles, lo que representa mejor la suavidad sutil de las líneas. ¡Gracias por tu respuesta!
heltonbiker