xticks barplot python image-processing

python - barplot - ¿Cómo puedo mejorar mi detección de pata?



pandas plot (3)

No soy un experto en detección de imágenes, y no conozco a Python, pero le daré un golpe ...

Para detectar patas individuales, primero debes seleccionar todo con una presión mayor que un pequeño umbral, muy cerca de ninguna presión en absoluto. Cada píxel / punto que está por encima de esto debe estar "marcado". Luego, cada píxel adyacente a todos los píxeles "marcados" se marca y este proceso se repite varias veces. Se formarán masas que están totalmente conectadas, por lo que tiene objetos distintos. Entonces, cada "objeto" tiene un valor mínimo y máximo de xey, por lo que los recuadros delimitadores pueden empaquetarse ordenadamente a su alrededor.

Pseudocódigo:

(MARK) ALL PIXELS ABOVE (0.5)

(MARK) ALL PIXELS (ADJACENT) TO (MARK) PIXELS

REPEAT (STEP 2) (5) TIMES

SEPARATE EACH TOTALLY CONNECTED MASS INTO A SINGLE OBJECT

MARK THE EDGES OF EACH OBJECT, AND CUT APART TO FORM SLICES.

Eso debería ser suficiente.

Después de mi pregunta anterior sobre la búsqueda de los dedos dentro de cada pata , comencé a cargar otras medidas para ver cómo se mantendría. Desafortunadamente, rápidamente encontré un problema con uno de los pasos anteriores: reconocer las patas.

Verá, mi prueba de concepto básicamente tomó la presión máxima de cada sensor con el tiempo y comenzaría a buscar la suma de cada fila, ¡hasta que encuentre eso! = 0.0. Luego hace lo mismo para las columnas y tan pronto como encuentra más de 2 filas con cero nuevamente. Almacena los valores mínimos y máximos de fila y columna en algún índice.

Como puede ver en la figura, esto funciona bastante bien en la mayoría de los casos. Sin embargo, hay muchas desventajas en este enfoque (aparte de ser muy primitivo):

  • Los humanos pueden tener ''pies huecos'' lo que significa que hay varias filas vacías dentro de la huella misma. Como temía que esto también pudiera pasar con perros (grandes), esperé al menos 2 o 3 hileras vacías antes de cortar la pata.

    Esto crea un problema si se crea otro contacto en una columna diferente antes de que llegue a varias filas vacías, expandiendo así el área. Me imagino que podría comparar las columnas y ver si exceden un cierto valor, deben ser patas separadas.

  • El problema empeora cuando el perro es muy pequeño o camina a un ritmo más alto. ¡Lo que sucede es que los dedos de los pies de la pata delantera siguen haciendo contacto, mientras que los dedos de los pies de la pata trasera comienzan a hacer contacto dentro de la misma área que la pata delantera!

    Con mi script simple, no podrá dividir estos dos, porque tendría que determinar qué marcos de esa área pertenecen a cada pata, mientras que actualmente solo tendría que mirar los valores máximos sobre todos los marcos.

Ejemplos de dónde comienza a ir mal:

Así que ahora estoy buscando una mejor forma de reconocer y separar las patas (¡después de lo cual abordaré el problema de decidir qué pata es!).

Actualizar:

He estado tratando de obtener la respuesta de Joe (¡increíble!) Implementada, pero estoy teniendo dificultades para extraer los datos de pata reales de mis archivos.

El codificado_paws me muestra todas las patas diferentes, cuando se aplica a la imagen de presión máxima (ver arriba). Sin embargo, la solución pasa por cada cuadro (para separar las patas superpuestas) y establece los cuatro atributos Rectangle, como coordenadas o alto / ancho.

No puedo entender cómo tomar estos atributos y almacenarlos en alguna variable que pueda aplicar a los datos de medición. Como necesito saber para cada pata, cuál es su ubicación durante la cual encuadra y acopla esto a qué pata es (adelante / atrás, izquierda / derecha).

Entonces, ¿cómo puedo usar los atributos de Rectángulos para extraer estos valores para cada pata?

Tengo las medidas que utilicé en la configuración de preguntas en mi carpeta pública de Dropbox ( ejemplo 1 , ejemplo 2 , ejemplo 3 ). Para cualquier persona interesada, también configuré un blog para mantenerte al día :-)


Si solo quieres regiones (semi) contiguas, ya hay una implementación fácil en Python: el módulo ndimage.morphology SciPy . Esta es una operación de morfología de imagen bastante común.

