Detecção de pico em uma matriz 2D


874

Estou ajudando uma clínica veterinária a medir a pressão sob uma pata de cachorro. Uso Python para minha análise de dados e agora estou tentando dividir as patas em sub-regiões (anatômicas).

Fiz uma matriz 2D de cada pata, que consiste nos valores máximos para cada sensor que foi carregado pela pata ao longo do tempo. Aqui está um exemplo de uma pata, em que usei o Excel para desenhar as áreas que quero 'detectar'. São 2 por 2 caixas ao redor do sensor com máximos locais, que juntos têm a maior soma.

texto alternativo

Então, tentei fazer algumas experiências e decido simplesmente procurar o máximo de cada coluna e linha (não consigo olhar em uma direção devido ao formato da pata). Isso parece 'detectar' a localização dos dedos separados razoavelmente bem, mas também marca os sensores vizinhos.

texto alternativo

Então, qual seria a melhor maneira de dizer ao Python quais desses máximos são os que eu quero?

Nota: Os quadrados 2x2 não podem se sobrepor, pois precisam ser dedos separados!

Também tomei 2x2 como uma conveniência, qualquer solução mais avançada é bem-vinda, mas sou simplesmente um cientista do movimento humano, por isso não sou um programador de verdade ou um matemático, por isso, mantenha-o 'simples'.

Aqui está uma versão que pode ser carregada comnp.loadtxt


Resultados

Então, tentei a solução do @ jextee (veja os resultados abaixo). Como você pode ver, funciona muito nas patas dianteiras, mas funciona menos bem nas patas traseiras.

Mais especificamente, ele não consegue reconhecer o pequeno pico que é o quarto dedo do pé. Obviamente, isso é inerente ao fato de o loop parecer de cima para baixo em direção ao valor mais baixo, sem levar em consideração onde está.

Alguém saberia como ajustar o algoritmo do @ jextee, para que ele também possa encontrar o quarto dedo do pé?

texto alternativo

Como ainda não processei nenhum outro teste, não posso fornecer outras amostras. Mas os dados que forneci antes eram as médias de cada pata. Este arquivo é uma matriz com os dados máximos de 9 patas na ordem em que eles entraram em contato com a placa.

Esta imagem mostra como eles foram espalhados espacialmente sobre o prato.

texto alternativo

Atualizar:

Criei um blog para qualquer pessoa interessada e configurei um SkyDrive com todas as medições brutas. Portanto, para quem solicita mais dados: mais poder para você!


Nova atualização:

Então, depois da ajuda que recebi com minhas perguntas sobre detecção e classificação de patas , finalmente pude verificar a detecção de dedos de todas as patas! Acontece que ele não funciona tão bem em nada, mas patas do tamanho de uma no meu próprio exemplo. É claro que, em retrospectiva, a culpa é minha por escolher o 2x2 tão arbitrariamente.

Aqui está um bom exemplo de onde dá errado: uma unha está sendo reconhecida como dedo do pé e o 'calcanhar' é tão largo que é reconhecido duas vezes!

texto alternativo

Como a pata é muito grande, o tamanho de 2x2 sem sobreposição faz com que alguns dedos sejam detectados duas vezes. Por outro lado, em cães pequenos, muitas vezes não consegue encontrar o quinto dedo do pé, o que suspeito ser causado pela área 2x2 ser muito grande.

Depois de tentar a solução atual em todas as minhas medidas , cheguei à conclusão impressionante de que, para quase todos os meus cães pequenos, não encontrava o quinto dedo do pé e que em mais de 50% dos impactos para os cães grandes encontrava mais!

Tão claramente que preciso mudar isso. Meu palpite era mudar o tamanho do neighborhoodpara algo menor para cães pequenos e maior para cães grandes. Mas generate_binary_structurenão me deixou mudar o tamanho da matriz.

Portanto, espero que alguém tenha uma sugestão melhor para localizar os dedos, talvez com a escala da área dos dedos com o tamanho da pata?


Entendo que as vírgulas são casas decimais em vez de separadores de valor?
Matth

