我在帮助一家兽医诊所测量狗爪下的压力。我使用Python进行数据分析,现在我正试图将爪子划分为(解剖学上的)子区域。

我为每个爪子制作了一个2D数组,其中包括爪子随时间加载的每个传感器的最大值。这里有一个单爪的例子,我使用Excel绘制我想要“检测”的区域。传感器周围是2 * 2的方框带有局部最大值,它们加起来的和最大。

所以我尝试了一些实验,并决定简单地寻找每一列和每一行的最大值(由于爪子的形状,不能只看一个方向)。这似乎能很好地“检测”到不同脚趾的位置,但也能标记出相邻的传感器。

那么告诉Python哪些最大值是我想要的最好方法是什么呢?

注意:2x2的方块不能重叠,因为它们必须是分开的脚趾!

此外,我选择了2x2作为方便,任何更高级的解决方案都是受欢迎的,但我只是一个人类运动科学家,所以我既不是真正的程序员也不是数学家,所以请保持“简单”。

下面是一个可以用np.loadtxt加载的版本


结果

所以我尝试了@jextee的解决方案(见下面的结果)。正如你所看到的,它对前爪很有效,但对后腿就不那么有效了。

更具体地说,它无法识别第四个脚趾的小峰。这显然是固有的事实,即循环从上向下看最低值,而不考虑它在哪里。

有没有人知道如何调整@jextee的算法,让它也能找到第四个脚趾?

因为我还没有处理其他的试验,所以我无法提供其他的样品。但我之前给出的数据是每个爪子的平均值。这个文件是一个数组,其中包含9只爪子的最大数据,按照它们与盘子接触的顺序排列。

这张照片显示了它们在空间上是如何分布在板块上的。

更新:

我已经为任何感兴趣的人建立了一个博客,我已经设置了一个OneDrive与所有的原始测量。因此,对于任何要求更多数据的人:给你更多力量!


新更新:

所以在我得到关于爪子检测和爪子分类的问题的帮助后,我终于能够检查每个爪子的脚趾检测!事实证明,除了像我自己例子中那样大的爪子,它在其他地方都不太管用。当然,事后看来,是我自己的错,我这么武断地选择了2x2。

这里有一个很好的例子,它错在哪里:指甲被识别为脚趾,而“脚跟”太宽了,它被识别了两次!

爪子太大了,所以采用没有重叠的2x2尺寸,会导致一些脚趾被检测两次。反过来,在小型犬中,它经常找不到第5个脚趾,我怀疑这是由于2x2面积太大造成的。

在对我所有的测量都尝试了当前的解决方案后,我得出了一个惊人的结论,几乎所有的小狗都没有发现第5个脚趾,而在超过50%的大型狗的冲击中,它会发现更多!

显然我需要改变它。我自己的想法是改变社区的大小,让小狗住的地方更小,大狗住的地方更大。但是generate_binary_structure不允许我改变数组的大小。

因此,我希望其他人有更好的建议来定位脚趾,也许是脚趾面积与爪子大小的比例?


我脑子里有几个想法:

取扫描的梯度(导数),看看是否消除了错误的调用 取局部极大值的最大值

你可能还想看看OpenCV,它有一个相当不错的Python API,可能有一些你会发现有用的函数。


也许一个简单的方法在这里就足够了:建立一个平面上所有2x2正方形的列表,按它们的和排序(降序)。

首先,在你的“爪子列表”中选择价值最高的方块。然后,迭代地选择4个次优正方形,这些正方形不与之前找到的任何正方形相交。


这里有一个想法:你计算图像的(离散)拉普拉斯量。我希望它在最大值处是(负的)大的,以一种比原始图像更引人注目的方式。因此,极大值更容易找到。

这里有另一个想法:如果你知道高压点的典型大小,你可以首先通过用相同大小的高斯函数卷积来平滑你的图像。这可能会让你更容易处理图像。


如果你一步一步地进行:你首先找到全局最大值,如果需要处理周围的点,然后将找到的区域设置为零,然后对下一个重复。


解决方案

数据文件:paw.txt。源代码:

from scipy import *
from operator import itemgetter

n = 5  # how many fingers are we looking for

d = loadtxt("paw.txt")
width, height = d.shape

# Create an array where every element is a sum of 2x2 squares.

fourSums = d[:-1,:-1] + d[1:,:-1] + d[1:,1:] + d[:-1,1:]

