Как отсортировать мои лапы? - PullRequest
119 голосов
/ 21 декабря 2010

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

alt text

Я вручную пометил лапы (RF = правая передняя часть, RH = правая задняя часть, LF = левая передняя сторона, LH = левая задняя часть).

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

Моей первоначальной мыслью было использовать эвристику для сортировки, например:

  • Существует весовое отношение ~ 60-40% между передней и задней лапами;
  • Задние лапы обычно меньше по поверхности;
  • Лапы (часто) пространственно разделены на левую и правую.

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

Кроме того, аннотация, предложенная Джо, иногда путается и не учитывает, как на самом деле выглядит лапа.

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

alt text

Так что я ищу лучший способ отсортировать результаты с соответствующей лапой.

Для всех, кто принял вызов, Я выбрал словарь с всеми нарезанными массивами, которые содержат данные о давлении каждой лапы (в комплекте по измерениям) и срез, который описывает их местоположение (расположение на тарелке и во времени).

Для уточнения: walk_sliced_data - это словарь, который содержит ['ser_3', 'ser_2', 'sel_1', 'sel_2', 'ser_1', 'sel_3'], которые являются названиями измерений. Каждое измерение содержит другой словарь, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] (пример из «sel_1»), который представляет воздействия, которые были извлечены.

Также обратите внимание, что «ложные» удары, например, при частичном измерении лапы (в пространстве или времени), можно игнорировать. Они полезны только потому, что могут помочь распознать шаблон, но не будет проанализировано

И для всех, кто заинтересован, Я веду блог со всеми обновлениями, касающимися проекта!

Ответы [ 3 ]

122 голосов
/ 28 декабря 2010

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

В качестве примечания,Я использую полный набор данных, который Ivo предоставил ссылку на в своем оригинальном вопросе .Это серия rar-файлов (по одному на собаку), каждый из которых содержит несколько разных экспериментальных прогонов, хранящихся в виде массивов ascii.Вместо того, чтобы пытаться скопировать и вставить отдельные примеры кода в этот вопрос, вот ртутный репозиторий bitbucket с полным, автономным кодом.Вы можете клонировать его с помощью

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


Обзор

Существуют два основных подхода к проблеме, как вы отметили вваш вопрос.Я на самом деле собираюсь использовать оба по-разному.

  1. Используйте (временной и пространственный) порядок ударов лапы, чтобы определить, какая лапа какая.
  2. Попробуйте определить«pawprint» основан исключительно на его форме.

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

Таким образом, мы можем использовать измерения, в которых он работал, для создания учебного набора данных (из ~ 2000 ударов лапы от ~ 30 разных собак), чтобы распознать, какая лапа есть, и проблема сводится к контролируемой классификации(С некоторыми дополнительными складками ... Распознавание изображений немного сложнее, чем «обычная» контролируемая задача классификации).


Анализ рисунка

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

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

Normal Impact Sequence

Однако, даже если все "нормально", это не работает.Это связано с трапециевидной формой рисунка.Задняя лапа пространственно отстает от предыдущей передней лапы.

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

Missed Hind Paw

Тем не менее,мы можем использовать форму рисунка удара лапы, чтобы определить, когда это произошло, и начали ли мы с левой или правой передней лапы.(Я фактически игнорирую проблемы с последним ударом здесь. Однако добавить его не сложно.)

def group_paws(data_slices, time):   
    # Sort slices by initial contact time
    data_slices.sort(key=lambda s: s[-1].start)

    # Get the centroid for each paw impact...
    paw_coords = []
    for x,y,z in data_slices:
        paw_coords.append([(item.stop + item.start) / 2.0 for item in (x,y)])
    paw_coords = np.array(paw_coords)

    # Make a vector between each sucessive impact...
    dx, dy = np.diff(paw_coords, axis=0).T

    #-- Group paws -------------------------------------------
    paw_code = {0:'LF', 1:'RH', 2:'RF', 3:'LH'}
    paw_number = np.arange(len(paw_coords))

    # Did we miss the hind paw impact after the first 
    # front paw impact? If so, first dx will be positive...
    if dx[0] > 0: 
        paw_number[1:] += 1

    # Are we starting with the left or right front paw...
    # We assume we're starting with the left, and check dy[0].
    # If dy[0] > 0 (i.e. the next paw impacts to the left), then
    # it's actually the right front paw, instead of the left.
    if dy[0] > 0: # Right front paw impact...
        paw_number += 2

    # Now we can determine the paw with a simple modulo 4..
    paw_codes = paw_number % 4
    paw_labels = [paw_code[code] for code in paw_codes]

    return paw_labels