Sim, são vírgulas. E @ Christian, estou tentando colocá-lo em um arquivo legível fácil, mas mesmo isso falha em mim :(
Ivo Flipse

3
Enquanto estou fazendo um estudo de viabilidade, tudo dá certo. Então, eu estou procurando tantas maneiras de definir a pressão, incluindo sub-regiões. Também preciso ser capaz de discriminar os lados 'dedão do pé' e 'dedinho do pé', para estimar a orientação. Mas desde que este não tenha sido feito antes, não há como dizer o que poderíamos encontrar :-)
Ivo Flipse

2
@ Ron: um dos objetivos deste estudo é verificar para que tamanho / peso dos cães o sistema é adequado, então sim, enquanto esse cão tinha cerca de 20 kg. Eu tenho alguns que são consideravelmente menores (e maiores) e espero que eu não seja capaz de fazer o mesmo nos pequenos reais.
Ivo Flipse 10/09/10

2
@frank as patas são medidas ao longo do tempo, daí a 3ª dimensão. No entanto, eles não se movem de seu lugar (relativamente falando), então estou mais interessado em saber onde os dedos estão localizados em 2D. O aspecto 3D é gratuito depois disso
Ivo Flipse

Respostas:


332

Detectei os picos usando um filtro máximo local . Aqui está o resultado do seu primeiro conjunto de dados de 4 patas: Resultado de detecção de picos

Também o executei no segundo conjunto de 9 patas e também funcionou .

Aqui está como você faz isso:

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

Tudo o que você precisa fazer depois é usar scipy.ndimage.measurements.labela máscara para rotular todos os objetos distintos. Então você poderá brincar com eles individualmente.

Observe que o método funciona bem porque o plano de fundo não é barulhento. Se fosse, você detectaria vários outros picos indesejados em segundo plano. Outro fator importante é o tamanho do bairro . Você precisará ajustá-lo se o tamanho do pico mudar (o valor deve permanecer aproximadamente proporcional).


1
Existe uma solução mais simples que (eroded_background ^ local_peaks). Basta fazer (primeiro plano e locais picos)
Ryan Soklaski

53

Solução

Arquivo de dados: paw.txt . Código fonte:

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"

Saída sem quadrados sobrepostos. Parece que as mesmas áreas estão selecionadas como no seu exemplo.

Alguns comentários

A parte complicada é calcular somas de todos os quadrados 2x2. Presumi que você precisava de todos eles, então pode haver alguma sobreposição. Usei fatias para cortar as primeiras / últimas colunas e linhas da matriz 2D original e, em seguida, sobrepor todas elas e calcular somas.

Para entender melhor, crie imagens de uma matriz 3x3:

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

Então você pode pegar as fatias:

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

Agora imagine que você os empilha um acima do outro e soma elementos nas mesmas posições. Essas somas serão exatamente as mesmas somas nos quadrados 2x2 com o canto superior esquerdo na mesma posição:

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

Quando você tem somas acima de quadrados 2x2, pode usar maxpara encontrar o máximo, ou sort, ou sortedpara encontrar os picos.

Para lembrar as posições dos picos, associo cada valor (a soma) à sua posição ordinal em uma matriz achatada (ver zip). Depois, calculo a posição da linha / coluna novamente quando imprimo os resultados.

Notas

Eu permiti que os quadrados 2x2 se sobrepusessem. A versão editada filtra alguns deles, de modo que apenas quadrados não sobrepostos apareçam nos resultados.

Escolhendo os dedos (uma ideia)

Outro problema é como escolher o que provavelmente será o dedo de todos os picos. Eu tenho uma ideia que pode ou não funcionar. Eu não tenho tempo para implementá-lo agora, então apenas pseudo-código.

Notei que, se os dedos da frente permanecerem quase em um círculo perfeito, o dedo traseiro deverá estar dentro desse círculo. Além disso, os dedos da frente estão mais ou menos igualmente espaçados. Podemos tentar usar essas propriedades heurísticas para detectar os dedos.

Pseudo-código:

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

Essa é uma abordagem de força bruta. Se N é relativamente pequeno, acho que é factível. Para N = 12, há C_12 ^ 5 = 792 combinações, vezes 5 maneiras de selecionar um dedo traseiro, portanto, 3960 casos para avaliar para cada pata.


