关于Python的峰值检测:在一个二维阵列

Peak detection in a 2D array

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

我为每个爪制作了一个二维阵列,其中包括爪随时间加载的每个传感器的最大值。这是一个例子,我用Excel绘制了我想‘检测’的区域。传感器周围有2×2个盒子和局部最大值,它们的总和最大。

alt text

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

alt text

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

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

另外,我把2x2作为一种方便,任何更先进的解决方案都是受欢迎的,但我只是一个人类运动科学家,所以我既不是一个真正的程序员也不是一个数学家,所以请保持简单。

这是一个可以加载np.loadtxt的版本

结果

所以我尝试了@jexte的解决方案(见下面的结果)。如你所见,它对前爪非常有效,但对后腿却不太有效。

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

有人知道如何调整@jexte的算法,以便它也能找到第四个脚趾吗?

alt text

因为我还没有处理过任何其他的试验,我不能提供任何其他的样品。但我之前给出的数据是每个爪子的平均值。该文件是一个数组,其中最大数据为9个爪与板接触的顺序。

这张图片显示了它们是如何在空间上分布在盘子上的。

alt text

更新:

我已经为感兴趣的人建立了一个博客,并且建立了一个包含所有原始测量数据的SkyDrive。所以对于任何要求更多数据的人来说:给你更多的权力!

新更新:

所以,在我得到关于爪子检测和爪子分类的问题的帮助后,我终于能够检查每个爪子的脚趾检测了!事实证明,除了像我自己例子中那样大小的爪子,它在任何情况下都不能很好地工作。事后看来,选择2x2是我自己的错。

这里有一个很好的例子来说明它哪里出错了:一颗钉子被认为是一个脚趾,"鞋跟"太宽了,它被识别了两次!

alt text

爪太大,所以取2x2大小,没有重叠,导致一些脚趾被检测两次。另一方面,在小狗身上,它经常找不到第五个脚趾,我怀疑这是由于2x2区域太大造成的。

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

很明显我需要改变它。我自己的猜测是把neighborhood的大小改为小狗用的小一些,大狗用的大一些。但是generate_binary_structure不允许我更改数组的大小。

因此,我希望其他人对脚趾的定位有更好的建议,也许用脚掌大小的脚趾面积比例?


我用一个局部最大滤波器检测到峰值。以下是您的第一个4爪数据集的结果:Peaks detection result

我也在第二个9个爪的数据集上运行它,它也工作正常。

您可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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.ndimage.measurements.label来标记所有不同的对象。然后你就可以单独玩了。

请注意,该方法工作得很好,因为背景没有噪音。如果是的话,你会在背景中检测到一些其他不需要的峰值。另一个重要因素是邻里的规模。如果峰值大小发生变化,则需要对其进行调整(应保持大致成比例)。


解决方案

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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,"
"


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],"
"

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

一些评论

棘手的部分是计算所有2x2平方的和。我以为你需要所有的,所以可能会有一些重叠。我使用切片从原始二维数组中剪切第一列/最后一列和行,然后将它们全部重叠并计算和。

要更好地理解它,请对3x3阵列进行成像:

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

然后你可以把它切片:

1
2
3
4
5
6
7
8
9
10
11
12
>>> 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平方上的和完全相同,左上角位于同一位置:

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

当求和超过2x2平方时,可以使用max求最大值,或使用sortsorted求峰值。

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

笔记

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

选择手指(想法)

另一个问题是如何从所有的峰值中选择可能是手指的。我有一个想法,可能会起作用,也可能不会起作用。我现在没有时间来实现它,所以只是伪代码。

我注意到,如果前指停留在一个几乎完美的圆圈上,后指应该在圆圈内。另外,前指的间距或多或少相等。我们可以尝试使用这些启发式属性来检测手指。

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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_5=792个组合,乘以5个方法选择一个后指,所以3960个病例评估每个爪。


这是一个图像注册问题。总体战略是:

  • 在数据上有一个已知的例子或某种先验。
  • 使您的数据适合示例,或使示例适合您的数据。
  • 如果您的数据在一开始就大致对齐,这会有所帮助。

这里有一个粗略而准备好的方法,"最愚蠢的事情,可能会工作":

  • 从五个脚趾坐标开始,大致在你期望的位置。
  • 每一个都要反复爬到山顶。即给定当前位置,如果其值大于当前像素,则移动到最大相邻像素。当脚趾坐标停止移动时停止。

