Rilevamento di picco in un array 2D


874

Sto aiutando una clinica veterinaria a misurare la pressione sotto una zampa di cane. Uso Python per la mia analisi dei dati e ora sono bloccato nel tentativo di dividere le zampe in sottoregioni (anatomiche).

Ho creato un array 2D per ogni zampa, che consiste dei valori massimi per ciascun sensore che è stato caricato dalla zampa nel tempo. Ecco un esempio di una zampa, in cui ho usato Excel per disegnare le aree che voglio "rilevare". Si tratta di scatole da 2 per 2 attorno al sensore con valori massimi locali, che insieme hanno la somma maggiore.

testo alternativo

Quindi ho provato alcuni esperimenti e ho deciso di cercare semplicemente i massimi di ogni colonna e riga (non posso guardare in una direzione a causa della forma della zampa). Questo sembra "rilevare" abbastanza bene la posizione delle dita separate, ma segna anche i sensori vicini.

testo alternativo

Quindi quale sarebbe il modo migliore per dire a Python quali di questi massimi sono quelli che voglio?

Nota: i quadrati 2x2 non possono sovrapporsi, poiché devono essere dita separate!

Inoltre ho preso 2x2 per comodità, qualsiasi soluzione più avanzata è benvenuta, ma sono semplicemente uno scienziato del movimento umano, quindi non sono né un vero programmatore o un matematico, quindi per favore mantienilo "semplice".

Ecco una versione che può essere caricatanp.loadtxt


risultati

Quindi ho provato la soluzione di @ jextee (vedere i risultati di seguito). Come puoi vedere, funziona molto bene sulle zampe anteriori, ma funziona meno bene per le zampe posteriori.

Più specificamente, non è in grado di riconoscere il piccolo picco che è il quarto dito. Ciò è ovviamente inerente al fatto che il ciclo guarda dall'alto verso il basso verso il valore più basso, senza tener conto di dove si trova.

Qualcuno saprebbe come modificare l'algoritmo di @exexee, in modo che possa trovare anche il 4o dito?

testo alternativo

Dal momento che non ho ancora elaborato altre prove, non posso fornire altri campioni. Ma i dati che ho dato prima erano le medie di ogni zampa. Questo file è un array con i dati massimi di 9 zampe nell'ordine in cui sono entrati in contatto con la piastra.

Questa immagine mostra come sono stati spazialmente spaziati sul piatto.

testo alternativo

Aggiornare:

Ho creato un blog per chiunque sia interessato e ho installato uno SkyDrive con tutte le misure grezze. Quindi, per chiunque richieda più dati: più potenza per te!


Nuovo aggiornamento:

Quindi, dopo l'aiuto che ho ricevuto con le mie domande sul rilevamento delle zampe e l' ordinamento delle zampe , sono stato finalmente in grado di controllare il rilevamento delle dita per ogni zampa! Si scopre che non funziona così bene in nient'altro che zampe di dimensioni simili a quella del mio esempio. Ovviamente col senno di poi, è colpa mia per aver scelto il 2x2 in modo arbitrario.

Ecco un bell'esempio di dove va storto: un chiodo viene riconosciuto come una punta e il "tallone" è così largo che viene riconosciuto due volte!

testo alternativo

La zampa è troppo grande, quindi prendere una dimensione 2x2 senza sovrapposizioni, fa rilevare due dita dei piedi. Al contrario, nei cani di piccola taglia spesso non riesce a trovare un 5 ° dito del piede, che sospetto sia causato dall'area di 2x2 troppo grande.

Dopo aver provato la soluzione attuale su tutte le mie misurazioni sono giunto alla sorprendente conclusione che per quasi tutti i miei cani di piccola taglia non ha trovato un 5o dito del piede e che in oltre il 50% degli impatti per i cani di grossa taglia ne avrebbe trovato di più!