Ele vai ter que filtrar as patas manualmente, dada a sua lista de resultados ... escolher os quatro resultados de nível superior vai dar-lhe as quatro possibilidades para construir um quadrado de 2x2 contendo o valor máximo de 6,8
Johannes Charra

As caixas de 2x2 pode não se sobrepõem, uma vez que se eu quiser fazer estatísticas, eu não quero estar usando a mesma região, eu quero comparar regiões :-)
Ivo Flipse

Eu editei a resposta. Agora não há quadrados sobrepostos nos resultados.
sastanin 10/09/10

1
Eu tentei e parece funcionar para as patas da frente, mas menos para as traseiras. Acho que nós vamos ter que tentar algo que sabe para onde olhar
Ivo Flipse

1
Expliquei minha idéia de como os dedos podem ser detectados no pseudo-código. Se você gosta, posso tentar implementá-lo amanhã à noite.
sastanin 10/09/10

34

Este é um problema de registro de imagem . A estratégia geral é:

  • Tenha um exemplo conhecido ou algum tipo de prévia dos dados.
  • Ajuste seus dados ao exemplo ou ajuste o exemplo aos seus dados.
  • Isso ajuda se seus dados estiverem aproximadamente alinhados em primeiro lugar.

Aqui está uma abordagem grosseira e pronta , "a coisa mais idiota que poderia funcionar":

  • Comece com cinco coordenadas nos dedos aproximadamente no lugar que você espera.
  • Com cada um, suba iterativamente até o topo da colina. ou seja, dada a posição atual, mova para o pixel vizinho máximo, se seu valor for maior que o pixel atual. Pare quando as coordenadas do seu dedo do pé pararem de se mover.

Para combater o problema de orientação, você pode ter 8 configurações iniciais para as direções básicas (norte, nordeste, etc). Execute cada um individualmente e jogue fora todos os resultados em que dois ou mais dedos do pé terminem no mesmo pixel. Pensarei um pouco mais sobre isso, mas esse tipo de coisa ainda está sendo pesquisado no processamento de imagens - não há respostas corretas!

Ideia um pouco mais complexa: K (ponderado) significa cluster. Não é tão ruim.

  • Comece com cinco coordenadas do dedo do pé, mas agora esses são "centros de cluster".

Em seguida, itere até a convergência:

  • Atribua cada pixel ao cluster mais próximo (faça uma lista para cada cluster).
  • Calcule o centro de massa de cada cluster. Para cada cluster, é: Soma (coordenada * valor da intensidade) / Soma (coordenada)
  • Mova cada cluster para o novo centro de massa.

Esse método quase certamente dará resultados muito melhores e você obtém a massa de cada cluster, o que pode ajudar na identificação dos dedos dos pés.

(Novamente, você especificou o número de clusters antecipadamente. Com o cluster, você deve especificar a densidade de uma maneira ou de outra: Escolha o número de clusters, apropriado neste caso, ou escolha um raio de cluster e veja quantos você termina Um exemplo deste último é o deslocamento médio .)

Desculpe a falta de detalhes de implementação ou outras especificidades. Eu codificaria isso, mas tenho um prazo. Se nada mais funcionar até a próxima semana, avise-me e eu vou tentar.


1
O problema é que as patas mudam de orientação e não tenho nenhuma calibração / linha de base de uma pata correta para começar. Além disso, temo que muitos dos algoritmos de reconhecimento de imagem estejam um pouco fora do meu alcance.
Ivo Flipse

A abordagem "áspera e pronta" é bem simples - talvez eu não tenha entendido bem a idéia. Vou colocar um pseudocódigo para ilustrar.
CakeMaster 10/09/10

Eu tenho um pressentimento de que sua sugestão ajudará a corrigir o reconhecimento das patas traseiras, eu simplesmente não sei 'como'
Ivo Flipse

Eu adicionei outra ideia. A propósito, se você tiver muitos dados bons, seria legal colocá-los online em algum lugar. Poderia ser útil para pessoas que estudam processamento de imagem de aprendizagem / máquina e você pode ter um pouco mais de código de fora ...
CakeMaster

1
Eu estava pensando em escrever meu processamento de dados em um blog Wordpress simples, simplesmente para ser útil para outros e preciso anotá-lo de qualquer maneira. Eu gosto de todas as suas sugestões, mas temo que vou ter que esperar por alguém sem um prazo ;-)
Ivo Flipse

