如何分类我的爪子?

在我之前的问题中,我得到了一个很好的答案 ,帮助我发现爪子碰到压力板的位置,但是现在我正努力将这些结果与他们相应的爪子联系起来:

替代文字

我手动注释了爪子(RF =右前方,RH =右后方,LF =左前方,LH =左后方)。

正如你所看到的,显然有一个重复的模式,几乎每一次测量都会回来。 以下是一个手动注释的6个试验的演示文稿的链接。

我最初的想法是使用启发式来进行sorting,如:

  • 前爪和后爪之间的负重比例约为60-40%。
  • 后爪通常在表面上较小;
  • 爪子(通常)在空间上左右分开。

不过,我对我的启发式有些怀疑,因为一旦遇到我没有想到的变化,他们就会失败。 他们也不能应付那些可能有自己的规则的跛脚狗。

此外,Joe提出的注释有时候会搞砸,并没有考虑到爪子的实际外观。

根据我收到的有关爪内峰值检测问题的答案,我希望有更先进的方法来分类爪子。 特别是因为每个单独的爪子的压力分布和进程是不同的,几乎就像指纹一样。 我希望有一种方法可以使用它来聚集我的爪子,而不是按照出现的顺序sorting。

替代文字

所以我正在寻找一个更好的方法来sorting结果与他们相应的爪子。

对于挑战的人来说, 我用一个 包含每个爪子的压力数据 (包括测量)和描述他们的位置 (盘子上的位置和时间) 的切片的所有切片arrays 腌制字典 。

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存储库 。 你可以用它克隆

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


概观

正如你在问题中提到的那样,解决问题的方法基本上有两种。 我实际上将以不同的方式使用两者。

  1. 使用爪子冲击的(时间和空间)顺序来确定哪个爪子是哪个爪子。
  2. 试图根据其形状来确定“印迹”。

基本上,第一种方法适用于狗的爪子遵循上面伊沃问题中所示的梯形图案,但是只要爪子不遵循该图案,就失败了。 以编程方式检测何时不起作用是相当容易的。

因此,我们可以使用测量工作来build立一个训练数据集(约30只不同的狗〜2000爪子的影响),以识别哪个爪子是哪个爪子,并且问题减less到监督分类(带有一些额外的皱纹)。图像识别比“正常”的监督分类问题要困难一些)。


模式分析

为了详细说明第一种方法,当一只狗正常行走(这些狗可能不是这些狗中的一些狗)时,我们期望爪子按照以下顺序影响:左前,右后,右前,后左,左前方等。图案可以从左前方或右前方爪开始。

如果情况总是如此,我们可以简单地按照最初的接触时间对碰撞进行分类,并用模4用爪子对它们进行分组。

正常影响顺序

然而,即使一切都是“正常的”,这是行不通的。 这是由于图案的梯形形状。 后爪在空间上落在前面的前爪之后。

因此,在初始前爪冲击之后的后爪冲击经常从传感器板上掉落,而不被logging。 类似地,最后一个爪子的冲击通常不是顺序中的下一个爪子,因为爪子碰到传感器板之前没有被logging下来。

错过了后爪

尽pipe如此,我们可以使用爪子撞击模式的形状来确定这是什么时候发生的,以及我们是以左前还是右前爪开始的。 (我实际上忽略了这里最后一个影响的问题,不过,添加它并不难)。

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

尽pipe如此,它往往不能正常工作。 完整数据集中的许多狗似乎正在跑步,并且爪子的影响不像狗走路时那样遵循相同的时间顺序。 (也许这只狗有严重的髋关节问题…)

异常影响序列

幸运的是,我们仍然可以通过编程的方式检测爪子是否会影响我们预期的空间模式:

 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 

因此,即使简单的空间分类不能一直工作,我们也可以确定它何时合理地工作。

培训数据集

从正确分类的基于模式的分类中,我们可以build立一个非常大的正确分类爪子的训练数据集(来自32只不同狗的〜2400个爪子的冲击!)。

我们现在可以开始看看左边的“平均”等等,爪子看起来像。

要做到这一点,我们需要某种“爪子指标”,这是与任何狗相同的维度。 (在完整的数据集中,既有非常大的狗,也有非常小的狗!)爱尔兰猎豹的爪子图案要比玩具贵宾犬的爪子图案要宽得多,“重得多”。 我们需要重新缩放每个爪印,以便a)它们具有相同数量的像素,并且b)压力值被标准化。 为此,我将每个爪印再次采样到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维vector(由paw_image函数返回),可以与这四个400维vector进行比较。

不幸的是,如果我们只是使用“正常的”监督分类algorithm(即,使用简单的距离查找4个模式中的哪一个最接近特定的爪印),则不能一致地工作。 事实上,在训练数据集上,它并不比随机机会好得多。

这是图像识别中常见的问题。 由于input数据的高维度以及图像的某些“模糊”性质(即相邻像素具有高协方差),仅仅从图像与模板图像的差异来看并不能很好地度量他们的形状相似。

Eigenpaws

为了解决这个问题,我们需要构build一组“特征脸”(就像面部识别中的“特征脸”),并将每个爪印描述为这些特征脸的组合。 这与主成分分析是相同的,基本上提供了一种方法来减less我们的数据的维度,所以距离是一个很好的度量形状。

因为我们有比维度更多的训练图像(2400 vs 400),所以没有必要为速度做“幻想”的线性代数。 我们可以直接使用训练数据集的协方差matrix:

 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”。

Eigenpaws

为了使用这些,我们简单地将每个爪图像(作为400维向量,而不是20×20图像)与基向量进行点(即matrix乘法)。 这给了我们一个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()] 

以下是一些结果: 替代文字替代文字替代文字

剩余的问题

还有一些问题,特别是对于小狗来说,不能做出明显的爪印(对于大型狗来说效果最好,因为脚趾在传感器的分辨率上更加明显)。另外,部分爪印也不能识别系统,而他们可以与梯形模式为基础的系统。

然而,因为特征分析本质上使用距离度量,所以我们可以两种方式对爪子进行分类,并且当特征分析与“码本”的最小距离超过某个阈值时,回退到基于梯形模式的系统。 不过,我还没有实现。

唷…那太久了! 我的帽子是给伊沃有这样一个有趣的问题!

使用纯粹基于持续时间的信息,我认为你可以应用build模运动学的技术; 即逆运动学 。 结合方向,长度,持续时间和总重量,给出一定程度的周期性,我希望可以成为解决你的“爪子sorting”问题的第一步。

所有的数据可以用来创build一个有界的多边形(或元组)列表,你可以用它来按步长sorting,然后用paw-ness [index]sorting。

你可以让技术人员手动进行testinginput第一个爪子(或前两个)? 这个过程可能是:

  • 显示技术步骤图像的顺序,并要求他们注释第一个爪子。
  • 根据第一个爪子标记其他爪子,并允许技术人员进行更正或重新运行testing。 这允许跛脚或三条腿的狗。