web-dev-qa-db-fra.com

Détection de pic dans un tableau 2D

J'aide une clinique vétérinaire à mesurer la pression sous une patte de chien. J'utilise Python pour l'analyse de mes données et maintenant je suis coincé pour essayer de diviser les pattes en sous-régions (anatomiques).

J'ai créé un tableau 2D de chaque patte, composé des valeurs maximales de chaque capteur chargé par la patte au fil du temps. Voici un exemple d'une patte, où j'ai utilisé Excel pour dessiner les zones que je veux «détecter». Ce sont 2 boîtes sur 2 autour du capteur avec les maxima locaux, qui ensemble ont la plus grande somme.

alt text

J'ai donc essayé quelques expériences et décidé de chercher simplement le maximum de chaque colonne et rangée (impossible de regarder dans une direction à cause de la forme de la patte). Cela semble "détecter" assez bien l’emplacement des orteils séparés, mais il marque également les capteurs voisins. 

alt text

Alors, quel serait le meilleur moyen de dire à Python lequel de ces maximums sont ceux que je veux? 

Remarque: les carrés 2x2 ne peuvent pas se chevaucher, car ils doivent être séparés les uns des autres!

De plus, j'ai pris 2x2 pour plus de commodité, toute solution plus avancée est la bienvenue, mais je suis simplement un scientifique du mouvement humain, je ne suis donc ni un vrai programmeur ni un mathématicien, alors restez simple. 

Voici une version pouvant être chargée avec np.loadtxt


Résultats

J'ai donc essayé la solution de @ jextee (voir les résultats ci-dessous). Comme vous pouvez le constater, cela fonctionne très bien sur les pattes antérieures, mais moins bien sur les pattes postérieures. 

Plus précisément, il ne peut pas reconnaître le petit sommet qui constitue le quatrième orteil. Ceci est évidemment inhérent au fait que la boucle semble aller de haut en bas vers la valeur la plus basse, sans tenir compte de l'endroit où elle se trouve. 

Quelqu'un saurait-il comment modifier l'algorithme de @ jextee afin qu'il puisse également trouver le 4ème orteil?

alt text

Étant donné que je n'ai pas encore traité d'autres essais, je ne peux pas fournir d'autres échantillons. Mais les données que j'ai données auparavant correspondent aux moyennes de chaque patte. Ce fichier est un tableau avec les données maximales de 9 pattes dans l'ordre dans lequel elles sont entrées en contact avec la plaque.

Cette image montre comment ils ont été répartis spatialement sur la plaque.

alt text

Mettre à jour:

J'AI CRÉÉ UN BLOG POUR LES PERSONNES INTÉRESSÉES et j'ai configuré un SkyDrive avec toutes les mesures brutes. Alors, à tous ceux qui demandent plus de données: plus de puissance pour vous! 


Nouvelle mise à jour:

Donc, après avoir obtenu de l'aide pour mes questions concernant détection de patte et tri de patte , _, j'ai finalement pu vérifier la détection d'orteil pour chaque patte! Il s'avère que cela ne fonctionne pas si bien que dans des pattes de la taille de celle de mon propre exemple. Bien entendu, c'est mon faute si j'ai choisi le 2x2 de façon arbitraire.

Voici un bel exemple de ce qui ne va pas: un clou est reconnu comme un orteil et le «talon» est si large qu'il est reconnu deux fois!

alt text

La patte est trop grande. Par conséquent, si vous prenez une taille de 2x2 sans chevauchement, certains orteils seront détectés deux fois. À l'inverse, chez les petits chiens, il est souvent impossible de trouver un cinquième orteil, ce qui, je suppose, est dû au fait que la zone 2x2 est trop grande.

Après essayer la solution actuelle sur toutes mes mesures je suis arrivé à la conclusion stupéfiante que pour presque tous mes petits chiens, il n'a pas trouvé de cinquième doigt et que dans plus de 50% des impacts pour les grands chiens, il trouverais plus!