18

Usando homologia persistente para analisar seu conjunto de dados, obtenho o seguinte resultado (clique para ampliar):

Resultado

Esta é a versão 2D do método de detecção de pico descrito nesta resposta do SO . A figura acima mostra simplesmente classes de homologia persistente em 0-dimensão classificadas por persistência.

Eu fiz um upscale do conjunto de dados original por um fator 2 usando scipy.misc.imresize (). No entanto, observe que eu considerei as quatro patas como um conjunto de dados; dividi-lo em quatro facilitaria o problema.

Metodologia. A idéia por trás disso é bem simples: considere o gráfico da função que atribui a cada pixel seu nível. Se parece com isso:

Gráfico de função 3D

Agora considere um nível de água na altura 255 que desce continuamente para níveis mais baixos. Nas ilhas máximas locais, aparece (nascimento). Nos pontos de sela, duas ilhas se fundem; consideramos que a ilha inferior se fundirá à ilha superior (morte). O chamado diagrama de persistência (das classes de homologia da 0ª dimensão, nossas ilhas) mostra os valores de morte por nascimento de todas as ilhas:

Diagrama de persistência

A persistência de uma ilha é então a diferença entre o nível de nascimento e a morte; a distância vertical de um ponto para a diagonal principal cinza. A figura rotula as ilhas diminuindo a persistência.

A primeira imagem mostra a localização dos nascimentos das ilhas. Este método não apenas fornece os máximos locais, mas também quantifica seu "significado" pela persistência acima mencionada. Um seria filtrar todas as ilhas com uma persistência muito baixa. No entanto, no seu exemplo, toda ilha (ou seja, todo máximo local) é um pico que você procura.

O código Python pode ser encontrado aqui .


16

Este problema foi estudado em profundidade por físicos. Existe uma boa implementação no ROOT . Veja as classes TSpectrum (especialmente TSpectrum2 para o seu caso) e a documentação para elas.

Referências:

  1. M.Morhac et al .: Métodos de eliminação de background para espectros de raios gama multidimensional por coincidência. Instrumentos e métodos nucleares na pesquisa em física A 401 (1997) 113-132.
  2. M.Morhac et al .: Deconvolução eficiente de ouro unidimensional e bidimensional e sua aplicação na decomposição de espectros de raios gama. Instrumentos e métodos nucleares na pesquisa em física A 401 (1997) 385-408.
  3. M.Morhac et al .: Identificação de picos em espectros de raios gama de coincidências multidimensionais. Instrumentos e Métodos Nucleares em Física de Pesquisa A 443 (2000), 108-125.

... e para aqueles que não têm acesso a uma assinatura do NIM:


Para dar uma olhada no artigo, parece descrever o mesmo processamento de dados do que estou tentando aqui, no entanto, temo que tenha superado muito minhas habilidades de programação :(
Ivo Flipse

@ Ivo: Eu nunca tentei implementá-lo sozinho. Eu apenas uso ROOT. Existem ligações python, no entanto, mas saiba que o ROOT é um pacote bastante pesado.
dmckee --- ex-moderador gatinho

@ Ivo Flipse: Eu concordo com o dmckee. Você tem muitos leads promissores em outras respostas. Se todos falharem e você desejar investir algum tempo, poderá se aprofundar no ROOT e (provavelmente) fará o que for necessário. Eu nunca conheci alguém que tentou aprender ROOT através das ligações python (em vez de C ++ natural), então desejo-lhe sorte.
Physicsmichael

13

Aqui está uma idéia: você calcula o (discreto) Laplaciano da imagem. Eu esperaria que fosse (negativo e) grande no máximo, de uma maneira mais dramática do que nas imagens originais. Assim, o máximo poderia ser mais fácil de encontrar.

Aqui está outra idéia: se você conhece o tamanho típico dos pontos de alta pressão, pode suavizar sua imagem convoluindo-a com um Gaussiano do mesmo tamanho. Isso pode fornecer imagens mais simples de processar.


11

Apenas algumas idéias em cima da minha cabeça:

  • faça o gradiente (derivado) da verificação, veja se isso elimina as chamadas falsas
  • tirar o máximo dos máximos locais

Você também pode querer dar uma olhada no OpenCV , ele possui uma API Python bastante decente e pode ter algumas funções que você achar úteis.


Com gradiente, você quer dizer que devo calcular a inclinação das encostas, uma vez que esse valor está acima de um certo valor, eu sei que há um "pico"? Eu tentei isso, mas alguns dos dedos têm picos muito baixos (1,2 N / cm) em comparação com outros (8 N / cm). Então, como devo lidar com picos com um gradiente muito baixo?
Ivo Flipse

2
O que funcionou para mim no passado se eu não pudesse usar o gradiente diretamente era olhar para o gradiente e o máximo, por exemplo, se o gradiente é um extremo local e eu estou no máximo local, então estou em um ponto interesse.
ChrisC

11

Tenho certeza de que você já tem o suficiente para continuar, mas não posso deixar de sugerir o uso do método de agrupamento k-means. O k-means é um algoritmo de cluster não supervisionado que leva os dados (em qualquer número de dimensões - por acaso faço isso em 3D) e os organiza em k clusters com limites distintos. Aqui é legal porque você sabe exatamente quantos dedos esses caninos devem ter.

Além disso, é implementado no Scipy, o que é muito bom ( http://docs.scipy.org/doc/scipy/reference/cluster.vq.html ).

Aqui está um exemplo do que ele pode fazer para resolver espacialmente clusters 3D: insira a descrição da imagem aqui

O que você quer fazer é um pouco diferente (2D e inclui valores de pressão), mas ainda acho que você poderia tentar.


10

obrigado pelos dados brutos. Estou no trem e isso é o mais longe que consegui (minha parada está chegando). Eu massageei seu arquivo txt com regexps e o coloquei em uma página html com algum javascript para visualização. Estou compartilhando aqui porque alguns, como eu, podem achar mais fácil de hackear do que python.

Eu acho que uma boa abordagem será invariável em escala e rotação, e meu próximo passo será investigar misturas de gaussianos. (cada pata é o centro de um 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>

texto alternativo


1
Eu acho que esta é uma prova de conceito de que as técnicas de Gauss recomendados poderia trabalhar, agora, se alguém pudesse provar isso com Python ;-)
Ivo Flipse

8

Solução do físico:
defina 5 marcadores de pata identificados por suas posições X_ie inicie-os com posições aleatórias. Definir alguma função energética que combine algum prêmio pela localização dos marcadores nas posições das patas com alguma punição pela sobreposição de marcadores; Digamos:

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

( S(X_i)é a força média em 2x2 quadrado ao redor X_i, alfaé um parâmetro a ser atingido experimentalmente)

Agora é hora de fazer mágica de Metropolis-Hastings:
1. Selecione um marcador aleatório e mova-o um pixel em direção aleatória.
2. Calcule dE, a diferença de energia que esse movimento causou.
3. Obtenha um número aleatório uniforme de 0-1 e chame-o de r.
4. Se dE<0ou exp(-beta*dE)>r, aceite a mudança e vá para 1; caso contrário, desfaça o movimento e vá para 1.
Isso deve ser repetido até que os marcadores converjam em patas. O Beta controla a varredura para otimizar o tradeoff, por isso também deve ser otimizado experimentalmente; também pode ser aumentado constantemente com o tempo da simulação (recozimento simulado).


Gostaria de mostrar como isso funcionaria no meu exemplo? Como eu realmente não gosto de matemática de alto nível, já tenho dificuldade em desvendar a fórmula que você propôs :(
Ivo Flipse

1
Isso é matemática do ensino médio, provavelmente minha anotação está ofuscada. Eu tenho um plano para verificar isso, portanto, fique atento.
mbq 10/09/10

4
Sou físico de partículas. Durante muito tempo, a ferramenta de software utilizada em nossa disciplina foi chamada PAW, e possuía uma entidade relacionada a gráficos chamada "marcador". Você pode imaginar o quão confusa eu encontrei essa resposta nas duas primeiras vezes ...
dmckee --- ex-moderator kitten

6

Heres outra abordagem que eu usei ao fazer algo semelhante para um grande telescópio:

1) Procure o pixel mais alto. Depois disso, procure em torno dele o melhor ajuste para 2x2 (talvez maximizando a soma de 2x2) ou faça um ajuste gaussiano em 2d dentro da sub-região, por exemplo, 4x4, centrado no pixel mais alto.

Em seguida, defina os pixels 2x2 encontrados como zero (ou talvez 3x3) ao redor do centro do pico

volte para 1) e repita até que o pico mais alto caia abaixo de um limite de ruído, ou você tenha todos os dedos dos pés necessários


Gostaria de compartilhar um exemplo de código que faça isso? Eu posso seguir o que você está tentando fazer, mas não têm idéia de como o código-lo eu mesmo
Ivo Flipse

Na verdade, eu venho trabalhando com o Matlab, então sim, isso já ajudaria. Mas se você usar funções realmente estrangeiras, pode ser difícil para mim replicá-lo com o Python
Ivo Flipse

6

Provavelmente vale a pena tentar com redes neurais se você conseguir criar alguns dados de treinamento ... mas isso precisa de muitas amostras anotadas manualmente.


Se vale a pena, eu não me importaria de anotar uma amostra grande à mão. Meu problema seria: como faço para implementar isso, já que eu não sei nada sobre a programação de redes neurais
Ivo Flipse

6

um esboço aproximado ...

você provavelmente desejaria usar um algoritmo de componentes conectados para isolar cada região de pata. O wiki tem uma descrição decente disso (com algum código) aqui: http://en.wikipedia.org/wiki/Connected_Component_Labeling

você terá que tomar uma decisão sobre o uso da conexão 4 ou 8. pessoalmente, para a maioria dos problemas, eu prefiro a conexão 6. de qualquer forma, depois de separar cada "impressão de pata" como uma região conectada, deve ser fácil o suficiente percorrer a região e encontrar os máximos. depois de encontrar o máximo, você poderá aumentar iterativamente a região até atingir um limite predeterminado para identificá-lo como um "dedo do pé" determinado.

Um problema sutil aqui é que, assim que você começa a usar as técnicas de visão computacional para identificar algo como uma pata direita / esquerda / dianteira / traseira e começa a observar os dedos dos pés individuais, deve começar a levar em consideração rotações, inclinações e traduções. isso é realizado através da análise dos chamados "momentos". existem alguns momentos diferentes a serem considerados nas aplicações de visão:

momentos centrais: invariantes da tradução momentos normalizados: invariantes da escala e da tradução hu momentos: invariantes da tradução, escala e rotação

mais informações sobre momentos podem ser encontradas pesquisando "momentos da imagem" no wiki.



4

Parece que você pode trapacear um pouco usando o algoritmo do jetxee. Ele está achando bem os três primeiros dedos, e você deve ser capaz de adivinhar onde o quarto se baseia.


4

Problema interessante. A solução que eu tentaria é a seguinte.

  1. Aplique um filtro passa-baixo, como convolução com uma máscara gaussiana 2D. Isso fornecerá vários valores (provavelmente, mas não necessariamente ponto flutuante).

  2. Realize uma supressão 2D não máxima usando o raio aproximado conhecido de cada almofada de pata (ou dedo do pé).

Isso deve fornecer as posições máximas sem ter vários candidatos próximos. Apenas para esclarecer, o raio da máscara na etapa 1 também deve ser semelhante ao raio usado na etapa 2. Esse raio pode ser selecionável ou o veterinário pode explicitamente medi-la de antemão (variará com a idade / raça / etc).

Algumas das soluções sugeridas (mudança média, redes neurais etc.) provavelmente funcionarão até certo ponto, mas são excessivamente complicadas e provavelmente não são ideais.


Eu tenho 0 experiência com matrizes de convolução e filtros gaussianos, então você gostaria de mostrar como isso funcionaria no meu exemplo?
Ivo Flipse 10/09/10

3

Bem, aqui está um código simples e não muito eficiente, mas para esse tamanho de um conjunto de dados está bom.

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]

