定位局部最大值的算法

时间:2010-07-14 02:08:53

标签: algorithm language-agnostic math function max

我的数据总是如下所示:

alt text http://michaelfogleman.com/static/images/chart.png

我需要一种算法来定位三个峰值。

x轴实际上是摄像机位置,y轴是该位置处的图像聚焦/对比度的度量。有三种不同距离的特征可以聚焦,我需要确定这三个点的x值。

中间的驼峰总是有点难以挑选出来,即使对于人来说也是如此。

我有一个主要工作的自制算法,但我想知道是否有一种标准方法可以从一个可能有一点噪音的函数中获取局部最大值。然而,峰值很容易克服噪音。

此外,作为相机数据,不需要扫描整个范围的算法可能很有用。

编辑:发布我最终使用的Python代码。它使用我的原始代码,在给定搜索阈值的情况下找到最大值,并进行二分搜索以找到导致所需最大数量的阈值。

修改:以下代码中包含的示例数据。新代码是O(n)而不是O(n ^ 2)。

def find_n_maxima(data, count):
    low = 0
    high = max(data) - min(data)
    for iteration in xrange(100): # max iterations
        mid = low + (high - low) / 2.0
        maxima = find_maxima(data, mid)
        if len(maxima) == count:
            return maxima
        elif len(maxima) < count: # threshold too high
            high = mid
        else: # threshold too low
            low = mid
    return None # failed

def find_maxima(data, threshold):
    def search(data, threshold, index, forward):
        max_index = index
        max_value = data[index]
        if forward:
            path = xrange(index + 1, len(data))
        else:
            path = xrange(index - 1, -1, -1)
        for i in path:
            if data[i] > max_value:
                max_index = i
                max_value = data[i]
            elif max_value - data[i] > threshold:
                break
        return max_index, i
    # forward pass
    forward = set()
    index = 0
    while index < len(data) - 1:
        maximum, index = search(data, threshold, index, True)
        forward.add(maximum)
        index += 1
    # reverse pass
    reverse = set()
    index = len(data) - 1
    while index > 0:
        maximum, index = search(data, threshold, index, False)
        reverse.add(maximum)
        index -= 1
    return sorted(forward & reverse)

data = [
    1263.900, 1271.968, 1276.151, 1282.254, 1287.156, 1296.513,
    1298.799, 1304.725, 1309.996, 1314.484, 1321.759, 1323.988,
    1331.923, 1336.100, 1340.007, 1340.548, 1343.124, 1353.717,
    1359.175, 1364.638, 1364.548, 1357.525, 1362.012, 1367.190,
    1367.852, 1376.275, 1374.726, 1374.260, 1392.284, 1382.035,
    1399.418, 1401.785, 1400.353, 1418.418, 1420.401, 1423.711,
    1425.214, 1436.231, 1431.356, 1435.665, 1445.239, 1438.701,
    1441.988, 1448.930, 1455.066, 1455.047, 1456.652, 1456.771,
    1459.191, 1473.207, 1465.788, 1488.785, 1491.422, 1492.827,
    1498.112, 1498.855, 1505.426, 1514.587, 1512.174, 1525.244,
    1532.235, 1543.360, 1543.985, 1548.323, 1552.478, 1576.477,
    1589.333, 1610.769, 1623.852, 1634.618, 1662.585, 1704.127,
    1758.718, 1807.490, 1852.097, 1969.540, 2243.820, 2354.224,
    2881.420, 2818.216, 2552.177, 2355.270, 2033.465, 1965.328,
    1824.853, 1831.997, 1779.384, 1764.789, 1704.507, 1683.615,
    1652.712, 1646.422, 1620.593, 1620.235, 1613.024, 1607.675,
    1604.015, 1574.567, 1587.718, 1584.822, 1588.432, 1593.377,
    1590.533, 1601.445, 1667.327, 1739.034, 1915.442, 2128.835,
    2147.193, 1970.836, 1755.509, 1653.258, 1613.284, 1558.576,
    1552.720, 1541.606, 1516.091, 1503.747, 1488.797, 1492.021,
    1466.720, 1457.120, 1462.485, 1451.347, 1453.224, 1440.477,
    1438.634, 1444.571, 1428.962, 1431.486, 1421.721, 1421.367,
    1403.461, 1415.482, 1405.318, 1399.041, 1399.306, 1390.486,
    1396.746, 1386.178, 1376.941, 1369.880, 1359.294, 1358.123,
    1353.398, 1345.121, 1338.808, 1330.982, 1324.264, 1322.147,
    1321.098, 1313.729, 1310.168, 1304.218, 1293.445, 1285.296,
    1281.882, 1280.444, 1274.795, 1271.765, 1266.857, 1260.161,
    1254.380, 1247.886, 1250.585, 1246.901, 1245.061, 1238.658,
    1235.497, 1231.393, 1226.241, 1223.136, 1218.232, 1219.658,
    1222.149, 1216.385, 1214.313, 1211.167, 1208.203, 1206.178,
    1206.139, 1202.020, 1205.854, 1206.720, 1204.005, 1205.308,
    1199.405, 1198.023, 1196.419, 1194.532, 1194.543, 1193.482,
    1197.279, 1196.998, 1194.489, 1189.537, 1188.338, 1184.860,
    1184.633, 1184.930, 1182.631, 1187.617, 1179.873, 1171.960,
    1170.831, 1167.442, 1177.138, 1166.485, 1164.465, 1161.374,
    1167.185, 1174.334, 1186.339, 1202.136, 1234.999, 1283.328,
    1347.111, 1679.050, 1927.083, 1860.902, 1602.791, 1350.454,
    1274.236, 1207.727, 1169.078, 1138.025, 1117.319, 1109.169,
    1080.018, 1073.837, 1059.876, 1050.209, 1050.859, 1035.003,
    1029.214, 1024.602, 1017.932, 1006.911, 1010.722, 1005.582,
    1000.332, 998.0721, 992.7311, 992.6507, 981.0430, 969.9936,
    972.8696, 967.9463, 970.1519, 957.1309, 959.6917, 958.0536,
    954.6357, 954.9951, 947.8299, 953.3991, 949.2725, 948.9012,
    939.8549, 940.1641, 942.9881, 938.4526, 937.9550, 929.6279,
    935.5402, 921.5773, 933.6365, 918.7065, 922.5849, 939.6088,
    911.3251, 923.7205, 924.8227, 911.3192, 936.7066, 915.2046,
    919.0274, 915.0533, 910.9783, 913.6773, 916.6287, 907.9267,
    908.0421, 908.7398, 911.8401, 914.5696, 912.0115, 919.4418,
    917.0436, 920.5495, 917.6138, 907.5037, 908.5145, 919.5846,
    917.6047, 926.8447, 910.6347, 912.8305, 907.7085, 911.6889,
]