Alors clairement, je dois le changer. Ma propre hypothèse était de changer la taille de la variable neighborhood en une taille plus petite pour les petits chiens et plus grande pour les gros chiens. Mais generate_binary_structure ne m'a pas laissé changer la taille du tableau. 

Par conséquent, j'espère que quelqu'un d'autre a une meilleure suggestion pour localiser les orteils, peut-être en ayant l'échelle de la zone des orteils avec la taille de la patte?

782
Ivo Flipse

J'ai détecté les pics à l'aide d'un filtre local maximum. Voici le résultat sur votre premier jeu de données de 4 pattes: Peaks detection result

Je l'ai également exécuté sur le deuxième ensemble de données de 9 pattes et cela a également bien fonctionné .

Voici comment vous le faites:

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

Il suffit ensuite d'utiliser scipy.ndimage.measurements.label sur le masque pour étiqueter tous les objets distincts. Ensuite, vous pourrez jouer avec eux individuellement.

Notez que la méthode fonctionne bien car l'arrière-plan n'est pas bruyant. Si c'était le cas, vous détecteriez un tas d'autres pics indésirables en arrière-plan. Un autre facteur important est la taille du quartier. Vous devrez l’ajuster si la taille du pic change (le doit rester à peu près proportionnel).

278
Ivan

Solution

Fichier de données: Paw.txt . Code source:

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"

Sortie sans chevauchement de carrés. Il semble que les mêmes zones soient sélectionnées comme dans votre exemple.

Certains commentaires

La partie délicate consiste à calculer les sommes de tous les 2x2 carrés. J'ai supposé que vous aviez besoin de tous, alors il pourrait y avoir un chevauchement. J'ai utilisé des tranches pour couper les premières/dernières colonnes et les lignes du tableau 2D d'origine, puis les superposer toutes ensemble et calculer des sommes.

Pour mieux le comprendre, imaginez un tableau 3x3:

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

Ensuite, vous pouvez prendre ses tranches:

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

Maintenant, imaginez que vous les empiliez les uns sur les autres et que vous additionniez des éléments aux mêmes positions. Ces sommes seront exactement les mêmes que sur les carrés 2x2 avec le coin en haut à gauche dans la même position:

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

Lorsque les sommes sont supérieures à 2x2 carrés, vous pouvez utiliser max pour rechercher le maximum, ou sort ou sorted pour rechercher les pics.

Pour mémoriser les positions des pics, je couple chaque valeur (la somme) à sa position ordinale dans un tableau aplati (voir Zip). Ensuite, je calcule à nouveau la position des lignes/colonnes lorsque j'imprime les résultats.

Remarques

J'ai permis aux carrés 2x2 de se chevaucher. La version modifiée en filtre certains, de sorte que seuls les carrés qui ne se chevauchent pas apparaissent dans les résultats.

Choisir les doigts (une idée)

Un autre problème est de savoir comment choisir ce qui est susceptible d’être les doigts de tous les sommets. J'ai une idée qui peut ou peut ne pas fonctionner. Je n'ai pas le temps de le mettre en œuvre pour le moment, donc utilisez simplement du pseudo-code.

J'ai remarqué que si les doigts avant restent sur un cercle presque parfait, le doigt arrière devrait se situer à l'intérieur de ce cercle. En outre, les doigts avant sont plus ou moins équidistants. Nous pouvons essayer d'utiliser ces propriétés heuristiques pour détecter les doigts.

Pseudo code:

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

C'est une approche de force brute. Si N est relativement petit, alors je pense que c'est faisable. Pour N = 12, il y a C_12 ^ 5 = 792 combinaisons, fois 5 façons de sélectionner un doigt arrière, soit 3960 cas à évaluer pour chaque patte.

43
sastanin

Ceci est un problème d'enregistrement d'image . La stratégie générale est la suivante:

  • Ayez un exemple connu ou une sorte de prior sur les données.
  • Ajustez vos données à l'exemple ou adaptez-le à vos données.
  • Cela aide si vos données sont approximativement alignées au départ.

