python - recognition - scikit-image
Detección de picos en una matriz 2D (21)
Estoy ayudando a una clínica veterinaria midiendo la presión bajo la pata de un perro. Utilizo Python para mi análisis de datos y ahora estoy atascado tratando de dividir las patas en subregiones (anatómicas).
Hice una matriz 2D de cada pata, que consta de los valores máximos para cada sensor que la pata ha cargado a lo largo del tiempo. Aquí hay un ejemplo de una pata, donde utilicé Excel para dibujar las áreas que quiero "detectar". Estas son cajas de 2 por 2 alrededor del sensor con máximos locales, que en conjunto tienen la suma más grande.
Así que probé algunos experimentos y decidí buscar simplemente los máximos de cada columna y fila (no puedo mirar en una dirección debido a la forma de la pata). Esto parece "detectar" bastante bien la ubicación de los dedos separados, pero también marca los sensores vecinos.
Entonces, ¿cuál sería la mejor manera de decirle a Python cuáles de estos máximos son los que quiero?
Nota: ¡Los cuadrados 2x2 no pueden superponerse, ya que tienen que ser dedos separados!
También tomé 2x2 como una conveniencia, cualquier solución más avanzada es bienvenida, pero soy simplemente un científico del movimiento humano, así que no soy ni un verdadero programador ni un matemático, así que manténgalo "simple".
Aquí hay una versión que se puede cargar con np.loadtxt
Resultados
Así que probé la solución de @jextee (ver los resultados a continuación). Como puede ver, funciona muy bien en las patas delanteras, pero funciona menos bien para las patas traseras.
Más específicamente, no puede reconocer el pequeño pico que es el cuarto dedo del pie. Obviamente, esto es inherente al hecho de que el bucle se ve de arriba abajo hacia el valor más bajo, sin tener en cuenta dónde está.
¿Alguien sabría cómo modificar el algoritmo de @ jextee para que también pueda encontrar el 4to dedo?
Como todavía no he procesado ningún otro ensayo, no puedo suministrar ninguna otra muestra. Pero los datos que di antes eran los promedios de cada pata. Este archivo es una matriz con los datos máximos de 9 patas en el orden en que hicieron contacto con la placa.
Esta imagen muestra cómo se dispersaron espacialmente sobre la placa.
Actualizar:
He configurado un blog para cualquier persona interesada y he configurado un SkyDrive con todas las mediciones en bruto. Así que para cualquiera que solicite más datos: ¡más poder para usted!
Nueva actualización:
Así que después de la ayuda que recibí con mis preguntas sobre la detección de patas y la clasificación de las patas , ¡finalmente pude verificar la detección de cada dedo! Resulta que no funciona tan bien en nada que no sean patas del tamaño de mi ejemplo. Por supuesto, en retrospectiva, es mi culpa por haber elegido el 2x2 de manera tan arbitraria.
Este es un buen ejemplo de dónde sale mal: un clavo se reconoce como un dedo del pie y el "talón" es tan ancho que se reconoce dos veces.
La pata es demasiado grande, por lo que al tomar un tamaño de 2x2 sin superposición, algunos dedos se detectan dos veces. A la inversa, en los perros pequeños a menudo no encuentra un quinto dedo del pie, que sospecho que está siendo causado por el área 2x2 que es demasiado grande.
Después de probar la solución actual en todas mis medidas , llegué a la asombrosa conclusión de que para casi todos mis perros pequeños no encontraba un quinto dedo y que en más del 50% de los impactos para los perros grandes, ¡encontraría más!
Claramente necesito cambiarlo. Mi propia suposición era cambiar el tamaño del neighborhood
a algo más pequeño para perros pequeños y más grande para perros grandes. Pero generate_binary_structure
no me permitiría cambiar el tamaño de la matriz.
Por lo tanto, espero que alguien más tenga una mejor sugerencia para ubicar los dedos de los pies, ¿quizás tenga la escala del área del dedo del pie con el tamaño de la pata?
Solución
Archivo de datos: paw.txt . Código fuente:
from scipy import *
from operator import itemgetter
n = 5 # how many fingers are we looking for
d = loadtxt("paw.txt")
width, height = d.shape
# Create an array where every element is a sum of 2x2 squares.
fourSums = d[:-1,:-1] + d[1:,:-1] + d[1:,1:] + d[:-1,1:]
# Find positions of the fingers.
# Pair each sum with its position number (from 0 to width*height-1),
pairs = zip(arange(width*height), fourSums.flatten())
# Sort by descending sum value, filter overlapping squares
def drop_overlapping(pairs):
no_overlaps = []
def does_not_overlap(p1, p2):
i1, i2 = p1[0], p2[0]
r1, col1 = i1 / (width-1), i1 % (width-1)
r2, col2 = i2 / (width-1), i2 % (width-1)
return (max(abs(r1-r2),abs(col1-col2)) >= 2)
for p in pairs:
if all(map(lambda prev: does_not_overlap(p,prev), no_overlaps)):
no_overlaps.append(p)
return no_overlaps
pairs2 = drop_overlapping(sorted(pairs, key=itemgetter(1), reverse=True))
# Take the first n with the heighest values
positions = pairs2[:n]
# Print results
print d, "/n"
for i, val in positions:
row = i / (width-1)
column = i % (width-1)
print "sum = %f @ %d,%d (%d)" % (val, row, column, i)
print d[row:row+2,column:column+2], "/n"
Output sin cuadrados superpuestos. Parece que se seleccionan las mismas áreas que en tu ejemplo.
Algunos comentarios
La parte difícil es calcular sumas de todos los cuadrados 2x2. Asumí que los necesita todos, por lo que podría haber alguna superposición. Utilicé cortes para cortar las primeras / últimas columnas y filas de la matriz 2D original, y luego las superpongo todas juntas y calculando sumas.
Para entenderlo mejor, imaginando una matriz de 3x3:
>>> a = arange(9).reshape(3,3) ; a
array([[0, 1, 2],
[3, 4, 5],
[6, 7, 8]])
Luego puedes tomar sus rodajas:
>>> a[:-1,:-1]
array([[0, 1],
[3, 4]])
>>> a[1:,:-1]
array([[3, 4],
[6, 7]])
>>> a[:-1,1:]
array([[1, 2],
[4, 5]])
>>> a[1:,1:]
array([[4, 5],
[7, 8]])
Ahora imagina que los apilas uno encima del otro y sumas los elementos en las mismas posiciones. Estas sumas serán exactamente las mismas sumas en los cuadrados 2x2 con la esquina superior izquierda en la misma posición:
>>> sums = a[:-1,:-1] + a[1:,:-1] + a[:-1,1:] + a[1:,1:]; sums
array([[ 8, 12],
[20, 24]])
Cuando tenga las sumas de más de 2x2 cuadrados, puede usar max
para encontrar el máximo, u sort
, u sorted
para encontrar los picos.
Para recordar las posiciones de los picos, acoplo cada valor (la suma) con su posición ordinal en una matriz aplanada (ver zip
). Luego calculo la posición de la fila / columna nuevamente cuando imprimo los resultados.
Notas
Permití que los cuadrados de 2x2 se superpongan. La versión editada filtra algunos de ellos de modo que solo aparecen cuadrados no superpuestos en los resultados.
Elegir los dedos (una idea)
Otro problema es cómo elegir qué es probable que sean dedos de todos los picos. Tengo una idea que puede o no funcionar. No tengo tiempo para implementarlo ahora, así que solo pseudo-código.
Noté que si los dedos delanteros permanecen en casi un círculo perfecto, el dedo trasero debería estar dentro de ese círculo. Además, los dedos frontales están más o menos separados por igual. Podemos tratar de usar estas propiedades heurísticas para detectar los dedos.
Pseudo código:
select the top N finger candidates (not too many, 10 or 12)
consider all possible combinations of 5 out of N (use itertools.combinations)
for each combination of 5 fingers:
for each finger out of 5:
fit the best circle to the remaining 4
=> position of the center, radius
check if the selected finger is inside of the circle
check if the remaining four are evenly spread
(for example, consider angles from the center of the circle)
assign some cost (penalty) to this selection of 4 peaks + a rear finger
(consider, probably weighted:
circle fitting error,
if the rear finger is inside,
variance in the spreading of the front fingers,
total intensity of 5 peaks)
choose a combination of 4 peaks + a rear peak with the lowest penalty
Este es un enfoque de fuerza bruta. Si N es relativamente pequeño, entonces creo que es factible. Para N = 12, hay C_12 ^ 5 = 792 combinaciones, multiplicado por 5 formas de seleccionar un dedo trasero, por lo que hay 3960 casos que evaluar para cada pata.
Al usar homología persistente para analizar su conjunto de datos, obtengo el siguiente resultado (haga clic para ampliar):
Esta es la versión 2D del método de detección de picos descrito en esta respuesta SO . La figura anterior simplemente muestra clases de homología persistente en 0 dimensiones ordenadas por persistencia.
Hice un aumento de escala del conjunto de datos original por un factor de 2 usando scipy.misc.imresize (). Sin embargo, tenga en cuenta que consideré las cuatro patas como un conjunto de datos; dividirlo en cuatro facilitaría el problema.
Metodología. La idea detrás de esto es bastante simple: considere la función gráfica de la función que asigna a cada píxel su nivel. Se parece a esto:
Ahora considere un nivel de agua en la altura 255 que desciende continuamente a niveles más bajos. En las islas locales máximas emergente (nacimiento). En los puntos de silla se unen dos islas; consideramos que la isla inferior se fusiona con la isla superior (muerte). El llamado diagrama de persistencia (de las clases de homología tridimensional, nuestras islas) muestra la muerte sobre los valores de nacimiento de todas las islas:
La persistencia de una isla es, entonces, la diferencia entre el nivel de nacimiento y el nivel de muerte; La distancia vertical de un punto a la diagonal principal gris. La figura etiqueta las islas al disminuir la persistencia.
La primera imagen muestra la ubicación de los nacimientos de las islas. Este método no solo da los máximos locales sino que también cuantifica su "significado" por la persistencia mencionada anteriormente. Uno entonces filtraría todas las islas con una persistencia demasiado baja. Sin embargo, en su ejemplo, cada isla (es decir, cada máximo local) es un pico que busca.
El código de Python se puede encontrar here .
Aquí hay otro enfoque que utilicé al hacer algo similar para un telescopio grande:
1) Busca el píxel más alto. Una vez que tenga eso, busque el mejor ajuste para 2x2 (tal vez maximizando la suma de 2x2), o haga un ajuste gaussiano 2d dentro de la sub región de, por ejemplo, 4x4 centrado en el píxel más alto.
Luego, establezca esos 2x2 píxeles que ha encontrado en cero (o tal vez 3x3) alrededor del centro del pico
vuelva a 1) y repita hasta que el pico más alto caiga por debajo de un umbral de ruido, o tenga todos los dedos que necesita
Aquí hay una idea: se calcula el laplaciano (discreto) de la imagen. Espero que sea (negativo y) grande en los máximos, de una manera más dramática que en las imágenes originales. Por lo tanto, los máximos podrían ser más fáciles de encontrar.
Aquí hay otra idea: si conoce el tamaño típico de los puntos de alta presión, primero puede suavizar su imagen por convolutivo con un gaussiano del mismo tamaño. Esto le puede dar imágenes más simples para procesar.
Bueno, aquí hay un código simple y no muy eficiente, pero para este tamaño de conjunto de datos está bien.
import numpy as np
grid = np.array([[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0.4,0.4,0.4,0,0,0],
[0,0,0,0,0.4,1.4,1.4,1.8,0.7,0,0,0,0,0],
[0,0,0,0,0.4,1.4,4,5.4,2.2,0.4,0,0,0,0],
[0,0,0.7,1.1,0.4,1.1,3.2,3.6,1.1,0,0,0,0,0],
[0,0.4,2.9,3.6,1.1,0.4,0.7,0.7,0.4,0.4,0,0,0,0],
[0,0.4,2.5,3.2,1.8,0.7,0.4,0.4,0.4,1.4,0.7,0,0,0],
[0,0,0.7,3.6,5.8,2.9,1.4,2.2,1.4,1.8,1.1,0,0,0],
[0,0,1.1,5,6.8,3.2,4,6.1,1.8,0.4,0.4,0,0,0],
[0,0,0.4,1.1,1.8,1.8,4.3,3.2,0.7,0,0,0,0,0],
[0,0,0,0,0,0.4,0.7,0.4,0,0,0,0,0,0]])
arr = []
for i in xrange(grid.shape[0] - 1):
for j in xrange(grid.shape[1] - 1):
tot = grid[i][j] + grid[i+1][j] + grid[i][j+1] + grid[i+1][j+1]
arr.append([(i,j),tot])
best = []
arr.sort(key = lambda x: x[1])
for i in xrange(5):
best.append(arr.pop())
badpos = set([(best[-1][0][0]+x,best[-1][0][1]+y)
for x in [-1,0,1] for y in [-1,0,1] if x != 0 or y != 0])
for j in xrange(len(arr)-1,-1,-1):
if arr[j][0] in badpos:
arr.pop(j)
for item in best:
print grid[item[0][0]:item[0][0]+2,item[0][1]:item[0][1]+2]
Básicamente, solo hago una matriz con la posición de la esquina superior izquierda y la suma de cada cuadrado 2x2 y la ordeno por la suma. Luego tomo el cuadrado 2x2 con la suma más alta fuera de la disputa, lo puse en la best
matriz y elimino todos los demás cuadrados 2x2 que usaron cualquier parte de este cuadrado eliminado de 2x2.
Parece que funciona bien, excepto con la última pata (la que tiene la suma más pequeña en el extremo derecho de la primera imagen), resulta que hay otros dos cuadrados de 2x2 elegibles con una suma más grande (y tienen una suma igual a El uno al otro). Uno de ellos sigue seleccionando un cuadrado de tu cuadrado 2x2, pero el otro está a la izquierda. Afortunadamente, por suerte, vemos que elegimos más de la que usted desearía, pero esto puede requerir que se usen algunas otras ideas para obtener lo que realmente desea todo el tiempo.
Detecté los picos usando un filtro máximo local . Aquí está el resultado en su primer conjunto de datos de 4 patas:
También lo ejecuté en el segundo conjunto de datos de 9 patas y funcionó también .
Así es como lo haces:
import numpy as np
from scipy.ndimage.filters import maximum_filter
from scipy.ndimage.morphology import generate_binary_structure, binary_erosion
import matplotlib.pyplot as pp
#for some reason I had to reshape. Numpy ignored the shape header.
paws_data = np.loadtxt("paws.txt").reshape(4,11,14)
#getting a list of images
paws = [p.squeeze() for p in np.vsplit(paws_data,4)]
def detect_peaks(image):
"""
Takes an image and detect the peaks usingthe local maximum filter.
Returns a boolean mask of the peaks (i.e. 1 when
the pixel''s value is the neighborhood maximum, 0 otherwise)
"""
# define an 8-connected neighborhood
neighborhood = generate_binary_structure(2,2)
#apply the local maximum filter; all pixel of maximal value
#in their neighborhood are set to 1
local_max = maximum_filter(image, footprint=neighborhood)==image
#local_max is a mask that contains the peaks we are
#looking for, but also the background.
#In order to isolate the peaks we must remove the background from the mask.
#we create the mask of the background
background = (image==0)
#a little technicality: we must erode the background in order to
#successfully subtract it form local_max, otherwise a line will
#appear along the background border (artifact of the local maximum filter)
eroded_background = binary_erosion(background, structure=neighborhood, border_value=1)
#we obtain the final mask, containing only peaks,
#by removing the background from the local_max mask (xor operation)
detected_peaks = local_max ^ eroded_background
return detected_peaks
#applying the detection and plotting results
for i, paw in enumerate(paws):
detected_peaks = detect_peaks(paw)
pp.subplot(4,2,(2*i+1))
pp.imshow(paw)
pp.subplot(4,2,(2*i+2) )
pp.imshow(detected_peaks)
pp.show()
Todo lo que necesita hacer después es usar scipy.ndimage.measurements.label
en la máscara para etiquetar todos los objetos distintos. Entonces podrás jugar con ellos individualmente.
Tenga en cuenta que el método funciona bien porque el fondo no es ruidoso. Si lo fuera, detectarías un montón de otros picos no deseados en el fondo. Otro factor importante es el tamaño del barrio . Tendrá que ajustarlo si el tamaño máximo cambia (debería ser aproximadamente proporcional).
Este es un problema de registro de imagen . La estrategia general es:
- Tenga un ejemplo conocido, o algún tipo de previo en los datos.
- Ajuste sus datos al ejemplo, o ajuste el ejemplo a sus datos.
- Ayuda si sus datos están alineados aproximadamente en primer lugar.
Aquí hay un acercamiento rudo y listo , "la cosa más tonta que podría funcionar":
- Comience con cinco coordenadas de punta en aproximadamente el lugar que espera.
- Con cada una de ellas, suba iterativamente a la cima de la colina. es decir, dada la posición actual, mover al píxel vecino máximo, si su valor es mayor que el píxel actual. Detente cuando las coordenadas de tu pie hayan dejado de moverse.
Para contrarrestar el problema de orientación, podría tener 8 o más ajustes iniciales para las direcciones básicas (Norte, Noreste, etc.). Ejecuta cada uno individualmente y tira cualquier resultado donde dos o más dedos terminen en el mismo píxel. Pensaré en esto un poco más, pero este tipo de cosas todavía se está investigando en el procesamiento de imágenes. ¡No hay respuestas correctas!
Idea un poco más compleja: (ponderada) K-significa agrupación. No está tan mal.
- Comience con cinco coordenadas de punta, pero ahora estos son "centros de clúster".
Luego iterar hasta la convergencia:
- Asigne cada píxel al clúster más cercano (solo haga una lista para cada clúster).
- Calcula el centro de masa de cada grupo. Para cada grupo, esto es: Suma (coordenada * valor de intensidad) / Suma (coordenada)
- Mueva cada grupo al nuevo centro de masa.
Es casi seguro que este método dará resultados mucho mejores, y obtendrá la masa de cada grupo, lo que puede ayudar a identificar los dedos de los pies.
(Una vez más, ha especificado la cantidad de agrupaciones por adelantado. Con la agrupación debe especificar la densidad de una forma u otra: elija la cantidad de agrupaciones, apropiada en este caso, o elija un radio de agrupación y vea cuántas finaliza con. Un ejemplo de este último es mean-shift .)
Lo siento por la falta de detalles de implementación u otros detalles. Yo codificaría esto pero tengo una fecha límite. Si nada más ha funcionado la próxima semana, avíseme y le daré una oportunidad.
Este problema ha sido estudiado con cierta profundidad por los físicos. Hay una buena implementación en ROOT . Mire las clases de TSpectrum (especialmente TSpectrum2 para su caso) y la documentación para ellas.
Referencias:
- M.Morhac et al .: Métodos de eliminación de fondo para espectros de rayos gamma de coincidencia multidimensional. Instrumentos y métodos nucleares en la investigación de física A 401 (1997) 113-132.
- M.Morhac et al .: eficiente deconvolución de oro en una y dos dimensiones y su aplicación a la descomposición de espectros de rayos gamma. Instrumentos y métodos nucleares en la investigación de física A 401 (1997) 385-408.
- M.Morhac et al .: Identificación de picos en espectros de rayos gamma de coincidencia multidimensional. Instrumentos y métodos nucleares en la investigación física A 443 (2000), 108-125.
... y para aquellos que no tienen acceso a una suscripción a NIM:
Estoy seguro de que ya tienes suficiente para continuar, pero no puedo dejar de sugerirte que utilices el método de agrupamiento de k-means. k-means es un algoritmo de agrupación sin supervisión que lo llevará a los datos (en cualquier número de dimensiones; esto sucede en 3D) y lo organiza en k agrupaciones con límites distintos. Está bien aquí porque sabes exactamente cuántos dedos de pies deben tener estos caninos.
Además, está implementado en Scipy, lo cual es realmente bueno ( http://docs.scipy.org/doc/scipy/reference/cluster.vq.html ).
Este es un ejemplo de lo que puede hacer para resolver espacialmente clústeres 3D:
Lo que quieres hacer es un poco diferente (2D e incluye valores de presión), pero aún creo que podrías darle una oportunidad.
Gracias por los datos en bruto. Estoy en el tren y esto es lo que he llegado (mi parada se acerca). Le di masajes a su archivo txt con expresiones regulares y lo coloqué en una página html con algún javascript para visualización. Lo estoy compartiendo aquí porque algunos, como yo, pueden encontrarlo más fácilmente hackeable que python.
Creo que un buen enfoque será invariante de escala y rotación, y mi próximo paso será investigar las mezclas de gaussianos. (cada almohadilla de la pata es el centro de un gaussiano).
<html>
<head>
<script type="text/javascript" src="http://vis.stanford.edu/protovis/protovis-r3.2.js"></script>
<script type="text/javascript">
var heatmap = [[[0,0,0,0,0,0,0,4,4,0,0,0,0],
[0,0,0,0,0,7,14,22,18,7,0,0,0],
[0,0,0,0,11,40,65,43,18,7,0,0,0],
[0,0,0,0,14,61,72,32,7,4,11,14,4],
[0,7,14,11,7,22,25,11,4,14,65,72,14],
[4,29,79,54,14,7,4,11,18,29,79,83,18],
[0,18,54,32,18,43,36,29,61,76,25,18,4],
[0,4,7,7,25,90,79,36,79,90,22,0,0],
[0,0,0,0,11,47,40,14,29,36,7,0,0],
[0,0,0,0,4,7,7,4,4,4,0,0,0]
],[
[0,0,0,4,4,0,0,0,0,0,0,0,0],
[0,0,11,18,18,7,0,0,0,0,0,0,0],
[0,4,29,47,29,7,0,4,4,0,0,0,0],
[0,0,11,29,29,7,7,22,25,7,0,0,0],
[0,0,0,4,4,4,14,61,83,22,0,0,0],
[4,7,4,4,4,4,14,32,25,7,0,0,0],
[4,11,7,14,25,25,47,79,32,4,0,0,0],
[0,4,4,22,58,40,29,86,36,4,0,0,0],
[0,0,0,7,18,14,7,18,7,0,0,0,0],
[0,0,0,0,4,4,0,0,0,0,0,0,0],
],[
[0,0,0,4,11,11,7,4,0,0,0,0,0],
[0,0,0,4,22,36,32,22,11,4,0,0,0],
[4,11,7,4,11,29,54,50,22,4,0,0,0],
[11,58,43,11,4,11,25,22,11,11,18,7,0],
[11,50,43,18,11,4,4,7,18,61,86,29,4],
[0,11,18,54,58,25,32,50,32,47,54,14,0],
[0,0,14,72,76,40,86,101,32,11,7,4,0],
[0,0,4,22,22,18,47,65,18,0,0,0,0],
[0,0,0,0,4,4,7,11,4,0,0,0,0],
],[
[0,0,0,0,4,4,4,0,0,0,0,0,0],
[0,0,0,4,14,14,18,7,0,0,0,0,0],
[0,0,0,4,14,40,54,22,4,0,0,0,0],
[0,7,11,4,11,32,36,11,0,0,0,0,0],
[4,29,36,11,4,7,7,4,4,0,0,0,0],
[4,25,32,18,7,4,4,4,14,7,0,0,0],
[0,7,36,58,29,14,22,14,18,11,0,0,0],
[0,11,50,68,32,40,61,18,4,4,0,0,0],
[0,4,11,18,18,43,32,7,0,0,0,0,0],
[0,0,0,0,4,7,4,0,0,0,0,0,0],
],[
[0,0,0,0,0,0,4,7,4,0,0,0,0],
[0,0,0,0,4,18,25,32,25,7,0,0,0],
[0,0,0,4,18,65,68,29,11,0,0,0,0],
[0,4,4,4,18,65,54,18,4,7,14,11,0],
[4,22,36,14,4,14,11,7,7,29,79,47,7],
[7,54,76,36,18,14,11,36,40,32,72,36,4],
[4,11,18,18,61,79,36,54,97,40,14,7,0],
[0,0,0,11,58,101,40,47,108,50,7,0,0],
[0,0,0,4,11,25,7,11,22,11,0,0,0],
[0,0,0,0,0,4,0,0,0,0,0,0,0],
],[
[0,0,4,7,4,0,0,0,0,0,0,0,0],
[0,0,11,22,14,4,0,4,0,0,0,0,0],
[0,0,7,18,14,4,4,14,18,4,0,0,0],
[0,4,0,4,4,0,4,32,54,18,0,0,0],
[4,11,7,4,7,7,18,29,22,4,0,0,0],
[7,18,7,22,40,25,50,76,25,4,0,0,0],
[0,4,4,22,61,32,25,54,18,0,0,0,0],
[0,0,0,4,11,7,4,11,4,0,0,0,0],
],[
[0,0,0,0,7,14,11,4,0,0,0,0,0],
[0,0,0,4,18,43,50,32,14,4,0,0,0],
[0,4,11,4,7,29,61,65,43,11,0,0,0],
[4,18,54,25,7,11,32,40,25,7,11,4,0],
[4,36,86,40,11,7,7,7,7,25,58,25,4],
[0,7,18,25,65,40,18,25,22,22,47,18,0],
[0,0,4,32,79,47,43,86,54,11,7,4,0],
[0,0,0,14,32,14,25,61,40,7,0,0,0],
[0,0,0,0,4,4,4,11,7,0,0,0,0],
],[
[0,0,0,0,4,7,11,4,0,0,0,0,0],
[0,4,4,0,4,11,18,11,0,0,0,0,0],
[4,11,11,4,0,4,4,4,0,0,0,0,0],
[4,18,14,7,4,0,0,4,7,7,0,0,0],
[0,7,18,29,14,11,11,7,18,18,4,0,0],
[0,11,43,50,29,43,40,11,4,4,0,0,0],
[0,4,18,25,22,54,40,7,0,0,0,0,0],
[0,0,4,4,4,11,7,0,0,0,0,0,0],
],[
[0,0,0,0,0,7,7,7,7,0,0,0,0],
[0,0,0,0,7,32,32,18,4,0,0,0,0],
[0,0,0,0,11,54,40,14,4,4,22,11,0],
[0,7,14,11,4,14,11,4,4,25,94,50,7],
[4,25,65,43,11,7,4,7,22,25,54,36,7],
[0,7,25,22,29,58,32,25,72,61,14,7,0],
[0,0,4,4,40,115,68,29,83,72,11,0,0],
[0,0,0,0,11,29,18,7,18,14,4,0,0],
[0,0,0,0,0,4,0,0,0,0,0,0,0],
]
];
</script>
</head>
<body>
<script type="text/javascript+protovis">
for (var a=0; a < heatmap.length; a++) {
var w = heatmap[a][0].length,
h = heatmap[a].length;
var vis = new pv.Panel()
.width(w * 6)
.height(h * 6)
.strokeStyle("#aaa")
.lineWidth(4)
.antialias(true);
vis.add(pv.Image)
.imageWidth(w)
.imageHeight(h)
.image(pv.Scale.linear()
.domain(0, 99, 100)
.range("#000", "#fff", ''#ff0a0a'')
.by(function(i, j) heatmap[a][j][i]));
vis.render();
}
</script>
</body>
</html>
La solución del físico:
Defina 5 marcadores de pata identificados por sus posiciones X_i
e inicie con posiciones aleatorias. Defina alguna función de energía combinando algún premio por la ubicación de marcadores en las posiciones de las patas con algún castigo por la superposición de marcadores; digamos:
E(X_i;S)=-Sum_i(S(X_i))+alfa*Sum_ij (|X_i-Xj|<=2*sqrt(2)?1:0)
( S(X_i)
es la fuerza media en 2x2 al cuadrado alrededor de X_i
, alfa
es un parámetro que debe alcanzarse como máximo experimentalmente)
Ahora es el momento de hacer algo de magia Metropolis-Hastings:
1. Seleccione un marcador aleatorio y muévalo un píxel en dirección aleatoria.
2. Calcula dE, la diferencia de energía causada por este movimiento.
3. Obtenga un número aleatorio uniforme de 0-1 y llámelo r.
4. Si dE<0
o exp(-beta*dE)>r
, acepte el movimiento y vaya a 1; Si no, deshaga el movimiento y vaya a 1.
Esto debe repetirse hasta que los marcadores converjan en patas. Beta controla el escaneo para optimizar la compensación, por lo que también debe optimizarse experimentalmente; También se puede aumentar constantemente con el tiempo de simulación (recocido simulado).
Probablemente valga la pena probar con redes neuronales si puede crear algunos datos de entrenamiento ... pero esto necesita muchas muestras anotadas a mano.
Quizás puedas usar algo como los modelos de mezcla gaussianos. Aquí hay un paquete de Python para hacer GMM (solo hizo una búsqueda en Google) http://www.ar.media.kyoto-u.ac.jp/members/david/softwares/em/
Solo un par de ideas de la cabeza:
- tomar el gradiente (derivado) del escaneo, ver si eso elimina las llamadas falsas
- Aprovecha al máximo los máximos locales.
También es posible que desee echar un vistazo a OpenCV , tiene una API de Python bastante decente y podría tener algunas funciones que le serían útiles.
un esquema aproximado ...
probablemente querrá usar un algoritmo de componentes conectados para aislar cada región de la pata. wiki tiene una descripción decente de esto (con algún código) aquí: http://en.wikipedia.org/wiki/Connected_Component_Labeling
Tendrá que tomar una decisión sobre si utilizar 4 u 8 conexiones. Personalmente, para la mayoría de los problemas prefiero 6-conectividad. de todos modos, una vez que haya separado cada "impresión de la pata" como una región conectada, debería ser lo suficientemente fácil para recorrer la región y encontrar los máximos. Una vez que haya encontrado el máximo, podría ampliar iterativamente la región hasta que alcance un umbral predeterminado para identificarlo como un "dedo del pie" dado.
un problema sutil aquí es que tan pronto como comienza a usar técnicas de visión artificial para identificar algo como una pata derecha / izquierda / delantera / trasera y comienza a mirar los dedos de los pies individuales, debe comenzar a tomar en cuenta las rotaciones, los sesgos y las traducciones. Esto se logra a través del análisis de los llamados "momentos". Hay algunos momentos diferentes para considerar en aplicaciones de visión:
momentos centrales: traducción invariante momentos normalizados: escalamiento y traducción invariantes hu momentos: traducción, escala y rotación invariantes
Puede encontrar más información acerca de los momentos buscando "momentos de imagen" en la wiki.
No estoy seguro de que esto responda a la pregunta, pero parece que solo puede buscar los n picos más altos que no tienen vecinos.
Aquí está la esencia. Tenga en cuenta que está en Ruby, pero la idea debe ser clara.
require ''pp''
NUM_PEAKS = 5
NEIGHBOR_DISTANCE = 1
data = [[1,2,3,4,5],
[2,6,4,4,6],
[3,6,7,4,3],
]
def tuples(matrix)
tuples = []
matrix.each_with_index { |row, ri|
row.each_with_index { |value, ci|
tuples << [value, ri, ci]
}
}
tuples
end
def neighbor?(t1, t2, distance = 1)
[1,2].each { |axis|
return false if (t1[axis] - t2[axis]).abs > distance
}
true
end
# convert the matrix into a sorted list of tuples (value, row, col), highest peaks first
sorted = tuples(data).sort_by { |tuple| tuple.first }.reverse
# the list of peaks that don''t have neighbors
non_neighboring_peaks = []
sorted.each { |candidate|
# always take the highest peak
if non_neighboring_peaks.empty?
non_neighboring_peaks << candidate
puts "took the first peak: #{candidate}"
else
# check that this candidate doesn''t have any accepted neighbors
is_ok = true
non_neighboring_peaks.each { |accepted|
if neighbor?(candidate, accepted, NEIGHBOR_DISTANCE)
is_ok = false
break
end
}
if is_ok
non_neighboring_peaks << candidate
puts "took #{candidate}"
else
puts "denied #{candidate}"
end
end
}
pp non_neighboring_peaks
Qué sucede si avanza paso a paso: primero localiza el máximo global, procesa, si es necesario, los puntos circundantes según su valor, luego establece la región encontrada en cero y repite para la siguiente.
Quizás un enfoque ingenuo sea suficiente aquí: cree una lista de todos los cuadrados 2x2 en su plano, ordénelos por su suma (en orden descendente).
Primero, seleccione el cuadrado de valor más alto en su "lista de pata". Luego, selecciona iterativamente 4 de los siguientes cuadrados mejores que no se intersecan con ninguno de los cuadrados encontrados anteriormente.
solo quiero decirles, hay una buena opción para encontrar máximos locales en imágenes con python.
from skimage.feature import peak_local_max
o para skimage 0.8.0
from skimage.feature.peak import peak_local_max
http://scikit-image.org/docs/0.8.0/api/skimage.feature.peak.html
Parece que puedes engañar un poco usando el algoritmo de Jetxee. Él está encontrando bien los primeros tres dedos, y deberías poder adivinar dónde se basa el cuarto.
Problema interesante. La solución que probaría es la siguiente.
Aplique un filtro de paso bajo, como convolución con una máscara gaussiana 2D. Esto le dará un montón de valores (probablemente, pero no necesariamente de punto flotante).
Realice una supresión no máxima en 2D utilizando el radio aproximado conocido de cada almohadilla (o punta) de la pata.
Esto debería darle las posiciones máximas sin tener múltiples candidatos que estén muy juntos. Solo para aclarar, el radio de la máscara en el paso 1 también debe ser similar al radio utilizado en el paso 2. Este radio podría ser seleccionable, o el veterinario podría medirlo explícitamente de antemano (variará con la edad / raza / etc.)
Algunas de las soluciones sugeridas (cambio medio, redes neuronales, etc.) probablemente funcionarán en algún grado, pero son demasiado complicadas y probablemente no ideales.