Quindi chiaramente ho bisogno di cambiarlo. La mia ipotesi stava cambiando le dimensioni di neighborhoodqualcosa di più piccolo per i cani di piccola taglia e più grande per i cani di grossa taglia. Ma generate_binary_structurenon mi permetterebbe di modificare le dimensioni dell'array.

Pertanto, spero che qualcun altro abbia un suggerimento migliore per localizzare le dita dei piedi, forse avendo la scala dell'area delle dita con le dimensioni della zampa?


Presumo che le virgole siano decimali anziché separatori di valori?
Matt

Sì, sono virgole. E @Christian, sto provando a inserirlo in un file facilmente leggibile, ma anche questo non riesce a me :(
Ivo Flipse

3
Mentre sto facendo uno studio di fattibilità, tutto va davvero. Quindi sto cercando tanti modi per definire la pressione, comprese le sottoregioni. Inoltre, devo essere in grado di discriminare tra il lato dell'alluce e quello dell'alluce, al fine di stimare l'orientamento. Ma dal momento che non è stato fatto prima, non si può dire cosa potremmo trovare :-)
Ivo Flipse

2
@Ron: uno degli obiettivi di questo studio è vedere per quale taglia / peso dei cani è adatto il sistema, quindi sì mentre questo cane era di circa 20 kg. Ne ho alcuni che sono considerevolmente più piccoli (e più grandi) e mi aspetto che non sarò in grado di fare lo stesso per quelli veri piccoli.
Ivo Flipse,

2
@frank le zampe vengono misurate nel tempo, quindi la 3a dimensione. Tuttavia, non si spostano dal loro punto (relativamente parlando), quindi sono principalmente interessato a dove si trovano le dita dei piedi in 2D. Dopo di che, l'aspetto 3D è gratuito
Ivo Flipse,

Risposte:


332

Ho rilevato i picchi utilizzando un filtro massimo locale . Ecco il risultato sul tuo primo set di dati di 4 zampe: Risultato del rilevamento dei picchi

L'ho anche eseguito sul secondo set di dati di 9 zampe e ha funzionato pure .

Ecco come lo fai:

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

Tutto quello che devi fare dopo è usare scipy.ndimage.measurements.labella maschera per etichettare tutti gli oggetti distinti. Quindi sarai in grado di giocare con loro individualmente.

Si noti che il metodo funziona bene perché lo sfondo non è rumoroso. Se lo fosse, rileveresti un mucchio di altre cime indesiderate sullo sfondo. Un altro fattore importante è la dimensione del quartiere . Dovrai regolarlo se cambiano le dimensioni del picco (dovrebbe rimanere approssimativamente proporzionale).


1
Esiste una soluzione più semplice di (eroded_background ^ local_peaks). Fai (picchi in primo piano e locali)
Ryan Soklaski il

53

Soluzione

File di dati: paw.txt . Codice sorgente:

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"

Uscita senza quadrati sovrapposti. Sembra che siano state selezionate le stesse aree del tuo esempio.

Alcuni commenti

La parte difficile è calcolare le somme di tutti i quadrati 2x2. Ho pensato che avessi bisogno di tutti loro, quindi potrebbero esserci delle sovrapposizioni. Ho usato le sezioni per tagliare le prime / ultime colonne e righe dall'array 2D originale, quindi sovrapporle tutte insieme e calcolare le somme.

Per capirlo meglio, imaging di un array 3x3:

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

Quindi puoi prendere le sue sezioni:

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

Ora immagina di impilarli uno sopra l'altro e di sommare gli elementi nelle stesse posizioni. Queste somme saranno esattamente le stesse somme sui quadrati 2x2 con l'angolo in alto a sinistra nella stessa posizione:

>>> sums = a[:-1,:-1] + a[1:,:-1] + a[:-1,1:] + a[1:,1:]; sums
array([[ 8, 12],
       [20, 24]])

Quando si hanno somme superiori a 2x2 quadrati, è possibile utilizzare maxper trovare il massimo, oppure sortoppure sortedper trovare i picchi.

Per ricordare le posizioni dei picchi ho accoppiato ogni valore (la somma) con la sua posizione ordinale in un array appiattito (vedi zip). Quindi calcolo nuovamente la posizione di riga / colonna quando stampo i risultati.

Appunti

Ho permesso la sovrapposizione dei quadrati 2x2. La versione modificata ne filtra alcune in modo tale che nei risultati compaiano solo quadrati non sovrapposti.

La scelta delle dita (un'idea)

Un altro problema è come scegliere quali sono le dita da tutte le vette. Ho un'idea che potrebbe funzionare o meno. Non ho tempo per implementarlo in questo momento, quindi solo pseudo-codice.

Ho notato che se le dita anteriori rimangono su un cerchio quasi perfetto, il dito posteriore dovrebbe trovarsi all'interno di quel cerchio. Inoltre, le dita anteriori sono più o meno equidistanti. Potremmo provare a usare queste proprietà euristiche per rilevare le dita.

Pseudo codice:

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

Questo è un approccio a forza bruta. Se N è relativamente piccolo, allora penso che sia fattibile. Per N = 12, ci sono combinazioni C_12 ^ 5 = 792, volte 5 modi per selezionare un dito posteriore, quindi 3960 casi da valutare per ogni zampa.


Dovrà filtrare manualmente le zampe, dato il tuo elenco di risultati ... la selezione dei quattro risultati più alti gli darà le quattro possibilità di costruire un quadrato 2x2 contenente il valore massimo 6,8
Johannes Charra

Le caselle 2x2 non possono sovrapporsi, poiché se voglio fare statistiche, non voglio usare la stessa regione, voglio confrontare le regioni :-)
Ivo Flipse

Ho modificato la risposta. Ora non ci sono quadrati sovrapposti nei risultati.
sastanin,

1
L'ho provato e sembra funzionare per le zampe anteriori, ma meno per quelle posteriori. Immagino che dovremo provare qualcosa che sa dove cercare
Ivo Flipse,

1
Ho spiegato la mia idea di come le dita possano essere rilevate nello pseudo codice. Se ti piace, potrei provare a implementarlo domani sera.
sastanin,

34

Questo è un problema di registrazione dell'immagine . La strategia generale è:

  • Avere un esempio noto o qualche tipo di precedente sui dati.
  • Adatta i tuoi dati all'esempio o adatta l'esempio ai tuoi dati.
  • Aiuta se i tuoi dati sono allineati approssimativamente in primo luogo.

Ecco un approccio approssimativo e pronto , "la cosa più stupida che potrebbe funzionare":

  • Inizia con cinque coordinate di punta in circa il luogo che ti aspetti.
  • Con ognuno, sali ripetutamente sulla cima della collina. cioè data la posizione corrente, passa al massimo pixel adiacente, se il suo valore è maggiore del pixel corrente. Fermati quando le coordinate delle dita dei piedi hanno smesso di muoversi.

Per contrastare il problema dell'orientamento, potresti avere circa 8 impostazioni iniziali per le direzioni di base (Nord, Nord Est, ecc.). Esegui ognuno singolarmente e getta via tutti i risultati in cui due o più dita dei piedi finiscono con lo stesso pixel. Ci penserò ancora un po ', ma questo genere di cose è ancora oggetto di ricerca nell'elaborazione delle immagini: non ci sono risposte giuste!

Idea leggermente più complessa: K (ponderato) significa clustering. Non è così male.

  • Inizia con cinque coordinate di punta, ma ora si tratta di "centri di cluster".

Quindi scorrere fino alla convergenza:

  • Assegna ogni pixel al cluster più vicino (basta creare un elenco per ciascun cluster).
  • Calcola il centro di massa di ciascun cluster. Per ogni cluster, questo è: Somma (coordinata * valore di intensità) / Somma (coordinata)
  • Spostare ciascun cluster nel nuovo centro di massa.

Questo metodo darà quasi sicuramente risultati molto migliori e otterrai la massa di ciascun cluster che potrebbe aiutare a identificare le dita dei piedi.

(Ancora una volta, hai specificato il numero di cluster in anticipo. Con il clustering devi specificare la densità in un modo o nell'altro: scegli il numero di cluster, appropriato in questo caso, oppure scegli un raggio del cluster e vedi quanti ne finisci con. Un esempio di quest'ultimo è il cambiamento di media .)

Ci scusiamo per la mancanza di dettagli di implementazione o altri dettagli. Vorrei codificarlo, ma ho una scadenza. Se nient'altro ha funzionato entro la prossima settimana fammi sapere e ci proverò.


1
Il problema è che le zampe cambiano il loro orientamento e non ho nessuna calibrazione / linea di base di una zampa corretta per cominciare. Inoltre temo che molti algoritmi di riconoscimento delle immagini siano un po 'fuori dalla mia portata.
Ivo Flipse,

L'approccio "grezzo e pronto" è piuttosto semplice - forse non ho avuto l'idea bene. Inserirò alcuni pseudocodici per illustrare.
CakeMaster,

Ho la sensazione che il tuo suggerimento ti aiuterà a correggere il riconoscimento delle zampe posteriori, non so "come"
Ivo Flipse,

Ho aggiunto un'altra idea. A proposito, se hai un sacco di buoni dati, sarebbe bello metterli online da qualche parte. Potrebbe essere utile per le persone che studiano l'elaborazione delle immagini / apprendimento automatico e potresti ottenere un po 'più di codice da esso ...
CakeMaster,

1
Stavo solo pensando di scrivere il mio trattamento dei dati su un semplice blog Wordpress, semplicemente per essere utile ad altri e devo scriverlo comunque. Mi piacciono tutti i tuoi suggerimenti, ma temo che dovrò aspettare qualcuno senza una scadenza ;-)
Ivo Flipse