for n in xrange(1, 6):
    print 'Looking for %d maxima:' % n
    indexes = find_n_maxima(data, n)
    print indexes
    print ', '.join(str(data[i]) for i in indexes)
    print

输出:

Looking for 1 maxima:
[78]
2881.42

Looking for 2 maxima:
[78, 218]
2881.42, 1927.083

Looking for 3 maxima:
[78, 108, 218]
2881.42, 2147.193, 1927.083

Looking for 4 maxima:
[78, 108, 218, 274]
2881.42, 2147.193, 1927.083, 936.7066

Looking for 5 maxima:
[78, 108, 218, 269, 274]
2881.42, 2147.193, 1927.083, 939.6088, 936.7066

9 个答案:

答案 0 :(得分:9)

局部最大值将是任何x点,其y值高于其左右邻居。为了消除噪声,您可以设置某种容差阈值(例如,x点的y值必须高于其邻居的n值)。

为了避免扫描每个点,您可以使用相同的方法,但一次5或10个点,以粗略地了解最大值的位置。然后回到这些区域进行更详细的扫描。

答案 1 :(得分:9)

你不能沿着图表移动,定期计算导数,如果它从正变为负,你就找到了一个峰值?

答案 2 :(得分:3)

您可以尝试将样条拟合到数据,然后找到样条曲线的极值。由于样条是分段多项式,因此可以使用相对简单的公式找到极值的确切位置。

答案 3 :(得分:3)

我练习,我发现效果很好的是使用扩张形态学操作来产生你的采样函数的扩张版本(数据点)然后识别局部最大值比较扩张版本与原始版本以及任何地方扩张版本等于原始版本应该是局部最大值。我发现2D +数据(即图像)效果非常好,但由于你有1D数据,因此可以更容易地将连续点之间的差异用作导数的近似值。

请注意,如果您使用扩张技术,则在扩张中使用的结构元素(大小和形状)将极大地决定您要寻找的峰的类型。

此外,如果数据中存在噪声,请使用低通滤波器对其进行平滑处理,如搜索前的1D高斯滤波。

有关扩张的更多信息,请点击此处:

这是在matlab中实现的想法:http://www.mathworks.com/matlabcentral/fileexchange/14498-local-maxima-minima

如果您不知道扩张是什么: http://en.wikipedia.org/wiki/Dilation_%28morphology%29