Несмотря на все это, он часто работает неправильно.Многие собаки в полном наборе данных, кажется, бегут, и удары лапы не следуют тому же временному порядку, что и при ходьбе собаки.(Или, возможно, у собаки просто серьезные проблемы с бедром ...)

Abnormal Impact Sequence

К счастью, мы все еще можем программно определять, следуют ли удары лапами по нашему ожидаемому пространственному образцу:

def paw_pattern_problems(paw_labels, dx, dy):
    """Check whether or not the label sequence "paw_labels" conforms to our
    expected spatial pattern of paw impacts. "paw_labels" should be a sequence
    of the strings: "LH", "RH", "LF", "RF" corresponding to the different paws"""
    # Check for problems... (This could be written a _lot_ more cleanly...)
    problems = False
    last = paw_labels[0]
    for paw, dy, dx in zip(paw_labels[1:], dy, dx):
        # Going from a left paw to a right, dy should be negative
        if last.startswith('L') and paw.startswith('R') and (dy > 0):
            problems = True
            break
        # Going from a right paw to a left, dy should be positive
        if last.startswith('R') and paw.startswith('L') and (dy < 0):
            problems = True
            break
        # Going from a front paw to a hind paw, dx should be negative
        if last.endswith('F') and paw.endswith('H') and (dx > 0):
            problems = True
            break
        # Going from a hind paw to a front paw, dx should be positive
        if last.endswith('H') and paw.endswith('F') and (dx < 0):
            problems = True
            break
        last = paw
    return problems

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

Набор обучающих данных

Из классификаций на основе шаблонов, где он работал правильно, мы можем создать очень большой набор обучающих данных с правильно классифицированными лапами (~ 2400 ударов лап от32 разных собаки!).

Теперь мы можем начать смотреть на то, как выглядит «средняя» передняя левая сторона и т. Д., Лапа.

Чтобы сделать это, нам нужна какая-то «метрика лапы», которая одинакова для любой собаки. (В полном наборе данных есть как очень большие, так и очень маленькие собаки!) Отпечаток лапы от ирландского эльхунда будет гораздо шире и намного «тяжелее», чем оттиск лапы у игрушечного пуделя. Нам нужно изменить масштаб каждого отпечатка лапы, чтобы а) они имели одинаковое количество пикселей и б) значения давления были стандартизированы. Чтобы сделать это, я пересэмплировал каждый отпечаток лапы на сетке 20x20 и перемасштабировал значения давления на основе максимального, минимального и среднего значения давления для удара лапы.

def paw_image(paw):
    from scipy.ndimage import map_coordinates
    ny, nx = paw.shape

    # Trim off any "blank" edges around the paw...
    mask = paw > 0.01 * paw.max()
    y, x = np.mgrid[:ny, :nx]
    ymin, ymax = y[mask].min(), y[mask].max()
    xmin, xmax = x[mask].min(), x[mask].max()

    # Make a 20x20 grid to resample the paw pressure values onto
    numx, numy = 20, 20
    xi = np.linspace(xmin, xmax, numx)
    yi = np.linspace(ymin, ymax, numy)
    xi, yi = np.meshgrid(xi, yi)  

    # Resample the values onto the 20x20 grid
    coords = np.vstack([yi.flatten(), xi.flatten()])
    zi = map_coordinates(paw, coords)
    zi = zi.reshape((numy, numx))

    # Rescale the pressure values
    zi -= zi.min()
    zi /= zi.max()
    zi -= zi.mean() #<- Helps distinguish front from hind paws...
    return zi

После всего этого мы наконец можем взглянуть на то, как выглядит средняя левая передняя, ​​задняя правая и т. Д. Лапа. Обратите внимание, что это усреднено по> 30 собакам очень разных размеров, и мы, кажется, получаем последовательные результаты!

Average Paws

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

Mean Paw

Теперь мы можем проанализировать отличия от среднего значения, которые немного легче распознать:

Differential Paws

Распознавание лап на основе изображений

Хорошо ... Наконец-то у нас есть набор шаблонов, с которыми мы можем начать пытаться сопоставить лапы. Каждую лапу можно рассматривать как 400-мерный вектор (возвращаемый функцией paw_image), который можно сравнить с этими четырьмя 400-мерными векторами.