18

Utilizzando l'omologia persistente per analizzare il tuo set di dati ottengo il seguente risultato (clicca per ingrandire):

Risultato

Questa è la versione 2D del metodo di rilevamento dei picchi descritto in questa risposta SO . La figura sopra mostra semplicemente le classi di omologia persistente a dimensione 0 ordinate per persistenza.

Ho fatto l'upscaling del set di dati originale di un fattore 2 usando scipy.misc.imresize (). Tuttavia, si noti che ho considerato le quattro zampe come un unico set di dati; dividerlo in quattro renderebbe il problema più semplice.

Metodologia. L'idea alla base di questo è abbastanza semplice: considera il grafico della funzione che assegna a ciascun pixel il suo livello. Sembra così:

Grafico delle funzioni 3D

Consideriamo ora un livello dell'acqua ad altezza 255 che scende continuamente a livelli inferiori. Alle isole locali dei massimi pop-up (nascita). Ai punti della sella si fondono due isole; riteniamo che l'isola inferiore sia unita all'isola superiore (morte). Il cosiddetto diagramma di persistenza (delle classi di omologia tridimensionale, le nostre isole) raffigura i valori di morte su nascita di tutte le isole:

Diagramma di persistenza

La persistenza di un'isola è quindi la differenza tra il livello di nascita e di morte; la distanza verticale di un punto dalla diagonale principale grigia. La figura identifica le isole diminuendo la persistenza.