# Find positions of the fingers.

# Pair each sum with its position number (from 0 to width*height-1),

pairs = zip(arange(width*height), fourSums.flatten())

# Sort by descending sum value, filter overlapping squares

def drop_overlapping(pairs):
    no_overlaps = []
    def does_not_overlap(p1, p2):
        i1, i2 = p1[0], p2[0]
        r1, col1 = i1 / (width-1), i1 % (width-1)
        r2, col2 = i2 / (width-1), i2 % (width-1)
        return (max(abs(r1-r2),abs(col1-col2)) >= 2)
    for p in pairs:
        if all(map(lambda prev: does_not_overlap(p,prev), no_overlaps)):
            no_overlaps.append(p)
    return no_overlaps

pairs2 = drop_overlapping(sorted(pairs, key=itemgetter(1), reverse=True))

# Take the first n with the heighest values

positions = pairs2[:n]

# Print results

print d, "\n"

for i, val in positions:
    row = i / (width-1)
    column = i % (width-1)
    print "sum = %f @ %d,%d (%d)" % (val, row, column, i)
    print d[row:row+2,column:column+2], "\n"

输出不重叠方块。似乎选择的区域与您的示例中相同。

一些评论

棘手的部分是计算所有2x2平方的和。我猜你们都需要,所以可能会有重叠。我使用切片从原始2D数组中切割第一个/最后一个列和行,然后将它们全部重叠在一起并计算总和。

为了更好地理解它,对一个3x3数组进行成像:

>>> a = arange(9).reshape(3,3) ; a
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])

然后你可以把它切片:

>>> a[:-1,:-1]
array([[0, 1],
       [3, 4]])
>>> a[1:,:-1]
array([[3, 4],
       [6, 7]])
>>> a[:-1,1:]
array([[1, 2],
       [4, 5]])
>>> a[1:,1:]
array([[4, 5],
       [7, 8]])

现在想象一下,你把它们一个叠在另一个上面,然后在相同的位置上求和。这些和将与左上角位置相同的2x2平方的和完全相同:

>>> sums = a[:-1,:-1] + a[1:,:-1] + a[:-1,1:] + a[1:,1:]; sums
array([[ 8, 12],
       [20, 24]])

当你有2x2平方的和时,你可以用max来求最大值,或者排序,或者排序来求峰值。

为了记住峰值的位置,我将每个值(总和)与其在扁平数组中的序数位置(参见zip)耦合起来。然后在打印结果时再次计算行/列位置。

笔记

我允许2x2平方重叠。编辑后的版本过滤掉其中的一些,这样只有不重叠的正方形出现在结果中。

选择手指(一个想法)

另一个问题是如何从所有的峰中选择可能是手指的峰。我有个主意,可能有用也可能没用。我现在没有时间实现它,所以只是伪代码。

我注意到,如果前面的手指几乎保持在一个完美的圆圈上,后面的手指应该在这个圆圈里。此外,食指的间距也差不多相等。我们可以尝试使用这些启发式属性来检测手指。

伪代码:

select the top N finger candidates (not too many, 10 or 12)
consider all possible combinations of 5 out of N (use itertools.combinations)
for each combination of 5 fingers:
    for each finger out of 5:
        fit the best circle to the remaining 4
        => position of the center, radius
        check if the selected finger is inside of the circle
        check if the remaining four are evenly spread
        (for example, consider angles from the center of the circle)
        assign some cost (penalty) to this selection of 4 peaks + a rear finger
        (consider, probably weighted:
             circle fitting error,
             if the rear finger is inside,
             variance in the spreading of the front fingers,
             total intensity of 5 peaks)
choose a combination of 4 peaks + a rear peak with the lowest penalty

这是一种蛮力方法。如果N相对较小,那么我认为它是可行的。对于N=12,有C_12^5 = 792种组合,乘以5种选择后指的方法,所以每个爪子要评估3960种情况。


好吧,这里有一些简单而不是非常有效的代码,但对于这样大的数据集来说,这是很好的。

import numpy as np
grid = np.array([[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
              [0,0,0,0,0,0,0,0,0.4,0.4,0.4,0,0,0],
              [0,0,0,0,0.4,1.4,1.4,1.8,0.7,0,0,0,0,0],
              [0,0,0,0,0.4,1.4,4,5.4,2.2,0.4,0,0,0,0],
              [0,0,0.7,1.1,0.4,1.1,3.2,3.6,1.1,0,0,0,0,0],
              [0,0.4,2.9,3.6,1.1,0.4,0.7,0.7,0.4,0.4,0,0,0,0],
              [0,0.4,2.5,3.2,1.8,0.7,0.4,0.4,0.4,1.4,0.7,0,0,0],
              [0,0,0.7,3.6,5.8,2.9,1.4,2.2,1.4,1.8,1.1,0,0,0],
              [0,0,1.1,5,6.8,3.2,4,6.1,1.8,0.4,0.4,0,0,0],
              [0,0,0.4,1.1,1.8,1.8,4.3,3.2,0.7,0,0,0,0,0],
              [0,0,0,0,0,0.4,0.7,0.4,0,0,0,0,0,0]])

arr = []
for i in xrange(grid.shape[0] - 1):
    for j in xrange(grid.shape[1] - 1):
        tot = grid[i][j] + grid[i+1][j] + grid[i][j+1] + grid[i+1][j+1]
        arr.append([(i,j),tot])

best = []

arr.sort(key = lambda x: x[1])

for i in xrange(5):
    best.append(arr.pop())
    badpos = set([(best[-1][0][0]+x,best[-1][0][1]+y)
                  for x in [-1,0,1] for y in [-1,0,1] if x != 0 or y != 0])
    for j in xrange(len(arr)-1,-1,-1):
        if arr[j][0] in badpos:
            arr.pop(j)


for item in best:
    print grid[item[0][0]:item[0][0]+2,item[0][1]:item[0][1]+2]

我基本上只是用左上角的位置和每个2x2平方的和来做一个数组,然后根据和来排序。然后我把和最高的2x2平方从竞争中取出,把它放在最好的数组中,然后删除所有其他使用了这个2x2平方的部分的2x2平方。

It seems to work fine except with the last paw (the one with the smallest sum on the far right in your first picture), it turns out that there are two other eligible 2x2 squares with a larger sum (and they have an equal sum to each other). One of them is still selects one square from your 2x2 square, but the other is off to the left. Fortunately, by luck we see to be choosing more of the one that you would want, but this may require some other ideas to be used to get what you actually want all of the time.


这是一个图像配准问题。总的策略是:

有一个已知的例子,或者有一些先验的数据。 将数据与示例相匹配,或将示例与数据相匹配。 如果您的数据在一开始就大致对齐,这是有帮助的。

这里有一个粗略而现成的方法,“可能起作用的最愚蠢的方法”:

从五个脚趾坐标开始,大致在你期望的位置。 使用每一个,迭代地爬到山顶。即给定当前位置,移动到最大邻近像素,如果它的值大于当前像素。当你的脚趾坐标停止移动时停止。

为了解决方向问题,你可以为基本方向设置8个左右的初始设置(北,东北等)。单独运行每一个,并丢弃任何两个或多个脚趾最终位于同一像素的结果。我会再思考一下这个问题,但这种事情在图像处理中还在研究中——没有正确的答案!

稍微复杂一点的想法:(加权)k -均值聚类。没那么糟。

从五个脚趾坐标开始,但现在这些是“集群中心”。

然后迭代直到收敛:

将每个像素分配到最近的集群(只需为每个集群制作一个列表)。 计算每个簇的质心。对于每个聚类,这是:Sum(坐标*强度值)/Sum(坐标) 移动每个星团到新的质心。

这种方法几乎肯定会得到更好的结果,你可以得到每个簇的质量,这可能有助于识别脚趾。

(同样,您已经预先指定了集群的数量。对于聚类,您必须以一种或另一种方式指定密度:要么选择集群的数量,在这种情况下合适,要么选择集群半径,看看最终有多少。后者的一个例子是mean-shift。)

很抱歉缺少实现细节或其他细节。我可以把它写出来,但我有最后期限了。如果到下周还没有效果,请告诉我,我会试试的。


似乎你可以用jetxee的算法来欺骗一下。他发现前三个脚趾很好,你应该可以根据这个猜测出第四个脚趾的位置。


我不确定这是否回答了问题,但似乎你可以只寻找n个没有邻居的最高的山峰。

这是要点。注意,它是用Ruby编写的,但其思想应该很清楚。

require 'pp'

NUM_PEAKS = 5
NEIGHBOR_DISTANCE = 1

data = [[1,2,3,4,5],
        [2,6,4,4,6],
        [3,6,7,4,3],
       ]

def tuples(matrix)
  tuples = []
  matrix.each_with_index { |row, ri|
    row.each_with_index { |value, ci|
      tuples << [value, ri, ci]
    }
  }
  tuples
end

def neighbor?(t1, t2, distance = 1)
  [1,2].each { |axis|
    return false if (t1[axis] - t2[axis]).abs > distance
  }
  true
end

# convert the matrix into a sorted list of tuples (value, row, col), highest peaks first
sorted = tuples(data).sort_by { |tuple| tuple.first }.reverse

# the list of peaks that don't have neighbors
non_neighboring_peaks = []

sorted.each { |candidate|
  # always take the highest peak
  if non_neighboring_peaks.empty?
    non_neighboring_peaks << candidate
    puts "took the first peak: #{candidate}"
  else
    # check that this candidate doesn't have any accepted neighbors
    is_ok = true
    non_neighboring_peaks.each { |accepted|
      if neighbor?(candidate, accepted, NEIGHBOR_DISTANCE)
        is_ok = false
        break
      end
    }
    if is_ok
      non_neighboring_peaks << candidate
      puts "took #{candidate}"
    else
      puts "denied #{candidate}"
    end
  end
}

pp non_neighboring_peaks

这是我在大型望远镜上做类似事情时使用的另一种方法:

1)搜索最高像素。 一旦你有了这个,在周围搜索2x2的最佳拟合(可能是最大化2x2的和),或者在4x4的子区域内做一个2d高斯拟合,以最高像素为中心。