Basicamente, apenas faço uma matriz com a posição do canto superior esquerdo e a soma de cada quadrado 2x2 e classifico pela soma. Então pego o quadrado 2x2 com a soma mais alta de contenção, coloco-o na bestmatriz e removo todos os outros quadrados 2x2 que usaram qualquer parte dele que acabou de remover o quadrado 2x2.

Parece funcionar bem, exceto com a última pata (aquela com a menor soma na extremidade direita da sua primeira foto), verifica-se que existem outros dois quadrados 2x2 elegíveis com uma soma maior (e eles têm uma soma igual a entre si). Um deles ainda seleciona um quadrado do seu quadrado 2x2, mas o outro está à esquerda. Felizmente, por sorte, vemos escolher mais do que você gostaria, mas isso pode exigir que outras idéias sejam usadas para obter o que você realmente quer o tempo todo.


Acho que seus resultados são os mesmos da resposta de @ Jextee. Ou pelo menos parece que estou testando.
Ivo Flipse


1

Talvez uma abordagem ingênua seja suficiente aqui: crie uma lista de todos os quadrados 2x2 do seu avião, ordene-os pela soma (em ordem decrescente).

Primeiro, selecione o quadrado de maior valor na sua "lista de patas". Em seguida, escolha iterativamente quatro dos próximos melhores quadrados que não se cruzam com nenhum dos quadrados encontrados anteriormente.


