Come ordinare le mie zampe?


121

Nella mia domanda precedente ho ottenuto un'ottima risposta che mi ha aiutato a rilevare dove una zampa ha colpito una piastra di pressione, ma ora sto lottando per collegare questi risultati alle zampe corrispondenti:

testo alternativo

Ho annotato manualmente le zampe (RF = anteriore destro, RH = posteriore destro, LF = anteriore sinistro, LH = posteriore sinistro).

Come puoi vedere c'è chiaramente uno schema ripetuto e ritorna in quasi tutte le misurazioni. Ecco un collegamento a una presentazione di 6 prove che sono state annotate manualmente.

Il mio pensiero iniziale era quello di utilizzare l'euristica per eseguire l'ordinamento, come:

  • C'è un rapporto di ~ 60-40% nel carico di peso tra le zampe anteriori e posteriori;
  • Le zampe posteriori sono generalmente più piccole in superficie;
  • Le zampe sono (spesso) divise spazialmente in sinistra e destra.

Tuttavia, sono un po 'scettico riguardo alla mia euristica, poiché fallirebbero su di me non appena incontro una variazione che non avevo pensato. Inoltre, non saranno in grado di far fronte alle misurazioni di cani zoppi, che probabilmente hanno regole proprie.

Inoltre, l'annotazione suggerita da Joe a volte è incasinata e non tiene conto di come appare effettivamente la zampa.

Sulla base delle risposte che ho ricevuto alla mia domanda sul rilevamento del picco all'interno della zampa , spero che ci siano soluzioni più avanzate per ordinare le zampe. Soprattutto perché la distribuzione della pressione e la sua progressione sono diverse per ogni singola zampa, quasi come un'impronta digitale. Spero che ci sia un metodo che possa usarlo per raggruppare le mie zampe, piuttosto che ordinarle semplicemente in ordine di occorrenza.

testo alternativo

Quindi sto cercando un modo migliore per ordinare i risultati con la zampa corrispondente.

Per chiunque sia all'altezza della sfida, ho messo in salamoia un dizionario con tutti gli array affettati che contengono i dati di pressione di ciascuna zampa (raggruppati per misurazione) e la fetta che descrive la loro posizione (posizione sul piatto e nel tempo).

Per chiarire: walk_sliced_data è un dizionario che contiene ["ser_3", "ser_2", "sel_1", "sel_2", "ser_1", "sel_3"], che sono i nomi delle misurazioni. Ogni misurazione contiene un altro dizionario, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] (esempio da "sel_1") che rappresenta gli impatti che sono stati estratti.

Si noti inoltre che gli impatti "falsi", come il punto in cui la zampa è parzialmente misurata (nello spazio o nel tempo), possono essere ignorati. Sono utili solo perché possono aiutare a riconoscere uno schema, ma non verranno analizzati.

E per chi fosse interessato, tengo un blog con tutti gli aggiornamenti riguardanti il ​​progetto!


1
Sì, l'approccio che stavo usando non funziona del tutto. Tanto per elaborare, l'approccio che stavo usando è quello di ordinare solo gli impatti e presumere che la prima zampa da toccare sia la stessa della quinta zampa da toccare, e così via. (cioè ordinare gli impatti e utilizzare un modulo 4). Il problema con questo è che a volte le zampe posteriori colpiscono il pad del sensore dopo che la prima zampa tocca terra. In tal caso, la prima zampa che impatta corrisponde alla 4a o 3a zampa all'impatto. Si spera che questo abbia un senso.
Joe Kington

1
Interpreterei correttamente le immagini in quanto un dito di ciascun piede posteriore esercita una pressione significativamente inferiore rispetto al resto? Sembra anche che la punta sia sempre verso "l'interno", cioè verso il centro di massa del cane. Potresti incorporarlo come euristico?
Thomas Langston

1
Ammetto che le mie limitate capacità di elaborazione delle immagini sono in qualche modo arrugginite, ma è facilmente possibile prendere la pendenza meno ripida del grande cuscinetto centrale di ciascuna zampa? Sembra che l'angolo di minore pendenza aiuterebbe immensamente (un esempio disegnato a mano per le zampe pubblicate: imgur.com/y2wBC imgur.com/yVqVU imgur.com/yehOc imgur.com/q0tcD )
user470379

