如何分类我的爪子?
在我之前的问题中,我得到了一个很好的答案,帮助我发现爪子碰到压力板的位置,但现在我正在努力将这些结果与他们相应的爪子联系起来:
我手动注释了爪子(RF =右前方,RH =右后方,LF =左前方,LH =左后方)。
正如你所看到的那样,显然有一种重复的模式,几乎每次测量都会回来。 以下是一个手动注释的6个试验的演示文稿的链接。
我最初的想法是使用启发式进行排序,如:
然而,我对我的启发式方法有点怀疑,因为一旦遇到我没有想到的变化,他们就会失败。 他们也无法应付来自可能有自己的规则的跛脚犬的测量。
此外,Joe提出的注释有时会弄糟,并没有考虑到爪子实际看起来像什么。
根据我在爪子峰值检测问题上收到的答案,我希望有更先进的解决方案来分类爪子。 特别是因为每个单独的爪子的压力分布和进程都不同,几乎像指纹一样。 我希望有一种方法可以使用它来聚集我的爪子,而不是按照发生的顺序对它们进行排序。
所以我正在寻找一种更好的方法来将结果与相应的爪子进行排序。
对于任何接受挑战的人来说,我已经用所有包含每个爪子(通过测量捆绑)的压力数据的切片阵列和描述它们的位置(在盘子上及时间上的位置)的切片腌字典。
clarfiy: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'),它们表示已提取的影响。
另外请注意,'假'影响,例如爪子部分测量的位置(空间或时间)可以忽略。 它们只是有用的,因为它们可以帮助识别模式,但不会被分析。
对于任何感兴趣的人,我都会在博客上留下有关该项目的所有更新信息!
好的! 我终于设法保持一贯的工作! 这个问题拉了我几天...有趣的东西! 对不起,这个答案的长度,但我需要详细说明一些事情......(尽管我可能会创造一个有史以来最长的非垃圾邮件stackoverflow答案记录!)
作为一个方面说明,我使用的是Ivo在其原始问题中提供链接的完整数据集。 这是一系列rar文件(每个狗一个),每个文件包含几个不同的实验运行,存储为ascii数组。 与其试图将独立代码示例复制粘贴到此问题中,以下是一个具有完整独立代码的bitbucket mercurial存储库。 你可以用它克隆它
hg clone https://joferkington@bitbucket.org/joferkington/paw-analysis
概观
正如你在问题中提到的那样,解决问题的方法基本上有两种。 我实际上会以不同的方式使用两者。
基本上,第一种方法与狗的爪子一起工作,遵循上面Ivo问题中所示的梯形样式,但是当爪子不遵循该模式时,失败。 以编程方式检测何时不起作用相当容易。
因此,我们可以使用测量结果来建立一个训练数据集(约30只不同的狗的〜2000爪子的影响),以识别哪个爪子是哪个爪子,并将问题简化为监督分类(带有一些额外的皱纹)。 ..图像识别比“正常”监督分类问题困难一些)。
模式分析
要详细说明第一种方法,当一只狗正常行走(不是跑步!)(这些狗可能不是这些狗中的一些)时,我们期望爪子按照以下顺序影响:左前,右后,右前,后左,左前方等。图案可以从左前方或右前方爪开始。
如果情况总是如此,我们可以简单地按照初始接触时间对影响进行分类,并使用模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
因此,即使简单的空间分类不能一直工作,我们也可以确定它何时合理地工作。
培训数据集
从正确分类的基于模式的分类中,我们可以建立一个非常大的正确分类爪子的训练数据集(来自32只不同狗的〜2400个爪子的冲击!)。
我们现在可以开始看看左边的“平均”左右,等等,爪子看起来像。
要做到这一点,我们需要某种“爪子指标”,这对任何狗都是一样的维度。 (在完整的数据集中,既有非常大的狗,也有非常小的狗!)来自爱尔兰猎豹的爪印将比玩具贵宾犬的爪印更宽更“更重”。 我们需要重新缩放每个爪印,以便a)它们具有相同数量的像素,并且b)压力值被标准化。 为此,我将每个爪印再次采样到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 vs 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维向量(每个基向量一个元素),我们可以用它来分类图像。 我们不是将20x20图像与每个“模板”爪子的20x20图像进行比较,而是将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()]
以下是一些结果:
剩余问题
仍然存在一些问题,特别是对于狗太小而不能形成明显爪印的情况...(它对于大型狗来说效果最好,因为脚趾在传感器的分辨率上更加明显地分离)。此外,部分爪印不能被识别系统,而他们可以与梯形模式为基础的系统。
然而,因为特征分析固有地使用距离度量,所以我们可以将爪子分类为两种方式,并且当特征分析与“码本”的最小距离超过某个阈值时回退到基于梯形模式的系统。 不过,我还没有实现。
唷......那很漫长! 我的帽子给伊沃带来这样一个有趣的问题!
使用纯粹基于持续时间的信息,我认为你可以应用建模运动学的技术; 即逆运动学。 结合方向,长度,持续时间和总重量,它给出了一定程度的周期性,我希望可以成为解决你的“爪子排序”问题的第一步。
所有这些数据都可以用来创建一个有界多边形(或元组)的列表,你可以用它来按步长进行排序,然后用paw-ness [index]进行排序。
您可以让技术人员手动输入第一个爪子(或前两个)吗? 这个过程可能是:
上一篇: How to sort my paws?