Detección de picos en una matriz 2D


874

Estoy ayudando a una clínica veterinaria que mide la presión debajo de una pata de perro. Uso 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 con el tiempo. Aquí hay un ejemplo de una pata, donde usé Excel para dibujar las áreas que quiero 'detectar'. Estos son cuadros de 2 por 2 alrededor del sensor con máximos locales, que juntos tienen la suma más grande.

texto alternativo

Así que intenté experimentar un poco y decidí simplemente buscar 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' la ubicación de los dedos separados bastante bien, pero también marca los sensores vecinos.

texto alternativo

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 de 2x2 no pueden superponerse, ya que tienen que ser dedos separados!

También tomé 2x2 por conveniencia, cualquier solución más avanzada es bienvenida, pero simplemente soy un científico del movimiento humano, así que no soy un verdadero programador o matemático, así que por favor manténgalo 'simple'.

Aquí hay una versión que se puede cargar connp.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 ajustar el algoritmo de @ jextee, para que también pueda encontrar el cuarto dedo del pie?

texto alternativo

Como todavía no he procesado ninguna otra prueba, 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 espaciaron espacialmente sobre la placa.

texto alternativo

Actualizar:

He configurado un blog para cualquier persona interesada y he configurado un SkyDrive con todas las medidas en bruto. Entonces, para cualquiera que solicite más datos: ¡más poder para usted!


Nueva actualización:

Entonces, después de la ayuda que recibí con mis preguntas sobre la detección y la clasificación de las patas , ¡finalmente pude verificar la detección de cada pata! Resulta que no funciona tan bien en otra cosa que no sean las patas del tamaño de mi propio ejemplo. Por supuesto, en retrospectiva, es mi culpa por elegir el 2x2 de manera tan arbitraria.

Aquí hay 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.

texto alternativo

La pata es demasiado grande, por lo que tomar un tamaño de 2x2 sin superposición hace que se detecten algunos dedos dos veces. A la inversa, en perros pequeños a menudo no puede encontrar un quinto dedo del pie, lo que sospecho es que el área de 2x2 es demasiado grande.

Después de probar la solución actual en todas mis mediciones , llegué a la sorprendente conclusión de que para casi todos mis perros pequeños no encontró un quinto dedo del pie 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 fue cambiar el tamaño del neighborhooda algo más pequeño para perros pequeños y más grande para perros grandes. Pero generate_binary_structureno me dejaba 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, ¿tal vez tener la escala del área de los dedos con el tamaño de la pata?


¿Supongo que las comas son decimales en lugar de separadores de valores?
MattH

Sí, son comas. Y @Christian, estoy tratando de pegarlo en un archivo fácil de leer, pero incluso eso me falla :(
Ivo Flipse

3
Como estoy haciendo un estudio de viabilidad, todo vale realmente. Así que estoy buscando tantas formas de definir la presión, incluidas las subregiones. También necesito poder discriminar entre los lados del "dedo gordo" y del "dedo pequeño", para poder estimar la orientación. Pero como esto no se ha hecho antes, no se sabe qué podríamos encontrar :-)
Ivo Flipse

2
@Ron: uno de los objetivos de este estudio es ver para qué tamaño / peso de perros es adecuado el sistema, así que sí, mientras este perro pesaba unos 20 kg. Tengo algunos que son considerablemente más pequeños (y más grandes) y espero que no pueda hacer lo mismo con los más pequeños.
Ivo Flipse

2
@frank las patas se miden con el tiempo, de ahí la tercera dimensión. Sin embargo, no se mueven de su lugar (en términos relativos), por lo que me interesa principalmente dónde se encuentran los dedos de los pies en 2D. El aspecto 3D es gratis después de eso
Ivo Flipse el

Respuestas:


332

Detecté los picos usando un filtro máximo local . Aquí está el resultado en su primer conjunto de datos de 4 patas: Resultado de detección de picos

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.labella 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ía un montón de otros picos no deseados en el fondo. Otro factor importante es el tamaño del barrio . Tendrá que ajustarlo si cambia el tamaño máximo (el debería permanecer más o menos proporcional).