Na verdade, fiz uma lista com todas as somas 2x2, mas quando as pedi, não tinha idéia de como compará-las iterativamente. Meu problema era que, ao classificá-lo, perdi o controle das coordenadas. Talvez eu possa colocá-los em um dicionário, com as coordenadas como a chave.
Ivo Flipse 10/09/10

Sim, algum tipo de dicionário seria necessário. Eu teria assumido que sua representação da grade já é algum tipo de dicionário.
Johannes Charra

Bem, a imagem que você vê acima é uma matriz numpy. O restante está atualmente armazenado em listas multidimensionais. Provavelmente seria melhor parar de fazer isso, embora eu não sou tão familiarizado com a iteração sobre dicionários
Ivo Flipse

1

Existem várias e extensas peças de software disponíveis na comunidade de astronomia e cosmologia - esta é uma área significativa de pesquisa tanto histórica quanto atualmente.

Não se assuste se você não é um astrônomo - alguns são fáceis de usar fora do campo. Por exemplo, você pode usar astropy / photutils:

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

[Parece um pouco grosseiro repetir seu código de exemplo curto aqui.]

Uma lista incompleta e ligeiramente tendenciosa de técnicas / pacotes / links que podem ser interessantes é dada abaixo - adicione mais nos comentários e atualizarei esta resposta conforme necessário. Obviamente, há uma troca de precisão versus recursos de computação. [Honestamente, existem muitos exemplos de códigos em uma única resposta como essa, por isso não tenho certeza se essa resposta será ou não correta.]

Extrator de origem https://www.astromatic.net/software/sextractor

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

Desafio de busca de fonte ASKAP / EMU: https://arxiv.org/abs/1509.03931

Você também pode procurar por desafios de extração de fonte Planck e / ou WMAP.

...


0

E se você prosseguir passo a passo: primeiro localize o máximo global, processe, se necessário, os pontos circundantes, considerando seu valor, depois defina a região encontrada para zero e repita para a próxima.


Hummm, essa configuração como zero o removeria de quaisquer cálculos adicionais, o que seria útil.
Ivo Flipse 10/09/10

Em vez de definir como zero, você pode calcular uma função gaussiana com parâmetros escolhidos a dedo e subtrair os valores encontrados das leituras de pressão originais. Portanto, se o dedo estiver pressionando seus sensores, encontrando o ponto de pressão mais alto, use-o para diminuir o efeito desse dedo nos sensores, eliminando as células vizinhas com valores de alta pressão. pt.wikipedia.org/wiki/File:Gaussian_2d.png
Daniyar

Gostaria de mostrar um exemplo com base nos meus dados de amostra @Daniyar? Como eu não sou muito familiarizado com esse tipo de processamento de dados
Ivo Flipse

0

Não sei se isso responde à pergunta, mas parece que você pode apenas procurar os n picos mais altos que não têm vizinhos.

Aqui está a essência. Observe que está em Ruby, mas a ideia deve ser clara.

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

Eu vou tentar e dar uma olhada e ver se eu posso convertê-lo em código Python :-)
Ivo Flipse

Inclua o código na própria publicação, em vez de vincular a uma essência, se tiver uma duração razoável.
agf
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.