Як сортувати мої лапи?


121

У своєму попередньому запитанні я отримав чудову відповідь, яка допомогла мені виявити, де лапа потрапила в напірну пластину, але зараз я намагаюся пов’язати ці результати з відповідними лапами:

alt текст

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

Як ви бачите, чітко повторюється закономірність, і вона повертається майже в кожному вимірі. Ось посилання на презентацію 6 випробувань, які вручну анотувались.

Моя початкова думка полягала в тому, щоб використовувати евристику для сортування, наприклад:

  • Існує ~ 60-40% співвідношення ваги між передніми та задніми лапами;
  • Задні лапи, як правило, менше поверхні;
  • Лапи (часто) просторово розділені на ліву і праву.

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

Крім того, анотація, запропонована Джо, іноді заплутується і не враховує, як насправді виглядає лапа.

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

alt текст

Тому я шукаю кращого способу сортування результатів відповідною лапою.

Для всіх, хто не вирішує проблему, я підібрав словник із усіма нарізаними масивами, які містять дані про тиск кожної лапи (в комплекті за вимірюванням) та фрагмент, який описує їх розташування (розташування на тарілці та час).

Для пояснення: 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'), який представляє вилучені впливи.

Також зауважте, що "помилкові" удари, наприклад, коли лапу частково виміряно (у просторі чи в часі), можна ігнорувати. Вони корисні лише тому, що можуть допомогти розпізнати шаблон, але не будуть проаналізовані.

А для всіх, хто цікавиться, я веду блог із усіма оновленнями щодо проекту!


1
Так, підхід, який я використовував, не дуже працює. Тільки для того, щоб розібратися, підхід, який я використовував, полягає лише в тому, щоб замовити удари, і припустити, що перша лапа, яка торкається, така ж, як і 5-а лапа, яку потрібно торкатися тощо. (тобто упорядкуйте удари та використовуйте модуль 4). Проблема в тому, що іноді задні лапи відштовхуються від сенсорної колодки після того, як перша лапа торкнеться вниз. У цьому випадку перша лапа для удару відповідає 4-й чи 3-й лапці для удару. Сподіваємось, це має певний сенс.
Джо Кінгтон

1
Чи правильно я інтерпретував би зображення в тому, що один палець кожної задньої стопи чинить значно менший тиск, ніж решта? Також виявляється, що носок завжди спрямований «всередину», тобто до центру маси собаки. Чи можете ви включити це як евристику?
Томас Ленґстон

1
Я визнаю, мої обмежені навички обробки зображень дещо іржаві, але чи легко можна взяти найменший крутий градієнт великого середнього майданчика кожної лапи? Здається, що кут найменшої крутості допоможе неабияк (мальований приклад для лап, розміщених: imgur.com/y2wBC imgur.com/yVqVU imgur.com/yehOc imgur.com/q0tcD )
user470379

Не могли б ви пояснити, як структуровані дані walk_sliced_data? Я бачу словник словників 3D-масивів. Якщо я зафіксую третій вимір і намічаю перші два як зображення, я думаю, що бачу лапи.
Стів Тьоа

@Thomas, так, кожна лапа чітко навантажена чітко. Я знаю, що я хотів би зробити програмою, але я не маю уявлення, як її запрограмувати ... @Steve, я додав уточнення внизу :-)
Іво Фліпс

Відповіді:


123

Добре! Нарешті мені вдалося змусити щось працювати послідовно! Ця проблема затягнула мене на кілька днів ... Веселі речі! Вибачте за тривалість цієї відповіді, але мені потрібно трохи детальніше зупинитися на деяких речах ... (Хоча я можу встановити рекорд для найдовшої відповіді на стаціонарний потік без спаму!)

В якості побічної записки я використовую повний набір даних, на який Іво надав посилання в своєму первісному запитанні . Це серія rar-файлів (один на собаку), кожен з яких містить кілька різних експериментальних циклів, що зберігаються у вигляді масивів ascii. Замість того, щоб спробувати скопіювати вставте окремі приклади коду в це запитання, тут є сховище біткоїна ртутного сховища з повним автономним кодом. Ви можете його клонувати

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


Огляд

По суті, два способи наблизитись до проблеми, як ви зазначили у своєму запитанні. Я фактично збираюся використовувати обидва по-різному.

  1. Використовуйте (часовий та просторовий) порядок ударів лапи, щоб визначити, яка саме лапа.
  2. Спробуйте визначити "відбиток лапи" виходячи виключно з його форми.

В основному, перший метод працює з лапами собаки за трапецієподібним малюнком, показаним у вищезазначеному питанні Іво, але виходить з ладу, коли лапи не дотримуються цього шаблону. Програмно виявити, коли це не працює, досить легко.