1
Hay una solución más simple que (eroded_background ^ local_peaks). Just do (primer plano y picos locales)
Ryan Soklaski

53

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"

Salida sin cuadrados superpuestos. Parece que se seleccionan las mismas áreas que en su ejemplo.

Algunos comentarios

La parte difícil es calcular sumas de todos los cuadrados de 2x2. Supuse que los necesita a todos, por lo que podría haber cierta superposición. Usé cortes para cortar las primeras / últimas columnas y filas de la matriz 2D original, y luego las superpuse todas juntas y calculé sumas.

Para entenderlo mejor, imaginando una matriz 3x3:

>>> a = arange(9).reshape(3,3) ; a
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])

Entonces puedes tomar sus rebanadas:

>>> 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 imagine que los apila uno encima del otro y suma elementos en las mismas posiciones. Estas sumas serán exactamente las mismas sumas sobre los cuadrados de 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 tienes las sumas sobre cuadrados 2x2, puedes usar maxpara encontrar el máximo, o sort, o sortedpara encontrar los picos.

Para recordar las posiciones de los picos, acople cada valor (la suma) con su posición ordinal en una matriz aplanada (ver zip). Luego calculo nuevamente la posición de la fila / columna 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 aparezcan cuadrados no superpuestos en los resultados.

Elegir dedos (una idea)

Otro problema es cómo elegir qué es probable que sean los dedos de todos los picos. Tengo una idea que puede o no funcionar. No tengo tiempo para implementarlo en este momento, así que solo pseudocódigo.

Noté que si los dedos delanteros permanecen en un círculo casi perfecto, el dedo trasero debe estar dentro de ese círculo. Además, los dedos delanteros están más o menos equidistantes. Podemos intentar usar estas propiedades heurísticas para detectar los dedos.

Pseudocó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 combinaciones de C_12 ^ 5 = 792, multiplicadas por 5 formas de seleccionar un dedo posterior, por lo que se deben evaluar 3960 casos para cada pata.


Tendrá que filtrar las patas manualmente, dada su lista de resultados ... elegir los cuatro resultados más altos le dará las cuatro posibilidades para construir un cuadrado de 2x2 que contenga el valor máximo 6.8
Johannes Charra

Los cuadros de 2x2 no pueden superponerse, ya que si quiero hacer estadísticas, no quiero usar la misma región, quiero comparar regiones :-)
Ivo Flipse

Edité la respuesta. Ahora no hay cuadrados superpuestos en los resultados.
sastanin

1
Lo probé y parece funcionar para las patas delanteras, pero no tanto para las traseras. Supongo que tendremos que probar algo que sepa dónde mirar
Ivo Flipse

1
Le expliqué mi idea de cómo se pueden detectar los dedos en pseudocódigo. Si te gusta, puedo intentar implementarlo mañana por la noche.
sastanin

34

Este es un problema de registro de imagen . La estrategia general es:

  • Tener un ejemplo conocido, o algún tipo de previo sobre los datos.
  • Ajuste sus datos al ejemplo, o ajuste el ejemplo a sus datos.
  • Ayuda si sus datos están más o menos alineados en primer lugar.

Aquí hay un enfoque aproximado y listo , "lo más tonto que podría funcionar":

  • Comience con las coordenadas de cinco dedos aproximadamente en el lugar que espera.
  • Con cada uno, sube iterativamente a la cima de la colina. es decir, dada la posición actual, muévase al píxel vecino máximo, si su valor es mayor que el píxel actual. Detente cuando las coordenadas de tus dedos hayan dejado de moverse.

Para contrarrestar el problema de orientación, podría tener aproximadamente 8 configuraciones iniciales para las direcciones básicas (Norte, Nordeste, etc.). Ejecute cada uno individualmente y deseche los resultados donde dos o más dedos terminen en el mismo píxel. Pensaré en esto un poco más, pero este tipo de cosas aún se está investigando en el procesamiento de imágenes: ¡no hay respuestas correctas!

Idea ligeramente más compleja: (ponderado) K-significa agrupamiento. No está tan mal.

  • Comience con cinco coordenadas del dedo del pie, pero ahora estos son "centros de agrupación".

