【问题标题】:Approximating a polygon with a circle用圆逼近多边形
【发布时间】:2013-02-12 14:21:35
【问题描述】:

嗯,用多边形近似圆和毕达哥拉斯的故事可能是众所周知的。 但是反过来呢?

我有一些多边形,实际上应该是圆形。然而,由于测量误差,它们不是。所以,我正在寻找的是最能“近似”给定多边形的圆。

在下图中,我们可以看到两个不同的示例。

我的第一个 Ansatz 是找到点到中心的最大距离以及最小值。我们正在寻找的圆圈可能介于两者之间。

有解决这个问题的算法吗?

【问题讨论】:

  • 或许可以使用最小二乘法。 Here is a PDF file 的一篇关于使用最小二乘法寻找圆的论文。不过,您正在做的事情可能有点复杂。如果这些点大致是一个圆,您可能会找到一种更基本的方法,它会很好地工作。
  • 如果您已经知道中心,那么最小二乘似乎是可行的方法。它本质上变成了极坐标的曲线拟合问题。
  • 检查Hough transform。它导致了一个简单的圆形检测解决方案。

标签: python computational-geometry


【解决方案1】:

我会使用scipy 将一个圆圈“拟合”到我的观点上。您可以通过简单的质心计算获得中心和半径的起​​点。如果点均匀分布在圆上,则此方法效果很好。如果不是,如下例所示,总比没有好!

拟合函数很简单,因为圆很简单。您只需要找到从拟合圆到点的径向距离,因为切线(径向)表面始终是最佳拟合。

import numpy as np
from scipy.spatial.distance import cdist
from scipy.optimize import fmin
import scipy

# Draw a fuzzy circle to test
N = 15
THETA = np.random.random(15)*2*np.pi
R     = 1.5 + (.1*np.random.random(15) - .05)
X = R*np.cos(THETA) + 5
Y = R*np.sin(THETA) - 2

# Choose the inital center of fit circle as the CM
xm = X.mean()
ym = Y.mean()

# Choose the inital radius as the average distance to the CM
cm = np.array([xm,ym]).reshape(1,2)
rm = cdist(cm, np.array([X,Y]).T).mean()

# Best fit a circle to these points
def err((w,v,r)):
    pts = [np.linalg.norm([x-w,y-v])-r for x,y in zip(X,Y)]
    return (np.array(pts)**2).sum()

xf,yf,rf = scipy.optimize.fmin(err,[xm,ym,rm])  

# Viszualize the results
import pylab as plt
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)

# Show the inital guess circle
circ = plt.Circle((xm, ym), radius=rm, color='y',lw=2,alpha=.5)
ax.add_patch(circ)

# Show the fit circle
circ = plt.Circle((xf, yf), radius=rf, color='b',lw=2,alpha=.5)
ax.add_patch(circ)

plt.axis('equal')
plt.scatter(X,Y)
plt.show()

【讨论】:

  • 只有在没有实际异常值的情况下才能正常工作,请参阅i.imgur.com/pki3Nn7.png 了解添加一个异常值后的结果。如果测量误差不包括这种类型的误差,那么这种方法确实很好。
  • @mmgp 同意。我接受了 OP 的假设,“我有一些多边形,实际上应该是圆”,底层的点是扰动的圆。好处是您可以轻松地从中得到拟合错误,并且您可以通过删除一两个错误点并重新检查来重复拟合。我还认为您可以通过将错误惩罚从 ^2 增加到 ^4 来偏向异常值。
  • 基本上这正是我所做的。我喜欢优化最佳拟合的额外想法。谢谢你。
【解决方案2】:

也许一个简单的算法是首先计算点的质心(假设它们通常大致规则间隔)。这是圆心。一旦你有了,你可以计算点的平均半径,给出圆的半径。

更复杂的答案可能是做一个简单的最小化,即最小化点到圆边缘的距离之和(或距离平方)。