Таким чином, ми можемо використовувати вимірювання, де це працювало, щоб створити навчальний набір даних (~ 2000 ударів лапи від ~ 30 різних собак), щоб визначити, яка саме лапа, і проблема зводиться до контрольованої класифікації (З деякими додатковими зморшками). .. Розпізнавання зображень трохи складніше, ніж "звичайна" контрольована проблема класифікації).


Аналіз шаблону

Для розробки першого методу, коли собака нормально ходить (не бігає!) (Чого деякі з цих собак можуть не бути), ми очікуємо, що лапи вдарять у порядку: Передня ліва, Задня права, Передня права, Задня Ліва , Передня ліва і т. Д. Візерунок може починатися з передньої лівої або передньої правої лапи.

Якби це завжди було так, ми могли б просто сортувати удари за початковим часом контакту та використовувати модуль 4, щоб згрупувати їх за лапою.

Нормальна послідовність впливу

Однак, навіть коли все "нормально", це не працює. Це пов’язано з трапецієподібною формою візерунка. Задня лапа просторово відстає від попередньої передньої лапи.

Тому удар задньої лапи після початкового удару передньою лапою часто падає з сенсорної пластини і не реєструється. Аналогічно, останній удар лапою часто не є наступною лапою в послідовності, оскільки удар лапи перед тим, як відбувся від сенсорної пластини і не був записаний.

Пропущена задня лапа

Тим не менш, ми можемо використовувати форму малюнка удару лапи, щоб визначити, коли це сталося, і чи почали ми з лівої або правої передньої лапи. (Я фактично ігнорую проблеми з останнім впливом тут. Однак це не так важко додати.)

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

Незважаючи на все це, воно часто працює неправильно. Багато собак у повному наборі даних, здається, бігають, а удари лапою не відповідають тому ж часовому порядку, як коли вигулює собака. (А може, у собаки просто виникають серйозні проблеми з стегнами ...)

Порушення аномального впливу

На щастя, ми все ще можемо програмно виявити, чи впливають лапи на нашу очікувану просторову схему:

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 різних собак!).

Тепер ми можемо почати розглядати, як виглядає "середня" передня ліва сторона тощо.

Для цього нам потрібна якась «метрика лапи», яка має однакову розмірність для будь-якої собаки. (У повному наборі даних є як дуже великі, так і дуже маленькі собаки!) Принт лапи з ірландського ельхаунда буде і набагато ширшим, і набагато «важчим», ніж принт лапи з іграшкового пуделя. Нам потрібно змінити масштаб кожного друку лапи, щоб а) вони мали однакову кількість пікселів, і б) значення тиску були стандартизовані. Для цього я перекомпонував кожен відбиток лапи на сітку 20х20 і змінив масштаби значень тиску виходячи з максимального, мінімуму та середнього значення тиску для удару лапи.

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 собак дуже різного розміру, і ми, здається, отримуємо стійкі результати!

Середні лапи

Однак, перш ніж робити аналіз на них, нам потрібно відняти середнє значення (середня лапа для всіх ніг усіх собак).

Середня лапа

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

Диференціальні лапи

Розпізнавання лап на основі зображення

Гаразд ... У нас нарешті є набір шаблонів, з якими ми можемо почати намагатися співставити лапи. Кожну лапу можна розглядати як 400-мірний вектор (повертається paw_imageфункцією), який можна порівняти з цими чотирма 400-мірними векторами.

На жаль, якщо ми просто використовуємо "звичайний" алгоритм класифікації (керований наглядом) (тобто знаходимо, який із 4 шаблонів найближчий до певного друку лапи за допомогою простої відстані), він не працює послідовно. Насправді це не набагато краще, ніж випадковий шанс на навчальному наборі даних.

Це поширена проблема в розпізнаванні зображень. Через високу розмірність вхідних даних та дещо «нечіткий» характер зображень (тобто сусідні пікселі мають високу коваріацію), просто дивлячись на відмінність зображення від зображення шаблону не дає дуже гарної міри подібність їх форм.

Власні лапи

Щоб обійти це, нам потрібно побудувати набір "власних лап" (подібно до "власних поверхонь" для розпізнавання обличчя) та описати кожен друк лап як комбінацію цих власних лап. Це ідентично аналізу основних компонентів і, в основному, забезпечує спосіб зменшення розмірності наших даних, так що відстань є хорошим показником форми.