Luego iterar hasta la convergencia:

  • Asigne cada píxel al grupo más cercano (solo haga una lista para cada grupo).
  • Calcule 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 que puede ayudar a identificar los dedos de los pies.

(Una vez más, ha especificado la cantidad de grupos por adelantado. Con la agrupación debe especificar la densidad de una forma u otra: elija la cantidad de grupos, apropiada en este caso, o elija un radio de grupo y vea cuántos termina arriba con. Un ejemplo de esto último es el cambio medio .)

Perdón por la falta de detalles de implementación u otros detalles. Codificaría esto, pero tengo una fecha límite. Si nada más ha funcionado para la próxima semana, avíseme y lo intentaré.


1
El problema es que las patas cambian su orientación y no tengo ninguna calibración / línea de base de una pata correcta para empezar. Además, me temo que muchos de los algoritmos de reconocimiento de imágenes están un poco fuera de mi alcance.
Ivo Flipse

El enfoque "aproximado y listo" es bastante simple, tal vez no tuve la idea bien. Pondré un pseudocódigo para ilustrar.
CakeMaster

Tengo la sensación de que su sugerencia ayudará a arreglar el reconocimiento de las patas traseras, simplemente no sé 'cómo'
Ivo Flipse

He añadido otra idea. Por cierto, si tiene una gran cantidad de buenos datos, sería genial ponerlo en línea en algún lugar. Podría ser útil para las personas que estudian el procesamiento de imágenes / aprendizaje automático y podría obtener más código de él ...
CakeMaster

1
Estaba pensando en escribir mi procesamiento de datos en un simple blog de Wordpress, simplemente para ser usado por otros y tengo que escribirlo de todos modos. Me gustan todas sus sugerencias, pero me temo que tendré que esperar a alguien sin una fecha límite ;-)
Ivo Flipse

18

Utilizando la homología persistente para analizar su conjunto de datos, obtengo el siguiente resultado (haga clic para ampliar):

Resultado

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 de 0 dimensiones ordenadas por persistencia.

Escalé el conjunto de datos original por un factor de 2 usando scipy.misc.imresize (). Sin embargo, tenga en cuenta que sí 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 el gráfico de función de la función que asigna a cada píxel su nivel. Se parece a esto:

Gráfico de funciones 3D

Ahora considere un nivel de agua a una altura 255 que desciende continuamente a niveles más bajos. En las islas máximas locales aparecen (nacimiento). En los puntos de silla se unen dos islas; consideramos que la isla inferior se fusionó con la isla superior (muerte). El llamado diagrama de persistencia (de las clases de homología de dimensión 0, nuestras islas) representa la muerte sobre los valores de nacimiento de todas las islas:

Diagrama de persistencia

La persistencia de una isla es entonces la diferencia entre el nivel de nacimiento y 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 proporciona los máximos locales sino que también cuantifica su "importancia" mediante la persistencia mencionada anteriormente. Entonces se filtrarían 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 aquí .


16

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

  1. 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.
  2. M.Morhac et al .: Desconvolución eficiente del oro de una y dos dimensiones y su aplicación a la descomposición de los espectros de rayos gamma. Instrumentos y métodos nucleares en la investigación de física A 401 (1997) 385-408.
  3. M.Morhac et al .: Identificación de picos en espectros de rayos gamma de coincidencia multidimensional. Instrumentos y métodos nucleares en física de investigación A 443 (2000), 108-125.

... y para aquellos que no tienen acceso a una suscripción a NIM:


