Обнаружение пиков в 2D-массиве

Я помогаю ветеринарной клинике измерять давление под собачьей лапой. Я использую Python для анализа данных, и теперь я застреваю, пытаясь разделить лапы на (анатомические) субрегионы.

Я сделал 2D-массив каждой лапы, который состоит из максимальных значений для каждого датчика, который был загружен лапой со временем. Вот пример одной лапы, где я использовал Excel для рисования областей, которые я хочу "обнаружить". Это 2 на 2 ячейки вокруг датчика с локальными максимумами, которые вместе имеют наибольшую сумму.

alt text

Итак, я попробовал несколько экспериментов и решил просто искать максимальные значения для каждого столбца и строки (не может выглядеть в одном направлении из-за формы лапы). Это, по-видимому, "обнаруживает" расположение отдельных пальцев ноги довольно хорошо, но также отмечает соседние датчики.

alt text

Итак, что было бы лучшим способом сказать Python, какие из этих максимальных значений я хочу?

Примечание. Квадраты 2x2 не могут пересекаться, так как они должны быть отдельными пальцами!

Также я взял 2x2 в качестве удобства, любое более продвинутое решение приветствуется, но я просто научный сотрудник по человеческому движению, поэтому я не настоящий программист или математик, поэтому, пожалуйста, держите его "простым".

Здесь версия, которая может быть загружена с помощью np.loadtxt


Результаты

Итак, я попробовал решение @jextee (см. результаты ниже). Как вы можете видеть, он очень хорошо работает на передних лапах, но он работает менее хорошо для задних ног.

Более конкретно, он не может распознать маленький пик, который имеет четвертый палец. Это, очевидно, связано с тем, что петля смотрит сверху вниз на самое низкое значение, не принимая во внимание, где это.

Кто-нибудь знает, как настроить алгоритм @jextee, чтобы он мог также найти 4-й носок?

alt text

Поскольку я еще не обработал никаких других испытаний, я не могу предоставить какие-либо другие образцы. Но данные, которые я дал раньше, были средними для каждой лапы. Этот файл представляет собой массив с максимальными данными из 9 лапок в том порядке, в котором они соприкасались с пластиной.

Это изображение показывает, как они были пространственно распределены по пластине.

alt text

Обновление:

Я создал блог для всех заинтересованных и Я установил SkyDrive со всеми необработанными измерениями. Таким образом, каждому, кто запрашивает больше данных, больше энергии для вас!


Новое обновление:

Итак, после получения справки, касающейся обнаружения лапы и сортировки лапы, я, наконец, в состоянии проверить обнаружение носка для каждой лапы! Оказывается, он не работает так хорошо ни в чем, кроме лап размером, как у моего собственного примера. Конечно, задним числом, это моя собственная ошибка для выбора 2x2 так произвольно.

Вот хороший пример того, где он идет не так: гвоздь распознается как палец, а "пятка" настолько широк, что он распознается дважды!

alt text

Лапа слишком большая, поэтому с размером 2x2 без перекрытия несколько признаков обнаружения пальцев. Другое дело, у маленьких собак часто не удается найти пятый палец, который, как я подозреваю, вызван слишком большой площадью 2x2.

После пробуя текущее решение во всех моих измерениях. Я пришел к ошеломляющему выводу, что почти для всех моих маленьких собак он не нашел 5-й палец и что более 50% ударов для крупных собак он найдет больше!

Поэтому я должен изменить его. Моя собственная догадка меняла размер neighborhood на меньшее, чем у маленьких собак, и больше для больших собак. Но generate_binary_structure не позволит мне изменить размер массива.

Поэтому я надеюсь, что у кого-то еще есть лучшее предложение для определения пальцев ног, возможно, с областью области пальца с размером лапы?

Ответы

Ответ 1

Я обнаружил пики, используя локальный максимальный фильтр. Вот результат на вашем первом наборе данных из 4 лап: Peaks detection result

Я также запускал его на втором наборе данных из 9 лапок и он тоже работал.