К сожалению, если мы просто используем «нормальный» контролируемый алгоритм классификации (т.е. находим, какой из 4 шаблонов ближе всего к отпечатку конкретной лапы, используя простое расстояние), он не будет работать согласованно. На самом деле, это не намного лучше, чем случайный шанс в наборе обучающих данных.

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

Eigenpaws

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

Поскольку у нас больше тренировочных образов, чем размеров (2400 против 400), нет необходимости создавать «модную» линейную алгебру для скорости. Мы можем работать напрямую с ковариационной матрицей набора обучающих данных:

def make_eigenpaws(paw_data):
    """Creates a set of eigenpaws based on paw_data.
    paw_data is a numdata by numdimensions matrix of all of the observations."""
    average_paw = paw_data.mean(axis=0)
    paw_data -= average_paw

    # Determine the eigenvectors of the covariance matrix of the data
    cov = np.cov(paw_data.T)
    eigvals, eigvecs = np.linalg.eig(cov)

    # Sort the eigenvectors by ascending eigenvalue (largest is last)
    eig_idx = np.argsort(eigvals)
    sorted_eigvecs = eigvecs[:,eig_idx]
    sorted_eigvals = eigvals[:,eig_idx]

    # Now choose a cutoff number of eigenvectors to use 
    # (50 seems to work well, but it's arbirtrary...
    num_basis_vecs = 50
    basis_vecs = sorted_eigvecs[:,-num_basis_vecs:]

    return basis_vecs

Эти basis_vecs являются "собственными лапами".

Eigenpaws

Чтобы использовать их, мы просто ставим точки (т.е. матричное умножение) каждое изображение лапы (как 400-мерный вектор, а не изображение 20x20) базовыми векторами. Это дает нам 50-мерный вектор (один элемент на базисный вектор), который мы можем использовать для классификации изображения. Вместо того чтобы сравнивать изображение 20х20 с изображением 20х20 каждой лапы «шаблона», мы сравниваем трансформированное 50-мерное изображение с каждой трансформированной 50-мерной лапой шаблона. Это гораздо менее чувствительно к небольшим изменениям в точном расположении каждого пальца и т. Д. И в основном сводит размерность проблемы к соответствующим размерам.

Классификация лап на основе собственной лапы

Теперь мы можем просто использовать расстояние между 50-мерными векторами и «шаблонными» векторами для каждой ноги, чтобы классифицировать, какая лапа есть какая:

codebook = np.load('codebook.npy') # Template vectors for each paw
average_paw = np.load('average_paw.npy')
basis_stds = np.load('basis_stds.npy') # Needed to "whiten" the dataset...
basis_vecs = np.load('basis_vecs.npy')
paw_code = {0:'LF', 1:'RH', 2:'RF', 3:'LH'}
def classify(paw):
    paw = paw.flatten()
    paw -= average_paw
    scores = paw.dot(basis_vecs) / basis_stds
    diff = codebook - scores
    diff *= diff
    diff = np.sqrt(diff.sum(axis=1))
    return paw_code[diff.argmin()]

Вот некоторые результаты: alt text alt text alt text

Остальные проблемы

Есть все еще некоторые проблемы, особенно с собаками, слишком маленькими, чтобы сделать четкий отпечаток ноги ... (Это лучше всего работает с большими собаками, поскольку пальцы ног более четко отделены при разрешении датчика.) Кроме того, частичные отпечатки пальцев не являютсяПризнано с этой системой, в то время как они могут быть с системой на основе трапециевидного шаблона.

Однако, поскольку анализ собственной лапы по своей сути использует метрику расстояния, мы можем классифицировать лапы в обоих направлениях и вернуться к системе, основанной на трапециевидных шаблонах, когда наименьшее расстояние анализа собственной лапы от «кодовой книги» законченокакой-то порог.Я еще не реализовал это.

Фу ... Это было долго!Моя шляпа снята с Иво за такой интересный вопрос!

4 голосов
/ 22 декабря 2010

Используя информацию, основанную исключительно на длительности, я думаю, вы могли бы применить методы моделирования кинематики;а именно обратная кинематика .В сочетании с ориентацией, длиной, продолжительностью и общим весом это дает некоторый уровень периодичности, который, я надеюсь, мог бы стать первым шагом в попытке решить вашу проблему «сортировки лап».

Все эти данные могут быть использованысоздать список ограниченных многоугольников (или кортежей), который можно использовать для сортировки по размеру шага, а затем по paw-ness [index].

2 голосов
/ 23 декабря 2010

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

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