📄 vgg_line3d_from_lp_nonlin.m
字号:
% vgg_line3d_from_lP_nonlin Non-linear estimation of (possibly constrained) 3D line segment from image line segments.
%
% SYNOPSIS
% L = vgg_line3d_from_lP_nonlin(s,P [,imsize] [,L0] [,X] [,nonlin_opt])
%
% s ... cell(K) of double(3,3), inv. covariance matrices of the K image line segments:-
% - If the segments are estimated from edges, it is s(:,k) = x*x',
% where x (3-by-N) are homog. coordinates of the edgels with last components 1.
% - If only end points are available, s(:,k) = d*x*y' where x, y (column 2-vectors)
% are the segment's end points and d its length.
%
% P ... K-cell with 3-by-4 camera matrices
%
% imsize ... size (2,K), image size(s) for preconditiong.
% Omit if s and P are already preconditioned.
%
% L0 ... double(4,2), initial scene line (optional). Homogeneous points L0(:,i) span
% the line. If omitted, linear estimation is done first.
%
% X ... constraint on L :-
% - if X is omitted: no constraint on L
% - if X is double(4,1): L goes thru point X
% - if X is double(4,2): L goes thru 3D line spanned by X(:,i)
%
% nonlin_opt ... options for Levenberg-Marquardt. It is comma-separated list
% of pairs ['option',value]. Possible options are :-
% opt ... options structure with possible fields :-
% - verbose ... 1 or 0 (default: 0)
% - niter_term ... maximum number of iterations
% - rmsstep_term ... terminating step of rms of residuals
% - lambda_term ... terminating value of lambda (default: 1e10)
% - lambda_init ... initial value of lambda
% E.g., vgg_line3d_from_lP_nonlin(...,'lambda_init',1e-9,'niter_term',5).
%
% L ... double(4,2), estimated 3D line. Points L(:,i) span the line.
%
% Note: use [] if you want to omit a parameter and use a later one, e.g.
% vgg_line3d_from_lP_nonlin(s,P,imsize,[],[],'verbose',1,'lam_init',1e-9)
%
% ALGORITHM
% - Minimization is done by Levenberg-Marquardt.
% - 3D line L is parameterized by image lines in the first two images.
% The positions of these image lines are possibly constrained by X.
% T.Werner, Feb 2002
function L = vgg_line3d_from_lP_nonlin(s,P,imsize,L,X,varargin)
if nargin < 3, imsize = []; end
if nargin < 4, L = []; end
if nargin < 5, X = []; end
if isempty(L)
L = vgg_line3d_from_lP_lin(s,P,imsize);
end
K = length(P); % number of images
if K<2
error('Cannot reconstruct 3D line from 1 image');
end
if isempty(X) & K==2 % no need for non-linear minimization
return
end
% Prepare square root of covariance matrices; now s{k}(:,n) has meaning of 3 homogeneous image points
for k = 1:K
[us,ss,vs] = svd(s{k},0);
s{k} = us*sqrt(ss);
end
% Preconditioning
if ~isempty(imsize)
for k = 1:K
H = vgg_conditioner_from_image(imsize(:,k));
P{k} = H*P{k};
s{k} = H*s{k};
scale(k) = H(1,1); % save the scales for evaluating objective function
end
else
scale = ones(1,K);
end
switch size(X,2)
case 0
% Scene line L is unconstrained, having thus 4 DOF.
% L is parameterized by two image lines in images 1 and 2, each having 2 DOF, as follows:
% l1 = l0(1,:) + p(1:2)'*ldelta{1}
% l2 = l0(2,:) + p(3:4)'*ldelta{2}
% where row 4-vector p represents 4 DOF of L.
for k = 1:2
l0(k,:) = normx(vgg_wedge(P{k}*L)')';
ldelta{k} = null(l0(k,:))';
end
% optimization
p = levmarq(@F, {vertcat(P{:}),s,scale,l0,ldelta},...
@normsolve,...
[0;0;0;0],...
varargin{:});
l = l12_from_p(p,l0,ldelta);
case 1
% Scene line L is constrained to intersect the scene point X, having thus 2 DOF.
% L is parameterized by two image lines in images 1 and 2, each having 1 DOF, as follows:
% l1 = l0(1,:) + p(1)*ldelta{1}
% l2 = l0(2,:) + p(2)*ldelta{2}
% where 2-vector p represents 2 DOF of L.
for k = 1:2
l0(k,:) = normx(vgg_wedge(P{k}*L)')';
x = P{k}*X;
% Since L might not intersect X, move l0(k,:) 'as little as possible' to intersect x.
l0(k,:) = l0(k,:) - (l0(k,:)*x)/(x'*x).*x';
Q = null(x')';
ldelta{k} = null(l0(k,:)*pinv(Q))'*Q;
end
% optimization
p = levmarq(@F, {vertcat(P{:}),s,scale,l0,ldelta},...
@normsolve,...
[0;0],...
varargin{:});
l = l12_from_p(p,l0,ldelta);
case 2
% Scene line L is constrained to intersect the scene line given by 2 points X.
% This constraint is given by
% l1*G*l2' = 0
% where G is 3x3 rank 2 matrix (analogical in fact to fundamental matrix) and
% G = P{1}*M*P{2}'
% where M is Pluecker matrix of line given by X, M = X(:,1)*X(:,2)'-X(:,2)*X(:,1).
%
% This constraint can be written as
% p(1:2)*D*p(3:4)' + p(1:2)*d{2}' + d{1}*p(3:4)' + c = 0
% where D, d, c are given below and 4-vector p are 4 parameters of L.
%
% L is parameterized by two image lines in images 1 and 2, each having 2 DOF, as follows:
% l1 = l0(1,:) + p(1:2)'*ldelta{1}
% l2 = l0(2,:) + p(3:4)'*ldelta{2}
% where p(1:3) are chosen freely and p(4) is computed from the above formula as
% p(4) = -(p(1:2)'*(D(:,1)*p(3)+d{1})+p(3)*d{2}(1)+c)/(p(1:2)'*D(:,2)+d{2}(2)).
Lpm = X(:,1)*X(:,2)' - X(:,2)*X(:,1)';
G = P{1}*Lpm*P{2}';
for k = 1:2
l0(k,:) = normx(vgg_wedge(P{k}*L)')';
end
% As L might not intersect line X, move l0(2,:) 'as little as possible' to enforce l0(1,:)*G*l0(2,:)'==0.
x = (l0(1,:)*G)';
l0(2,:) = l0(2,:) - (l0(2,:)*x)/(x'*x).*x';
for k = 1:2
ldelta{k} = null(l0(k,:))';
end
D = ldelta{1}*G*ldelta{2}';
d{1} = ldelta{1}*G*l0(2,:)';
d{2} = ldelta{2}*G'*l0(1,:)';
c = l0(1,:)*G*l0(2,:)';
% optimization
p = levmarq(@F, {vertcat(P{:}),s,scale,l0,ldelta,D,d,c},...
@normsolve,...
[0;0;0],...
varargin{:});
l = l12_from_p(p,l0,ldelta,D,d,c);
end
if all(~isnan(l(:)))
L = null([l(1,:)*P{1}; l(2,:)*P{2}]);
end
return
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% objective function
% Objective function of Levenberg-Marquardt
function [y,w,J] = F(p,P,s,scale,varargin)
K = length(s);
% l := lines in images 1, 2 from p
l = l12_from_p(p,varargin{:});
% l := reprojection of lines in images 1, 2 to all images
if all(abs(p) < inf)
[dummy,dummy,L]= svd([l(1,:)*P(1:3,:); l(2,:)*P(4:6,:)],0);
else
L = inf*ones(4);
end
l = norml(vgg_wedge(reshape(P*L(:,3),[3 K]),reshape(P*L(:,4),[3 K])));
% compute residual function
y = [];
for k = 1:K
y = [y l(k,:)*s{k}];
end
y = y';
w = [1;1;1] * (1./scale);
w = w(:);
% else, compute also jacobian
if nargout < 2
return
end
dif = 1e-6;
J = zeros(length(y),length(p));
for i = 1:length(p)
pdif = p;
pdif(i) = pdif(i) + dif;
J(:,i) = (F(pdif,P,s,scale,varargin{:}) - y)/dif;
end
return
% The following function computes lines in the first two images
% from parameters p. Explanation see above.
function l = l12_from_p(p,l0,ldelta,D,d,c)
switch length(p)
case 4 % unconstrained
l = [l0(1,:) + p(1:2)'*ldelta{1}
l0(2,:) + p(3:4)'*ldelta{2}];
case 2 % going thru X
l = [l0(1,:) + p(1)*ldelta{1}
l0(2,:) + p(2)*ldelta{2}];
case 3 % going thru L
p(4) = -(p(1:2)'*(D(:,1)*p(3)+d{1})+p(3)*d{2}(1)+c)/(p(1:2)'*D(:,2)+d{2}(2));
l = [l0(1,:) + p(1:2)'*ldelta{1}
l0(2,:) + p(3:4)'*ldelta{2}];
end
return
function dp = normsolve(J,Y,w,lambda)
OLDWARN = warning('off');
dp = -( J'*diag(w)*J + lambda*eye(size(J,2)) ) \ ( J'*(Y.*w) );
warning(OLDWARN);
return
function x = normx(x)
if ~isempty(x)
x = x./(ones(size(x,1),1)*sqrt(sum(x.*x)));
end
function l = norml(l)
% l = norml(l) Multiplies hyperplane l by scalar so that for each n, norm(l(1:end-1,n))==1.
l = l./(sqrt(sum(l(:,1:end-1).^2,2))*ones(1,size(l,2)));
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% a = levmarq(@RES,PARAMS,@NORMSOLVE,a [,opt]) Non-linear least-squares by Levenberg-Marquardt.
%
% Minimizes f(a)'*W*f(a) over a.
%
% @RES ... residual function f called like [e,w,J] = RES(a,PARAMS{:}), where
% - a ... double(M,1), parameter vector
% - e ... double(N,1), residual vector
% - J ... double(N,M), derivative of e wrt a
% - w ... double(N,1), weights of e; covariance matrix of e is diag(1/e.^2).
% Use 1 instead of ones(N,1).
% For efficiency, RES should not compute the jacobian if called with two output parameters only.
%
% @NORMSOLVE ... function solving normal equations, called like da = NORMSOLVE(J,e,W,lambda).
% a ... initial parameter vector
% opt ... options structure, see code
function [a,w] = levmarq(RES,PARAMS,NORMSOLVE,a0,varargin)
% options
[opt,rem_opt] = vgg_argparse( { 'niter_term', +inf,...
'drmsrel_term', 0,...
'loglambda_term', 6,...
'loglambda_init', -4,...
'verbose', 0 },...
varargin );
if ~isempty(rem_opt), if ~isempty(fieldnames(rem_opt))
error(['Unknown option(s) ' fieldnames(rem_opt)]);
end, end
% Initial statistics
if opt.verbose
[e0,w0] = feval(RES,a0,PARAMS{:});
ssd0 = sum( (e0.*w0).^2 );
fprintf( ' [rms=%14.12g] [maxabs=%14.12g]\n',...
sqrt(ssd0/length(e0)),...
max(abs(e0.*w0)) );
end
loglambda = opt.loglambda_init;
niter = 0;
while 1
% Compute actual residual and jacobian
[e0,w0,J] = feval(RES,a0,PARAMS{:});
% Update a as a := a0 + da, by finding
% optimal lambda and solving normal equations for da.
nfail = 1;
while (loglambda < opt.loglambda_term)
niter = niter + 1;
a = a0 + feval(NORMSOLVE,J,e0,w0,10^loglambda);
[e,w] = feval(RES,a,PARAMS{:});
if sum((e.*w).^2) < sum((e0.*w0).^2) % success
a0 = a;
loglambda = loglambda - 1;
break
end
if opt.verbose
fprintf('%4i.%.2i: [loglambda=%3i] [REJECTED]\n',niter,nfail,loglambda);
end
loglambda = loglambda + 1;
nfail = nfail + 1;
end
% Print statistic after successful iteration
ssd0 = sum( (e0.*w0).^2 );
ssd = sum( (e.*w).^2 );
if opt.verbose
fprintf( '%4i : [loglambda=%3i] [rms=%14.12g] [maxabs=%14.12g] [drmsrel=%4g%%]\n',...
niter,...
round(loglambda),...
sqrt(ssd/length(e)),...
max(abs(e.*w)),...
100*(1-sqrt(ssd/ssd0)) );
end
% Termination criteria
test(1) = loglambda < opt.loglambda_term;
test(2) = ssd0-ssd >= opt.drmsrel_term^2*ssd0;
test(3) = niter < opt.niter_term;
if any(test==0)
break
end
end
if opt.verbose
onoff = {'YES','no'};
fprintf( ' Levenberg-Marquardt finished succesfully.\n Reason for termination:\n' );
fprintf( ' lambda = %s\n', onoff{test(1)+1} );
fprintf( ' drmsrel = %s\n', onoff{test(2)+1} );
fprintf( ' niter = %s\n', onoff{test(3)+1} );
end
return
function print_statistics(niter,loglambda,e0,w0,e,w,opt)
if opt.verbose
ssd0 = sum( (e0.*w0).^2 );
ssd = sum( (e.*w).^2 );
fprintf( '%4i : [loglambda=%3i] [rms=%14.12g] [maxabs=%14.12g] [drmsrel=%11.5g]\n',...
niter,...
round(loglambda),...
sqrt(ssd/length(e)),...
max(abs(e.*w)),...
sqrt(1-ssd/ssd0) );
end
return
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -