在一个二维数组中进行峰值检测。

1001
我正在帮助一家兽医诊所测量狗爪下的压力。我使用Python进行数据分析,但现在我遇到了一个问题,即如何将爪子划分为(解剖学上的)子区域。
我创建了一个二维数组,其中包含每个爪子的最大值,这些最大值是由爪子随时间加载的传感器产生的。以下是一个示例,我使用Excel绘制了我想要“检测”的区域。这些区域是围绕具有局部极大值的传感器的2x2方框,它们的总和最大。

alt text

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

alt text

这其中哪一个最大值是我想要的,告诉Python最好的方式是什么?
注意:2x2的方块不能重叠,因为它们必须是分开的。
我把2x2当作一种便利方法,欢迎任何更高级的解决方案,但我只是一个人类运动科学家,既不是真正的程序员也不是数学家,所以请保持“简单”。
这是一个可以使用`np.loadtxt`加载的版本

结果

所以我尝试了 @jextee 的解决方案(请参见下面的结果)。正如您所看到的,它在前脚上运作得非常好,但对于后腿来说效果不太好。

更具体地说,它无法识别作为第四趾的小峰。这显然是由于该循环从上向下看最低值,并未考虑其位置造成的固有问题。

是否有人知道如何调整 @jextee 的算法,使其能够找到第四趾?

alt text

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

这张图显示了它们在板子上如何空间分布。

alt text

更新:

我为所有感兴趣的人建立了一个博客,并且我已经设置了一个包含所有原始测量数据的OneDrive。因此,对于任何请求更多数据的人:祝你好运!


新的更新:

在我关于 爪子检测爪子分类 的问题得到帮助后,我终于能够检测每只爪子的脚趾!结果发现,除了与我自己示例中的爪子尺寸相似的爪子外,它在其他情况下的效果并不好。事后想想,选择2x2的尺寸是我的任性。

以下是一个错误的例子:一根指甲被识别为脚趾,而“脚跟”太宽,被识别了两次!

alt text

爪子太大了,所以采用2x2的尺寸且不重叠,会导致有些脚趾被检测到两次。反过来,在小狗身上通常无法找到第五个脚趾,我怀疑是因为2x2区域太大的原因。

对所有我的测量值尝试当前解决方案后,令人惊讶的结论是几乎所有的小狗都没有找到第五个脚趾,而在大狗的超过50%的情况下会找到更多!

所以显然我需要进行改变。我自己的猜测是将neighborhood的大小对于小狗设定得更小,对于大狗设定得更大。但是generate_binary_structure不允许我更改数组的大小。

有人对于定位脚趾有更好的建议吗?也许能够使脚趾区域与爪子大小成比例变化?


1
我理解逗号是小数点而不是值分隔符,对吗? - MattH
1
是的,它们是逗号。@Christian,我试图将其放入一个易于阅读的文件中,但即使这样也失败了 :( - Ivo Flipse
5
由于我正在进行可行性研究,因此任何方法都可以。所以我正在寻找尽可能多的定义压力的方法,包括亚区域。此外,我需要能够区分“大脚趾”和“小脚趾”两侧,以估计方向。但由于这之前没有做过,所以我们无法预测会发现什么 :-) - Ivo Flipse
3
@Ron:这项研究的目标之一是看这个系统适用于什么体型/重量的狗,所以是的,虽然这只狗大约有20公斤。我还有一些明显较小(和更大)的狗,并预计我不会能够对真正小的狗做同样的事情。 - Ivo Flipse
3
@frank,爪子的尺寸是随时间测量的,因此有第三个维度。然而,它们不会移动位置(相对而言),所以我更关注爪子在二维空间中的位置。在此基础上,第三个维度是额外得到的。 - Ivo Flipse
显示剩余5条评论
21个回答

11

物理学家的解决方案:
通过它们的位置定义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正方形内的平均力,alfa是一个需要通过实验确定的参数)

时间进行Metropolis-Hastings算法的魔法:
1. 随机选择一个标记,并将其沿着任意方向移动一个像素。
2. 计算dE,即此次移动引起的能量差异。
3. 获取一个从0到1的均匀随机数,并将其称为r。
4. 如果 dE<0 或者 exp(-beta*dE)>r,则接受此次移动并返回1;否则撤销移动并返回1。
这应该重复进行直到标记收敛于爪子。Beta控制扫描的优化权衡,因此它也应该通过实验进行优化。它也可以随着模拟的时间不断增加(模拟退火)。