Para echar un vistazo al artículo, parece describir el mismo procesamiento de datos que lo que estoy intentando aquí, sin embargo, me temo que superó en gran medida mis habilidades de programación :(
Ivo Flipse

@Ivo: nunca he intentado implementarlo yo mismo Solo uso ROOT. Hay enlaces de python, no obstante, pero tenga en cuenta que ROOT es un paquete bastante pesado.
dmckee --- ex-gatito moderador

@Ivo Flipse: estoy de acuerdo con dmckee. Tienes muchas pistas prometedoras en otras respuestas. Si todos fallan y tienes ganas de invertir algo de tiempo, puedes profundizar en ROOT y (probablemente) hará lo que necesites. Nunca he conocido a nadie que haya intentado aprender ROOT a través de los enlaces de Python (en lugar de ser C ++ natural), así que le deseo suerte.
physicsmichael

13

Aquí hay una idea: calcula el laplaciano (discreto) de la imagen. Esperaría que fuera (negativo y) grande en máximos, de una manera que sea 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 convolucionándola con un gaussiano del mismo tamaño. Esto puede darle imágenes más simples para procesar.


11

Solo un par de ideas fuera de mi cabeza:

  • tome el gradiente (derivado) del escaneo, vea si eso elimina las llamadas falsas
  • tomar el máximo de 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.


Con gradiente, ¿quiere decir que debería calcular la inclinación de las pendientes, una vez que esto supere un cierto valor, sé que hay 'un pico'? Intenté esto, pero algunos de los dedos de los pies solo tienen picos muy bajos (1.2 N / cm) en comparación con algunos de los otros (8 N / cm). Entonces, ¿cómo debo manejar los picos con un gradiente muy bajo?
Ivo Flipse

2
Lo que funcionó para mí en el pasado si no podía usar el gradiente directamente era mirar el gradiente y los máximos, por ejemplo, si el gradiente es un extremo local y estoy en un máximo local, entonces estoy en un punto de interesar.
ChrisC

11

Estoy seguro de que ya tiene suficiente para continuar, pero no puedo evitar sugerirle que utilice el método de agrupación k-means. k-means es un algoritmo de agrupamiento no supervisado que tomará datos (en cualquier cantidad de dimensiones, lo haré en 3D) y los organizará en k grupos con límites distintos. Es agradable aquí porque sabes exactamente cuántos dedos de estos caninos (deberían) tener.

Además, se implementa en Scipy, lo cual es realmente agradable ( http://docs.scipy.org/doc/scipy/reference/cluster.vq.html ).

Aquí hay un ejemplo de lo que puede hacer para resolver espacialmente los clústeres 3D: ingrese la descripción de la imagen aquí

Lo que quieres hacer es un poco diferente (2D e incluye valores de presión), pero sigo pensando que podrías intentarlo.


10

Gracias por los datos en bruto. Estoy en el tren y esto es lo más lejos que he llegado (mi parada se acerca). Masajeé su archivo txt con expresiones regulares y lo puse en una página html con algunos javascript para visualización. Lo estoy compartiendo aquí porque algunos, como yo, podrían encontrarlo más fácilmente pirateable que Python.

Creo que un buen enfoque será invariante en escala y rotación, y mi próximo paso será investigar 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>

texto alternativo


1
Creo que esta es una prueba de concepto de que las técnicas gaussianas recomendadas podrían funcionar, ahora si alguien pudiera probarlo con Python ;-)
Ivo Flipse

8

Solución del físico:
defina 5 marcadores de pata identificados por sus posiciones X_ie inícielos con posiciones aleatorias. Definir alguna función energética combinando algún premio por la ubicación de los 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 un cuadrado de 2x2 alrededor X_i, alfaes un parámetro para alcanzar su punto máximo experimentalmente)

Ahora es el momento de hacer magia con Metropolis-Hastings:
1. Seleccione un marcador aleatorio y muévalo un píxel en dirección aleatoria.
2. Calcule dE, la diferencia de energía que causó este movimiento.
3. Obtenga un número aleatorio uniforme de 0-1 y llámelo r.
4. Si dE<0o exp(-beta*dE)>r, acepta el movimiento y ve 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).


