Python 如何拟合闭合轮廓?

Python 如何拟合闭合轮廓?,python,numpy,plot,scipy,curve-fitting,Python,Numpy,Plot,Scipy,Curve Fitting,我有一个表示闭合轮廓(带噪声)的数据: 有没有简单的方法来拟合轮廓?有。但是,如果重复x值,它将失败,并且需要一些努力来确定多项式的适当阶数。如果要确定多项式阶数,只需使用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

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


有没有简单的方法来拟合轮廓?有。但是,如果重复x值,它将失败,并且需要一些努力来确定多项式的适当阶数。

如果要确定多项式阶数,只需使用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])
协方差矩阵的对角线是每个参数的方差,因此不确定性是它的平方根

有关装配步骤的更多信息,请参阅


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

如果您确定多项式次数,只需使用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])
协方差矩阵的对角线是每个参数的方差,因此不确定性是它的平方根

有关装配步骤的更多信息,请参阅


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

从一个点到要拟合的轮廓的距离是以该点为中心的极坐标中角度的周期函数。该函数可表示为正弦(或余弦)函数的组合,可通过傅里叶变换精确计算。事实上,根据这项研究,通过傅里叶变换计算出的截断为前N个函数的线性组合最适合这N个函数

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

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

编辑:示例代码,工作:

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
# this is 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_coeffs)

# 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()

另外,有一种特殊情况可能需要注意,当轮廓非凸(可重入)到一定程度时,某些光线沿一个角度穿过选定的中心点与之相交两次。在这种情况下,选择不同的中心点可能会有所帮助。在极端情况下,可能没有不具有此属性的中心点(如果轮廓看起来)。在这种情况下,您仍然可以使用上面的方法来刻划或限定您拥有的形状,但这本身并不是一种适合的方法。此方法用于拟合“块状”椭圆(如土豆),而不是“扭曲”椭圆(如椒盐卷饼):)

从一点到要拟合的轮廓的距离是以该点为中心的极坐标中角度的周期函数。该函数可表示为正弦(或余弦)函数的组合,可通过傅里叶变换精确计算。事实上,根据这项研究,通过傅里叶变换计算出的截断为前N个函数的线性组合最适合这N个函数

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

剩下的一个问题是,转换为极坐标的轮廓在等距角度上没有距离值。这是职业选手