问题
I'm trying to fit an ellipse to a set of points described by x and y coordinates.
I found a detailed explanation of how to do it here http://nicky.vanforeest.com/misc/fitEllipse/fitEllipse.html and tried out the code, but it doesn't seem to work.
It finds the center correctly, but the angle and axes are completely wrong, as you can see in this image: https://i.imgur.com/VLEeNKQ.png
The red points are my data points and the blue aptch is an ellipse drawn from the obtained parameters. Now, the data isn't a perfect ellipse, but the fit is nowhere near it. I'd like to get the fit closer to the actual data.
Here's the code in question.
import numpy as np
from numpy.linalg import eig, inv
def fitEllipse(x,y):
x = x[:,np.newaxis]
y = y[:,np.newaxis]
D = np.hstack((x*x, x*y, y*y, x, y, np.ones_like(x)))
S = np.dot(D.T,D)
C = np.zeros([6,6])
C[0,2] = C[2,0] = 2; C[1,1] = -1
E, V = eig(np.dot(inv(S), C))
n = np.argmax(np.abs(E))
a = V[:,n]
return a
def ellipse_center(a):
b,c,d,f,g,a = a[1]/2, a[2], a[3]/2, a[4]/2, a[5], a[0]
num = b*b-a*c
x0=(c*d-b*f)/num
y0=(a*f-b*d)/num
return np.array([x0,y0])
def ellipse_angle_of_rotation( a ):
b,c,d,f,g,a = a[1]/2, a[2], a[3]/2, a[4]/2, a[5], a[0]
return 0.5*np.arctan(2*b/(a-c))
def ellipse_axis_length( a ):
b,c,d,f,g,a = a[1]/2, a[2], a[3]/2, a[4]/2, a[5], a[0]
up = 2*(a*f*f+c*d*d+g*b*b-2*b*d*f-a*c*g)
down1=(b*b-a*c)*( (c-a)*np.sqrt(1+4*b*b/((a-c)*(a-c)))-(c+a))
down2=(b*b-a*c)*( (a-c)*np.sqrt(1+4*b*b/((a-c)*(a-c)))-(c+a))
res1=np.sqrt(up/down1)
res2=np.sqrt(up/down2)
return np.array([res1, res2])
def ellipse_angle_of_rotation2( a ):
b,c,d,f,g,a = a[1]/2, a[2], a[3]/2, a[4]/2, a[5], a[0]
if b == 0:
if a > c:
return 0
else:
return np.pi/2
else:
if a > c:
return np.arctan(2*b/(a-c))/2
else:
return np.pi/2 + np.arctan(2*b/(a-c))/2
And here's my full dataset. Does anyone have any idea why it's not being fitted correctly?
# --------------------------------------------------------------------------
x = np.array([ 5727.53135, 7147.62235, 10330.93573, 8711.17228, 7630.40262,
4777.24983, 4828.27655, 9449.94416, 5203.81323, 6299.44811,
6494.21906])
y = np.array([67157.77567 , 66568.50068 , 55922.56257 , 54887.47348 ,
65150.14064 , 66529.91705 , 65934.25548 , 55351.57612 ,
63123.5103 , 67181.141725, 56321.36025 ])
# -----------------------------------------------------------------------------
a = fitEllipse(x,y)
center = ellipse_center(a)
#phi = ellipse_angle_of_rotation(a)
phi = ellipse_angle_of_rotation2(a)
axes = ellipse_axis_length(a)
# get the individual axes
a, b = axes
from matplotlib.patches import Ellipse
import matplotlib.pyplot as plt
ell = Ellipse(center, a, b, phi)
fig, ax = plt.subplots(subplot_kw={'aspect': 'equal'})
ax.add_artist(ell)
ell.set_clip_box(ax.bbox)
ax.set_xlim(0, 100000)
ax.set_ylim(0, 100000)
plt.show()
scat = plt.scatter(x, y, c = "r")
回答1:
Your code is absolutely fine, it is the patch
definition that makes the trouble here.
a
and b
of Ellipse
are the full width. So you have to multiply the result of your fit by 2
. Moreover the angle is in degree so you have to multiply by 180/np.pi
. Finally, the zero is not at the same position, so you have to add 90
.
Long story short change
Ellipse(center, a, b, phi)
to
ell = Ellipse(center, 2 * a, 2 * b, phi * 180 / np.pi + 90 )
and you are good.
来源:https://stackoverflow.com/questions/52818206/fitting-an-ellipse-to-a-set-of-2-d-points