Вот как вы это делаете:

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

Все, что вам нужно сделать, это использовать scipy.ndimage.measurements.label в маске для маркировки всех отдельных объектов. Затем вы сможете играть с ними индивидуально.

Примечание, что метод работает хорошо, потому что фон не шумный. Если бы это было так, то в фоновом режиме вы обнаружили бы кучу других нежелательных пиков. Другим важным фактором является размер окрестности. Вам нужно будет отрегулировать его, если размер пика изменится (он должен оставаться примерно пропорциональным).

Ответ 2

Решение

Файл данных: paw.txt. Исходный код:

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"

Выход без перекрывающихся квадратов. Кажется, что те же самые области выбраны, как в вашем примере.

Некоторые комментарии

Сложная часть - рассчитать суммы всех квадратов 2x2. Я предположил, что вам нужны все они, поэтому могут быть некоторые перекрытия. Я использовал срезы для вырезания первых/последних столбцов и строк из исходного 2D-массива, а затем перекрывал их все вместе и вычислял суммы.

Чтобы понять это лучше, создайте массив 3x3:

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

Затем вы можете взять его фрагменты:

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

Теперь представьте, что вы складываете их один над другим и суммируете элементы в тех же позициях. Эти суммы будут точно такими же суммами по квадратам 2x2 с верхним левым углом в том же положении:

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

Если у вас есть суммы по квадратам 2x2, вы можете использовать max, чтобы найти максимум, или sort или sorted, чтобы найти пики.

Чтобы запомнить позиции пиков, я соединяю каждое значение (сумму) с его порядковым положением в сплющенном массиве (см. zip). Затем я вычисляю позицию столбца/столбца, когда я печатаю результаты.

Примечания

Я допустил, чтобы квадраты 2x2 перекрывались. Отредактированная версия отфильтровывает некоторые из них, так что в результатах появляются только неперекрывающиеся квадраты.

Выбор пальцев (идея)

Другая проблема заключается в том, как выбрать то, что может быть пальцем из всех пиков. У меня есть идея, которая может или не может работать. У меня нет времени для его реализации прямо сейчас, поэтому просто псевдокод.

Я заметил, что если передние пальцы остаются на почти идеальном круге, задний палец должен быть внутри этого круга. Кроме того, передние пальцы расположены более или менее равномерно. Мы можем попытаться использовать эти эвристические свойства для обнаружения пальцев.

Псевдокод:

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

Это подход грубой силы. Если N относительно мало, то я думаю, что это выполнимо. Для N = 12 существует C_12 ^ 5 = 792 комбинаций, раз 5 способов выбрать задний палец, поэтому 3960 случаев для оценки для каждой лапы.

Ответ 3

Это проблема с регистрацией изображений. Общая стратегия:

  • Имейте известный пример или какой-либо предшествующий данные.
  • Примените свои данные к примеру или поместите пример в свои данные.
  • Это помогает, если ваши данные грубо выровнены в первую очередь.

Здесь грубый и готовый подход, "самая тупая вещь, которая могла бы работать":

  • Начните с пяти пальцевых координат примерно в том месте, которое вы ожидаете.
  • С каждым из них итеративно поднимайтесь на вершину холма. то есть с учетом текущей позиции, переместитесь в максимальный соседний пиксель, если его значение больше, чем текущий пиксель. Остановитесь, когда ваши пальцы ног перестали двигаться.

Чтобы противостоять проблеме ориентации, у вас может быть 8 первоначальных настроек для основных направлений (север, северо-восток и т.д.). Запускайте каждый из них индивидуально и удаляйте любые результаты, когда два или более пальцев заканчиваются на одном и том же пикселе. Я подумаю об этом еще немного, но такие вещи все еще исследуются при обработке изображений - правильных ответов нет!

Немного более сложная идея: (взвешенная) кластеризация K-средних. Это не так уж плохо.

  • Начните с пяти пальцевых координат, но теперь это "кластерные центры".

