skimage scikit recognition pil book array python image-processing

python - recognition - scikit-image



¿Cómo ordenar mis patas? (3)

¡Bien! ¡Finalmente logré que algo funcione de manera consistente! Este problema me atrapó por varios días ... ¡Cosas divertidas! Perdón por la longitud de esta respuesta, pero necesito explicar un poco sobre algunas cosas ... (¡Aunque puedo establecer un récord para la respuesta más larga sin spam de stackover nunca!)

Como nota al margen, estoy usando el conjunto de datos completo al que Ivo proporcionó un enlace en su pregunta original . Es una serie de archivos rar (uno por perro) que contienen varias ejecuciones de experimento diferentes almacenadas como matrices ascii. En lugar de intentar copiar y pegar ejemplos de código autónomo en esta pregunta, aquí hay un repositorio mercurial bitbucket con código completo e independiente. Puedes clonarlo con

hg clone https://[email protected]/joferkington/paw-analysis

Visión de conjunto

En esencia, hay dos formas de abordar el problema, como lo señaló en su pregunta. En realidad voy a usar ambos de diferentes maneras.

  1. Use el orden (temporal y espacial) de los impactos de la pata para determinar qué pata es cuál.
  2. Intente identificar la "huella de la pata" basada puramente en su forma.

Básicamente, el primer método que funciona con las patas del perro sigue el patrón trapezoidal que se muestra en la pregunta anterior de Ivo, pero falla siempre que las patas no siguen ese patrón. Es bastante fácil detectar mediante programación cuando no funciona.

Por lo tanto, podemos usar las medidas donde funcionó para construir un conjunto de datos de entrenamiento (de ~ 2000 impactos de pata de ~ 30 perros diferentes) para reconocer qué pata es cuál, y el problema se reduce a una clasificación supervisada (con algunas arrugas adicionales). .. El reconocimiento de imágenes es un poco más difícil que un problema de clasificación supervisada "normal").

Análisis de patrones

Para explicar el primer método, cuando un perro camina (¡no corriendo!) Normalmente (lo que algunos de estos perros pueden no ser), esperamos que las patas impacten en el orden de: Delantero izquierdo, trasero derecho, delantero derecho, trasero izquierdo , Delantero izquierdo, etc. El patrón puede comenzar con la pata frontal izquierda o delantera derecha.

Si este fuera siempre el caso, podríamos simplemente clasificar los impactos por tiempo de contacto inicial y usar un módulo 4 para agruparlos por pata.

Sin embargo, incluso cuando todo es "normal", esto no funciona. Esto se debe a la forma similar a un trapezoide del patrón. Una pata trasera cae espacialmente detrás de la pata delantera anterior.

Por lo tanto, el impacto de la pata trasera después del impacto inicial de la pata delantera a menudo cae de la placa del sensor y no se registra. De manera similar, el último impacto de la pata no suele ser la siguiente pata de la secuencia, ya que la pata impacta antes de que ocurra fuera de la placa del sensor y no se registró.

No obstante, podemos usar la forma del patrón de impacto de la pata para determinar cuándo sucedió esto y si hemos comenzado con una pata delantera izquierda o derecha. (De hecho, estoy ignorando los problemas con el último impacto aquí. No es muy difícil agregarlo).

def group_paws(data_slices, time): # Sort slices by initial contact time data_slices.sort(key=lambda s: s[-1].start) # Get the centroid for each paw impact... paw_coords = [] for x,y,z in data_slices: paw_coords.append([(item.stop + item.start) / 2.0 for item in (x,y)]) paw_coords = np.array(paw_coords) # Make a vector between each sucessive impact... dx, dy = np.diff(paw_coords, axis=0).T #-- Group paws ------------------------------------------- paw_code = {0:''LF'', 1:''RH'', 2:''RF'', 3:''LH''} paw_number = np.arange(len(paw_coords)) # Did we miss the hind paw impact after the first # front paw impact? If so, first dx will be positive... if dx[0] > 0: paw_number[1:] += 1 # Are we starting with the left or right front paw... # We assume we''re starting with the left, and check dy[0]. # If dy[0] > 0 (i.e. the next paw impacts to the left), then # it''s actually the right front paw, instead of the left. if dy[0] > 0: # Right front paw impact... paw_number += 2 # Now we can determine the paw with a simple modulo 4.. paw_codes = paw_number % 4 paw_labels = [paw_code[code] for code in paw_codes] return paw_labels

