如何分类我的爪子?

Posted

技术标签:

【中文标题】如何分类我的爪子?【英文标题】:How to sort my paws? 【发布时间】:2011-05-29 00:12:33 【问题描述】:

在my previous question I got an excellent answer 中,它帮助我检测了爪子撞击压力板的位置,但现在我正在努力将这些结果与它们对应的爪子联系起来:

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

如您所见,有一个明显的重复模式,并且几乎在每次测量中都会出现。 Here's a link to a presentation of 6 trials that were manually annotated.

我最初的想法是使用启发式进行排序,例如:

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

但是,我对我的启发式方法有点怀疑,因为一旦我遇到我没有想到的变化,他们就会失败。他们也无法应对跛脚狗的测量,这些狗可能有自己的规则。

此外,Joe 建议的注释有时会搞砸,并且没有考虑爪子的实际外观。

根据我收到的on my question about peak detection within the paw 的答案,我希望有更高级的解决方案来对爪子进行分类。特别是因为每个单独的爪子的压力分布及其进展是不同的,几乎就像指纹一样。我希望有一种方法可以使用它来聚集我的爪子,而不仅仅是按出现的顺序对它们进行排序。

所以我正在寻找一种更好的方法来用相应的爪子对结果进行排序。

对于任何敢于挑战的人,I have pickled a dictionary 与all the sliced arrays that contain the pressure data of each paw(按测量捆绑)和the slice that describes their location(在盘子上的位置和时间)。

澄清一下: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”的示例),它表示提取的影响。

另请注意,可以忽略“错误”影响,例如部分测量爪子的位置(空间或时间)。它们之所以有用,只是因为它们可以帮助识别模式,但 不会分析。

对于任何感兴趣的人,I’m keeping a blog with all the updates regarding the project!

【问题讨论】:

是的,我使用的方法不太奏效。详细说明一下,我使用的方法是对冲击进行排序,并假设第一个接触的爪子与第五个接触的爪子相同,依此类推。 (即对影响排序并使用模 4)。这样做的问题是,有时后爪会在第一只爪子触地后撞击传感器垫。在这种情况下,第一个撞击的爪子与第四个或第三个撞击的爪子相匹配。希望这有点道理。 每只后脚的一个脚趾施加的压力明显小于其他脚趾,我能正确解释图像吗?似乎脚趾总是朝向“内部”,即朝向狗的重心。你能把它作为一种启发式方法吗? 我承认我有限的图像处理能力有些生疏,但是是否可以轻松地采取每只爪子的大中间垫的最陡坡度?似乎最小陡度的角度会有很大帮助(张贴的爪子的手绘示例:imgur.com/y2wBCimgur.com/yVqVUimgur.com/yehOcimgur.com/q0tcD) 您能否澄清walk_sliced_data 中的数据结构?我看到了一本 3D 数组字典。如果我修复第三维并将前两个绘制为图像,我想我看到了爪子。 @Thomas,是的,每只爪子都清楚地以不同的方式加载。我知道我希望程序做什么,但我不知道如何编程...@Steve,我在底部添加了说明:-) 【参考方案1】:

好的!我终于设法让某些东西始终如一地工作!这个问题把我拖了好几天......有趣的东西!对不起这个答案的长度,但我需要详细说明一些事情......(虽然我可能会创下最长的非垃圾邮件 *** 答案的记录!)

附带说明,我使用的是 Ivo provided a link to 在他的 original question 中的完整数据集。这是一系列 rar 文件(每只狗一个),每个文件包含几个不同的实验运行,存储为 ascii 数组。与其尝试将独立代码示例复制粘贴到此问题中,不如使用带有完整独立代码的bitbucket mercurial repository。您可以使用

克隆它

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 种图案中的哪一种最接近特定的爪印),它就无法始终如一地工作。事实上,它在训练数据集上的表现并不比随机机会好多少。

这是图像识别中的常见问题。由于输入数据的高维性,以及图像有点“模糊”的性质(即相邻像素具有高协方差),简单地查看图像与模板图像的差异并不能很好地衡量它们形状的相似性。

本征爪

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

因为我们的训练图像比尺寸多(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 维向量(每个基向量一个元素),我们可以使用它来对图像进行分类。我们不是将 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()]

以下是一些结果:

遗留问题

仍然存在一些问题,尤其是狗太小而无法形成清晰的爪印......(它最适合大型狗,因为在传感器的分辨率下脚趾更清晰地分开。)此外,部分爪印不是用这个系统识别,而它们可以用基于梯形图案的系统。

但是,由于 eigenpaw 分析固有地使用距离度量,我们可以对爪子进行双向分类,并在 eigenpaw 分析与“码本”的最小距离超过某个阈值时回退到基于梯形图案的系统。不过我还没有实现。

呸……太长了!我向 Ivo 致敬,因为我提出了这么有趣的问题!

【讨论】:

很好的答案。我也尝试过特征爪法,但没有你那么坚持。我看到的一个问题是爪子注册,即面部注册是面部识别。您在规范每只爪子的位置和旋转时遇到任何问题吗?如果是这样,那么也许可以在进行 PCA 之前将爪子预处理为一些平移-旋转不变特征。 @Steve,尽管我与 Joe 讨论了如何进一步改进它,但我还没有尝试轮换它们。但是,要暂时完成我的项目,I manually annotated all the paws 以便我可以结束它。幸运的是,这也允许我们创建不同的训练集以使识别更加敏感。为了旋转爪子,我打算使用脚趾,但是正如您在我的博客上看到的那样,这并不像我的第一个问题看起来那样简单...... @Basic 是的,我切换到托管自己的网站并将所有 Wordpress 内容移到了那里,但我无法再在这里编辑我的评论了。你应该可以在这里找到它们:flipserd.com/blog/ivoflipse/post/improving-the-paw-detection【参考方案2】:

您能否让运行测试的技术人员手动输入第一只爪子(或前两只)?该过程可能是:

向技术人员展示步骤图像并要求他们注释第一只爪子。 根据第一只爪子标记其他爪子,并允许技术人员进行更正或重新运行测试。这允许跛脚或三足狗。

【讨论】:

我实际上有第一只爪子的注释,尽管它们并非完美无缺。但是,第一只爪子总是前爪,不会帮助我分开后爪。此外,正如乔所说的那样,排序并不完美,因为这需要在开始时两个正面都接触到盘子。 注释在使用图像识别时会很有用,因为我有 24 次测量,至少 24 只爪子已经被注释了。如果然后将它们聚类为 4 组,则其中两组应包含合理数量的任一前爪,足以使算法相当确定聚类。 除非我读错了,否则链接的注释试验显示后爪在 6 次试验中有 4 次首先接触。 啊,我的意思是时间方面。如果您循环浏览文件,前爪应始终是第一个接触盘子的人。【参考方案3】:

使用纯粹基于持续时间的信息,我认为您可以应用运动学建模技术;即Inverse Kinematics。结合方向、长度、持续时间和总重量,它给出了一定程度的周期性,我希望这可能是解决“爪子分类”问题的第一步。

所有这些数据都可用于创建有界多边形(或元组)列表,您可以使用该列表按步长排序,然后按爪度 [index]。

【讨论】:

以上是关于如何分类我的爪子?的主要内容,如果未能解决你的问题,请参考以下文章

如何测试我的分类器是不是过拟合?

如何使用任何分类器对我的数据进行分类,每个数据点由一组浮点值组成?

如何针对我的 NLP 朴素贝叶斯分类器测试新词集

如何修复我的分类器的类精度

如何改进我的文本主题分类器?

如何对我的 TODO 进行分类?