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