如何整理我的爪子?

Ivo*_*pse 120 python image-processing

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

替代文字

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

正如您所看到的那样,显然有一种重复的模式,它几乎在每次测量中都会出现.这是一个手动注释的6个试验的演示链接.

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

  • 前爪和后爪的重量比约为60-40%;
  • 后爪通常表面较小;
  • 爪子(通常)在空间上分为左右两侧.

但是,我对我的启发式方法持怀疑态度,因为一旦遇到我没想过的变化,他们就会对我失败.他们也无法应对可能有自己规则的跛脚犬的测量结果.

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

基于我在关于爪子内峰值检测的问题上收到的答案,我希望有更先进的解决方案来对爪子进行分类.特别是因为每个单独的爪子的压力分布和其进展是不同的,几乎像指纹.我希望有一种方法可以使用它来聚集我的爪子,而不是按照发生的顺序对它们进行排序.

替代文字

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

对于接受挑战的任何人,挑选了一个包含所有切片阵列的字典,其中包含每个爪子的压力数据(通过测量捆绑)和描述其位置的切片(板上和时间上的位置).

为了澄清: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'),表示提取的影响.

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

对于任何有兴趣的人,我都会在博客上保留有关该项目的所有更新!

Joe*_*ton 123

好的!我终于设法让事情始终如一!这个问题让我陷入了好几天......有趣的东西!很抱歉这个答案的长度,但我需要详细说明一些事情...(虽然我可能会设置最长的非垃圾邮件堆栈溢出答案的记录!)

作为旁注,我正在使用Ivo 在其原始问题中提供链接的完整数据集.它是一系列rar文件(每个狗一个),每个文件包含存储为ascii数组的几个不同的实验运行.而不是尝试将独立代码示例复制粘贴到此问题中,这里是一个带有完整独立代码的bitbucket mercurial存储库.你可以用它克隆它

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


概观

正如您在问题中所指出的,基本上有两种方法可以解决问题.我实际上会以不同的方式使用它们.

  1. 使用爪子撞击的(时间和空间)顺序来确定哪个爪子是哪个.
  2. 尝试仅仅根据其形状来识别"pawprint".

基本上,第一种方法适用于狗的爪子遵循上面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
Run Code Online (Sandbox Code Playgroud)

尽管如此,它经常无法正常工作.完整数据集中的许多狗似乎正在运行,并且爪子撞击不遵循与狗行走时相同的时间顺序.(或许狗只有严重的髋关节问题...)

异常影响序列

幸运的是,我们仍然可以以编程方式检测爪子撞击是否符合我们预期的空间模式:

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
Run Code Online (Sandbox Code Playgroud)

因此,即使简单的空间分类并非始终有效,我们也可以确定它何时以合理的信心运作.

培训数据集

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

我们现在可以开始看一下"平均"左前方等等,爪子看起来像什么.

要做到这一点,我们需要某种"爪子度量",这对于任何狗来说都是相同的维度.(在完整的数据集中,有非常大的和非常小的狗!)来自爱尔兰elkhound的爪印将比玩具贵宾犬的爪印更宽,更"重".我们需要重新调整每个爪印,以便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
Run Code Online (Sandbox Code Playgroud)

在完成所有这些之后,我们终于可以看看平均左前方,后方右侧等爪子的样子.请注意,这是大约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
Run Code Online (Sandbox Code Playgroud)

这些basis_vecs是"特征爪".

Eigenpaws

为了使用这些,我们简单地用基础矢量点(即矩阵乘法)每个爪图像(作为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()]
Run Code Online (Sandbox Code Playgroud)

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

剩下的问题

仍然存在一些问题,特别是对于太小而不能制作清晰的粪便的狗...(它最适合大型犬,因为在传感器的分辨率下,脚趾更清楚地分离.)此外,部分爪子无法识别系统,虽然它们可以与基于梯形图案的系统.

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

Phew ......那太长了!我的帽子是因为有这样一个有趣的问题而离开伊沃!

  • 很好的答案.我也尝试过eigenpaw方法,但并不像你那样坚持不懈.我看到的一个问题是爪子注册,即面部注册是面部识别.您是否在规范每个爪子的位置和旋转方面遇到任何问题?如果是这样,那么也许爪子可以在进行PCA之前被预处理成一些平移 - 旋转不变特征. (2认同)
  • @Steve,我没有试过旋转它们虽然我和Joe讨论了如何进一步改进它.但是,为了完成我的项目,[我手动注释了所有的爪子](http://superivo.wordpress.com/2011/01/12/time-to-get-my-hands-dirty/)所以我可以包起来.幸运的是,这也允许我们创建不同的训练集,使识别更敏感.为了旋转爪子,我打算使用脚趾,但正如你可以在我的博客上看到的那样,这并不像我的第一个问题看起来那么容易...... (2认同)