然后在峰值中心周围将那些2x2像素设置为0(或者3x3)

回到1),重复直到峰值低于噪声阈值,或者你有所有你需要的脚趾


如果你能够创建一些训练数据,那么使用神经网络可能是值得尝试的……但这需要手工标注许多样本。


大概的轮廓……

你可能想要使用连接组件算法来隔离每个爪子区域。wiki对此有一个很好的描述(有一些代码):http://en.wikipedia.org/wiki/Connected_Component_Labeling

你必须决定是使用4连通性还是8连通性。就我个人而言,对于大多数问题,我更喜欢6连通性。无论如何,一旦你将每个“爪印”分离为一个连通区域,就很容易遍历该区域并找到最大值。一旦你找到了最大值,你就可以迭代地扩大区域,直到达到预定的阈值,以确定它是给定的“脚趾”。

这里有一个微妙的问题是,一旦你开始使用计算机视觉技术来识别某个东西是右/左/前/后爪,你开始观察单个脚趾,你就必须开始考虑旋转、倾斜和平移。这是通过分析所谓的“矩”来完成的。在视觉应用中有几个不同的时刻需要考虑:

中心矩:平移不变量 归一化矩:缩放和平移不变量 胡矩:平移、尺度和旋转不变量

更多关于moments的信息可以在wiki上搜索“image moments”。


也许你可以使用高斯混合模型。这是一个用于执行GMMs的Python包(刚刚进行了谷歌搜索) http://www.ar.media.kyoto-u.ac.jp/members/david/softwares/em/


物理学家的解决办法: 定义5个爪标记,用它们的位置X_i来标识,并用随机的位置初始化它们。 定义一个能量函数,结合标记物在爪子位置的定位奖励和标记物重叠惩罚;比方说:

E(X_i;S)=-Sum_i(S(X_i))+alfa*Sum_ij (|X_i-Xj|<=2*sqrt(2)?1:0)

(S(X_i)是围绕X_i的2x2平方的平均力,阿尔法是一个实验峰值参数)

Now time to do some Metropolis-Hastings magic: 1. Select random marker and move it by one pixel in random direction. 2. Calculate dE, the difference of energy this move caused. 3. Get an uniform random number from 0-1 and call it r. 4. If dE<0 or exp(-beta*dE)>r, accept the move and go to 1; if not, undo the move and go to 1. This should be repeated until the markers will converge to paws. Beta controls the scanning to optimizing tradeoff, so it should be also optimized experimentally; it can be also constantly increased with the time of simulation (simulated annealing).


有趣的问题。我想尝试的解决方案如下。

应用一个低通滤波器,比如用二维高斯掩码卷积。这将给你一堆(可能,但不一定是浮点数)值。 使用每个爪垫(或脚趾)的已知近似半径执行2D非最大抑制。