¿Te gustaría mostrar cómo funcionaría esto en mi ejemplo? Como realmente no estoy interesado en las matemáticas de alto nivel, ya tengo dificultades para desentrañar la fórmula que propusiste :(
Ivo Flipse

1
Esto es matemática en la escuela secundaria, probablemente mi notación está ofuscada. Tengo un plan para verificarlo, así que estad atentos.
mbq

44
Soy un físico de partículas. Durante mucho tiempo, la herramienta de software de referencia en nuestra disciplina se llamaba PAW, y tenía una entidad relacionada con los gráficos llamada "marcador". Puedes imaginar lo confuso que encontré esta respuesta las primeras veces ...
dmckee --- ex-gatito moderador

6

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 (quizás maximizando la suma de 2x2), o haga un ajuste gaussiano 2d dentro de la subregión de, digamos, 4x4 centrado en el píxel más alto.

Luego configure esos píxeles de 2x2 que haya encontrado en cero (o tal vez 3x3) alrededor del centro del pico

regrese a 1) y repita hasta que el pico más alto caiga por debajo de un umbral de ruido, o tenga todos los dedos de los pies que necesita


¿Le gustaría compartir un ejemplo de código que haga esto? Puedo seguir lo que intentas hacer, pero no tengo idea de cómo codificarlo yo mismo
Ivo Flipse

De hecho, vengo de trabajar con Matlab, así que sí, eso ya ayudaría. Pero si usa funciones realmente extrañas, podría ser difícil para mí replicarlo con Python
Ivo Flipse

6

Probablemente valga la pena probar con redes neuronales si puede crear algunos datos de entrenamiento ... pero esto necesita muchas muestras anotadas a mano.


Si vale la pena, no me importaría anotar una muestra grande a mano. Mi problema sería: ¿cómo implemento esto, ya que no sé nada sobre la programación de redes neuronales?
Ivo Flipse

6

un esbozo ...

probablemente desee utilizar 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ás que tomar una decisión sobre si usar 4 u 8 conexiones. personalmente, para la mayoría de los problemas, prefiero la conectividad 6. de todos modos, una vez que haya separado cada "huella de la pata" como una región conectada, debería ser bastante fácil recorrer la región y encontrar los máximos. una vez que haya encontrado los máximos, podría ampliar iterativamente la región hasta alcanzar un umbral predeterminado para identificarlo como un "dedo del pie" dado.

Un problema sutil aquí es que tan pronto como comience a usar técnicas de visión por computadora para identificar algo como una pata derecha / izquierda / delantera / trasera y comience a mirar los dedos de los pies individuales, debe comenzar a tener en cuenta las rotaciones, sesgos y traducciones. Esto se logra mediante el análisis de los llamados "momentos". Hay algunos momentos diferentes a considerar en las aplicaciones de visión:

momentos centrales: invariante de traducción momentos normalizados: escala y traducción invariante momentos hu: invariante de traslación, escala y rotación

Se puede encontrar más información sobre los momentos buscando "momentos de imagen" en la wiki.



4

Parece que puedes engañar un poco usando el algoritmo de jetxee. Está encontrando bien los primeros tres dedos del pie, y deberías poder adivinar dónde se basa el cuarto.


4

Interesante problema La solución que probaría es la siguiente.

  1. Aplique un filtro de paso bajo, como la convolución con una máscara gaussiana 2D. Esto le dará un montón de valores (probablemente, pero no necesariamente de coma flotante).

  2. Realice una supresión 2D no máxima utilizando el radio aproximado conocido de cada almohadilla de la pata (o dedo del pie).

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 (desplazamiento medio, redes neuronales, etc.) probablemente funcionarán hasta cierto punto, pero son demasiado complicadas y probablemente no sean ideales.


Tengo 0 experiencia con matrices de convolución y filtros gaussianos, ¿le gustaría mostrar cómo funcionaría en mi ejemplo?
Ivo Flipse

3

Bueno, aquí hay un código simple y no terriblemente 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, simplemente hago una matriz con la posición de la esquina superior izquierda y la suma de cada cuadrado de 2x2 y la ordeno por la suma. Luego tomo el cuadrado de 2x2 con la suma más alta fuera de la contienda, lo pongo en la bestmatriz y elimino todos los otros cuadrados de 2x2 que usaron cualquier parte de este cuadrado de 2x2 recién eliminado.