La primissima immagine mostra i luoghi di nascita delle isole. Questo metodo non solo fornisce i massimi locali ma quantifica anche il loro "significato" in base alla persistenza sopra menzionata. Si filtrerebbero quindi tutte le isole con una persistenza troppo bassa. Tuttavia, nel tuo esempio ogni isola (cioè ogni massimo locale) è un picco che cerchi.

Il codice Python può essere trovato qui .


16

Questo problema è stato studiato in modo approfondito dai fisici. C'è una buona implementazione in ROOT . Guarda le classi TSpectrum (in particolare TSpectrum2 per il tuo caso) e la documentazione per esse.

Riferimenti:

  1. M.Morhac et al .: metodi di eliminazione dello sfondo per spettri di raggi gamma a coincidenza multidimensionale. Strumenti e metodi nucleari nella ricerca di fisica A 401 (1997) 113-132.
  2. M.Morhac et al .: efficiente deconvoluzione dell'oro monodimensionale e bidimensionale e sua applicazione alla decomposizione degli spettri di raggi gamma. Strumenti e metodi nucleari nella ricerca di fisica A 401 (1997) 385-408.
  3. M.Morhac et al .: Identificazione di picchi negli spettri di raggi gamma per coincidenza multidimensionale. Strumenti e metodi nucleari nella fisica della ricerca A 443 (2000), 108-125.