Potrebbe gentilmente chiarire come walk_sliced_datasono strutturati i dati in ? Vedo un dizionario di dizionari di array 3D. Se aggiusto la terza dimensione e traccia le prime due come un'immagine, penso di vedere le zampe.
Steve Tjoa

@Thomas, sì, ogni zampa è chiaramente caricata in modo distinto. So cosa vorrei che facesse il programma, ma non ho idea di come programmarlo ... @Steve, ho aggiunto dei chiarimenti in fondo :-)
Ivo Flipse

Risposte:


123

Tutto a posto! Sono finalmente riuscito a far funzionare qualcosa in modo coerente! Questo problema mi ha coinvolto per diversi giorni ... Roba divertente! Ci scusiamo per la lunghezza di questa risposta, ma ho bisogno di elaborare un po 'su alcune cose ... (Anche se potrei stabilire un record per la più lunga risposta di stackoverflow non spam mai!)

Come nota a margine, sto usando il set di dati completo di Ivo fornito un collegamento nella sua domanda originale . È una serie di file rar (uno per cane), ciascuno contenente diverse esecuzioni di esperimenti archiviate come array ASCII. Piuttosto che provare a copiare e incollare esempi di codice autonomo in questa domanda, ecco un repository mercurial bitbucket con codice completo e autonomo. Puoi clonarlo con

hg clone https://joferkington@bitbucket.org/joferkington/paw-analysis


Panoramica

Esistono essenzialmente due modi per affrontare il problema, come hai notato nella tua domanda. In realtà userò entrambi in modi diversi.

  1. Usa l'ordine (temporale e spaziale) degli impatti della zampa per determinare quale zampa è quale.
  2. Cerca di identificare "l'impronta della zampa" basandoti esclusivamente sulla sua forma.

Fondamentalmente, il primo metodo funziona con le zampe del cane seguendo lo schema trapezoidale mostrato nella domanda di Ivo sopra, ma fallisce ogni volta che le zampe non seguono quello schema. È abbastanza facile rilevare programmaticamente quando non funziona.