这将为你提供最大的职位,而不是有多个候选人。澄清一下,第1步中蒙版的半径也应该与第2步中使用的半径相似。这个半径是可以选择的,或者兽医可以事先明确测量它(它会随着年龄/品种等而变化)。

建议的一些解决方案(均值漂移、神经网络等)可能在某种程度上是可行的,但它们过于复杂,可能并不理想。


物理学家对这个问题进行了深入的研究。在ROOT中有一个很好的实现。查看TSpectrum类(特别是TSpectrum2)和它们的文档。

引用:

M.Morhac et al.: Background elimination methods for multidimensional coincidence gamma-ray spectra. Nuclear Instruments and Methods in Physics Research A 401 (1997) 113-132. M.Morhac et al.: Efficient one- and two-dimensional Gold deconvolution and its application to gamma-ray spectra decomposition. Nuclear Instruments and Methods in Physics Research A 401 (1997) 385-408. M.Morhac et al.: Identification of peaks in multidimensional coincidence gamma-ray spectra. Nuclear Instruments and Methods in Research Physics A 443(2000), 108-125.

...对于那些没有订阅NIM的人:

Spectrum.doc SpectrumDec.ps.gz SpectrumSrc.ps.gz SpectrumBck.ps.gz


谢谢你的原始数据。我在火车上,这是我到的最远的地方(我的站就要到了)。我用regexps按摩了你的txt文件,并将其放入一个html页面,使用一些javascript进行可视化。我在这里分享它是因为一些人,比如我自己,可能会发现它比python更容易被破解。

我认为一个很好的方法是尺度和旋转不变,我的下一步将是研究高斯的混合。(每个爪垫是高斯分布的中心)。

    <html>