Parece funcionar bien, excepto con la última pata (la que tiene la suma más pequeña en el extremo derecho en su primera imagen), resulta que hay otros dos cuadrados elegibles de 2x2 con una suma mayor (y tienen una suma igual a El uno al otro). Uno de ellos todavía selecciona un cuadrado de su cuadrado de 2x2, pero el otro está a la izquierda. Afortunadamente, por suerte, vemos que estamos eligiendo más del que desea, pero esto puede requerir que se usen otras ideas para obtener lo que realmente quiere todo el tiempo.


Creo que sus resultados son los mismos que los de la respuesta de @ Jextee. O al menos así parece que lo probé.
Ivo Flipse


1

Quizás un enfoque ingenuo sea suficiente aquí: construya una lista de todos los cuadrados de 2x2 en su avión, ordénelos por su suma (en orden descendente).

Primero, seleccione el cuadrado de mayor valor en su "lista de patas". Luego, elija iterativamente 4 de los siguientes cuadrados mejores que no se crucen con ninguno de los cuadrados encontrados anteriormente.


Realmente hice una lista con todas las sumas de 2x2, pero cuando las ordené no tenía idea de cómo compararlas iterativamente. Mi problema fue que cuando lo ordené, perdí el rastro de las coordenadas. Quizás podría pegarlos en un diccionario, con las coordenadas como clave.
Ivo Flipse

Sí, sería necesario algún tipo de diccionario. Habría asumido que su representación de la cuadrícula ya es una especie de diccionario.
Johannes Charra

Bueno, la imagen que ves arriba es una matriz numpy. El resto está actualmente almacenado en listas multidimensionales. Probablemente sería mejor dejar de hacerlo, aunque no estoy tan familiarizado con la iteración sobre los diccionarios
Ivo Flipse

1

Hay varias y extensas piezas de software disponibles de la comunidad de astronomía y cosmología: esta es un área importante de investigación tanto histórica como actual.

No se alarme si no es un astrónomo; algunos son fáciles de usar fuera del campo. Por ejemplo, podría usar astropy / photutils:

https://photutils.readthedocs.io/en/stable/detection.html#local-peak-detection

[Parece un poco grosero repetir su breve código de muestra aquí.]

A continuación se proporciona una lista incompleta y ligeramente sesgada de técnicas / paquetes / enlaces que pueden ser de interés: agregue más en los comentarios y actualizaré esta respuesta según sea necesario. Por supuesto, existe una compensación entre la precisión y los recursos informáticos. [Honestamente, hay demasiados para dar ejemplos de código en una sola respuesta como esta, así que no estoy seguro de si esta respuesta volará o no.]

Extractor de código fuente https://www.astromatic.net/software/sextractor

MultiNest https://github.com/farhanferoz/MultiNest [+ pyMultiNest]

Desafío de búsqueda de fuente ASKAP / EMU: https://arxiv.org/abs/1509.03931

También puede buscar desafíos de extracción de fuentes de Planck y / o WMAP.

...


0

¿Qué sucede si continúa paso a paso: primero localiza el máximo global, procesa si es necesario los puntos circundantes dado su valor, luego establece la región encontrada en cero y repite para el siguiente?


Hmmm, ese ajuste a cero al menos lo eliminaría de cualquier cálculo adicional, eso sería útil.
Ivo Flipse

En lugar de establecer a cero, puede calcular una función gaussiana con parámetros seleccionados a mano y restar los valores encontrados de las lecturas de presión originales. Entonces, si el dedo del pie está presionando sus sensores, al encontrar el punto de presión más alto, lo usa para disminuir el efecto de ese dedo sobre los sensores, eliminando así las celdas vecinas con valores de presión altos. en.wikipedia.org/wiki/File:Gaussian_2d.png
Daniyar

¿Le gustaría mostrar un ejemplo basado en mis datos de muestra @Daniyar? Como realmente no estoy familiarizado con este tipo de procesamiento de datos
Ivo Flipse

0

No estoy seguro de que esto responda la pregunta, pero parece que puedes 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

Voy a intentar echar un vistazo y ver si puedo convertirlo al código Python :-)
Ivo Flipse

Incluya el código en la publicación misma, en lugar de vincularlo a una esencia, si es una longitud razonable.
agf
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.