Voici une approche approximative, "la chose la plus stupide qui puisse fonctionner":

  • Commencez avec cinq coordonnées d'orteil à peu près à l'endroit souhaité.
  • Avec chacun d'eux, montez de manière itérative au sommet de la colline. c'est-à-dire, compte tenu de la position actuelle, passez au pixel voisin maximum, si sa valeur est supérieure au pixel actuel. Arrêtez-vous lorsque vos coordonnées d'orteil ont cessé de bouger.

Pour contrer le problème d’orientation, vous pouvez définir environ 8 réglages initiaux pour les directions de base (Nord, Nord-Est, etc.). Exécutez-les individuellement et jetez tous les résultats où deux orteils ou plus se retrouvent au même pixel. J'y penserai un peu plus, mais ce genre de choses fait encore l'objet de recherches dans le traitement d'images - il n'y a pas de bonne réponse!

Idée légèrement plus complexe: clustering K-signifie (pondéré). C'est pas si mal.

  • Commencez avec cinq coordonnées d'orteil, mais ce sont maintenant des "centres de grappes".

Puis itérez jusqu'à la convergence:

  • Attribuez chaque pixel au cluster le plus proche (créez simplement une liste pour chaque cluster).
  • Calculez le centre de masse de chaque groupe. Pour chaque cluster, il s’agit de: Sum (coordonnée * valeur d’intensité)/Sum (coordonnée)
  • Déplacez chaque groupe vers le nouveau centre de masse.

Cette méthode donnera presque certainement de bien meilleurs résultats, et vous obtenez la masse de chaque groupe qui peut aider à identifier les orteils.

(Encore une fois, vous avez spécifié le nombre de clusters à l’avance. Avec le cluster, vous devez spécifier la densité d’une manière ou d’une autre: soit choisissez le nombre de clusters, approprié dans ce cas, ou choisissez un rayon de cluster et voyez combien vous en arrêtez. Un exemple de ce dernier est mean-shift .)

Désolé pour le manque de détails de mise en œuvre ou d'autres détails. Je codifierais cela, mais j'ai une date limite. Si rien d'autre n'a fonctionné d'ici la semaine prochaine, faites-le-moi savoir et je tenterai le coup.

27
CakeMaster

Ce problème a été étudié en profondeur par les physiciens. Il y a une bonne implémentation dans ROOT . Regardez le TSpectrum classes (en particulier TSpectrum2 pour votre cas) et la documentation correspondante.

Références:

  1. M. Morhac et al.: Méthodes d'élimination de fond pour les spectres gamma de coïncidences multidimensionnelles. Instruments et méthodes nucléaires dans la recherche en physique A 401 (1997) 113-132.
  2. M. Morhac et al.: Déconvolution efficace de l'or à une et deux dimensions et son application à la décomposition des spectres gamma. Instruments et méthodes nucléaires dans la recherche en physique A 401 (1997) 385-408.
  3. M. Morhac et al.: Identification des pics dans les spectres gamma de coïncidences multidimensionnelles. Instruments et méthodes nucléaires en physique de la recherche A 443 (2000), 108-125. 

... et pour ceux qui n'ont pas accès à un abonnement à NIM:

12
dmckee

En utilisant une homologie persistante pour analyser votre ensemble de données, j'obtiens le résultat suivant (cliquez pour agrandir):

 Result

Ceci est la version 2D de la méthode de détection de pic décrite dans cette SO answer . La figure ci-dessus montre simplement les classes d'homologie persistante à 0 dimensions triées par persistance.

J'ai mis à l'échelle le jeu de données d'origine d'un facteur 2 à l'aide de scipy.misc.imresize (). Cependant, notez que j'ai considéré les quatre pattes comme un seul jeu de données; le diviser en quatre rendrait le problème plus facile.

Méthodologie. L'idée derrière ceci est assez simple: considérons le graphe de fonction de la fonction qui attribue son niveau à chaque pixel. Cela ressemble à ceci:

3D function graph

Maintenant, considérons un niveau d’eau à la hauteur 255 qui descend continuellement vers des niveaux plus bas. Aux îles maxima locales apparaissent (naissance). Aux points de selle, deux îles se confondent; nous considérons que l'île inférieure est fusionnée avec l'île supérieure (mort). Le diagramme dit de persistance (des classes d'homologie de la 0ème dimension, nos îles) décrit les valeurs de mort sur naissance de toutes les îles:

Persistence diagram

La persistance d'une île est alors la différence entre le niveau naissance et le niveau mort; la distance verticale d'un point à la diagonale grise principale. La figure marque les îles en diminuant la persistance.

La toute première image montre les lieux de naissance des îles. Cette méthode non seulement donne les maxima locaux, mais en quantifie également la "signification" par la persistance susmentionnée. On filtrerait alors toutes les îles avec une persistance trop faible. Cependant, dans votre exemple, chaque île (c’est-à-dire chaque maximum local) est un sommet que vous recherchez.

Le code Python peut être trouvé ici .

10
S. Huber

Voici une idée: vous calculez le laplacien (discret) de l'image. Je m'attendrais à ce qu'il soit (négatif et) grand au maximum, d'une manière plus dramatique que dans les images originales. Ainsi, les maxima pourraient être plus faciles à trouver.

Voici une autre idée: si vous connaissez la taille typique des points à haute pression, vous pouvez d’abord lisser votre image en la convolant avec un gaussien de même taille. Cela peut vous donner des images plus simples à traiter.

10
Eric O Lebigot

Quelques idées me viennent à l’esprit:

  • prendre le dégradé (dérivé) de l'analyse, voir si cela élimine les faux appels
  • prendre le maximum des maxima locaux

Vous voudrez peut-être aussi jeter un coup d'œil à OpenCV , il dispose d'une API Python plutôt correcte et de certaines fonctions que vous jugeriez utiles.

9
ChrisC

Je suis sûr que vous en avez déjà assez pour continuer, mais je ne peux pas m'empêcher de suggérer d'utiliser la méthode de regroupement k-means. k-means est un algorithme de clustering non supervisé qui vous emmènera des données (dans un nombre quelconque de dimensions - je le fais en 3D) et les arrange en k grappes avec des limites distinctes. C'est bien ici parce que vous savez exactement combien d'orteils ces canines devraient avoir.

De plus, il est implémenté dans Scipy, qui est vraiment Nice ( http://docs.scipy.org/doc/scipy/reference/cluster.vq.html ). 

Voici un exemple de ce qu’il peut faire pour résoudre spatialement les clusters 3D: enter image description here

Ce que vous voulez faire est un peu différent (2D et comprend les valeurs de pression), mais je pense toujours que vous pourriez essayer. 

8
astromax

merci pour les données brutes. Je suis dans le train et c'est tout ce que j'ai eu (mon arrêt est à venir). J'ai massé votre fichier txt avec regexps et l'ai placé dans une page html avec du javascript pour la visualisation. Je le partage ici parce que certains, comme moi, pourraient le trouver plus facile à pirater que le python.

Je pense qu'une bonne approche sera invariante d'échelle et de rotation, et ma prochaine étape sera d'étudier des mélanges de gaussiennes. (chaque patte est le centre d’un gaussien).

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

alt text

7
Ron

La solution du physicien:
Définir 5 marqueurs de patte identifiés par leur position X_i et les initier avec des positions aléatoires . Définir une fonction énergétique combinant une récompense pour la position des marqueurs dans les positions des pattes avec une punition pour le chevauchement des marqueurs; Disons:

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

(S(X_i) est la force moyenne en 2x2 carrés autour de X_i, alfa est un paramètre à piquer expérimentalement)

Il est maintenant temps de faire un peu de magie Metropolis-Hastings:
1. Sélectionnez un marqueur aléatoire et déplacez-le d'un pixel dans une direction aléatoire.
2. Calculez dE, la différence d'énergie provoquée par ce mouvement.
3. Obtenez un nombre aléatoire uniforme compris entre 0 et 1 et appelez-le r.
4. Si dE<0 ou exp(-beta*dE)>r, acceptez le déplacement et passez à 1; sinon, annulez le déménagement et passez à 1.
Cela devrait être répété jusqu'à ce que les marqueurs convergent vers les pattes. La bêta contrôle le balayage pour optimiser le compromis, il devrait donc également être optimisé à titre expérimental. il peut aussi être constamment augmenté avec le temps de la simulation (recuit simulé).

6
mbq

Voici une autre approche que j'ai utilisée lorsque je fais quelque chose de similaire pour un grand télescope:

1) Recherchez le pixel le plus élevé. Une fois que vous avez cela, recherchez le meilleur ajustement pour 2x2 (maximisant peut-être la somme de 2x2), ou effectuez un ajustement gaussien en 2d à l'intérieur de la sous-région de 4x4 centrée sur le pixel le plus élevé.

Puis définissez les 2x2 pixels trouvés à zéro (ou peut-être 3x3) autour du centre du pic

retournez à 1) et répétez l'opération jusqu'à ce que le pic le plus élevé tombe en dessous d'un seuil de bruit ou que vous ayez tous les orteils nécessaires