... e per coloro che non hanno accesso a un abbonamento a NIM:


Per dare un'occhiata all'articolo sembra che descriva lo stesso trattamento dei dati di quello che sto provando qui, tuttavia temo che abbia superato di gran lunga le mie capacità di programmazione :(
Ivo Flipse

@Ivo: non ho mai provato a implementarlo da solo. Uso solo ROOT. Esistono comunque dei collegamenti Python, ma tieni presente che ROOT è un pacchetto piuttosto pesante.
dmckee --- ex gattino moderatore

@Ivo Flipse: sono d'accordo con dmckee. Hai molti contatti promettenti in altre risposte. Se falliscono tutti e hai voglia di investire un po 'di tempo, puoi approfondire ROOT e (probabilmente) farà ciò di cui hai bisogno. Non ho mai conosciuto nessuno che abbia cercato di imparare ROOT tramite i binding di Python (piuttosto che è un C ++ naturale), quindi ti auguro buona fortuna.
physicsmichael,

13

Ecco un'idea: si calcola il Laplaciano (discreto) dell'immagine. Mi aspetto che sia (negativo e) grande ai massimi, in un modo più drammatico che nelle immagini originali. Pertanto, maxima potrebbe essere più facile da trovare.

Ecco un'altra idea: se conosci la dimensione tipica dei punti ad alta pressione, puoi prima attenuare l'immagine contorcendola con un gaussiano della stessa dimensione. Questo potrebbe darti immagini più semplici da elaborare.


11

Solo un paio di idee sulla cima della mia testa:

  • prendere il gradiente (derivato) della scansione, vedere se questo elimina le false chiamate
  • prendere il massimo dei massimi locali

Potresti anche dare un'occhiata a OpenCV , ha un'API Python abbastanza decente e potrebbe avere alcune funzioni che potresti trovare utili.


Con pendenza, vuoi dire che dovrei calcolare la pendenza delle pendenze, una volta che questo supera un certo valore, so che c'è un "picco"? Ho provato questo, ma alcune dita dei piedi hanno solo picchi molto bassi (1,2 N / cm) rispetto ad alcune delle altre (8 N / cm). Quindi, come dovrei gestire i picchi con una pendenza molto bassa?
Ivo Flipse,

2
Ciò che ha funzionato per me in passato se non potevo usare direttamente il gradiente era guardare il gradiente e i massimi, ad esempio se il gradiente è un extrema locale e sono a un massimo locale, allora sono in un punto di interesse.
ChrisC,

11

Sono sicuro che hai abbastanza per continuare, ma non posso fare a meno di suggerire di utilizzare il metodo di clustering k-means. k-means è un algoritmo di clustering senza supervisione che ti porterà i dati (in qualsiasi numero di dimensioni - mi capita di farlo in 3D) e li organizza in k cluster con confini distinti. È bello qui perché sai esattamente quante dita di questi cani (dovrebbero) avere.

Inoltre, è implementato in Scipy, il che è davvero carino ( http://docs.scipy.org/doc/scipy/reference/cluster.vq.html ).

Ecco un esempio di cosa può fare per risolvere spazialmente i cluster 3D: inserisci qui la descrizione dell'immagine

Quello che vuoi fare è un po 'diverso (2D e include valori di pressione), ma penso ancora che potresti provarlo.


10

grazie per i dati grezzi. Sono sul treno e questo è quanto ho ottenuto (la mia fermata sta salendo). Ho massaggiato il tuo file txt con regexps e l'ho inserito in una pagina html con alcuni javascript per la visualizzazione. Lo sto condividendo qui perché alcuni, come me, potrebbero trovarlo più facilmente hackerabile di Python.

Penso che un buon approccio sarà la scala e la rotazione invariante, e il mio prossimo passo sarà studiare le miscele di gaussiani. (ogni pedana è il centro di 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>

testo alternativo


1
Suppongo che questa sia una prova del concetto che le tecniche gaussiane raccomandate potrebbero funzionare, ora se solo qualcuno potesse dimostrarlo con Python ;-)
Ivo Flipse,

8

Soluzione del fisico:
definire 5 marcatori di zampa identificati dalle loro posizioni X_ie iniziarli con posizioni casuali. Definire alcune funzioni energetiche combinando un premio per la posizione dei marker nelle posizioni delle zampe con una punizione per la sovrapposizione dei marker; diciamo:

E(X_i;S)=-Sum_i(S(X_i))+alfa*Sum_ij (|X_i-Xj|<=2*sqrt(2)?1:0)

( S(X_i)è la forza media in 2x2 quadrati intorno X_i, alfaè un parametro che deve essere raggiunto il picco sperimentalmente)

Ora è il momento di fare un po 'di magia su Metropolis-Hastings:
1. Seleziona un marcatore casuale e spostalo di un pixel in direzione casuale.
2. Calcola dE, la differenza di energia causata da questa mossa.
3. Ottieni un numero casuale uniforme da 0-1 e chiamalo r.
4. Se dE<0o exp(-beta*dE)>r, accetta lo spostamento e vai su 1; in caso contrario, annulla la mossa e vai a 1.
Questo dovrebbe essere ripetuto fino a quando i marker non convergeranno in zampe. Beta controlla la scansione per ottimizzare il compromesso, quindi dovrebbe essere ottimizzata anche a livello sperimentale; può anche essere costantemente aumentato con il tempo di simulazione (ricottura simulata).


Ti interessa mostrare come funzionerebbe sul mio esempio? Dato che non mi piace molto la matematica di alto livello, quindi ho già difficoltà a svelare la formula che hai proposto :(
Ivo Flipse

1
Questa è la matematica del liceo, probabilmente la mia notazione è solo offuscata. Ho un piano per controllarlo, quindi rimanete sintonizzati.
mbq,

4
Sono un fisico delle particelle. Per molto tempo lo strumento software di riferimento nella nostra disciplina è stato chiamato PAW e aveva un'entità correlata ai grafici chiamata "marker". Potete immaginare quanto confuso ho trovato questa risposta nelle prime due volte ...
dmckee --- ex gattino moderatore

6

Ecco un altro approccio che ho usato quando facevo qualcosa di simile per un grande telescopio:

1) Cerca il pixel più alto. Una volta che hai quello, cerca in quella per il miglior adattamento per 2x2 (forse massimizzando la somma di 2x2), o esegui un adattamento gaussiano 2d all'interno della sottoregione di dire 4x4 centrato sul pixel più alto.

Quindi imposta quei pixel 2x2 che hai trovato a zero (o forse 3x3) attorno al centro del picco

torna a 1) e ripeti finché il picco più alto non scende al di sotto di una soglia di rumore, oppure hai tutte le dita dei piedi che ti servono


Vuoi condividere un esempio di codice che fa questo? Posso seguire quello che stai cercando di fare, ma non ho idea di come codificarlo da solo
Ivo Flipse

In realtà vengo dal lavorare con Matlab, quindi sì, questo sarebbe già di aiuto. Ma se usi funzioni davvero estranee, potrebbe essere difficile replicarlo con Python
Ivo Flipse

6

Probabilmente vale la pena provare con le reti neurali se sei in grado di creare alcuni dati di allenamento ... ma questo richiede molti esempi annotati a mano.


Se ne vale la pena, non mi dispiacerebbe annotare manualmente un grande campione. Il mio problema sarebbe: come posso implementarlo, dal momento che non so nulla sulla programmazione di reti neurali
Ivo Flipse

6

uno schema approssimativo ...

probabilmente vorresti usare un algoritmo di componenti connessi per isolare ogni regione della zampa. wiki ha una descrizione decente di questo (con un po 'di codice) qui: http://en.wikipedia.org/wiki/Connected_Component_Labeling

dovrai decidere se utilizzare la connessione 4 o 8. personalmente, per la maggior parte dei problemi preferisco la connessione 6. comunque, una volta separate ogni "stampa della zampa" come una regione connessa, dovrebbe essere abbastanza facile da scorrere la regione e trovare i massimi. una volta trovati i massimi, è possibile ingrandire in modo iterativo la regione fino a raggiungere una soglia predeterminata per identificarla come un "dito" dato.

un sottile problema qui è che non appena inizi a utilizzare le tecniche di visione artificiale per identificare qualcosa come una zampa destra / sinistra / anteriore / posteriore e inizi a guardare le singole dita dei piedi, devi iniziare a prendere in considerazione rotazioni, inclinazioni e traduzioni. ciò si ottiene attraverso l'analisi dei cosiddetti "momenti". ci sono alcuni momenti diversi da considerare nelle applicazioni di visione:

momenti centrali: invarianti di traduzione momenti normalizzati: ridimensionamento e traduzione di invarianti hu momenti: traduzione, scala e invarianti di rotazione

ulteriori informazioni sui momenti possono essere trovate cercando "momenti immagine" sul wiki.



4

Sembra che puoi imbrogliare un po 'usando l'algoritmo di jetxee. Sta trovando bene le prime tre dita e dovresti essere in grado di indovinare da dove si basa la quarta.


4

Problema interessante. La soluzione che vorrei provare è la seguente.

  1. Applicare un filtro passa basso, come la convoluzione con una maschera gaussiana 2D. Questo ti darà un sacco di valori (probabilmente, ma non necessariamente in virgola mobile).

  2. Esegui una soppressione 2D non massima utilizzando il raggio approssimativo noto di ciascuna zampa (o punta).

Questo dovrebbe darti le posizioni massime senza avere più candidati vicini tra loro. Solo per chiarire, il raggio della maschera nel passaggio 1 dovrebbe anche essere simile al raggio utilizzato nel passaggio 2. Questo raggio potrebbe essere selezionabile o il veterinario potrebbe misurarlo esplicitamente in anticipo (varierà con l'età / razza / ecc.).

Alcune delle soluzioni suggerite (spostamento medio, reti neurali e così via) probabilmente funzioneranno in una certa misura, ma sono eccessivamente complicate e probabilmente non ideali.


Ho 0 esperienze con matrici di convoluzione e filtri gaussiani, quindi ti piacerebbe mostrare come funzionerebbe sul mio esempio?
Ivo Flipse,

3

Bene, ecco un codice semplice e non terribilmente efficiente, ma per questa dimensione di un set di dati va bene.

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]

Fondamentalmente faccio semplicemente un array con la posizione in alto a sinistra e la somma di ogni quadrato 2x2 e lo ordino per somma. Quindi prendo il quadrato 2x2 con la somma più alta di contesa, lo metto nella bestmatrice e rimuovo tutti gli altri quadrati 2x2 che hanno usato qualsiasi parte di questo quadrato 2x2 appena rimosso.

Sembra funzionare bene tranne con l'ultima zampa (quella con la somma più piccola all'estrema destra nella tua prima foto), si scopre che ci sono altri due quadrati 2x2 ammissibili con una somma maggiore (e hanno una somma uguale a l'un l'altro). Uno di questi seleziona ancora un quadrato dal tuo 2x2, ma l'altro è a sinistra. Fortunatamente, per fortuna vediamo che scegliamo più di quello che vorresti, ma questo potrebbe richiedere alcune altre idee da usare per ottenere ciò che effettivamente vuoi sempre.


Ritengo che i tuoi risultati siano gli stessi di quelli nella risposta di @Jextee. O almeno così mi sembra di provarlo.
Ivo Flipse,


1

Forse qui è sufficiente un approccio ingenuo: costruisci un elenco di tutti i quadrati 2x2 sul tuo piano, ordinali in base alla loro somma (in ordine decrescente).

Innanzitutto, seleziona il quadrato più apprezzato nella tua "lista di zampe". Quindi, scegli in modo iterativo 4 dei migliori quadrati successivi che non si intersecano con nessuno dei quadrati precedentemente trovati.


In realtà ho fatto un elenco con tutte le somme 2x2, ma quando le ho ordinate non avevo idea di come confrontarle ripetutamente. Il mio problema era che quando l'ho ordinato, ho perso la traccia delle coordinate. Forse potrei inserirli in un dizionario, con le coordinate come chiave.
Ivo Flipse,

Sì, sarebbe necessario un qualche tipo di dizionario. Pensavo che la tua rappresentazione della griglia fosse già una sorta di dizionario.
Johannes Charra,

Bene l'immagine che vedi sopra è un array intorpidito. Il resto è attualmente archiviato in elenchi multidimensionali. Probabilmente sarebbe meglio smettere di farlo, anche se non ho familiarità con l'iterazione sui dizionari
Ivo Flipse,

1

Ci sono diversi ed estesi software disponibili dalla comunità di astronomia e cosmologia - questa è un'area significativa di ricerca sia storicamente che attualmente.

Non allarmarti se non sei un astronomo: alcuni sono facili da usare fuori dal campo. Ad esempio, potresti usare astropia / photutils:

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

[Sembra un po 'maleducato ripetere qui il loro breve codice di esempio.]

Di seguito è riportato un elenco incompleto e leggermente distorto di tecniche / pacchetti / collegamenti che potrebbero essere di interesse - aggiungere altro nei commenti e aggiornerò questa risposta se necessario. Naturalmente c'è un compromesso tra accuratezza e risorse di calcolo. [Onestamente, ci sono troppi per fornire esempi di codice in una singola risposta come questa, quindi non sono sicuro che questa risposta volerà o meno.]

Source Extractor https://www.astromatic.net/software/sextractor

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

Sfida di ricerca della fonte ASKAP / EMU: https://arxiv.org/abs/1509.03931

Puoi anche cercare sfide Planck e / o WMAP per l'estrazione della fonte.

...


0

Che cosa succede se si procede passo dopo passo: prima individuare il massimo globale, elaborare se necessario i punti circostanti dato il loro valore, quindi impostare la regione trovata su zero e ripetere per quella successiva.


Hmmm che l'impostazione su zero lo rimuoverà almeno da qualsiasi altro calcolo, sarebbe utile.
Ivo Flipse,

Invece di impostare su zero, è possibile calcolare una funzione gaussiana con i parametri scelti a mano e sottrarre i valori trovati dalle letture di pressione originali. Quindi, se la punta sta premendo i tuoi sensori, quindi trovando il punto di pressione più alto, lo usi per diminuire l'effetto di quella punta sui sensori, eliminando così le celle vicine con valori di alta pressione. en.wikipedia.org/wiki/File:Gaussian_2d.png
Daniyar

Vuoi mostrare un esempio basato sui miei dati di esempio @Daniyar? Dato che non ho familiarità con questo tipo di elaborazione dei dati
Ivo Flipse,

0

Non sono sicuro che questo risponda alla domanda, ma sembra che tu possa semplicemente cercare le n vette più alte che non hanno vicini.

Ecco l'essenza. Nota che è in Ruby, ma l'idea dovrebbe essere chiara.

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

Proverò a dare un'occhiata e vedrò se posso convertirlo in codice Python :-)
Ivo Flipse

Si prega di includere il codice nel post stesso, piuttosto che il collegamento a una sintesi, se è una lunghezza ragionevole.
AGF
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.