【讨论】:

    【解决方案3】:

    在维基百科页面smallest-circle problem 上,有两种不同的 O(n) 算法可用于确定您绘制的包含一系列点的最小圆。从这里开始绘制第二个圆应该相当容易,只需确定您之前找到的圆的中心,然后找到最接近该点的点。第二个圆的半径就是那个。

    这可能不是你想要的,但这就是我要开始的方式。

    【讨论】:

      【解决方案4】:

      这个问题可能和Smallest-circle problem一样。

      但由于您的测量误差可能包括异常值,因此 RANSAC 是一个不错的选择。有关该方法的概述(以及其他基本技术),请参阅 http://cs.gmu.edu/~kosecka/cs482/lect-fitting.pdf,在 http://www.asl.ethz.ch/education/master/info-process-rob/Hough-Ransac.pdf 中有更多关于圆拟合的信息。

      【讨论】:

      • 我不确定我是否同意。假设他正在寻找圆上点的位置并且测量中存在错误导致这些点不再落在一个完美的圆上,他的目标不是找到包含所有点的最小圆,而是找到最有可能的圆这将导致他的特定观点。
      • 我可能误解了要求,是的。
      • +1 向我介绍了 RANSAC 和优秀的裁判!不过,这似乎需要您具体了解您在问题中可能遇到的错误和异常值的类型。
      • @Hooked 很好,是的,它确实需要很多参数。如果数据点太少,那么它必然会失败。
      • 感谢您提供有用的链接。我注意到它们是为了将来的工作。然而,在我看来,使用如此复杂的解决方案,我的问题已经“微不足道”了。
      【解决方案5】:

      很容易找到一些近似值:

      def find_circle_deterministically(x,y):
          center = x.mean(), y.mean()
          radius = np.sqrt((x-center[0])**2 + (y-center[1])**2).mean()
          return center, radius
      

      解释:把圆的中心放在你的点的平均 x 和平均 y 上。然后,对于每个点,确定到中心的距离并取所有点的平均值。那是你的半径。

      这个完整的脚本:

      import numpy as np
      import matplotlib.pyplot as plt
      
      n_points = 10
      radius = 4
      noise_std = 0.3
      
      angles = np.linspace(0,2*np.pi,n_points,False)
      x = np.cos(angles) * radius
      y = np.sin(angles) * radius
      x += np.random.normal(0,noise_std,x.shape)
      y += np.random.normal(0,noise_std,y.shape)
      
      plt.axes(aspect="equal")
      plt.plot(x,y,"bx")
      
      def find_circle_deterministically(x,y):
          center = x.mean(), y.mean()
          radius = np.sqrt((x-center[0])**2 + (y-center[1])**2).mean()
          return center, radius
      
      center, radius2 = find_circle_deterministically(x,y)
      angles2 = np.linspace(0,2*np.pi,100,True)
      x2 = center[0] + np.cos(angles2) * radius2
      y2 = center[1] + np.sin(angles2) * radius2
      plt.plot(x2,y2,"r-")
      
      plt.show()
      

      产生这个情节:

      这会很好,因为您的多边形会出现测量错误。如果您的点在角度[0,2pi[ 上的分布不大致相等,那么它的性能就会很差。

      更一般地说,您可以使用优化。

      【讨论】:

      • 没有不尊重的意思,但这与我所做的有什么不同,除了没有优化?正如 mmgp 所指出的,它也会受到任何异常值的影响并且,正如你所注意到的,如果点不是在圆上均匀地选择,它就会失败。
      • 确实,为了公平起见:i.imgur.com/4f5Ip42.png,异常值位于 (50, 50)。 RANSAC 会找到合适的圈子(是的,我在卖我的答案)。
      • 我在您的答案出现在 SO 上之前写下了我的答案。这只是作为优化的起点。我打算从我的包eegpy 中放一些代码,在这里我做的几乎一样,但是对于 3d 和球体中的电极坐标:github.com/thorstenkranz/eegpy/blob/master/eegpy/plot/topo.py
      • 是的,异常值是个问题,但你们创建的示例与 OP 定义的相差甚远。
      猜你喜欢
      • 2014-05-06
      • 1970-01-01
      • 2020-02-01
      • 2018-06-30
      • 2010-10-11
      • 1970-01-01
      • 2021-10-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多