Básicamente, tienes 5 pasos:

def find_paws(data, smooth_radius=5, threshold=0.0001): data = sp.ndimage.uniform_filter(data, smooth_radius) thresh = data > threshold filled = sp.ndimage.morphology.binary_fill_holes(thresh) coded_paws, num_paws = sp.ndimage.label(filled) data_slices = sp.ndimage.find_objects(coded_paws) return object_slices

  1. Desenfoque los datos de entrada un poco para asegurarse de que las patas tengan una huella continua. (Sería más eficiente usar simplemente un kernel más grande (la structure kwarg de varias funciones scipy.ndimage.morphology ) pero esto no funciona del todo por alguna razón ...)

  2. Umbral de la matriz para que tenga una matriz booleana de lugares donde la presión supera algún valor umbral (es decir, umbral thresh = data > value )

  3. Rellena los agujeros internos para que tengas regiones más limpias ( filled = sp.ndimage.morphology.binary_fill_holes(thresh) )

  4. Encuentre las regiones contiguas separadas ( coded_paws, num_paws = sp.ndimage.label(filled) ). Esto devuelve una matriz con las regiones codificadas por número (cada región es un área contigua de un entero único (1 hasta el número de patas) con ceros en cualquier otro lugar)).

  5. Aísle las regiones contiguas utilizando data_slices = sp.ndimage.find_objects(coded_paws) . Esto devuelve una lista de tuplas de objetos de slice , por lo que podría obtener la región de los datos para cada pata con [data[x] for x in data_slices] . En su lugar, dibujaremos un rectángulo basado en estas divisiones, lo que requiere un poco más de trabajo.

Las dos animaciones siguientes muestran sus datos de ejemplo "Patas superpuestas" y "Patas agrupadas". Este método parece estar funcionando perfectamente. (Y para lo que sea, esto funciona mucho mejor que las imágenes GIF de abajo en mi máquina, por lo que el algoritmo de detección de pata es bastante rápido ...)

Aquí hay un ejemplo completo (ahora con explicaciones mucho más detalladas). La gran mayoría de esto es leer la entrada y hacer una animación. La detección real de la pata es solo 5 líneas de código.

import numpy as np import scipy as sp import scipy.ndimage import matplotlib.pyplot as plt from matplotlib.patches import Rectangle def animate(input_filename): """Detects paws and animates the position and raw data of each frame in the input file""" # With matplotlib, it''s much, much faster to just update the properties # of a display object than it is to create a new one, so we''ll just update # the data and position of the same objects throughout this animation... infile = paw_file(input_filename) # Since we''re making an animation with matplotlib, we need # ion() instead of show()... plt.ion() fig = plt.figure() ax = fig.add_subplot(111) fig.suptitle(input_filename) # Make an image based on the first frame that we''ll update later # (The first frame is never actually displayed) im = ax.imshow(infile.next()[1]) # Make 4 rectangles that we can later move to the position of each paw rects = [Rectangle((0,0), 1,1, fc=''none'', ec=''red'') for i in range(4)] [ax.add_patch(rect) for rect in rects] title = ax.set_title(''Time 0.0 ms'') # Process and display each frame for time, frame in infile: paw_slices = find_paws(frame) # Hide any rectangles that might be visible [rect.set_visible(False) for rect in rects] # Set the position and size of a rectangle for each paw and display it for slice, rect in zip(paw_slices, rects): dy, dx = slice rect.set_xy((dx.start, dy.start)) rect.set_width(dx.stop - dx.start + 1) rect.set_height(dy.stop - dy.start + 1) rect.set_visible(True) # Update the image data and title of the plot title.set_text(''Time %0.2f ms'' % time) im.set_data(frame) im.set_clim([frame.min(), frame.max()]) fig.canvas.draw() def find_paws(data, smooth_radius=5, threshold=0.0001): """Detects and isolates contiguous regions in the input array""" # Blur the input data a bit so the paws have a continous footprint data = sp.ndimage.uniform_filter(data, smooth_radius) # Threshold the blurred data (this needs to be a bit > 0 due to the blur) thresh = data > threshold # Fill any interior holes in the paws to get cleaner regions... filled = sp.ndimage.morphology.binary_fill_holes(thresh) # Label each contiguous paw coded_paws, num_paws = sp.ndimage.label(filled) # Isolate the extent of each paw data_slices = sp.ndimage.find_objects(coded_paws) return data_slices def paw_file(filename): """Returns a iterator that yields the time and data in each frame The infile is an ascii file of timesteps formatted similar to this: Frame 0 (0.00 ms) 0.0 0.0 0.0 0.0 0.0 0.0 Frame 1 (0.53 ms) 0.0 0.0 0.0 0.0 0.0 0.0 ... """ with open(filename) as infile: while True: try: time, data = read_frame(infile) yield time, data except StopIteration: break def read_frame(infile): """Reads a frame from the infile.""" frame_header = infile.next().strip().split() time = float(frame_header[-2][1:]) data = [] while True: line = infile.next().strip().split() if line == []: break data.append(line) return time, np.array(data, dtype=np.float) if __name__ == ''__main__'': animate(''Overlapping paws.bin'') animate(''Grouped up paws.bin'') animate(''Normal measurement.bin'')

