% Algorithms 11.6. , 11.7, 11.8
% Step-by-step euclidean reconstruction algorithm from multiple views
% as described in Chapter 11, "An introduction to 3-D Vision"
% by Y. Ma, S. Soatto, J. Kosecka, S. Sastry (MASKS)
% Code distributed free for non-commercial use
% Copyright (c) MASKS, 2003
%
% Last modified 5/5/2005

% Following shell loads tracked point features
% and corresponding frames and computes the motion and 
% 3D structure of tracked features and focal lenght of the camera
% the skew and center of projection of the calibration matrix 
% is assumed to be known

% 1. Given n features in m view
% 2. Compute fundamental matrix and projective reconstuction from 2 views
% 3. Use rank based factorization for multiview projective reconstruction 
% 4. Solve for uknown focal lenght using absolute quadric constraints
% 5. Upgrade projective structure to euclidean one
% 6. Compute rectifying transformations
% 7. Warp the first and last view of the sequence
% ==================================================================

close all; clear;

% with the affine tracker and undone radial distortion
seq_name = 'oldhouse2/A2000';
image_type = 'bmp';
load oldhouse2/A2000_result_ST;  % tracks from 110 to 200

% specify index of starting frame fs, end frame fe 
% and the subsampling factor ft
fs = 0; fe = 88; ft = 12;
findex = [fs:ft:fe]; offset = 1;
indf = 1:size(findex,2);

% find features tracked in all the frames
ind = find(goodfeat ~= 0); 
j = 0; 
for i = (1+offset-1):ft:(fe-fs+1)
   j = j+1;
   xim(1,:,j) = xttfirst(2,ind) + SaveSTB(2,ind,i);
   xim(2,:,j) = xttfirst(1,ind) + SaveSTB(1,ind,i);
   xim(3,:,j) = 1;
end;

% consider only indf frames
xim = xim(:,:,indf);
[s, n, m] = size(xim);   

opt = '%03d'; 
imfile = sprintf('%s%s.%s',seq_name,sprintf(opt,findex(1)),image_type)
seq(1).im = (imread(imfile));

% read images
for i = 2:m 
  if findex(1) < 10 opt = '%03d'; 
  elseif findex(1) < 100 opt = '%02d'; 
  else  opt = '%01d'; 
  end;
  imfile = sprintf('%s%s.%s',seq_name,sprintf(opt,findex(i)),image_type)
  seq(i).im = imread(imfile);
end;


[ydim,xdim,cdim] = size(seq(1).im);

for i = 1:m
  imagesc(seq(i).im); colormap gray; hold on; 
  axis equal; axis image; axis off; 
  plot(xim(1,:,i), xim(2,:,i),'y.'); 
 for k = 1:n
   t = text(xim(1,k,i)+3, xim(2,k,i)+3,num2str(k)); 
   set(t, 'Color', 'yellow');
 end
end

   disp('tracked features - press ENTER to continue');
pause

%-------------------------------------------------------------
% Structure and motion and focal length recovery given xim

% guess intrinsic parameter matrix
[s, n, m] = size(xim);   
fguess = 700; % max(xdim,ydim);
Aguess = [fguess 0 xdim/2; 0 fguess ydim/2; 0 0 1];

%----------------------------
% Normalize the measurements
for i = 1:m
   xn(:,:,i) = inv(Aguess)*xim(:,:,i);   % partially calibrated views
end

