如何将2D椭圆拟合到给定点

时间:2017-12-18 17:44:17

标签: python-2.7 numpy cartesian

我想用椭圆函数拟合 2D数组(x / a)²+(y / b)²= 1 ----&gt ; (以及获取a和b)

然后,能够在我的图表上重新绘制它。 我在互联网上找到了很多例子,但没有人用这个简单的笛卡尔方程。我可能搜索得很厉害! 我认为这个问题的基本解决方案可以帮助很多人。

以下是数据示例:

enter image description here

可悲的是,我不能把这些值......所以我们假设我有一个X,Y数组定义了每个点的坐标

3 个答案:

答案 0 :(得分:2)

根据ErroriSalvo的建议,这是使用SVD拟合椭圆的完整过程。数组x,y是给定点的坐标,假设有N个点。然后从形状(2,N)的居中坐标阵列的SVD获得U,S,V。因此,U是2乘2正交矩阵(旋转),S是长度为2的矢量(奇异值),而我们不需要的V是N乘N正交矩阵。

将单位圆转换为最佳拟合椭圆的线性映射是

sqrt(2/N) * U * diag(S) 

其中diag(S)是在对角线上具有奇异值的对角矩阵。为了理解为什么需要sqrt(2 / N)因子,想象点x,y是从单位圆中均匀取的。那么sum(x**2) + sum(y**2)是N,因此坐标矩阵由两个长度为sqrt(N / 2)的正交行组成,因此它的范数(最大奇异值)是sqrt(N / 2)。我们需要将其降低到1以获得单位圈。

N = 300
t = np.linspace(0, 2*np.pi, N)
x = 5*np.cos(t) + 0.2*np.random.normal(size=N) + 1
y = 4*np.sin(t+0.5) + 0.2*np.random.normal(size=N)
plt.plot(x, y, '.')     # given points

xmean, ymean = x.mean(), y.mean()
x -= xmean
y -= ymean
U, S, V = np.linalg.svd(np.stack((x, y)))

tt = np.linspace(0, 2*np.pi, 1000)
circle = np.stack((np.cos(tt), np.sin(tt)))    # unit circle
transform = np.sqrt(2/N) * U.dot(np.diag(S))   # transformation matrix
fit = transform.dot(circle) + np.array([[xmean], [ymean]])
plt.plot(fit[0, :], fit[1, :], 'r')
plt.show()

example

但如果你认为没有轮换,那么np.sqrt(2/N) * S就是你所需要的;这些是椭圆方程中的ab

答案 1 :(得分:1)

您可以尝试数据矩阵的奇异值分解。 https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.linalg.svd.html

首先通过从每列中减去X,Y的平均值来对数据进行居中。

X=X-np.mean(X)
Y=Y-np.mean(Y)

D=np.vstack(X,Y)

然后,应用SVD并提取 -eigenvalues(s的成员) - >轴长 - 特征向量(U) - >轴方向

U, s, V = np.linalg.svd(D, full_matrices=True)

这应该是最小二乘拟合。 当然,事情可能比这更复杂,请看 https://www.emis.de/journals/BBMS/Bulletin/sup962/gander.pdf

答案 2 :(得分:1)

这可以使用最小二乘法直接求解。您可以将此框架设置为最小化数量的平方和(alpha * x_i ^ 2 + beta * y_i ^ 2 - 1),其中alpha为1 / a ^ 2且beta为1 / b ^ 2。你有X中的所有x_i和Y中的y_i,所以你可以找到|| Ax - b || ^ 2的最小化,其中A是Nx2矩阵(即[X ^ 2,Y ^ 2]),x是列向量[alpha; beta]和b是所有的列向量。

以下代码解决了形式为Ax ^ 2 + Bxy + Cy ^ 2 + Dx + Ey = 1的椭圆的更一般问题,尽管这个想法完全相同。 print语句给出0.0776x ^ 2 + 0.0315xy + 0.125y ^ 2 + 0.00457x + 0.00314y = 1,生成的椭圆图像也低于

import numpy as np
import matplotlib.pyplot as plt
alpha = 5
beta = 3
N = 500
DIM = 2

np.random.seed(2)

# Generate random points on the unit circle by sampling uniform angles
theta = np.random.uniform(0, 2*np.pi, (N,1))
eps_noise = 0.2 * np.random.normal(size=[N,1])
circle = np.hstack([np.cos(theta), np.sin(theta)])

# Stretch and rotate circle to an ellipse with random linear tranformation
B = np.random.randint(-3, 3, (DIM, DIM))
noisy_ellipse = circle.dot(B) + eps_noise

# Extract x coords and y coords of the ellipse as column vectors
X = noisy_ellipse[:,0:1]
Y = noisy_ellipse[:,1:]

# Formulate and solve the least squares problem ||Ax - b ||^2
A = np.hstack([X**2, X * Y, Y**2, X, Y])
b = np.ones_like(X)
x = np.linalg.lstsq(A, b)[0].squeeze()

# Print the equation of the ellipse in standard form
print('The ellipse is given by {0:.3}x^2 + {1:.3}xy+{2:.3}y^2+{3:.3}x+{4:.3}y = 1'.format(x[0], x[1],x[2],x[3],x[4]))

# Plot the noisy data
plt.scatter(X, Y, label='Data Points')

# Plot the original ellipse from which the data was generated
phi = np.linspace(0, 2*np.pi, 1000).reshape((1000,1))
c = np.hstack([np.cos(phi), np.sin(phi)])
ground_truth_ellipse = c.dot(B)
plt.plot(ground_truth_ellipse[:,0], ground_truth_ellipse[:,1], 'k--', label='Generating Ellipse')

# Plot the least squares ellipse
x_coord = np.linspace(-5,5,300)
y_coord = np.linspace(-5,5,300)
X_coord, Y_coord = np.meshgrid(x_coord, y_coord)
Z_coord = x[0] * X_coord ** 2 + x[1] * X_coord * Y_coord + x[2] * Y_coord**2 + x[3] * X_coord + x[4] * Y_coord
plt.contour(X_coord, Y_coord, Z_coord, levels=[1], colors=('r'), linewidths=2)

plt.legend()
plt.xlabel('X')
plt.ylabel('Y')
plt.show()

enter image description here