Хорошо!Мне наконец удалось заставить что-то работать последовательно!Эта проблема тянула меня на несколько дней ... Забавно!Извините за длину этого ответа, но мне нужно немного остановиться на некоторых вещах ... (Хотя я могу установить рекорд для самого длинного ответа на стеке, не являющегося спамом!)
В качестве примечания,Я использую полный набор данных, который Ivo предоставил ссылку на в своем оригинальном вопросе .Это серия rar-файлов (по одному на собаку), каждый из которых содержит несколько разных экспериментальных прогонов, хранящихся в виде массивов ascii.Вместо того, чтобы пытаться скопировать и вставить отдельные примеры кода в этот вопрос, вот ртутный репозиторий bitbucket с полным, автономным кодом.Вы можете клонировать его с помощью
hg clone https://joferkington@bitbucket.org/joferkington/paw-analysis
Обзор
Существуют два основных подхода к проблеме, как вы отметили вваш вопрос.Я на самом деле собираюсь использовать оба по-разному.
- Используйте (временной и пространственный) порядок ударов лапы, чтобы определить, какая лапа какая.
- Попробуйте определить«pawprint» основан исключительно на его форме.
По сути, первый метод работает с лапами собаки, следуя трапециевидному образцу, показанному в вопросе Иво выше, но терпит неудачу всякий раз, когда лапы не следуют этому.шаблон.Это довольно легко программно определить, когда это не работает.
Таким образом, мы можем использовать измерения, в которых он работал, для создания учебного набора данных (из ~ 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 разных собаки!).
Теперь мы можем начать смотреть на то, как выглядит «средняя» передняя левая сторона и т. Д., Лапа.
Чтобы сделать это, нам нужна какая-то «метрика лапы», которая одинакова для любой собаки. (В полном наборе данных есть как очень большие, так и очень маленькие собаки!) Отпечаток лапы от ирландского эльхунда будет гораздо шире и намного «тяжелее», чем оттиск лапы у игрушечного пуделя. Нам нужно изменить масштаб каждого отпечатка лапы, чтобы а) они имели одинаковое количество пикселей и б) значения давления были стандартизированы. Чтобы сделать это, я пересэмплировал каждый отпечаток лапы на сетке 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 собакам очень разных размеров, и мы, кажется, получаем последовательные результаты!
Однако, прежде чем проводить какой-либо анализ, нам нужно вычесть среднее (средняя лапа для всех ног всех собак).
Теперь мы можем проанализировать отличия от среднего значения, которые немного легче распознать:
Распознавание лап на основе изображений
Хорошо ... Наконец-то у нас есть набор шаблонов, с которыми мы можем начать пытаться сопоставить лапы. Каждую лапу можно рассматривать как 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
являются "собственными лапами".
Чтобы использовать их, мы просто ставим точки (т.е. матричное умножение) каждое изображение лапы (как 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()]
Вот некоторые результаты:
Остальные проблемы
Есть все еще некоторые проблемы, особенно с собаками, слишком маленькими, чтобы сделать четкий отпечаток ноги ... (Это лучше всего работает с большими собаками, поскольку пальцы ног более четко отделены при разрешении датчика.) Кроме того, частичные отпечатки пальцев не являютсяПризнано с этой системой, в то время как они могут быть с системой на основе трапециевидного шаблона.
Однако, поскольку анализ собственной лапы по своей сути использует метрику расстояния, мы можем классифицировать лапы в обоих направлениях и вернуться к системе, основанной на трапециевидных шаблонах, когда наименьшее расстояние анализа собственной лапы от «кодовой книги» законченокакой-то порог.Я еще не реализовал это.
Фу ... Это было долго!Моя шляпа снята с Иво за такой интересный вопрос!