如何拟合闭合的轮廓?

问题描述 投票:1回答:2

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

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

有没有简单的方法可以拟合轮廓?有numpy.polyfit。但是,如果重复x值,它将失败,并且需要付出一定的努力才能确定多项式的适当次数。

python numpy plot scipy curve-fitting
2个回答
3
投票

从点到要拟合的轮廓的距离是极坐标以该点为中心的角度的周期函数。该函数可以表示为正弦(或余弦)函数的组合,可以通过傅里叶变换精确计算出它们。实际上,根据Parseval's theorem,通过傅立叶变换计算出的线性组合被截断为前N个函数is与这N个函数的最佳拟合。

要在实践中使用此方法,请选择一个中心点(也许是轮廓的重心),将轮廓转换为极坐标,然后计算到该中心点的距离进行傅立叶变换。拟合轮廓由前几个傅立叶系数给出。

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

EDIT:

示例代码,有效:
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()

P.S。有一种特殊情况可能需要引起注意,即轮廓的高度不凸(凹入),以至于某些光线沿着选定的中心点沿某个角度相交两次。在这种情况下,选择其他中心点可能会有所帮助。在极端情况下,可能没有不具有此属性的中心点(如果轮廓看起来为like this)。在这种情况下,您仍然可以使用上面的方法来刻画或外接您所拥有的形状,但这本身并不是适合其拟合的合适方法。此方法适用于拟合像土豆一样的“块状”椭圆,而不是像椒盐脆饼那样的“扭曲”椭圆:)

如果您修复了多项式,则只需使用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

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

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

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

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

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了解有关安装步骤的更多信息。

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


1
投票

如果您修复了多项式,则只需使用scipy.optimize

© www.soinside.com 2019 - 2024. All rights reserved.