能否演示一下在我的例子上如何运作?因为我真的不擅长高等数学,所以我已经很难理解你提出的公式了 :( - Ivo Flipse

10

如果您能够创建一些训练数据的话,尝试使用神经网络可能是值得的......但这需要手动注释许多样本。


2
如果值得的话,我不介意手动注释大量样本。我的问题是:由于我对编程神经网络一无所知,所以我该如何实现它。 - Ivo Flipse

9
这里是我在为一台大型望远镜做类似事情时使用的另一种方法:
1)搜索最高像素。一旦找到了它,就在其周围搜索最适合2x2(也许是最大化2x2总和),或在以最高像素为中心的4x4子区域内进行2D高斯拟合。
然后将这些已找到的2x2像素(或者可能是3x3)设置为峰值中心周围的零点。
2)回到1),重复此过程,直到最高峰值低于噪声阈值,或者您已经获得所需的所有峰值。

可以分享一个实现这个功能的代码示例吗?我能够理解你想做什么,但不知道如何编写代码。 - Ivo Flipse

9
粗略的概述如下:您可能希望使用连通组件算法来分离每个爪子区域。维基百科在此处提供了一个不错的描述(附有一些代码):http://en.wikipedia.org/wiki/Connected_Component_Labeling。您需要决定是使用4个还是8个连接性。对于大多数问题,我个人更喜欢使用6个连接性。无论如何,一旦将每个“爪印”作为一个连接区域分离出来,就可以轻松地遍历该区域并找到最大值。找到最大值后,您可以迭代地扩大该区域,直到达到预定的阈值,以将其识别为给定的“趾”。这里一个微妙的问题是,一旦您开始使用计算机视觉技术将某物识别为右/左/前/后脚,并开始查看单个趾部,您必须开始考虑旋转、倾斜和平移。这是通过分析所谓的“矩”来实现的。在视觉应用中需要考虑几种不同的矩:中心矩:平移不变、归一化矩:缩放和平移不变、Hu矩:平移、缩放和旋转不变。在维基上搜索“图像矩”可以找到更多关于矩的信息。

8

7
我会尝试的解决方案如下:
1. 应用低通滤波器,例如使用2D高斯掩模进行卷积。这将给出一系列(可能是浮点数)值。
2. 使用已知的爪垫(或脚趾)的大致半径进行2D非极大值抑制。
这样可以得到最大位置,而不会有多个彼此靠近的候选项。需要澄清的是,步骤1中掩模的半径也应与步骤2中使用的半径相似。这个半径可以是可选择的,或者兽医可以事先明确测量它(它会随年龄/品种等而变化)。
一些建议的解决方案(均值漂移、神经网络等)可能在某种程度上有效,但过于复杂,可能不是理想的选择。

我对卷积矩阵和高斯滤波器完全没有经验,你能否给我演示一下如何在我的例子中使用它们? - Ivo Flipse

6

看起来您可以使用jetxee的算法稍微作弊一下。他已经很好地找到了前三个脚趾,您应该能够根据此猜出第四个脚趾的位置。


5

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

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正方形排除在外,将其放入best数组中,并删除所有使用此刚刚删除的2x2正方形的任何其他2x2正方形。
它似乎工作得很好,除了最后一只爪子(第一张图片最右边的总和最小的那只),结果发现还有另外两个符合条件且总和更大的2x2正方形(它们彼此之间的总和相等)。其中一个仍然从你的2x2正方形中选择一个正方形,但另一个则偏向左侧。幸运的是,我们似乎更多地选择了你想要的那个,但这可能需要使用一些其他想法才能始终获得所需的结果。

5

我不确定这是否回答了问题,但似乎你只需要查找没有相邻峰的前n个最高峰。

这是gist链接,请注意它是用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

我认为这个程序总体表现不佳。它在噪音方面表现不佳。而且,它检测到的4个点中,并没有保证有些不会位于同一个脚趾垫上。 - user1142217

2
也许一个天真的方法在这里足够了:在您的平面上建立一个所有2x2正方形的列表,按它们的和(按降序)排序。
首先,将最高价值的正方形选择到您的“抓手列表”中。然后,迭代地选择4个下一个最好的正方形,这些正方形不与以前找到的任何正方形相交。

我实际上已经用所有2x2和制作了一个列表,但当我将它们排序后,不知道如何迭代比较它们。我的问题是,在排序时,我失去了坐标的跟踪。也许我可以将它们放在一个字典中,以坐标作为键。 - Ivo Flipse
是的,某种字典是必要的。我本来以为你对网格的表示已经是某种字典了。 - Johannes Charra
上面你看到的图像是一个numpy数组。其余部分目前存储在多维列表中。尽管我不太熟悉遍历字典,但最好停止这样做。 - Ivo Flipse

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接