A pesar de todo esto, con frecuencia no funciona correctamente. Muchos de los perros en el conjunto de datos completo parecen estar funcionando, y los impactos de la pata no siguen el mismo orden temporal que cuando el perro está caminando. (O tal vez el perro solo tiene problemas graves de cadera ...)

Afortunadamente, todavía podemos detectar programáticamente si los impactos de la pata siguen o no nuestro patrón espacial esperado:

def paw_pattern_problems(paw_labels, dx, dy): """Check whether or not the label sequence "paw_labels" conforms to our expected spatial pattern of paw impacts. "paw_labels" should be a sequence of the strings: "LH", "RH", "LF", "RF" corresponding to the different paws""" # Check for problems... (This could be written a _lot_ more cleanly...) problems = False last = paw_labels[0] for paw, dy, dx in zip(paw_labels[1:], dy, dx): # Going from a left paw to a right, dy should be negative if last.startswith(''L'') and paw.startswith(''R'') and (dy > 0): problems = True break # Going from a right paw to a left, dy should be positive if last.startswith(''R'') and paw.startswith(''L'') and (dy < 0): problems = True break # Going from a front paw to a hind paw, dx should be negative if last.endswith(''F'') and paw.endswith(''H'') and (dx > 0): problems = True break # Going from a hind paw to a front paw, dx should be positive if last.endswith(''H'') and paw.endswith(''F'') and (dx < 0): problems = True break last = paw return problems

Por lo tanto, aunque la clasificación espacial simple no funciona todo el tiempo, podemos determinar cuándo funciona con una confianza razonable.

Dataset de entrenamiento

A partir de las clasificaciones basadas en patrones donde funcionó correctamente, podemos construir un conjunto de datos de entrenamiento muy grande de patas clasificadas correctamente (¡~ 2400 impactos de pata de 32 perros diferentes!).

Ahora podemos comenzar a ver qué aspecto tiene una frente "promedio", etc.

Para hacer esto, necesitamos algún tipo de "métrica de la pata" que sea la misma dimensionalidad para cualquier perro. (En el conjunto de datos completo, ¡hay perros muy grandes y muy pequeños!) Una huella de pata de un perro elfo irlandés será mucho más ancha y mucho más "pesada" que una huella de pata de un caniche de juguete. Necesitamos reescalar cada impresión de pata para que a) tengan el mismo número de píxeles, yb) los valores de presión estén estandarizados. Para hacer esto, volví a muestrear cada impresión de pata en una grilla de 20x20 y volví a escalar los valores de presión basados ​​en el valor máximo, mínimo y de presión media para el impacto de la pata.

def paw_image(paw): from scipy.ndimage import map_coordinates ny, nx = paw.shape # Trim off any "blank" edges around the paw... mask = paw > 0.01 * paw.max() y, x = np.mgrid[:ny, :nx] ymin, ymax = y[mask].min(), y[mask].max() xmin, xmax = x[mask].min(), x[mask].max() # Make a 20x20 grid to resample the paw pressure values onto numx, numy = 20, 20 xi = np.linspace(xmin, xmax, numx) yi = np.linspace(ymin, ymax, numy) xi, yi = np.meshgrid(xi, yi) # Resample the values onto the 20x20 grid coords = np.vstack([yi.flatten(), xi.flatten()]) zi = map_coordinates(paw, coords) zi = zi.reshape((numy, numx)) # Rescale the pressure values zi -= zi.min() zi /= zi.max() zi -= zi.mean() #<- Helps distinguish front from hind paws... return zi

Después de todo esto, finalmente podemos ver cómo luce una pata delantera izquierda, derecha, etc. Tenga en cuenta que esto se promedia en> 30 perros de tamaños muy diferentes, ¡y parece que estamos obteniendo resultados consistentes!

Sin embargo, antes de hacer un análisis de estos, debemos restar la media (la pata promedio para todas las patas de todos los perros).

Ahora podemos analizar las diferencias de la media, que son un poco más fáciles de reconocer:

Reconocimiento de pata basado en imágenes

Ok ... Finalmente tenemos un conjunto de patrones con los que podemos comenzar a tratar de hacer coincidir las patas. Cada pata se puede tratar como un vector de 400 dimensiones (devuelto por la función paw_image ) que se puede comparar con estos cuatro vectores de 400 dimensiones.

Desafortunadamente, si solo usamos un algoritmo de clasificación supervisado "normal" (es decir, buscamos cuál de los 4 patrones está más cerca de una huella en particular usando una distancia simple), no funciona de manera consistente. De hecho, no es mucho mejor que una oportunidad aleatoria en el conjunto de datos de entrenamiento.

Este es un problema común en el reconocimiento de imágenes. Debido a la alta dimensionalidad de los datos de entrada, y la naturaleza algo "difusa" de las imágenes (es decir, los píxeles adyacentes tienen una alta covarianza), simplemente mirando la diferencia de una imagen desde una plantilla no da una muy buena medida del similitud de sus formas.

Eigenpaws

Para evitar esto, necesitamos construir un conjunto de "eigenpaws" (al igual que "eigenfaces" en reconocimiento facial), y describir cada huella de pata como una combinación de estas eigenpaws. Esto es idéntico al análisis de componentes principales, y básicamente proporciona una forma de reducir la dimensionalidad de nuestros datos, de modo que la distancia es una buena medida de forma.

Debido a que tenemos más imágenes de entrenamiento que dimensiones (2400 vs 400), no hay necesidad de hacer álgebra lineal "de lujo" para la velocidad. Podemos trabajar directamente con la matriz de covarianza del conjunto de datos de entrenamiento:

def make_eigenpaws(paw_data): """Creates a set of eigenpaws based on paw_data. paw_data is a numdata by numdimensions matrix of all of the observations.""" average_paw = paw_data.mean(axis=0) paw_data -= average_paw # Determine the eigenvectors of the covariance matrix of the data cov = np.cov(paw_data.T) eigvals, eigvecs = np.linalg.eig(cov) # Sort the eigenvectors by ascending eigenvalue (largest is last) eig_idx = np.argsort(eigvals) sorted_eigvecs = eigvecs[:,eig_idx] sorted_eigvals = eigvals[:,eig_idx] # Now choose a cutoff number of eigenvectors to use # (50 seems to work well, but it''s arbirtrary... num_basis_vecs = 50 basis_vecs = sorted_eigvecs[:,-num_basis_vecs:] return basis_vecs

Estos basis_vecs son los "eigenpaws".

Para usar estos, simplemente punteamos (es decir, multiplicación de matrices) cada imagen de pata (como un vector de 400 dimensiones, en lugar de una imagen de 20x20) con los vectores de base. Esto nos da un vector de 50 dimensiones (un elemento por vector de base) que podemos usar para clasificar la imagen. En lugar de comparar una imagen de 20x20 con la imagen de 20x20 de cada pata de "plantilla", comparamos la imagen transformada de 50 dimensiones con cada pata de plantilla transformada de 50 dimensiones. Esto es mucho menos sensible a pequeñas variaciones en la forma exacta en que se posiciona cada dedo, etc., y básicamente reduce la dimensionalidad del problema a solo las dimensiones relevantes.

Clasificación de pata basada en Eigenpaw

Ahora podemos simplemente usar la distancia entre los vectores de 50 dimensiones y los vectores de "plantilla" para cada pierna para clasificar qué pata es cuál:

codebook = np.load(''codebook.npy'') # Template vectors for each paw average_paw = np.load(''average_paw.npy'') basis_stds = np.load(''basis_stds.npy'') # Needed to "whiten" the dataset... basis_vecs = np.load(''basis_vecs.npy'') paw_code = {0:''LF'', 1:''RH'', 2:''RF'', 3:''LH''} def classify(paw): paw = paw.flatten() paw -= average_paw scores = paw.dot(basis_vecs) / basis_stds diff = codebook - scores diff *= diff diff = np.sqrt(diff.sum(axis=1)) return paw_code[diff.argmin()]

Aquí están algunos de los resultados:

Problemas restantes

