0001 function [fittedEllipse3d, TFM3D] = fitEllipse3d(points, varargin)
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025
0026
0027
0028
0029
0030
0031
0032
0033
0034
0035
0036
0037
0038
0039 parser = inputParser;
0040 addRequired(parser, 'points', @(x) validateattributes(x, {'numeric'},...
0041 {'ncols',3,'real','finite','nonnan'}));
0042 addOptional(parser,'visualization',false,@islogical);
0043 parse(parser,points,varargin{:});
0044 points=parser.Results.points;
0045
0046
0047 meanPoint = mean(points,1);
0048
0049 centeredPoints = bsxfun(@minus, points, meanPoint);
0050
0051
0052 [~,~,R]=svd(centeredPoints);
0053 tfmPoints = transformPoint3d(centeredPoints, R');
0054
0055
0056 parEllipse = ellipsefit_direct(tfmPoints(:,1),tfmPoints(:,2));
0057
0058 [X, Y, A, B, phi2D] = ellipse_im2ex(parEllipse);
0059
0060
0061 TFM2D = createRotationOz(phi2D);
0062 TFM2D(1:3,4)=[X; Y; 0];
0063
0064
0065 TFM3D = [inv(R'), meanPoint'; 0 0 0 1]*TFM2D;
0066
0067
0068 center = TFM3D(1:3,4)';
0069
0070
0071 TFM3D_ROT=TFM3D(1:3,1:3);
0072
0073 [PHI, THETA, PSI] = rotation3dToEulerAngles(TFM3D_ROT,'ZYZ');
0074
0075
0076 TFM3D_test = eulerAnglesToRotation3d(PHI, THETA, PSI,'ZYZ');
0077 if ~all(all(ismembertol(TFM3D_test(1:3,1:3), TFM3D_ROT)))
0078 PSI=-1*PSI;
0079 end
0080
0081
0082 fittedEllipse3d=[center A B THETA PHI PSI];
0083
0084
0085 if parser.Results.visualization
0086
0087 figure('Color','w'); axis equal tight; hold on; view(3)
0088 xlabel('x'); ylabel('y'); zlabel('z');
0089
0090
0091 scatter3(points(:,1),points(:,2),points(:,3), 'r', 'filled')
0092
0093
0094 scatter3(centeredPoints(:,1),centeredPoints(:,2),centeredPoints(:,3), 'g', 'filled')
0095
0096
0097 scatter3(tfmPoints(:,1),tfmPoints(:,2),tfmPoints(:,3), 'b', 'filled')
0098
0099
0100
0101
0102
0103 drawEllipse3d(fittedEllipse3d)
0104 end
0105
0106
0107 function p = ellipsefit_direct(x,y)
0108
0109
0110
0111
0112
0113
0114
0115
0116
0117
0118
0119
0120
0121
0122
0123
0124
0125 narginchk(2,2);
0126 validateattributes(x, {'numeric'}, {'real','nonempty','vector'});
0127 validateattributes(y, {'numeric'}, {'real','nonempty','vector'});
0128 x = x(:);
0129 y = y(:);
0130
0131
0132 mx = mean(x);
0133 my = mean(y);
0134 sx = (max(x)-min(x))/2;
0135 sy = (max(y)-min(y))/2;
0136 smax = max(sx,sy);
0137 sx = smax;
0138 sy = smax;
0139 x = (x-mx)/sx;
0140 y = (y-my)/sy;
0141
0142
0143 D = [ x.^2 x.*y y.^2 x y ones(size(x)) ];
0144
0145
0146 S = D'*D;
0147
0148
0149 C = zeros(6,6);
0150 C(1,3) = -2;
0151 C(2,2) = 1;
0152 C(3,1) = -2;
0153
0154 if 1
0155 p = ellipsefit_robust(S,-C);
0156 elseif 0
0157
0158 [gevec, geval] = eig(S,C);
0159 geval = diag(geval);
0160
0161
0162 p = gevec(:,geval < 0 & ~isinf(geval));
0163 r = geval(geval < 0 & ~isinf(geval));
0164 elseif 0
0165
0166 gamma = 0;
0167 cvx_begin sdp
0168 variable('gamma');
0169 variable('lambda');
0170
0171 maximize(gamma);
0172 lambda >= 0;
0173
0174
0175
0176 S + lambda*C >= 0;
0177 lambda - gamma >= 0;
0178 cvx_end
0179
0180
0181 [evec, eval] = eig(S + lambda*C);
0182 eval = diag(eval);
0183 [~,ix] = min(abs(eval));
0184 p = evec(:,ix);
0185 end
0186
0187
0188 p(:) = ...
0189 [ p(1)*sy*sy ...
0190 ; p(2)*sx*sy ...
0191 ; p(3)*sx*sx ...
0192 ; -2*p(1)*sy*sy*mx - p(2)*sx*sy*my + p(4)*sx*sy*sy ...
0193 ; -p(2)*sx*sy*mx - 2*p(3)*sx*sx*my + p(5)*sx*sx*sy ...
0194 ; p(1)*sy*sy*mx*mx + p(2)*sx*sy*mx*my + p(3)*sx*sx*my*my - p(4)*sx*sy*sy*mx - p(5)*sx*sx*sy*my + p(6)*sx*sx*sy*sy ...
0195 ];
0196
0197 p = p ./ norm(p);
0198 end
0199
0200 function p = ellipsefit_robust(R, Q)
0201
0202
0203
0204
0205
0206
0207
0208
0209
0210
0211
0212
0213
0214
0215
0216
0217
0218
0219
0220 validateattributes(R, {'numeric'}, {'real','2d','size',[6,6]});
0221 validateattributes(Q, {'numeric'}, {'real','2d','size',[6,6]});
0222
0223
0224 assert( nnz(Q(4:6,:)) == 0 );
0225 assert( nnz(Q(:,4:6)) == 0 );
0226
0227 S1 = R(1:3,1:3);
0228 S2 = R(1:3,4:6);
0229 S3 = R(4:6,4:6);
0230 T = -(S3 \ S2');
0231 M = S1 + S2 * T;
0232 M = Q(1:3,1:3) \ M;
0233 [evec,~] = eig(M);
0234
0235
0236 cond = zeros(1,size(evec,2));
0237 for k = 1 : numel(cond)
0238 cond(k) = evec(:,k)'*Q(1:3,1:3)*evec(:,k);
0239 end
0240
0241
0242 evec = evec(:,cond > 0);
0243 cond = cond(cond > 0);
0244 [~,ix] = min(cond);
0245 p1 = evec(:,ix);
0246
0247
0248 p = [p1 ; T * p1];
0249 end
0250
0251 function varargout = ellipse_im2ex(varargin)
0252
0253
0254
0255
0256
0257
0258 if nargin > 1
0259 narginchk(6,6);
0260 for k = 1 : 6
0261 validateattributes(varargin{k}, {'numeric'}, {'real','scalar'});
0262 end
0263 [c1,c2,a,b,phi] = ellipse_explicit(varargin{:});
0264 else
0265 narginchk(1,1);
0266 p = varargin{1};
0267 validateattributes(p, {'numeric'}, {'real','vector'});
0268 p = p(:);
0269 validateattributes(p, {'numeric'}, {'size',[6 1]});
0270 [c1,c2,a,b,phi] = ellipse_explicit(p(1), 0.5*p(2), p(3), 0.5*p(4), 0.5*p(5), p(6));
0271 end
0272 if nargout > 1
0273 varargout = num2cell([c1,c2,a,b,phi]);
0274 else
0275 varargout{1} = [c1,c2,a,b,phi];
0276 end
0277
0278 function [c1,c2,semia,semib,phi] = ellipse_explicit(a,b,c,d,f,g)
0279
0280
0281
0282 N = 2*(a*f^2+c*d^2+g*b^2-2*b*d*f-a*c*g);
0283 D = b^2-a*c;
0284 S = realsqrt((a-c)^2+4*b^2);
0285
0286
0287 ap = realsqrt( N/(D*(S-(a+c))) );
0288 bp = realsqrt( N/(D*(-S-(a+c))) );
0289 semia = max(ap,bp);
0290 semib = min(ap,bp);
0291
0292
0293 c1 = (c*d-b*f)/D;
0294 c2 = (a*f-b*d)/D;
0295
0296
0297 if b ~= 0
0298 if abs(a) < abs(c)
0299 phi = 0.5*acot((a-c)/(2*b));
0300 else
0301 phi = 0.5*pi+0.5*acot((a-c)/(2*b));
0302 end
0303 else
0304 if abs(a) < abs(c)
0305 phi = 0;
0306 else
0307 phi = 0.5*pi;
0308 end
0309 end
0310 end
0311 end
0312 end