Pertanto, possiamo utilizzare le misurazioni in cui ha funzionato per costruire un set di dati di allenamento (di ~ 2000 impatti sulla zampa di ~ 30 cani diversi) per riconoscere quale zampa è quale, e il problema si riduce a una classificazione supervisionata (con alcune rughe aggiuntive. .. Il riconoscimento delle immagini è un po 'più difficile di un "normale" problema di classificazione supervisionato).


Analisi dei modelli

Per elaborare il primo metodo, quando un cane cammina (non corre!) Normalmente (cosa che alcuni di questi cani potrebbero non essere), ci aspettiamo che le zampe abbiano un impatto nell'ordine di: Anteriore sinistro, Posteriore destro, Anteriore destro, Posteriore sinistro , Anteriore sinistra, ecc. Il disegno può iniziare con la zampa anteriore sinistra o anteriore destra.

Se fosse sempre così, potremmo semplicemente ordinare gli impatti in base al tempo di contatto iniziale e utilizzare un modulo 4 per raggrupparli per zampa.

Sequenza di impatto normale

Tuttavia, anche quando tutto è "normale", questo non funziona. Ciò è dovuto alla forma trapezoidale del motivo. Una zampa posteriore cade spazialmente dietro la zampa anteriore precedente.

Pertanto, l'impatto della zampa posteriore dopo l'impatto iniziale della zampa anteriore spesso cade dalla piastra del sensore e non viene registrato. Allo stesso modo, l'ultimo impatto della zampa spesso non è la zampa successiva nella sequenza, poiché l'impatto della zampa prima che si verificasse fuori dalla piastra del sensore e non fosse registrato.

Zampa posteriore persa

Tuttavia, possiamo usare la forma del modello di impatto della zampa per determinare quando è successo e se abbiamo iniziato con una zampa anteriore sinistra o destra. (In realtà sto ignorando i problemi con l'ultimo impatto qui. Non è troppo difficile aggiungerlo, però.)

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

Nonostante tutto ciò, spesso non funziona correttamente. Molti dei cani nel set di dati completo sembrano correre e gli impatti della zampa non seguono lo stesso ordine temporale di quando il cane sta camminando. (O forse il cane ha solo gravi problemi all'anca ...)

Sequenza di impatto anormale

Fortunatamente, possiamo ancora rilevare programmaticamente se gli impatti della zampa seguono o meno il nostro modello spaziale previsto:

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

Pertanto, anche se la semplice classificazione spaziale non funziona sempre, possiamo determinare quando funziona con ragionevole sicurezza.

Set di dati di formazione

Dalle classificazioni basate su schemi in cui ha funzionato correttamente, possiamo costruire un set di dati di allenamento molto ampio di zampe classificate correttamente (~ 2400 impatti delle zampe da 32 cani diversi!).

Ora possiamo iniziare a vedere che aspetto ha una zampa anteriore sinistra "media", ecc.

Per fare questo, abbiamo bisogno di una sorta di "metrica della zampa" che abbia la stessa dimensionalità per qualsiasi cane. (Nel set di dati completo, ci sono sia cani molto grandi che molto piccoli!) Un'impronta della zampa di un elkhound irlandese sarà sia molto più larga che molto più "pesante" di un'impronta della zampa di un barboncino giocattolo. Dobbiamo riscalare ogni impronta in modo che a) abbiano lo stesso numero di pixel eb) i valori di pressione siano standardizzati. Per fare ciò, ho ricampionato ciascuna impronta della zampa su una griglia 20x20 e ho riscalato i valori di pressione in base al valore massimo, minimo e medio per l'impatto della zampa.

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

Dopo tutto questo, possiamo finalmente dare un'occhiata a come appare una zampa media anteriore sinistra, posteriore destra, ecc. Si noti che questa viene calcolata in media su> 30 cani di taglie molto diverse e sembra che stiamo ottenendo risultati coerenti!

Zampe medie

Tuttavia, prima di fare qualsiasi analisi su questi, dobbiamo sottrarre la media (la zampa media per tutte le gambe di tutti i cani).

Zampa media

Ora possiamo analizzare le differenze dalla media, che sono un po 'più facili da riconoscere:

Zampe differenziali

Riconoscimento della zampa basato su immagini

Ok ... Finalmente abbiamo una serie di schemi con cui possiamo iniziare a provare ad abbinare le zampe. Ogni zampa può essere trattata come un vettore a 400 dimensioni (restituito dalpaw_image funzione) che può essere confrontato con questi quattro vettori a 400 dimensioni.

Sfortunatamente, se usiamo solo un algoritmo di classificazione "normale" supervisionato (cioè troviamo quale dei 4 modelli è più vicino a una particolare impronta usando una semplice distanza), non funziona in modo coerente. In effetti, non fa molto meglio delle possibilità casuali sul set di dati di addestramento.

Questo è un problema comune nel riconoscimento delle immagini. A causa dell'elevata dimensionalità dei dati di input e della natura un po '"sfocata" delle immagini (cioè i pixel adiacenti hanno un'elevata covarianza), il semplice esame della differenza di un'immagine da un'immagine modello non fornisce una misura molto buona della somiglianza delle loro forme.

Eigenpaws

Per aggirare questo problema dobbiamo costruire una serie di "autovetture" (proprio come le "autovetture" nel riconoscimento facciale), e descrivere ogni impronta della zampa come una combinazione di queste autovetture. Questo è identico all'analisi delle componenti principali e fondamentalmente fornisce un modo per ridurre la dimensionalità dei nostri dati, in modo che la distanza sia una buona misura della forma.

Poiché abbiamo più immagini di addestramento che dimensioni (2400 contro 400), non c'è bisogno di fare algebra lineare "fantasia" per la velocità. Possiamo lavorare direttamente con la matrice di covarianza del set di dati di addestramento:

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

Questi basis_vecssono gli "autovelox".

Eigenpaws

Per usarli, semplicemente punteggiamo (cioè moltiplicazione di matrici) ogni immagine della zampa (come un vettore a 400 dimensioni, piuttosto che un'immagine 20x20) con i vettori di base. Questo ci dà un vettore a 50 dimensioni (un elemento per vettore base) che possiamo usare per classificare l'immagine. Invece di confrontare un'immagine 20x20 con l'immagine 20x20 di ciascuna zampa "modello", confrontiamo l'immagine trasformata a 50 dimensioni con ciascuna zampa modello trasformata a 50 dimensioni. Questo è molto meno sensibile alle piccole variazioni nel modo esatto in cui ogni dito è posizionato, ecc., E sostanzialmente riduce la dimensionalità del problema solo alle dimensioni rilevanti.

Classificazione della zampa basata su Eigenpaw

Ora possiamo semplicemente usare la distanza tra i vettori a 50 dimensioni e i vettori "modello" per ciascuna gamba per classificare quale zampa è quale:

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()]

Ecco alcuni dei risultati: testo alternativo testo alternativo testo alternativo

Problemi rimanenti

Ci sono ancora alcuni problemi, in particolare con i cani troppo piccoli per fare un'impronta chiara ... (Funziona meglio con i cani di grossa taglia, poiché le dita dei piedi sono più chiaramente separate alla risoluzione del sensore). Inoltre, le impronte parziali non vengono riconosciute con questo , mentre possono essere con il sistema basato su modelli trapezoidali.

Tuttavia, poiché l'analisi autofiorente utilizza intrinsecamente una metrica della distanza, possiamo classificare le zampe in entrambi i modi e ricorrere al sistema basato su schemi trapezoidali quando la distanza minima dell'analisi autofocus dal "libro dei codici" supera una certa soglia. Tuttavia, non l'ho ancora implementato.

Uff ... è stato lungo! Tanto di cappello a Ivo per aver fatto una domanda così divertente!


2
Bella risposta. Ho provato anche il metodo eigenpaw, ma non sono stato perseverante come te. Un problema che vedo è la registrazione della zampa, cioè la registrazione del viso è il riconoscimento facciale. Hai riscontrato problemi nel normalizzare la posizione e la rotazione di ciascuna zampa? Se è così, allora forse la zampa può essere preelaborata in qualche caratteristica invariante di rotazione di traslazione prima di eseguire la PCA.
Steve Tjoa

2
@Steve, non ho provato a ruotarli anche se ho discusso con Joe su come migliorarli ulteriormente. Tuttavia, per finire il mio progetto per ora, ho annotato manualmente tutte le zampe in modo da poterlo avvolgere. Fortunatamente questo ci permette anche di creare diversi training set per rendere il riconoscimento più sensibile. Per ruotare le zampe, stavo progettando di usare le dita dei piedi, ma come puoi leggere sul mio blog, non è così facile come la mia prima domanda ha fatto sembrare ...
Ivo Flipse

@Basic sì, sono passato all'hosting del mio sito Web e ho spostato tutto il contenuto di Wordpress, ma non ho più potuto modificare il mio commento qui. Dovresti riuscire a trovarli qui: flipserd.com/blog/ivoflipse/post/improving-the-paw-detection
Ivo Flipse

4

Utilizzando le informazioni basate esclusivamente sulla durata, penso che potresti applicare tecniche di modellazione cinematica; ovvero Cinematica inversa . Combinato con orientamento, lunghezza, durata e peso totale fornisce un certo livello di periodicità che, spero, possa essere il primo passo per cercare di risolvere il tuo problema di "smistamento delle zampe".

Tutti questi dati potrebbero essere usati per creare un elenco di poligoni limitati (o tuple), che potresti usare per ordinare per dimensione del passo e poi per paw-ness [indice].


2

Potete chiedere al tecnico che esegue il test di inserire manualmente la prima zampa (o le prime due)? Il processo potrebbe essere:

  • Mostra ai tecnici l'ordine dei passaggi e richiedi loro di annotare la prima zampa.
  • Etichetta le altre zampe in base alla prima e consenti al tecnico di apportare correzioni o rieseguire il test. Ciò consente cani zoppi oa 3 zampe.

In realtà ho annotazioni sulle prime zampe, anche se non sono impeccabili. Tuttavia, la prima zampa è sempre una zampa anteriore e non mi aiuterebbe a separare le zampe posteriori. Inoltre, l'ordinazione non è perfetta come ha detto Joe, perché richiede che entrambe le parti anteriori tocchino il piatto all'inizio.
Ivo Flipse

Le annotazioni sarebbero utili quando si utilizza il riconoscimento delle immagini, a causa delle 24 misurazioni che ho, almeno 24 zampe sarebbero già annotate. Se fossero quindi raggruppati in 4 gruppi, due di questi dovrebbero contenere una quantità ragionevole di una delle zampe anteriori sufficiente a rendere un algoritmo abbastanza certo del raggruppamento.
Ivo Flipse

A meno che non le legga in modo errato, le prove annotate collegate mostrano la zampa posteriore che si tocca per prima in 4 di 6 prove.
Jamie Ide

Ah, intendevo il tempo. Se esegui il loop della lima, la zampa anteriore dovrebbe sempre essere la prima a toccare la piastra.
Ivo Flipse
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.