为了解决方向问题,您可以为基本方向(北、东北等)设置8个左右的初始设置。分别运行每一个,并丢弃两个或多个脚趾以相同像素结束的任何结果。我会再想一想,但这类事情在图像处理中仍在研究中-没有正确的答案!

稍微复杂一点的想法:(加权)k表示聚类。还不错。

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

然后迭代直到收敛:

  • 将每个像素分配给最近的簇(只需为每个簇创建一个列表)。
  • 计算每个星团的质心。对于每个簇,这是:和(坐标*强度值)/和(坐标)
  • 将每个簇移动到新的质心。

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

(同样,您已经指定了前面的集群数量。对于集群,您必须以一种或另一种方式指定密度:要么选择集群的数量,在本例中是适当的,要么选择集群半径,然后查看最终有多少集群。后者的一个例子是均值漂移。)

对于缺少实现细节或其他细节,我们深表歉意。我会把这个编出来,但我有一个最后期限。如果到下个星期还没有其他工作,请告诉我,我会试试的。


物理学家对这个问题作了一些深入的研究。根目录中有一个很好的实现。查看tspectrum类(尤其是案例的tspectrum2)及其文档。

参考文献:

  • M.Morhac等人:多维符合伽马射线谱的背景消除方法。物理研究中的核仪器和方法A 401(1997)113-132。
  • M.Morhac等人:有效的一维和二维黄金反褶积及其在伽马射线光谱分解中的应用。物理研究中的核仪器和方法A 401(1997)385-408。
  • M.Morhac等人:多维符合伽马射线谱中峰的识别。研究物理学中的核仪器和方法A 443(2000),108-125。
  • …对于那些无法访问NIM订阅的用户:

    • 波谱
    • 光谱DEC.PS.GZ
    • 光谱src.ps.gz
    • 光谱Bck.ps.gz


    这里有一个想法:你计算图像的(离散)拉普拉斯。我希望它在Maxima上是(负的和)大的,在某种程度上比原始图像更具戏剧性。因此,马克西玛可能更容易找到。

    这里还有一个想法:如果你知道高压点的典型尺寸,你可以先用同样尺寸的高斯卷积来平滑你的图像。这可以让您处理更简单的图像。


    使用持久的同源性来分析您的数据集,我得到以下结果(单击放大):

    Result

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

    我使用scipy.misc.imresize()将原始数据集放大了2倍。但是,请注意,我确实将四个爪视为一个数据集;将其拆分为四个爪将使问题更容易解决。

    方法论。这背后的想法很简单:考虑函数的函数图,它为每个像素分配其级别。看起来是这样的:

    3D function graph

    现在考虑一个高度为255的水位,它不断下降到较低的水位。在当地的马克西玛群岛出现(出生)。在鞍点,两个岛合并;我们认为较低的岛合并到较高的岛(死亡)。所谓的持久性图(在我们的岛屿的第0维同源类中)描述了所有岛屿的死亡-出生值:

    Persistence diagram

    岛屿的持续性就是出生和死亡水平之间的差异,即一个点到灰色主对角线的垂直距离。该图通过减少持久性来标记孤岛。

    第一张照片显示了这些岛屿的出生地点。这种方法不仅给出局部极大值,而且通过上述的持续性来量化它们的"意义"。然后,一个人会用太低的持久性过滤掉所有的岛屿。然而,在您的示例中,每个岛(即每个局部最大值)都是您要查找的峰值。

    这里可以找到python代码。


    我脑子里只有几个想法:

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

    您可能还想看看opencv,它有一个相当不错的python API,并且可能有一些您认为有用的函数。


    我相信你现在已经有足够的时间继续下去了,但是我不得不建议使用k-均值聚类方法。k-means是一种无监督的聚类算法,它将把你的数据(在任何维度中——我碰巧在3d中这样做)排列成具有不同边界的k簇。这里很好,因为你知道这些狗应该有多少脚趾。

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

    下面是一个例子,说明它可以在空间上解决3D集群:enter image description here

    你想做的是有点不同(二维,包括压力值),但我仍然认为你可以给它一个机会。


    感谢您提供原始数据。我在火车上,这是我所能到达的地方(我的站快到了)。我用regexps对您的txt文件进行了按摩,并用一些javascript将其放到一个HTML页面中进行可视化处理。我在这里分享它是因为有些人,像我自己,可能会发现它比Python更容易被黑客攻击。

    我认为一个好的方法将是规模和旋转不变量,我的下一步将是研究高斯混合体。(每个爪垫为高斯中心)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
        <html>
    <head>
        <script type="text/javascript" src="http://vis.stanford.edu/protovis/protovis-r3.2.js">
        <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],
    ]
    ];

    </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();
    }

      </body>
    </html>

    alt text


    物理学家的解决方案:定义5个由它们的位置X_i标识的爪标记,并用随机位置初始化它们。定义一些能量函数,结合对标记在爪位置的位置的奖励和对标记重叠的惩罚,假设:

    1
    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是实验上要达到峰值的参数)

    现在是时候做些大都会黑斯廷斯魔法了:1。选择随机标记并随机移动一个像素。2。计算这个移动所引起的能量差。三。从0-1中得到一个统一的随机数,称之为r。4。如果是dE<0exp(-beta*dE)>r,接受移动并转到1;如果不是,撤消移动并转到1。这应该重复,直到标记收敛到爪。beta控制扫描以优化权衡,因此也应通过实验进行优化;它还可以随着模拟时间(模拟退火)不断增加。


    粗略的轮廓…

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

    你必须决定是否使用4或8连通性。就个人而言,对于大多数问题,我更喜欢6-连通性。不管怎样,一旦你将每个"爪印"分离为一个连接的区域,就可以很容易地遍历该区域并找到最大值。一旦你找到了最大值,你可以迭代地扩大这个区域,直到你达到一个预定的阈值,以便把它识别为一个给定的"脚趾"。

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

    中心矩:平移不变量规范化矩:缩放和平移不变量Hu矩:平移、缩放和旋转不变量

    通过在wiki上搜索"图片时刻",可以找到关于时刻的更多信息。


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


    下面是我为大型望远镜做类似工作时使用的另一种方法:

    1)搜索最高像素。一旦你有了它,在它周围搜索2x2的最佳匹配(可能最大化2x2和),或者在4x4的子区域内以最高像素为中心进行二维高斯拟合。

    然后将你发现的2x2像素设置为围绕峰值中心零(或者3x3)。

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


    也许你可以使用高斯混合模型。这是一个做GMMS的python包(刚刚做了一个谷歌搜索)http://www.ar.media.京都-u.ac.jp/members/david/softwares/em/


    似乎你可以用Jetxee的算法作弊。他发现前三个脚趾很好,你应该能猜出第四个脚趾是基于什么。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    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正方形中选择一个正方形,但另一个则是向左。幸运的是,幸运的是,我们会选择更多你想要的,但这可能需要一些其他的想法来得到你一直想要的。


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

  • 应用低通滤波器,如用二维高斯掩模卷积。这将为您提供一系列(可能,但不一定是浮点)值。

  • 使用每个爪垫(或脚趾)的已知近似半径执行二维非最大抑制。

  • 这应该给你最大的职位,而不是有多个候选人是密切联系在一起。为了澄清,步骤1中的遮罩半径也应与步骤2中使用的半径相似。这个半径可以选择,或者兽医可以事先明确测量它(它将随年龄/品种等而变化)。

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


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

    1
    from skimage.feature import peak_local_max

    或者对于skimage 0.8.0

    1
    from skimage.feature.peak import peak_local_max

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


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

    首先,在"爪形列表"中选择最高值的正方形。然后,迭代地选择4个不与任何先前发现的正方形相交的次优正方形。


    天文学和宇宙学界提供了大量的软件——这是一个重要的历史和当前研究领域。

    如果你不是天文学家,不要惊慌——有些在野外很容易使用。例如,您可以使用Astropy/Photutils:

    https://photutils.readthedocs.io/en/stable/detection.html本地峰值检测

    [在这里重复他们的简短示例代码似乎有点粗鲁。]

    下面给出了可能感兴趣的技术/软件包/链接的不完整和稍有偏差的列表-请在评论中添加更多内容,我将根据需要更新此答案。当然,精度与计算资源之间存在着权衡。[老实说,在这样一个答案中,有太多的代码示例无法给出,因此我不确定这个答案是否可行。]

    源抽取器https://www.astromatic.net/software/sextractor

    多嵌套https://github.com/farhanferoz/multinest[+pymultinest]

    ASKAP/EMU寻源挑战:https://arxiv.org/abs/1509.03931

    您还可以搜索Planck和/或WMAP源提取挑战。


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

    这是要点。注意,它在Ruby中,但是这个想法应该很清楚。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    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


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