%--------------------------------------------
% uncalibrated case - two view initialization
% compute F and decompose it
Rhat(:,:,1) = diag([1 1 1]);
That(:,1) = zeros(3,1);
F = dfundamental(xn(:,:,1), xn(:,:,m))
[uf, sf, vf] = svd(F'); ep = vf(:,3);
M = skew(ep)'*F; %  + rand(3,1)*ep';
Rhat(:,:,m) = M;
That(:,m) = ep;

%----------------------------
% compute projective stucture 
[X,lambda] = compute3DStructure(xn(:,:,1),xn(:,:,m),Rhat(:,:,m),That(:,m));
alpha = 1./lambda; 
alpha = alpha/alpha(1); 

%--------------------------------
% plot projective reconstruction
figure; hold on;
plot3(X(1,:,1),X(2,:,1),X(3,:,1),'k.'); 
xlabel('x'); ylabel('y'); zlabel('z');
draw_scale = X(3,1,1)/10;
Ts = That(:,1)*draw_scale;
plot3(Ts(1),Ts(2),Ts(3),'r.','MarkerSize',14);
Ts = That(:,m)*draw_scale;
plot3(Ts(1),Ts(2),Ts(3),'r.','MarkerSize',14);
title('Projective reconstruction from two views'); 
view(220,20); box on; grid off; axis equal; 
negative_depth = (~isempty(find(lambda < 0))) 

disp('end two view initialization - press ENTER');
pause;

%------------------------------------------------
% Compute initial motion estimates for all frames
init_error = 10; fs = [100]; ns = [];
errabs = init_error; err_prev = 500; iter = 1; errrel = init_error;

while (errabs > 1e-4) &  (iter < 30)  &  (errrel > 1e-5) % (errlambda > 1e-4) 
  for k = 2:m
     j = indf(k);
     Q = []; %  setup matrix P
     for i = 1:n
       Q = [Q; kron(skew(xn(:,i,j)),xn(:,i,1)') alpha(i)*skew(xn(:,i,j))];
     end;
     [um, sm, vm] = svd(Q);
     That(:,j) = vm(10:12,12);
     Rhat(:,:,j) = reshape(vm(1:9,12),3,3)';
  end;
  %--------------------------------------
  % recompute alpha's based on all views 
  lambda_prev = lambda;
  % recompute alpha's
  for i = 1:n
     M = [];  % setup matrix M
     for k = 2:m        % set up Hl matrix for all m views
       j = indf(k);
       a = [ skew(xn(:,i,j))*That(:,j) skew(xn(:,i,j))*Rhat(:,:,j)*xn(:,i,1)];
       M = [M; a];
     end;
     alpha(i) = -M(:,1)'*M(:,2)/norm(M(:,1))^2;
   end;
   scale = alpha(1);
   alpha = alpha/scale; % set the global scale
   lambda = 1./alpha;
   X = [lambda.*xn(1,:,1); lambda.*xn(2,:,1); lambda.*xn(3,:,1); ones(1,n)];
   res = [];
   i = 1;
   for l = 1:m
      j = indf(l);
      P(i*3-2:i*3,:) = [Rhat(:,:,j) scale*That(:,j)];
      tt = P(i*3-2:i*3,:)*X;
      if sum(sign(tt(3,:))) < -n/2
        P(i*3-2:i*3,:) = -[Rhat(:,:,j) scale*That(:,j)];
        Rhat(:,:,j) = -Rhat(:,:,j);
        That(:,j) = -That(:,j);
        tt = P(i*3-2:i*3,:)*X;
      end;
      xr(:,:,i) = Aguess*project(tt);
      xd = xim(1:2,:,j) - xr(1:2,:,i);
      errnorm = sqrt(xd(1,:).^2 + xd(2,:).^2);
      res = [res, errnorm];
      i = i+1;
   end

   f = sum(res)/(n*m);
   iter = iter + 1;
   fs = [fs f];
   if iter > 1 
     errrel = norm(fs(iter-1) - f);
     if fs(iter - 1) < f 
       errrel = 1e-10; 
     end;
   end;
   errabs = norm(f);
   errlambda = norm(lambda_prev-lambda);
   lambda_prev = lambda;
end % end while iter

disp('end rank based factorization - press ENTER'); 
pause;

clear xres;  
res = [];
%-------------------
% final reprojection
for i = 1:m
    j = indf(i);
    XP(:,:,i) = P(i*3-2:i*3,:)*X;
    xres(:,:,i)  = Aguess*project(XP(:,:,i));
    PC(:,:,i) = [Rhat(:,:,i) That(:,i)];
    xd = xim(1:2,:,j) - xres(1:2,:,i);
    errnorm = sqrt(xd(1,:).^2 + xd(2,:).^2);
    res(i) = sum(errnorm)/(m*n);
end

figure; hold  on; 
plot3(2*X(1,:),2*X(2,:),2*X(3,:),'k.'); box on; view(220,20);
title('Projective reconstruction from multiple views');
xlabel('x'); ylabel('y'); zlabel('z');
for i = 1:m
   Ts = That(:,i)*2*i*scale;
   plot3(Ts(1),Ts(2),Ts(3),'r.','MarkerSize',14); 
end

disp('projective reconstruction from multiple views - press ENTER');
pause;

%------------------------------------------------------------
% self-calibration - linear algorithm for unknown focal length
% set up the constraints on absolute quadric

Omega = quadric_linear_f(PC);
if Omega(1,1) < 0; 
    Omega = - Omega
end

for k = 1:m
    t = PC(:,:,k)*Omega*PC(:,:,k)';
    fest(k) = sqrt(t(1,1)/t(3,3));
    fest2(k) = sqrt(t(2,2)/t(3,3));
    Atmp(:,:,k) = Aguess*[fest(k) 0 0; 0 fest(k) 0; 0 0 1];
end

%-------------------------------------------------------
% Estimate the projective to euclidean upgrade transf Hp

v = -[Omega(1,4)/(fest(1)^2); Omega(2,4)/(fest2(1)^2); Omega(3,4)]; 
K1 = [fest(1) 0 0; 0 fest2(1) 0 ; 0 0 1];
v4 = 1;
Hp = [K1 zeros(3,1);  -v'*K1  v4];

%------------------------------------
% update the structure to Euclidean 

Xe = inv(Hp)*X; 
Xe = [Xe(1,:,1)./Xe(4,:,1);Xe(2,:,1)./Xe(4,:,1);Xe(3,:,1)./Xe(4,:,1);ones(1,size(X,2))];
if (sum(sign(Xe(3,:,1))) < 0) & (sum(sign(Xe(3,:,1))) == -size(Xe,2)) 
    Hp = [K1 zeros(3,1);  -v'*K1  -v4];
    Xe = inv(Hp)*X;
    warning('Flipping the sign of Hp');
elseif (sum(sign(Xe(3,:,1))) < 0) & (sum(sign(Xe(3,:,1))) ~= -size(Xe,2)) 
    error('Projective upgrade: some of the depths are negative');
end;

%-------------------------------    
% update the projection matrices 
PP = P*Hp; 

%------------------------------------------------------
% rigid body motion and 3D Euclidean structure recovery

for i = 1:m
   k = indf(i);
   Re(:,:,i) =  PP(i*3-2:i*3,1:3);
   [r,q] = rq(Re(:,:,i));
   Arem(:,:,i) = r; % /r(3,3); % calibration matrix 
   Re(:,:,i) = q; % rotation matrix 
   Te(:,i) =  inv(Arem(:,:,i))*PP(i*3-2:i*3,4);
   Xe(:,:,i) = [Re(:,:,i) Te(:,i); 0 0 0 1]*[Xe(:,:,1)];
end;

%------------------------------
% plot euclidean reconstruction
figure;  
gs = 100; % global scale
plot3(gs*Xe(1,:,1),gs*Xe(2,:,1),gs*Xe(3,:,1),'k.');  hold on; box on;
xlabel('x'); ylabel('y'); zlabel('z');
draw_scale = gs*Xe(3,1,1)/6;
for i=1:size(Xe,2)
    text(gs*Xe(1,i,1)+2, gs*Xe(2,i,1)+2, gs*Xe(3,i,1), num2str(i)); 
end;
title('Euclidean reconstruction from multiple views'); 
view(220,20); grid off; axis equal; %  axis off; 
for i=1:4:m
    draw_frame_scaled([Re(:,:,i)' gs*(-Re(:,:,i)'*Te(:,i))], draw_scale);
end
axis equal; box on; % view(-8,-60);

disp('Euclidean reconstruction complete - press ENTER');
pause

%-------------------------------------------------------------
% Here should go nonlinear refinement to improve the estimates
%-------------------------------------------------------------

%------------------------------------------
% estimate rectifying transformations H1, H2
Fr = dfundamental(xim(:,:,1), xim(:,:,m));
im0 = seq(1).im;
im1 = seq(m).im;

for i=1:n
   epil2r(:,i) = Fr*xim(:,i,1);
   epil1r(:,i) = Fr'*xim(:,i,m);
end;

[H1, H2] = projRectify(Fr,xim(:,:,1), xim(:,:,m), xdim, ydim)
Tr = [1 0 -xdim/2; 0 1 -ydim/2 ; 0 0 1]; 
H1 = inv(Tr)*H1;
H2 = inv(Tr)*H2;
xim1r = project(H1*xim(:,:,1));
xim2r = project(H2*xim(:,:,m));
epil1rw = inv(H1)'*epil1r;
epil2rw = inv(H2)'*epil2r;

%-------------------------------------------------
% warped the two views such that the epipolar lines 
% correpond to scanlines
[im0w, xi0, yi0] = Hwarp(H1, im0); 
[im1w, xi1, yi1] = Hwarp(H2, im1);
[ydim0, xdim0] = size(im0w);
[ydim1, xdim1] = size(im1w);
f1 = figure; imagesc(im0w); colormap gray; hold on; axis image;
f2 = figure; imagesc(im1w); colormap gray; hold on; axis image;

%------------------------
% plot few epipolar lines
indl = [1, 3, 26, 61];
for k = 1:size(indl,2);
    i = indl(k);
    figure(f1); hold on; 
    plotLineNormal(epil1rw(:,i),xdim0,ydim0); 
    axis([1 xdim0 1 ydim0]);
    figure(f2); hold on; 
    plotLineNormal(epil2rw(:,i),xdim1,ydim1);
    axis([1 xdim1 1 ydim1]);
end;


disp('warped first and last view - END');