<head>
    <script type="text/javascript" src="http://vis.stanford.edu/protovis/protovis-r3.2.js"></script> 
    <script type="text/javascript">
    var heatmap = [[[0,0,0,0,0,0,0,4,4,0,0,0,0],
[0,0,0,0,0,7,14,22,18,7,0,0,0],
[0,0,0,0,11,40,65,43,18,7,0,0,0],
[0,0,0,0,14,61,72,32,7,4,11,14,4],
[0,7,14,11,7,22,25,11,4,14,65,72,14],
[4,29,79,54,14,7,4,11,18,29,79,83,18],
[0,18,54,32,18,43,36,29,61,76,25,18,4],
[0,4,7,7,25,90,79,36,79,90,22,0,0],
[0,0,0,0,11,47,40,14,29,36,7,0,0],
[0,0,0,0,4,7,7,4,4,4,0,0,0]
],[
[0,0,0,4,4,0,0,0,0,0,0,0,0],
[0,0,11,18,18,7,0,0,0,0,0,0,0],
[0,4,29,47,29,7,0,4,4,0,0,0,0],
[0,0,11,29,29,7,7,22,25,7,0,0,0],
[0,0,0,4,4,4,14,61,83,22,0,0,0],
[4,7,4,4,4,4,14,32,25,7,0,0,0],
[4,11,7,14,25,25,47,79,32,4,0,0,0],
[0,4,4,22,58,40,29,86,36,4,0,0,0],
[0,0,0,7,18,14,7,18,7,0,0,0,0],
[0,0,0,0,4,4,0,0,0,0,0,0,0],
],[
[0,0,0,4,11,11,7,4,0,0,0,0,0],
[0,0,0,4,22,36,32,22,11,4,0,0,0],
[4,11,7,4,11,29,54,50,22,4,0,0,0],
[11,58,43,11,4,11,25,22,11,11,18,7,0],
[11,50,43,18,11,4,4,7,18,61,86,29,4],
[0,11,18,54,58,25,32,50,32,47,54,14,0],
[0,0,14,72,76,40,86,101,32,11,7,4,0],
[0,0,4,22,22,18,47,65,18,0,0,0,0],
[0,0,0,0,4,4,7,11,4,0,0,0,0],
],[
[0,0,0,0,4,4,4,0,0,0,0,0,0],
[0,0,0,4,14,14,18,7,0,0,0,0,0],
[0,0,0,4,14,40,54,22,4,0,0,0,0],
[0,7,11,4,11,32,36,11,0,0,0,0,0],
[4,29,36,11,4,7,7,4,4,0,0,0,0],
[4,25,32,18,7,4,4,4,14,7,0,0,0],
[0,7,36,58,29,14,22,14,18,11,0,0,0],
[0,11,50,68,32,40,61,18,4,4,0,0,0],
[0,4,11,18,18,43,32,7,0,0,0,0,0],
[0,0,0,0,4,7,4,0,0,0,0,0,0],
],[
[0,0,0,0,0,0,4,7,4,0,0,0,0],
[0,0,0,0,4,18,25,32,25,7,0,0,0],
[0,0,0,4,18,65,68,29,11,0,0,0,0],
[0,4,4,4,18,65,54,18,4,7,14,11,0],
[4,22,36,14,4,14,11,7,7,29,79,47,7],
[7,54,76,36,18,14,11,36,40,32,72,36,4],
[4,11,18,18,61,79,36,54,97,40,14,7,0],
[0,0,0,11,58,101,40,47,108,50,7,0,0],
[0,0,0,4,11,25,7,11,22,11,0,0,0],
[0,0,0,0,0,4,0,0,0,0,0,0,0],
],[
[0,0,4,7,4,0,0,0,0,0,0,0,0],
[0,0,11,22,14,4,0,4,0,0,0,0,0],
[0,0,7,18,14,4,4,14,18,4,0,0,0],
[0,4,0,4,4,0,4,32,54,18,0,0,0],
[4,11,7,4,7,7,18,29,22,4,0,0,0],
[7,18,7,22,40,25,50,76,25,4,0,0,0],
[0,4,4,22,61,32,25,54,18,0,0,0,0],
[0,0,0,4,11,7,4,11,4,0,0,0,0],
],[
[0,0,0,0,7,14,11,4,0,0,0,0,0],
[0,0,0,4,18,43,50,32,14,4,0,0,0],
[0,4,11,4,7,29,61,65,43,11,0,0,0],
[4,18,54,25,7,11,32,40,25,7,11,4,0],
[4,36,86,40,11,7,7,7,7,25,58,25,4],
[0,7,18,25,65,40,18,25,22,22,47,18,0],
[0,0,4,32,79,47,43,86,54,11,7,4,0],
[0,0,0,14,32,14,25,61,40,7,0,0,0],
[0,0,0,0,4,4,4,11,7,0,0,0,0],
],[
[0,0,0,0,4,7,11,4,0,0,0,0,0],
[0,4,4,0,4,11,18,11,0,0,0,0,0],
[4,11,11,4,0,4,4,4,0,0,0,0,0],
[4,18,14,7,4,0,0,4,7,7,0,0,0],
[0,7,18,29,14,11,11,7,18,18,4,0,0],
[0,11,43,50,29,43,40,11,4,4,0,0,0],
[0,4,18,25,22,54,40,7,0,0,0,0,0],
[0,0,4,4,4,11,7,0,0,0,0,0,0],
],[
[0,0,0,0,0,7,7,7,7,0,0,0,0],
[0,0,0,0,7,32,32,18,4,0,0,0,0],
[0,0,0,0,11,54,40,14,4,4,22,11,0],
[0,7,14,11,4,14,11,4,4,25,94,50,7],
[4,25,65,43,11,7,4,7,22,25,54,36,7],
[0,7,25,22,29,58,32,25,72,61,14,7,0],
[0,0,4,4,40,115,68,29,83,72,11,0,0],
[0,0,0,0,11,29,18,7,18,14,4,0,0],
[0,0,0,0,0,4,0,0,0,0,0,0,0],
]
];
</script>
</head>
<body>
    <script type="text/javascript+protovis">    
    for (var a=0; a < heatmap.length; a++) {
    var w = heatmap[a][0].length,
    h = heatmap[a].length;
var vis = new pv.Panel()
    .width(w * 6)
    .height(h * 6)
    .strokeStyle("#aaa")
    .lineWidth(4)
    .antialias(true);
vis.add(pv.Image)
    .imageWidth(w)
    .imageHeight(h)
    .image(pv.Scale.linear()
        .domain(0, 99, 100)
        .range("#000", "#fff", '#ff0a0a')
        .by(function(i, j) heatmap[a][j][i]));
vis.render();
}
</script>
  </body>
</html>


我用局部最大滤波器检测了峰值。下面是你的第一个4个爪子数据集的结果:

我还在9个爪子的第二个数据集上运行了它,效果也很好。