5
Paulus

une ébauche ...

vous voudrez probablement utiliser un algorithme de composants connectés pour isoler chaque région de Paw. wiki a une description décente de cela (avec du code) ici: http://en.wikipedia.org/wiki/Connected_Component_Labeling

vous devrez choisir entre 4 ou 8 connexions. Personnellement, pour la plupart des problèmes, je préfère une connexion à six. de toute façon, une fois que vous avez séparé chaque "empreinte de patte" en tant que région connectée, il devrait être assez facile de parcourir la région et de trouver les maxima. une fois que vous avez trouvé les maxima, vous pouvez agrandir de manière itérative la région jusqu'à atteindre un seuil prédéterminé afin de l'identifier comme un "orteil" donné. 

un problème subtil est que dès que vous commencez à utiliser des techniques de vision par ordinateur pour identifier quelque chose en tant que patte droite/gauche/avant/arrière et que vous commencez à regarder les orteils individuels, vous devez commencer à tenir compte des rotations, des biais et des traductions. ceci est accompli par l'analyse de soi-disant "moments". Il existe plusieurs moments à prendre en compte dans les applications de vision: 

moments centraux: invariant de traduction moments normalisés: échelle et invariant de traductionhu moments: translation, échelle et invariant de rotation 

plus d'informations sur les moments peuvent être trouvées en cherchant "moments d'image" sur wiki. 

5
joshua

Il est probablement intéressant d'essayer avec des réseaux de neurones si vous êtes capable de créer des données d'entraînement ... mais cela nécessite de nombreux échantillons annotés à la main.

5
antirez

Peut-être que vous pouvez utiliser quelque chose comme les modèles de mélange gaussien. Voici un paquet Python pour faire des GMM (vient de faire une recherche Google) http://www.ar.media.kyoto-u.ac.jp/members/david/softwares/em/

4
Aidan Brumsickle

Il semble que vous puissiez tricher un peu en utilisant l'algorithme de jetxee. Il trouve que les trois premiers orteils sont bons et vous devriez pouvoir deviner où le quatrième est basé.

3
geoff

Problème intéressant. La solution que je voudrais essayer est la suivante.

  1. Appliquez un filtre passe-bas, tel que la convolution avec un masque gaussien 2D. Cela vous donnera un tas de valeurs (probablement, mais pas nécessairement à virgule flottante).

  2. Effectuez une suppression 2D non maximale en utilisant le rayon approximatif connu de chaque coussinet de patte (ou orteil).