Затем итерации до сходимости:

  • Назначьте каждый пиксель ближайшему кластеру (просто создайте список для каждого кластера).
  • Рассчитайте центр масс каждого кластера. Для каждого кластера это: Сумма (координата * значение интенсивности)/Сумма (координата)
  • Переместите каждый кластер в новый центр масс.

Этот метод почти наверняка даст гораздо лучшие результаты, и вы получите массу каждого кластера, которая может помочь в идентификации пальцев ног.

(Опять же, вы указали количество кластеров спереди. С кластеризацией вы должны указать плотность так или иначе: либо выберите количество кластеров, подходящее в этом случае, либо выберите радиус кластера и посмотрите, как многие из них в конечном итоге. Пример последнего mean-shift.)

Извините за отсутствие деталей реализации или других особенностей. Я бы это записал, но у меня есть крайний срок. Если на следующей неделе ничего не сработает, дайте мне знать, и я сделаю это.

Ответ 4

Эта проблема была изучена в некоторой степени физиками. Существует хорошая реализация в ROOT. Посмотрите на классы TSpectrum (особенно TSpectrum2 для вашего случая) и документацию для них.

Литература:

  • M.Morhac et al.: методы устранения фона для спектров гамма-спектров многомерных совпадений. Ядерные приборы и методы в физических исследованиях 401 (1997) 113-132.
  • M.Morhac et al.: эффективная одно- и двумерная деконволюция золота и ее применение к разложению спектров гамма-лучей. Ядерные приборы и методы в физических исследованиях A 401 (1997) 385-408.
  • M.Morhac et al.: Идентификация пиков в многомерных совпадениях гамма-спектров. Ядерные приборы и методы в физике исследований A 443 (2000), 108-125.

... и для тех, у кого нет доступа к подписке на NIM:

Ответ 5

Всего несколько идей с головы:

  • возьмите градиент (производную) от сканирования, посмотрите, устраняет ли это ложные вызовы
  • взять максимум локальных максимумов

Вы также можете взглянуть на OpenCV, он получил довольно приличный API Python и может иметь некоторые функции, которые вы бы использовали находят полезным.

Ответ 6

Вот идея: вы вычисляете (дискретный) лапласиан изображения. Я ожидал бы, что это будет (отрицательное и) большое в максимумах, более драматичным, чем в исходных изображениях. Таким образом, максимумы можно было бы легче найти.

Вот еще одна идея: если вы знаете типичный размер пятен высокого давления, вы можете сначала сгладить свое изображение, свернув его гауссовым размером того же размера. Это может дать вам более простые изображения для обработки.

Ответ 7

Физическое решение:
Определите 5 лапа-маркеров, идентифицированных по их позициям X_i, и запустите их со случайными позициями. Определите некоторую функцию энергии, сочетающую некоторую награду за расположение маркеров в позициях лап с некоторым наказанием за перекрытие маркеров; скажем:

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

(S(X_i) - средняя сила в квадрате 2x2 вокруг X_i, alfa - параметр, который должен быть экспериментально достигнут)

Теперь время, чтобы сделать магию Метрополиса-Гастингса:
1. Выберите случайный маркер и переместите его на один пиксель в случайном направлении.
2. Рассчитайте dE, разницу в энергии, вызванную этим движением.
3. Получите равномерное случайное число от 0-1 и назовите его r.
4. Если dE<0 или exp(-beta*dE)>r, примите ход и перейдите к 1; если нет, отмените ход и перейдите к 1.
Это должно повторяться до тех пор, пока маркеры не будут сходиться к лапам. Бета контролирует сканирование для оптимизации компромисса, поэтому его также следует оптимизировать экспериментально; он может также постоянно увеличиваться со временем моделирования (имитированный отжиг).

Ответ 8

спасибо за необработанные данные. Я нахожусь в поезде, и это насколько я получил (моя остановка подходит). Я массировал ваш txt файл с помощью регулярных выражений и наложил его на html-страницу с некоторым javascript для визуализации. Я делюсь этим, потому что некоторые, как и я, могут найти его более легко взломанным, чем python.

