去除折线图中多余的点

3

我正在尝试使用某个库绘制大量点。这些点按时间排序,它们的值可以被认为是不可预测的。

目前我的问题是,点的数量太多了,使得绘图库渲染时间过长。许多点是冗余的(也就是说,它们在由函数y = ax + b定义的同一条线上)。有没有一种方法来检测和去除冗余的点以加速渲染?

感谢您的时间。


可能需要更具体一些:您使用的是哪个库,点是如何存储的? - slhck
@slhck 我正在寻找一个算法,我不认为库会影响它。这些点被存储为大量的 (x, y) 值列表。 - nc3b
1
你是真的想说“random”吗?还是你实际上想说“arbitrary”?或者是“unpredictable”? - Lightness Races in Orbit
@Tomalak Geret'kal 谢谢您的纠正 :) - nc3b
3个回答

8
以下是针对1.5D图表的Ramer-Douglas-Peucker算法变体:
  1. 计算第一个点和最后一个点之间的直线方程
  2. 检查所有其他点,找到距离直线最远的点
  3. 如果最差的点低于您想要的容差,则输出单个段落
  4. 否则,使用最差的点作为分裂器,递归调用考虑两个子数组
在Python中,这可能是这样的:
def simplify(pts, eps):
    if len(pts) < 3:
        return pts
    x0, y0 = pts[0]
    x1, y1 = pts[-1]
    m = float(y1 - y0) / float(x1 - x0)
    q = y0 - m*x0
    worst_err = -1
    worst_index = -1
    for i in xrange(1, len(pts) - 1):
        x, y = pts[i]
        err = abs(m*x + q - y)
        if err > worst_err:
            worst_err = err
            worst_index = i
    if worst_err < eps:
        return [(x0, y0), (x1, y1)]
    else:
        first = simplify(pts[:worst_index+1], eps)
        second = simplify(pts[worst_index:], eps)
        return first + second[1:]

print simplify([(0,0), (10,10), (20,20), (30,30), (50,0)], 0.1)

输出结果为[(0, 0), (30, 30), (50, 0)]

关于Python数组语法的一些可能不太明显的部分:

  • x[a:b]是从索引a到索引b(不包括)的数组部分
  • x[n:]是使用x中从索引n到结尾的元素构成的数组
  • x[:n]是使用x的前n个元素构成的数组
  • a+b,当ab是数组时,表示连接
  • x[-1]是数组的最后一个元素

在具有100,000个点的图上使用递增的eps值运行此实现的结果示例可以在这里看到。


我不确定我理解第一个和最后一个点是什么。 - nc3b
2
@Tomalak:我认为你把问题搞混了。我们这里不是在移除异常值,而是在移除“无聊”的点。如果第一个和最后一个点远离拟合线,那么根本没有任何问题。当然,这种简化可能不是“最优”的,但它非常快速和易于编码。 - 6502
@nc3b:我指的是数组中的第一个和最后一个点。我添加了一个Python实现;即使您不知道Python但熟悉其他命令式语言,它也应该很容易阅读。请注意,可以通过避免数据复制并改用起始/停止索引来使其更快。 - 6502
@Tomalak:最佳拟合线是你坚持要的,但这与这个问题完全无关。输入不是一条(直)线,而是一个图形。我们不是在寻找一条线,而是在寻找一个类似的图形,但点数更少。如果即使这个算法的显示结果都没有让你明白这一点,那我很抱歉,但我认为没有什么能解释清楚了。请注意,我实际上非常喜欢最小二乘法...在我为同事编写的公式手册中,56页中有20页是关于最小二乘法的(例如见第47页:http://goo.gl/QmszB)。然而,在我看来,对于这个问题,它们只是错误的工具。 - 6502
1
@Tomalak:你显然没有理解这一点,如果输出必须连接成直线段,那么就没有自由度可以使用LSQ。我还发现,该算法已经在1972年由Ramar-Douglas-Peucker以我最初想到的形式(通用n维折线而不是图形)发明了,因此我添加了一个相关页面的链接。顺便说一句:我还注意到你甚至没有花时间点击结果链接(goo.gl告诉我),所以我会将你归类为恶意挑衅并离开。 - 6502
显示剩余7条评论

0

我在想法后遇到了这个编程问题。跳过图表中的冗余点。我相信我想出了一个更好、更简单的解决办法,很高兴将其作为我的第一个SO建议解决方案分享。我已经编写并测试了它,效果很好。它还考虑了屏幕比例因素。在这些图表点之间可能有100个值,但如果用户的图表尺寸较小,他们就无法看到这些值。

因此,在迭代数据/图表循环之前,在绘制/添加下一个数据点之前,查看前面的下一个值并计算屏幕比例(或值)的变化(但我认为由于上述原因,屏幕比例更好)。现在对于往后的下一个值做同样的操作(获取这些值只是通过查看您的数组/集合/列表等向前推进一步进行增量添加(可能是1/2)到当前的值而已),如果 2个值相同(或者根据自己的喜好可能存在非常小的变化),则可以通过在循环中简单地添加“continue”来跳过图表中的这一个点,跳过添加数据点,因为该点恰好位于其前后点之间的斜率上。

使用这种方法,我将一个具有963点的图表减少到427点,而绝对没有视觉变化。

我认为你可能需要多读几遍才能理解,但这比其他提到的最佳解决方案简单得多,更轻量级,并且对你的绘图没有任何视觉影响。


-2

我可能会应用“最小二乘法”算法来获得最佳拟合直线。然后,您可以浏览您的点并向下过滤接近该直线的连续点。您只需要绘制异常值和将曲线带回最佳拟合直线的点。

编辑:您可能不需要使用“最小二乘法”;如果您的输入预计围绕“y = ax + b”徘徊,那么这已经是您的最佳拟合直线,您可以直接使用它。 :)


这可能会起作用,但是我该如何选择定义y的点呢?这个图一直在上下波动:-? - nc3b
数据不是单一的一行...但有许多部分看起来是线性的,可以从中删除样本而不改变图表的含义。换句话说,y=mx+q仅适用于正在绘制的数据的某些部分。 - 6502
@nc3b:这就是“最小二乘法”算法为您所做的事情。去查一下吧! - Lightness Races in Orbit
这个回答对于问题没有意义。OP所问的是一种在不可见形状改变的情况下简化图形的方法,而不是进行线性回归。LSQ拟合是一种强大的技术,但在这里不适用。 - 6502
@6502:我实际上已经多次实现了解决此问题的方案,它们无一例外地采用这种形式。要找到数据集的基线描述,必须应用最佳拟合直线。要找到最佳拟合直线,必须考虑所有点。LSQ专门为此目的而设计。我不明白为什么它不相关。 - Lightness Races in Orbit

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