Actualización: en cuanto a identificar qué pata está en contacto con el sensor en qué momentos, la solución más simple es hacer el mismo análisis, pero usar todos los datos a la vez. (es decir, apilar la entrada en una matriz 3D y trabajar con ella, en lugar de los marcos de tiempo individuales). Debido a que las funciones de nimagen de SciPy están pensadas para trabajar con matrices n-dimensionales, no tenemos que modificar la función original de búsqueda de la pata en absoluto.

# This uses functions (and imports) in the previous code example!! def paw_regions(infile): # Read in and stack all data together into a 3D array data, time = [], [] for t, frame in paw_file(infile): time.append(t) data.append(frame) data = np.dstack(data) time = np.asarray(time) # Find and label the paw impacts data_slices, coded_paws = find_paws(data, smooth_radius=4) # Sort by time of initial paw impact... This way we can determine which # paws are which relative to the first paw with a simple modulo 4. # (Assuming a 4-legged dog, where all 4 paws contacted the sensor) data_slices.sort(key=lambda dat_slice: dat_slice[2].start) # Plot up a simple analysis fig = plt.figure() ax1 = fig.add_subplot(2,1,1) annotate_paw_prints(time, data, data_slices, ax=ax1) ax2 = fig.add_subplot(2,1,2) plot_paw_impacts(time, data_slices, ax=ax2) fig.suptitle(infile) def plot_paw_impacts(time, data_slices, ax=None): if ax is None: ax = plt.gca() # Group impacts by paw... for i, dat_slice in enumerate(data_slices): dx, dy, dt = dat_slice paw = i%4 + 1 # Draw a bar over the time interval where each paw is in contact ax.barh(bottom=paw, width=time[dt].ptp(), height=0.2, left=time[dt].min(), align=''center'', color=''red'') ax.set_yticks(range(1, 5)) ax.set_yticklabels([''Paw 1'', ''Paw 2'', ''Paw 3'', ''Paw 4'']) ax.set_xlabel(''Time (ms) Since Beginning of Experiment'') ax.yaxis.grid(True) ax.set_title(''Periods of Paw Contact'') def annotate_paw_prints(time, data, data_slices, ax=None): if ax is None: ax = plt.gca() # Display all paw impacts (sum over time) ax.imshow(data.sum(axis=2).T) # Annotate each impact with which paw it is # (Relative to the first paw to hit the sensor) x, y = [], [] for i, region in enumerate(data_slices): dx, dy, dz = region # Get x,y center of slice... x0 = 0.5 * (dx.start + dx.stop) y0 = 0.5 * (dy.start + dy.stop) x.append(x0); y.append(y0) # Annotate the paw impacts ax.annotate(''Paw %i'' % (i%4 +1), (x0, y0), color=''red'', ha=''center'', va=''bottom'') # Plot line connecting paw impacts ax.plot(x,y, ''-wo'') ax.axis(''image'') ax.set_title(''Order of Steps'')


Nota: digo pixel, pero esto podría ser regiones usando un promedio de los píxeles. La optimización es otro problema ...

Parece que necesita analizar una función (presión a lo largo del tiempo) para cada píxel y determinar dónde gira la función (cuando cambia> X en la otra dirección, se considera un turno para contrarrestar los errores).

Si sabe en qué marcos gira, sabrá el marco donde la presión fue más dura y sabrá dónde fue el menos duro entre las dos patas. En teoría, entonces sabría los dos marcos donde las patas presionaron más fuerte y puede calcular un promedio de esos intervalos.

después de lo cual abordaré el problema de decidir qué pata es.

Este es el mismo recorrido que antes, saber cuándo se aplica cada pata la mayor presión te ayuda a decidir.