Я думаю, что хороший подход будет масштабным и поворотным инвариантом, и моим следующим шагом будет исследование смесей гауссонов. (каждая лапка - центр гауссова).

    <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

Ответ 9

Вот еще один подход, который я использовал при выполнении чего-то подобного для большого телескопа:

1) Найдите самый высокий пиксель. Как только у вас это получится, найдите вокруг него лучшее место для 2x2 (возможно, максимизируя сумму 2x2) или сделайте 2d гауссовскую подгонку внутри субобласти, например, 4x4 с центром на самом высоком пикселе.

Затем установите эти 2x2 пиксели, которые вы нашли равными нулю (или, возможно, 3x3) вокруг центра пика

вернуться к 1) и повторить до тех пор, пока наивысший пик не опустится ниже порога шума, или у вас есть все необходимые пальцы.

Ответ 10

Возможно, стоит попробовать с нейронными сетями, если вы можете создать некоторые данные обучения... но для этого требуется много образцов, аннотированных вручную.

Ответ 11

грубая схема...

вы, вероятно, захотите использовать алгоритм связанных компонентов, чтобы изолировать каждую область лапы. wiki имеет достойное описание этого (с некоторым кодом) здесь: http://en.wikipedia.org/wiki/Connected_Component_Labeling

вам нужно будет принять решение о том, использовать ли 4 или 8 подключений. лично, для большинства проблем я предпочитаю 6-связность. во всяком случае, как только вы отделите каждую "лапкую печать" как связанную область, должно быть достаточно легко пройти через область и найти максимумы. как только вы найдете максимумы, вы можете итеративно увеличить область до тех пор, пока не достигнете заданного порога, чтобы идентифицировать ее как данный "носок".

одна тонкая проблема заключается в том, что, как только вы начнете использовать компьютерные методы зрения, чтобы идентифицировать что-то как правую/левую/переднюю/заднюю лапу, и вы начинаете смотреть на отдельные пальцы ног, вам нужно начинать принимать вращения, перекос и переводы в учетную запись. это достигается путем анализа так называемых "моментов". в приложениях видения есть несколько разных моментов:

центральные моменты: трансляционный инвариант нормированные моменты: масштабирование и трансляционный инвариант hu моменты: перевод, масштаб и инвариант вращения

Дополнительная информация о моментах может быть найдена путем поиска "моментов изображения" в вики.

Ответ 12

Ну, вот какой простой и не очень эффективный код, но для этого размера набора данных это нормально.

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]

Я в основном просто делаю массив с позицией верхнего левого и суммой каждого квадрата 2x2 и сортирую его по сумме. Затем я беру квадрат 2x2 с наивысшей суммой аргументов, помещаю его в массив best и удаляю все остальные квадраты 2x2, которые использовали любую часть этого только что снятого квадрата 2x2.

Кажется, что он работает отлично, за исключением последней лапы (той, которая имеет наименьшую сумму справа на первой картинке), оказывается, что есть два других подходящих квадрата 2x2 с большей суммой (и они имеют равная сумма друг другу). Один из них по-прежнему выбирает один квадрат из квадрата 2x2, а другой - влево. К счастью, к счастью, мы выбираем больше того, что вам нужно, но для этого могут потребоваться некоторые другие идеи, чтобы получить то, что вы действительно хотите все время.

Ответ 13

Кажется, вы можете немного обмануть алгоритм jetxee. Он набирает первые три пальца в порядке, и вы должны угадать, где четвертый базирующийся на этом.

Ответ 14

Возможно, вы можете использовать что-то вроде Гауссовских моделей смешивания. Здесь пакет Python для выполнения GMM (только что сделал поиск Google) http://www.ar.media.kyoto-u.ac.jp/members/david/softwares/em/

Ответ 15

