fit_ellipse.m 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. function ellipse_t = fit_ellipse( x,y,plot_opt )
  2. %
  3. % fit_ellipse - finds the best fit to an ellipse for the given set of points.
  4. %
  5. % Format: ellipse_t = fit_ellipse( x,y,axis_handle )
  6. %
  7. % Input: x,y - a set of points in 2 column vectors. AT LEAST 5 points are needed !
  8. % axis_handle - optional. a handle to an axis, at which the estimated ellipse
  9. % will be drawn along with it's axes
  10. %
  11. % Output: ellipse_t - structure that defines the best fit to an ellipse
  12. % a - sub axis (radius) of the X axis of the non-tilt ellipse
  13. % b - sub axis (radius) of the Y axis of the non-tilt ellipse
  14. % phi - orientation in radians of the ellipse (tilt)
  15. % X0 - center at the X axis of the non-tilt ellipse
  16. % Y0 - center at the Y axis of the non-tilt ellipse
  17. % X0_in - center at the X axis of the tilted ellipse
  18. % Y0_in - center at the Y axis of the tilted ellipse
  19. % long_axis - size of the long axis of the ellipse
  20. % short_axis - size of the short axis of the ellipse
  21. % status - status of detection of an ellipse
  22. %
  23. % Note: if an ellipse was not detected (but a parabola or hyperbola), then
  24. % an empty structure is returned
  25. % =====================================================================================
  26. % Ellipse Fit using Least Squares criterion
  27. % =====================================================================================
  28. % We will try to fit the best ellipse to the given measurements. the mathematical
  29. % representation of use will be the CONIC Equation of the Ellipse which is:
  30. %
  31. % Ellipse = a*x^2 + b*x*y + c*y^2 + d*x + e*y + f = 0
  32. %
  33. % The fit-estimation method of use is the Least Squares method (without any weights)
  34. % The estimator is extracted from the following equations:
  35. %
  36. % g(x,y;A) := a*x^2 + b*x*y + c*y^2 + d*x + e*y = f
  37. %
  38. % where:
  39. % A - is the vector of parameters to be estimated (a,b,c,d,e)
  40. % x,y - is a single measurement
  41. %
  42. % We will define the cost function to be:
  43. %
  44. % Cost(A) := (g_c(x_c,y_c;A)-f_c)'*(g_c(x_c,y_c;A)-f_c)
  45. % = (X*A+f_c)'*(X*A+f_c)
  46. % = A'*X'*X*A + 2*f_c'*X*A + N*f^2
  47. %
  48. % where:
  49. % g_c(x_c,y_c;A) - vector function of ALL the measurements
  50. % each element of g_c() is g(x,y;A)
  51. % X - a matrix of the form: [x_c.^2, x_c.*y_c, y_c.^2, x_c, y_c ]
  52. % f_c - is actually defined as ones(length(f),1)*f
  53. %
  54. % Derivation of the Cost function with respect to the vector of parameters "A" yields:
  55. %
  56. % A'*X'*X = -f_c'*X = -f*ones(1,length(f_c))*X = -f*sum(X)
  57. %
  58. % Which yields the estimator:
  59. %
  60. % ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  61. % | A_least_squares = -f*sum(X)/(X'*X) ->(normalize by -f) = sum(X)/(X'*X) |
  62. % ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  63. %
  64. % (We will normalize the variables by (-f) since "f" is unknown and can be accounted for later on)
  65. %
  66. % NOW, all that is left to do is to extract the parameters from the Conic Equation.
  67. % We will deal the vector A into the variables: (A,B,C,D,E) and assume F = -1;
  68. %
  69. % Recall the conic representation of an ellipse:
  70. %
  71. % A*x^2 + B*x*y + C*y^2 + D*x + E*y + F = 0
  72. %
  73. % We will check if the ellipse has a tilt (=orientation). The orientation is present
  74. % if the coefficient of the term "x*y" is not zero. If so, we first need to remove the
  75. % tilt of the ellipse.
  76. %
  77. % If the parameter "B" is not equal to zero, then we have an orientation (tilt) to the ellipse.
  78. % we will remove the tilt of the ellipse so as to remain with a conic representation of an
  79. % ellipse without a tilt, for which the math is more simple:
  80. %
  81. % Non tilt conic rep.: A`*x^2 + C`*y^2 + D`*x + E`*y + F` = 0
  82. %
  83. % We will remove the orientation using the following substitution:
  84. %
  85. % Replace x with cx+sy and y with -sx+cy such that the conic representation is:
  86. %
  87. % A(cx+sy)^2 + B(cx+sy)(-sx+cy) + C(-sx+cy)^2 + D(cx+sy) + E(-sx+cy) + F = 0
  88. %
  89. % where: c = cos(phi) , s = sin(phi)
  90. %
  91. % and simplify...
  92. %
  93. % x^2(A*c^2 - Bcs + Cs^2) + xy(2A*cs +(c^2-s^2)B -2Ccs) + ...
  94. % y^2(As^2 + Bcs + Cc^2) + x(Dc-Es) + y(Ds+Ec) + F = 0
  95. %
  96. % The orientation is easily found by the condition of (B_new=0) which results in:
  97. %
  98. % 2A*cs +(c^2-s^2)B -2Ccs = 0 ==> phi = 1/2 * atan( b/(c-a) )
  99. %
  100. % Now the constants c=cos(phi) and s=sin(phi) can be found, and from them
  101. % all the other constants A`,C`,D`,E` can be found.
  102. %
  103. % A` = A*c^2 - B*c*s + C*s^2 D` = D*c-E*s
  104. % B` = 2*A*c*s +(c^2-s^2)*B -2*C*c*s = 0 E` = D*s+E*c
  105. % C` = A*s^2 + B*c*s + C*c^2
  106. %
  107. % Next, we want the representation of the non-tilted ellipse to be as:
  108. %
  109. % Ellipse = ( (X-X0)/a )^2 + ( (Y-Y0)/b )^2 = 1
  110. %
  111. % where: (X0,Y0) is the center of the ellipse
  112. % a,b are the ellipse "radiuses" (or sub-axis)
  113. %
  114. % Using a square completion method we will define:
  115. %
  116. % F`` = -F` + (D`^2)/(4*A`) + (E`^2)/(4*C`)
  117. %
  118. % Such that: a`*(X-X0)^2 = A`(X^2 + X*D`/A` + (D`/(2*A`))^2 )
  119. % c`*(Y-Y0)^2 = C`(Y^2 + Y*E`/C` + (E`/(2*C`))^2 )
  120. %
  121. % which yields the transformations:
  122. %
  123. % X0 = -D`/(2*A`)
  124. % Y0 = -E`/(2*C`)
  125. % a = sqrt( abs( F``/A` ) )
  126. % b = sqrt( abs( F``/C` ) )
  127. %
  128. % And finally we can define the remaining parameters:
  129. %
  130. % long_axis = 2 * max( a,b )
  131. % short_axis = 2 * min( a,b )
  132. % Orientation = phi
  133. %
  134. %
  135. % initialize
  136. orientation_tolerance = 1e-3;
  137. % empty warning stack
  138. warning( '' );
  139. % prepare vectors, must be column vectors
  140. x = x(:);
  141. y = y(:);
  142. % remove bias of the ellipse - to make matrix inversion more accurate. (will be added later on).
  143. mean_x = mean(x);
  144. mean_y = mean(y);
  145. x = x-mean_x;
  146. y = y-mean_y;
  147. % the estimation for the conic equation of the ellipse
  148. X = [x.^2, x.*y, y.^2, x, y ];
  149. a = sum(X)/(X'*X);
  150. % check for warnings
  151. if ~isempty( lastwarn )
  152. disp( 'stopped because of a warning regarding matrix inversion' );
  153. ellipse_t = [];
  154. return
  155. end
  156. % extract parameters from the conic equation
  157. [a,b,c,d,e] = deal( a(1),a(2),a(3),a(4),a(5) );
  158. % remove the orientation from the ellipse
  159. if ( min(abs(b/a),abs(b/c)) > orientation_tolerance )
  160. orientation_rad = 1/2 * atan( b/(c-a) );
  161. cos_phi = cos( orientation_rad );
  162. sin_phi = sin( orientation_rad );
  163. [a,b,c,d,e] = deal(...
  164. a*cos_phi^2 - b*cos_phi*sin_phi + c*sin_phi^2,...
  165. 0,...
  166. a*sin_phi^2 + b*cos_phi*sin_phi + c*cos_phi^2,...
  167. d*cos_phi - e*sin_phi,...
  168. d*sin_phi + e*cos_phi );
  169. [mean_x,mean_y] = deal( ...
  170. cos_phi*mean_x - sin_phi*mean_y,...
  171. sin_phi*mean_x + cos_phi*mean_y );
  172. else
  173. orientation_rad = 0;
  174. cos_phi = cos( orientation_rad );
  175. sin_phi = sin( orientation_rad );
  176. end
  177. % check if conic equation represents an ellipse
  178. test = a*c;
  179. switch (1)
  180. case (test>0), status = '';
  181. case (test==0), status = 'Parabola found'; warning( 'fit_ellipse: Did not locate an ellipse' );
  182. case (test<0), status = 'Hyperbola found'; warning( 'fit_ellipse: Did not locate an ellipse' );
  183. end
  184. % if we found an ellipse return it's data
  185. if (test>0)
  186. % make sure coefficients are positive as required
  187. if (a<0), [a,c,d,e] = deal( -a,-c,-d,-e ); end
  188. % final ellipse parameters
  189. X0 = mean_x - d/2/a;
  190. Y0 = mean_y - e/2/c;
  191. F = 1 + (d^2)/(4*a) + (e^2)/(4*c);
  192. [a,b] = deal( sqrt( F/a ),sqrt( F/c ) );
  193. long_axis = 2*max(a,b);
  194. short_axis = 2*min(a,b);
  195. % rotate the axes backwards to find the center point of the original TILTED ellipse
  196. R = [ cos_phi sin_phi; -sin_phi cos_phi ];
  197. P_in = R * [X0;Y0];
  198. X0_in = P_in(1);
  199. Y0_in = P_in(2);
  200. % pack ellipse into a structure
  201. ellipse_t = struct( ...
  202. 'a',a,...
  203. 'b',b,...
  204. 'phi',orientation_rad,...
  205. 'X0',X0,...
  206. 'Y0',Y0,...
  207. 'X0_in',X0_in,...
  208. 'Y0_in',Y0_in,...
  209. 'long_axis',long_axis,...
  210. 'short_axis',short_axis,...
  211. 'status','' );
  212. else
  213. % report an empty structure
  214. ellipse_t = struct( ...
  215. 'a',[],...
  216. 'b',[],...
  217. 'phi',[],...
  218. 'X0',[],...
  219. 'Y0',[],...
  220. 'X0_in',[],...
  221. 'Y0_in',[],...
  222. 'long_axis',[],...
  223. 'short_axis',[],...
  224. 'status',status );
  225. end
  226. % check if we need to plot an ellipse with it's axes.
  227. %if (nargin>2) & ~isempty( axis_handle ) & (test>0)
  228. % rotation matrix to rotate the axes with respect to an angle phi
  229. R = [ cos_phi sin_phi; -sin_phi cos_phi ];
  230. % the axes
  231. ver_line = [ [X0 X0]; Y0+b*[-1 1] ];
  232. horz_line = [ X0+a*[-1 1]; [Y0 Y0] ];
  233. new_ver_line = R*ver_line;
  234. new_horz_line = R*horz_line;
  235. % the ellipse
  236. theta_r = linspace(0,2*pi);
  237. ellipse_x_r = X0 + a*cos( theta_r );
  238. ellipse_y_r = Y0 + b*sin( theta_r );
  239. xaligned_ellipse = [ellipse_x_r;ellipse_y_r];
  240. rotated_ellipse = R * [ellipse_x_r;ellipse_y_r];
  241. if (strcmp(plot_opt,'y'))
  242. % draw
  243. hold on
  244. plot( rotated_ellipse(1,:),rotated_ellipse(2,:),'r' );
  245. drawnow
  246. end
  247. ellipse_t.xaligned_ellipse = xaligned_ellipse;
  248. ellipse_t.rotated_ellipse = rotated_ellipse;
  249. ellipse_t.ellipse_x_r = ellipse_x_r;
  250. ellipse_t.ellipse_y_r = ellipse_y_r;
  251. ellipse_t.R = R;