如何拟合封闭的轮廓?

时间:2012-11-28 12:01:45

标签: python numpy plot scipy curve-fitting

我有一个表示闭合轮廓(有噪音)的数据:

contour = [(x1, y1), (x2, y2), ...]

有没有简单的方法来适应轮廓?有numpy.polyfit。但如果重复x值并且需要一些努力来确定多项式的适当程度,它就会失败。

2 个答案:

答案 0 :(得分:3)

从点到您尝试拟合的轮廓的距离是以该点为中心的极坐标中的角度的周期函数。该函数可以表示为正弦(或余弦)函数的组合,其可以通过傅里叶变换精确计算。实际上,根据Parseval's theorem,由截断到前N个函数的傅立叶变换计算的线性组合最适合那些N个函数。

要在实践中使用它,选择一个中心点(可能是轮廓的重心),将轮廓转换为极坐标,并计算距中心点的距离的傅立叶变换。拟合轮廓由前几个傅里叶系数给出。

剩下的一个问题是转换为极坐标的轮廓在均匀间隔的角度上没有距离值。这是Irregular Sampling问题。由于您可能具有相当高密度的样本,因此您可以非常简单地通过在两个最接近均匀间隔角度的点之间使用线性插值,或者(根据您的数据)使用小窗口进行平均。对于不规则采样的大多数其他解决方案在这里都非常复杂和不必要。

编辑:示例代码,有效:

import numpy, scipy, scipy.ndimage, scipy.interpolate, numpy.fft, math

# create simple square
img = numpy.zeros( (10, 10) )
img[1:9, 1:9] = 1
img[2:8, 2:8] = 0

# find contour
x, y = numpy.nonzero(img)

# find center point and conver to polar coords
x0, y0 = numpy.mean(x), numpy.mean(y)
C = (x - x0) + 1j * (y - y0)
angles = numpy.angle(C)
distances = numpy.absolute(C)
sortidx = numpy.argsort( angles )
angles = angles[ sortidx ]
distances = distances[ sortidx ]

# copy first and last elements with angles wrapped around. needed so can interpolate over full range -pi to pi
angles = numpy.hstack(([ angles[-1] - 2*math.pi ], angles, [ angles[0] + 2*math.pi ]))
distances = numpy.hstack(([distances[-1]], distances, [distances[0]]))

# interpolate to evenly spaced angles
f = scipy.interpolate.interp1d(angles, distances)
angles_uniform = scipy.linspace(-math.pi, math.pi, num=100, endpoint=False) 
distances_uniform = f(angles_uniform)

# fft and inverse fft
fft_coeffs = numpy.fft.rfft(distances_uniform)
# zero out all but lowest 10 coefficients
fft_coeffs[11:] = 0
distances_fit = numpy.fft.irfft(fft_coefs)

# plot results
import matplotlib.pyplot as plt
plt.polar(angles, distances)
plt.polar(angles_uniform, distances_uniform)
plt.polar(angles_uniform, distances_fit)
plt.show()

P.S。有一种特殊情况可能需要注意,当轮廓非凸(重入)到足够的程度时,一些光线沿着通过所选中心点的角度与它相交两次。在这种情况下,选择不同的中心点可能会有所帮助。在极端情况下,可能没有没有此属性的中心点(如果轮廓看起来like this)。在这种情况下,你仍然可以使用上面的方法来记录或限制你的形状,但这本身并不适合它。这种方法适用于装饰像土豆一样的“块状”椭圆形,而不是像椒盐脆饼那样“扭曲”的椭圆形。)

答案 1 :(得分:1)

如果你修正了多项式学位,你只需使用 scipy.optimize

中的 leastsq 函数即可。

假设您生成一个简单的圆圈。我将它分为x和y分量

data = [ [cos(t)+0.1*randn(),sin(t)+0.1*randn()] for t in rand(100)*2*np.pi ]
contour = array(data)
x,y = contour.T

写一个简单的函数,在给定多项式的系数的情况下,评估每个点与0的差值。我们将曲线拟合为以原点为中心的圆。

def f(coef):
    a = coef
    return a*x**2+a*y**2-1

我们可以简单地使用leastsq函数来找到最佳系数。

from scipy.optimize import leastsq
initial_guess = [0.1,0.1]
coef = leastsq(f,initial_guess)[0]
# coef = array([ 0.92811554])

我只接受返回元组的第一个元素,因为leastsq返回了许多我们不需要的其他信息。

如果你需要拟合一个更复杂的多项式,例如一个带有通用中心的椭圆,你可以简单地使用一个更复杂的函数:

def f(coef):
    a,b,cx,cy = coef
    return a*(x-cx)**2+b*(y-cy)**2-1

initial_guess = [0.1,0.1,0.0,0.0]
coef = leastsq(f,initial_guess)[0]
# coef = array([ 0.92624664,  0.93672577,  0.00531   ,  0.01269507])

编辑:

如果由于某种原因需要估计拟合参数的不确定性,可以从结果的协方差矩阵中获取此信息:

res = leastsq(f,initial_guess,full_output=True)
coef = res[0]
cov  = res[1]
#cov = array([[ 0.02537329, -0.00970796, -0.00065069,  0.00045027],
#             [-0.00970796,  0.03157025,  0.0006394 ,  0.00207787],
#             [-0.00065069,  0.0006394 ,  0.00535228, -0.00053483],
#             [ 0.00045027,  0.00207787, -0.00053483,  0.00618327]])

uncert = sqrt(diag(cov))
# uncert = array([ 0.15928997,  0.17768018,  0.07315927,  0.07863377])

协方差矩阵的对角线是每个参数的方差,所以不确定性是它的平方根

请查看http://www.scipy.org/Cookbook/FittingData以获取有关拟合程序的更多信息。

我使用minimalsq而不是curve_fit函数的原因是更容易使用,因为curve_fit需要y = f(x)形式的显式函数,并不是每个隐式多项式都可以转换为该形式(或更好,几乎没有任何有趣的隐式多项式)