Интересная проблема. Решение, которое я бы постарался, следующее.

  • Примените фильтр нижних частот, например свертку с 2D-гауссовской маской. Это даст вам кучу значений (возможно, но не обязательно с плавающей запятой).

  • Выполните двумерное немаксимальное подавление с использованием известного приблизительного радиуса каждой лапы (или пальца ноги).

Это должно дать вам максимальные позиции, не имея нескольких кандидатов, которые находятся близко друг к другу. Для пояснения радиус маски на шаге 1 также должен быть аналогичен радиусу, использованному на шаге 2. Этот радиус может быть выбран, или ветеринар может заранее заранее измерить его (он будет варьироваться в зависимости от возраста/породы/и т.д.).

Некоторые предлагаемые решения (средний сдвиг, нейронные сети и т.д.), вероятно, будут работать в некоторой степени, но слишком сложны и, вероятно, не идеальны.

Ответ 16

Я уверен, что у вас достаточно времени для продолжения, но я не могу не предлагать использовать метод кластеризации k-mean. k-mean - это неконтролируемый алгоритм кластеризации, который будет принимать ваши данные (в любом количестве измерений - я бы это сделал в 3D) и упорядочил его на k кластеров с четкими границами. Здесь хорошо, потому что вы точно знаете, сколько пальцев у этих собак (должно) иметь.

Кроме того, он реализован в Scipy, который действительно хорош (http://docs.scipy.org/doc/scipy/reference/cluster.vq.html).

Вот пример того, что он может сделать для пространственного разрешения 3D-кластеров: enter image description here

То, что вы хотите сделать, немного отличается (2D и включает значения давления), но я все же думаю, что вы могли бы сделать это.

Ответ 17

Используя постоянную гомологию для анализа вашего набора данных, я получаю следующий результат (нажмите, чтобы увеличить):

Result

Это 2D-версия метода обнаружения пиков, описанного в этом SO-ответе. На приведенном выше рисунке просто показаны 0-мерные классы устойчивой гомологии, отсортированные по сохранности.

Я сделал масштабирование исходного набора данных в 2 раза с помощью scipy.misc.imresize(). Однако обратите внимание, что я рассматривал четыре лапы как один набор данных; разделение его на четыре облегчит задачу.

Методология. Идея этого довольно проста: рассмотрим график функций функции, который присваивает каждому пикселю свой уровень. Это выглядит так:

3D function graph

Теперь рассмотрим уровень воды на высоте 255, который непрерывно спускается до более низких уровней. На локальных максимумах всплывают острова (рождение). В седловых точках сливаются два острова; мы считаем, что нижний остров сливается с высшим островом (смерть). Так называемая диаграмма персистентности (классов 0-го класса гомологии, наши острова) изображает смерть над значениями рождения всех островов:

Persistence diagram

Сохранение острова - это тогда разница между уровнем рождаемости и смерти; вертикальное расстояние точки до серой главной диагонали. Фигура обозначает острова, уменьшая настойчивость.

Самая первая картина показывает места рождения островов. Этот метод не только дает локальные максимумы, но и количественно определяет их "значимость" вышеупомянутой стойкостью. Затем можно было бы отфильтровать все острова со слишком низкой настойчивостью. Однако в вашем примере каждый остров (т.е. Каждый локальный максимум) является пиком, который вы ищете.

Ответ 18

Возможно, здесь наивного подхода достаточно: постройте список всех квадратов 2x2 на своем плане, закажите их по их сумме (в порядке убывания).

Сначала выберите квадрат наивысшего значения в свой "список лапы". Затем итеративно выберите 4 из следующих лучших квадратов, которые не пересекаются ни с одним из ранее найденных квадратов.

Ответ 19

Что делать, если вы шаг за шагом продолжаете: сначала найдите глобальный максимум, обработайте, если необходимо, окружающие точки, учитывая их значение, затем установите найденную область в ноль и повторите для следующей.

Ответ 20

Я не уверен, что это отвечает на вопрос, но кажется, что вы можете просто искать n самых высоких пиков, у которых нет соседей.

Вот суть. Обратите внимание, что это в Ruby, но идея должна быть ясной.

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