Cela devrait vous donner les positions maximales sans avoir plusieurs candidats qui sont proches les uns des autres. Juste pour clarifier, le rayon du masque à l'étape 1 devrait également être similaire au rayon utilisé à l'étape 2. Ce rayon peut être sélectionné, ou le vétérinaire peut le mesurer explicitement à l'avance (il varie avec l'âge/la race/etc.).

Certaines des solutions suggérées (décalage moyen, réseaux de neurones, etc.) fonctionneront probablement dans une certaine mesure, mais sont trop compliquées et probablement pas idéales.

3
Bob Mottram

Eh bien, voici un code simple et pas très efficace, mais pour cette taille d’ensemble de données, c’est parfait.

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]

Fondamentalement, je viens de faire un tableau avec la position de la partie supérieure gauche et la somme de chaque carré 2x2 et trier par la somme. Je prends ensuite le carré 2x2 avec la somme la plus élevée hors du conflit, le mets dans le tableau best et supprime tous les autres carrés 2x2 ayant utilisé une partie quelconque de ce carré 2x2 simplement supprimé.

Cela semble bien fonctionner, sauf avec la dernière patte (celle avec la plus petite somme à l'extrême droite dans votre première photo), il s'avère qu'il y a deux autres carrés 2x2 éligibles avec une somme plus importante (et ils ont une somme égale à L'un et l'autre). L'un d'eux sélectionne toujours un carré de votre carré 2x2, mais l'autre est à gauche. Heureusement, nous avons la chance de choisir davantage celui que vous voudriez, mais il faudra peut-être utiliser d'autres idées pour obtenir ce que vous voulez réellement tout le temps.

3
Justin Peel

je veux juste vous dire qu'il existe une option intéressante pour trouver des maxima locaux dans des images avec python.

from skimage.feature import peak_local_max

ou pour skimage 0.8.0

from skimage.feature.peak import peak_local_max

http://scikit-image.org/docs/0.8.0/api/skimage.feature.peak.html

2
Thomio

Je ne suis pas sûr que cela réponde à la question, mais il semble que vous puissiez simplement rechercher les n sommets les plus élevés sans voisins.

Voici le Gist. Notez que c'est en Ruby, mais l'idée devrait être claire.

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
0
Rick Hull

La communauté de l'astronomie et de la cosmologie propose de nombreux logiciels de grande envergure. Il s'agit d'un domaine de recherche important, tant historique qu'actuel.

Ne vous inquiétez pas si vous n'êtes pas astronome - certains sont faciles à utiliser en dehors du terrain. Par exemple, vous pouvez utiliser astropy/photutils:

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

[Il semble un peu impoli de répéter leur court exemple de code ici.]

Vous trouverez ci-dessous une liste incomplète et légèrement biaisée de techniques/packages/liens pouvant vous intéresser - ajoutez-en davantage dans les commentaires et je mettrai à jour cette réponse si nécessaire. Bien sûr, il y a un compromis entre précision et ressources de calcul. [Honnêtement, il y en a trop pour donner des exemples de code dans une seule réponse telle que celle-ci, alors je ne suis pas sûr que cette réponse vole ou non.]

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

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

Problème de recherche de sources ASKAP/EMU: https://arxiv.org/abs/1509.03931

Vous pouvez également rechercher des problèmes d’extraction de source Planck et/ou WMAP.

...

0
jtlz2

Une approche naïve est peut-être suffisante ici: créez une liste de tous les carrés 2x2 de votre avion, ordonnez-les en fonction de leur somme (par ordre décroissant). 

Commencez par sélectionner le carré de valeur la plus élevée dans votre "liste de pattes". Ensuite, sélectionnez de manière itérative 4 des meilleurs carrés suivants qui ne se croisent pas avec les carrés précédemment trouvés. 

0
Johannes Charra

Et si vous procédiez pas à pas: vous devez d’abord localiser le maximum global, traiter si nécessaire les points environnants en fonction de leur valeur, puis définir la région trouvée sur zéro et répéter l’opération suivante.

0
Cedric H.