Оскільки у нас більше навчальних зображень, ніж розмірів (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"власні лапи".

Власні лапи

Для їх використання ми просто крапкуємо (тобто матричне множення) кожного зображення лапи (як 400-мірний вектор, а не зображення 20х20) основними векторами. Це дає нам 50-мірний вектор (один елемент на базовий вектор), який ми можемо використовувати для класифікації зображення. Замість того, щоб порівнювати зображення розміром 20x20 із зображенням зображення 20x20 кожної лапи "шаблону", ми порівнюємо 50-мірне перетворене зображення з кожною 50-мірною формою перетвореної шаблону. Це набагато менш чутливо до невеликих варіацій у тому, як саме розміщується кожен палець тощо, і в основному зменшує розмірність проблеми лише до відповідних розмірів.

Класифікація лап на основі Eigenpaw

Тепер ми можемо просто використовувати відстань між 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 текст alt текст alt текст

Залишилися проблеми

Є ще деякі проблеми, особливо з собаками, занадто маленькими, щоб зробити чіткий відбиток фарби ... (Найкраще це працює з великими собаками, оскільки пальці ніг чіткіше відокремлюються в роздільній здатності датчика.) Також часткові відбитки лапи не розпізнаються при цьому системи, хоча вони можуть бути із системою, що базується на трапеції.

Однак, оскільки для аналізу власних лап по суті використовується метрика відстані, ми можемо класифікувати лапи обома способами і повернутися до системи на основі трапеції, коли найменша відстань аналізу власних лап від "кодової книги" перевищує деякий поріг. Я цього ще не втілив.

Фу ... Це було довго! Моя шапка знята Іво за таке цікаве запитання!


2
Чудова відповідь. Я також спробував метод eigenpaw, але був не таким наполегливим, як ви. Я бачу одну проблему - реєстрація лапи, тобто, оскільки реєстрація обличчя має розпізнавати обличчя. Чи виникали у вас проблеми з нормалізацією розташування та обертання кожної лапи? Якщо так, то, можливо, лапу можна попередньо обробити в якусь інваріантну функцію перекладу-обертання, перш ніж робити PCA.
Стів Тьоа

2
@Steve, я не намагався їх обертати, хоча у мене були деякі дискусії з Джо про те, як далі вдосконалити. Однак, щоб зараз закінчити свій проект, я вручну помітив усі лапи, щоб я міг його обернути. На щастя, це також дозволяє нам створювати різні навчальні набори, щоб зробити визнання більш чутливим. Для обертання лап я планував використовувати пальці ніг, але, як ви можете прочитати на моєму блозі, це не так просто, як моє перше запитання зробило це схожим на ...
Іво Фліпс

@Basic Так, я перейшов на розміщення власного веб-сайту і перемістив весь вміст Wordpress, але я не міг більше редагувати свій коментар. Ви можете знайти їх тут: flipserd.com/blog/ivoflipse/post/improving-the-paw-detection
Іво Фліпс

4

Використовуючи інформацію виключно на основі тривалості, я думаю, ви можете застосувати методи моделювання кінематики; а саме зворотна кінематика . У поєднанні з орієнтацією, довжиною, тривалістю та загальною вагою це дає певний рівень періодичності, який, я би сподіваюся, міг би стати першим кроком, намагаючись вирішити вашу проблему "сортування лап".

Усі ці дані можуть бути використані для створення списку обмежених багатокутників (або кортежів), які можна використати для сортування за розмірами кроків, а потім за лапкою [індекс].


2

Чи можете ви, щоб технік, який проводив тест, вручну вводив першу лапу (або перші дві)? Процес може бути таким:

  • Покажіть техніці порядок зображень кроків і вимагайте від них анотувати першу лапу.
  • Намітьте інші лапи на основі першої лапи і дозвольте техніці вносити виправлення або повторно запустити тест. Це дозволяє кульгавим або триногим собакам.

У мене фактично є примітки про перші лапи, хоча вони не бездоганні. Однак перша лапа - це завжди передня лапа, і вона не допомогла мені розділити задні лапи. Крім того, замовлення не є ідеальним, як згадував Джо, тому що для початку потрібно торкнутись таблички обома спереду.
Іво Фліпсе

Анотації будуть корисні при використанні розпізнавання зображень, оскільки з 24 вимірювань, які я маю, принаймні 24 лапи були б уже помічені. Якщо вони потім будуть кластеризовані в 4 групи, дві з них повинні містити достатню кількість будь-якої передньої лапи, достатньої для того, щоб алгоритм був досить певним щодо кластеризації.
Іво Фліпсе

Якщо я не читаю їх неправильно, пов’язані примітки з примітками показують, що задня лапа торкається спочатку в 4 з 6 випробувань.
Джеймі Іде

А, я мав на увазі час. Якщо ви прокручуєте файл, передня лапа завжди повинна першою звернутися до пластини.
Іво Фліпсе
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.