(一旦你理解了它,这很简单就是一个非常简单的解释) http://homepages.inf.ed.ac.uk/rbf/HIPR2/dilate.htm

答案 4 :(得分:3)

直接接近,类似这样:

#include <stdio.h>
#include <stdlib.h>

#define MAXN 100

double smooth(double arr[], int n, int i)
{
        double l,r,smoo;
        l = (i - 1 < 0)?arr[0]:arr[i-1];
        r = (i + 1 >= n)?arr[n-1]:arr[i+1];
        smoo = (l + 2*arr[i] + r)/4;
        return smoo;
}

void findmax(double arr[], int n)
{
        double prev = arr[0];
        int i;
        int goup = 1;

        for(i = 0; i < n; i++)
        {
                double cur = smooth(arr,n,i);
                if(goup) {
                        if(prev > cur && i > 0) {
                                printf("max at %d = %lf\n", i-1, arr[i-1]);
                                goup = 0;
                        }
                } else {
                        if(prev < cur)
                                goup = 1;
                }
                prev = cur;
        }
}

int main()
{
        double arr[MAXN] = {0,0,1,2,3,4,4,3,2,2,3,4,6,8,7,8,6,3,1,0};
        int n = 20, i;

        for(i = 0; i < n; i++)
                printf("%.1lf ",arr[i]);
        printf("\n");

        findmax(arr,n);
        return 0;
}

输出:

0.0 0.0 1.0 2.0 3.0 4.0 4.0 3.0 2.0 2.0 3.0 4.0 6.0 8.0 7.0 8.0 6.0 3.0 1.0 0.0
max at 6 = 4.000000
max at 14 = 7.000000

1)设置state = goup:向上走曲线;
2)如果previuos值大于current,则有最大值:
   打印它    将州设置为仓库 3)当处于仓库状态时,等到前一个小于当前并切换到(1)

减少噪音使用一些平滑功能平滑()

答案 5 :(得分:2)

您知道数据的衍生物吗? 如果是,你可以象征性地解决系统,那么你可以找到导数等于零的点。

如果您没有公式(您的OP似乎就是这种情况),那么您可能想尝试滤除一些噪音,然后执行以下操作:

如果你不能象征性地解决那么你可以使用Newton-Raphson方法来获得局部最大值并从该范围中随机选择起始点以尝试捕获所有最大值。

如果你没有衍生数据,那么你可能想尝试一种不需要导数的爬山算法,并在多个不同的随机选择点开始它。然后,您可以跟踪在迭代爬山部分算法终止时找到的点。这只会概率地找到所有局部最大值,但它可能足以满足您的目的。

编辑:鉴于3个峰值大致位于相同位置,您应该尝试保证这些算法的起点在运行迭代算法的至少一些时间内接近这些点。

答案 6 :(得分:2)

您可以尝试使用带通滤波器来抑制噪声,并更容易可靠地选择这些最大值。

带通(而不是低通)的点是将几乎常数拉低至零。这样,您在过滤结果中找到的最高值可能是最清晰的峰值。

当然,如果您可以为信号定义一个较窄的频率范围并应用一个非常有选择性的滤波器,那么它应该是一个相当简单的最大值查找算法 - 例如一个简单的样本 - 高于任何一个邻居的扫描。

可能不需要复杂的过滤器 - 您可以在一个精心选择的规模上使用mexican hat wavelet。一个比例可能意味着它不再是一个小波 - 只是一个带通FIR滤波器。

修改

有一个不对称的小波(我忘记了名字),如果墨西哥帽类似于余弦,则扮演正弦的角色。我提到它,因为它结合了带通滤波和一种导数 - 结果中的过零点是原始信号的带通滤波版本的固定点。

“去抖”扫描可以通过查找此“衍生”信号中的交叉点来识别可靠的最大值。

答案 7 :(得分:2)

正如其他人提到的衍生物或与当地邻居相比,通常对我有用。如果您担心噪音,我可以推荐median filtration作为一种非常快速和可靠的过滤方案。我一直使用它并反转中值滤波来抑制声学传感器中的噪音,效果很好。

答案 8 :(得分:2)

另一种方法是创建我称之为步行坡度的平均值。 我不知道是否有它的名字,但它是这样的,你的数据集是例如1000个数字,你取x [n] + x [n..1234567]数字 说7个数字ahaed,取平均前3和后3 使用它们来查找放在它们上面的线是否会上升或下降。

当它下降时,你经过一个山峰偷看号码,一个这样的样本等待它再次升起。因此,只有在向上之后,您才会跟踪下降的第一时刻。并重复一遍。

它将检测顶部,并根据斜线长度(7)... 15 ..33等,您还可以去除噪音。