Todavía hay algunos problemas, particularmente con perros demasiado pequeños para hacer una huella clara ... (Funciona mejor con perros grandes, ya que los dedos se separan más claramente a la resolución del sensor). Además, las huellas parciales no se reconocen con este sistema, mientras que pueden estar con el sistema basado en patrones trapezoidales.

Sin embargo, dado que el análisis de eigenpaw utiliza una medida de distancia, podemos clasificar las patas en ambos sentidos y recurrir al sistema basado en patrones trapezoidales cuando la distancia más pequeña del análisis de eigenpaw al "libro de códigos" supera algún umbral. Sin embargo, aún no lo he implementado.

Uff ... ¡Eso fue largo! ¡No estoy de acuerdo con Ivo por tener una pregunta tan divertida!

En mi pregunta anterior recibí una respuesta excelente que me ayudó a detectar dónde golpeó una pata una placa de presión, pero ahora estoy luchando para vincular estos resultados con sus patas correspondientes:

Anoté manualmente las patas (RF = frontal derecho, RH = trasero derecho, LF = frontal izquierdo, LH = trasero izquierdo).

Como puede ver, hay claramente un patrón repetitivo y regresa en casi todas las mediciones. Aquí hay un enlace a una presentación de 6 ensayos que fueron anotados manualmente.

Mi pensamiento inicial fue utilizar la heurística para hacer la clasificación, como:

  • Hay una relación ~ 60-40% en peso entre las patas delanteras y traseras;
  • Las patas traseras son generalmente más pequeñas en superficie;
  • Las patas están (a menudo) espacialmente divididas en izquierda y derecha.

Sin embargo, soy un poco escéptico sobre mi heurística, ya que me fallarían tan pronto como encuentre una variación que no pensé. Tampoco podrán hacer frente a las mediciones de los perros cojos, que probablemente tengan reglas propias.

Además, la anotación sugerida por Joe a veces se confunde y no tiene en cuenta cómo es realmente la pata.

En base a las respuestas que recibí sobre mi pregunta sobre la detección de picos dentro de la pata , espero que haya soluciones más avanzadas para clasificar las patas. Especialmente porque la distribución de presión y la progresión de la misma son diferentes para cada pata separada, casi como una huella dactilar. Espero que haya un método que pueda usar esto para agrupar mis patas, en lugar de solo ordenarlas por orden de aparición.

Así que estoy buscando una mejor manera de ordenar los resultados con su pata correspondiente.

Para cualquiera que esté a la altura del desafío, seleccioné un diccionario con todas las matrices en corte que contienen los datos de presión de cada pata (agrupados por medición) y el corte que describe su ubicación (ubicación en el plato y en el tiempo).

Para clarificar: walk_sliced_data es un diccionario que contiene [''ser_3'', ''ser_2'', ''sel_1'', ''sel_2'', ''ser_1'', ''sel_3''], que son los nombres de las mediciones. Cada medida contiene otro diccionario, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] (ejemplo de ''sel_1'') que representan los impactos que se extrajeron.

También tenga en cuenta que se pueden ignorar los impactos "falsos", como dónde se mide parcialmente la pata (en espacio o tiempo). Solo son útiles porque pueden ayudar a reconocer un patrón, pero no serán analizados.

Y para cualquier persona interesada, ¡estoy guardando un blog con todas las actualizaciones relacionadas con el proyecto!


¿Puede hacer que el técnico que ejecuta la prueba ingrese manualmente la primera pata (o las dos primeras)? El proceso podría ser:

  • Muestre la tecnología del orden de los pasos de la imagen y solicite que anoten la primera pata.
  • Etiquete las otras patas según la primera pata y permita que el técnico haga correcciones o vuelva a ejecutar la prueba. Esto permite perros cojos o de 3 patas.

Usando la información puramente basada en la duración, creo que podría aplicar técnicas de modelado cinemático; a saber, cinemática inversa . Combinado con la orientación, la duración, la duración y el peso total, proporciona un cierto nivel de periodicidad que, espero, podría ser el primer paso para tratar de resolver el problema de la "clasificación de las patas".

Todos esos datos podrían usarse para crear una lista de polígonos (o tuplas) acotados, que podría usar para clasificar por tamaño de paso y luego por paw-ness [índice].