你可以这样做:

import numpy as np
from scipy.ndimage.filters import maximum_filter
from scipy.ndimage.morphology import generate_binary_structure, binary_erosion
import matplotlib.pyplot as pp

#for some reason I had to reshape. Numpy ignored the shape header.
paws_data = np.loadtxt("paws.txt").reshape(4,11,14)

#getting a list of images
paws = [p.squeeze() for p in np.vsplit(paws_data,4)]


def detect_peaks(image):
    """
    Takes an image and detect the peaks usingthe local maximum filter.
    Returns a boolean mask of the peaks (i.e. 1 when
    the pixel's value is the neighborhood maximum, 0 otherwise)
    """

    # define an 8-connected neighborhood
    neighborhood = generate_binary_structure(2,2)

    #apply the local maximum filter; all pixel of maximal value 
    #in their neighborhood are set to 1
    local_max = maximum_filter(image, footprint=neighborhood)==image
    #local_max is a mask that contains the peaks we are 
    #looking for, but also the background.
    #In order to isolate the peaks we must remove the background from the mask.

    #we create the mask of the background
    background = (image==0)

    #a little technicality: we must erode the background in order to 
    #successfully subtract it form local_max, otherwise a line will 
    #appear along the background border (artifact of the local maximum filter)
    eroded_background = binary_erosion(background, structure=neighborhood, border_value=1)

    #we obtain the final mask, containing only peaks, 
    #by removing the background from the local_max mask (xor operation)
    detected_peaks = local_max ^ eroded_background

    return detected_peaks


#applying the detection and plotting results
for i, paw in enumerate(paws):
    detected_peaks = detect_peaks(paw)
    pp.subplot(4,2,(2*i+1))
    pp.imshow(paw)
    pp.subplot(4,2,(2*i+2) )
    pp.imshow(detected_peaks)

pp.show()

之后你所需要做的就是在蒙版上使用scipy. nmage .measurements.label来标记所有不同的对象。然后你就可以单独和他们玩了。

注意,该方法工作得很好,因为背景没有噪声。如果是的话,你会在背景中检测到一堆其他不想要的峰。另一个重要因素是社区的大小。如果峰值大小发生变化,您将需要调整它(应该保持大致成比例)。


我相信你现在已经有足够的信息了,但我忍不住建议使用k-均值聚类方法。k-means是一种无监督聚类算法,它将你的数据(在任何维度上-我碰巧在3D中这样做),并将其排列成k个具有明显边界的聚类。这里很好,因为你确切地知道这些犬齿应该有多少脚趾。

此外,它是在Scipy中实现的,非常好(http://docs.scipy.org/doc/scipy/reference/cluster.vq.html)。

下面是它在空间上解析3D集群的一个例子:

你想要做的有点不同(2D,包括压力值),但我仍然认为你可以尝试一下。


使用持久同源分析你的数据集,我得到了以下结果(点击放大):

这是这个SO答案中描述的峰值检测方法的2d版本。上图仅显示了按持久性排序的0维持久性同源类。

我确实使用scipy.misc.imresize()将原始数据集提升了2倍。但是,请注意,我确实将四个爪子视为一个数据集;把它分成四份会使问题更简单。

方法。 这背后的思想非常简单:考虑函数的函数图,该函数为每个像素分配其级别。它是这样的:

现在考虑高度为255的水位不断下降到较低的水位。在局部极大值时,岛屿弹出(出生)。两个岛屿在鞍点合并;我们认为低岛与高岛合并(死亡)。所谓的持久性图(0维同调类,我们的岛屿)描述了所有岛屿的死亡-出生值:

岛屿的持续存在就是出生水平和死亡水平之间的差异;点到灰色主对角线的垂直距离。这个数字通过减少持久性来标记岛屿。

第一张图片显示了这些岛屿的诞生地点。该方法不仅给出了局部极大值,而且通过上述持久性量化了它们的“意义”。这样就会过滤掉所有持久性过低的岛屿。但是,在您的示例中,每个岛(即每个局部最大值)都是您要寻找的峰值。

Python代码可以在这里找到。


只是想告诉你们,有一个很好的选项可以用python在图像中找到局部最大值:

from skimage.feature import peak_local_max

或者对于skimage 0.8.0:

from skimage.feature.peak import peak_local_max

http://scikit-image.org/docs/0.8.0/api/skimage.feature.peak.html