Rev 212 | Rev 214 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
function MSE = alignSubScansMarkers(calibrationFileName, scanDir, alnFileName)
%ALIGNSUBSCANSMARKERS Determines an exact alignment of sub scans (scans
% from e.g. one revolution of the rotation stage).
% The method searches for circular white markers of a specific diameter.
% White frames corresponding to each sub scan must be available.
% A coarse alignment in the form of an aln-file must be provided.
%
% 2017 Jakob Wilm, DTU
initialAlign = readMeshLabALN(alnFileName);
whiteFrameDirs = dir(fullfile(scanDir, 'sequence_*'));
assert(length(whiteFrameDirs) == length(initialAlign));
calibration = readOpenCVXML(calibrationFileName);
% full projection matrices in Matlab convention
P0 = transpose(calibration.K0*[eye(3) zeros(3,1)]);
P1 = transpose(calibration.K1*[calibration.R1 calibration.T1']);
% matlab cam params for undistortion
camParams0 = cameraParameters('IntrinsicMatrix', calibration.K0', 'RadialDistortion', calibration.k0([1 2 5]), 'TangentialDistortion', calibration.k0([3 4]));
camParams1 = cameraParameters('IntrinsicMatrix', calibration.K1', 'RadialDistortion', calibration.k1([1 2 5]), 'TangentialDistortion', calibration.k1([3 4]));
% matlab struct for triangulation
camStereoParams = stereoParameters(camParams0, camParams1, calibration.R1', calibration.T1');
nSubScans = length(whiteFrameDirs);
% 3D coordinates of markers in local camera frame
E = cell(nSubScans, 1);
% 3D coordinates of markers in global initial alignment
Eg = cell(size(E));
% find 3D markers coordinates
for i=1:nSubScans
% load point cloud
pc = pcread(fullfile(scanDir, initialAlign(i).FileName));
Q = pc.Location;
idString = initialAlign(i).FileName(12:end-4);
% load white frames
frame0 = imread(fullfile(scanDir, ['sequence_' idString], 'frames0_0.png'));
frame1 = imread(fullfile(scanDir, ['sequence_' idString], 'frames1_0.png'));
e0Coords = autoDetectMarkers(frame0, P0, Q);
e1Coords = autoDetectMarkers(frame1, P1, Q);
%e0Coords = manuallyDetectMarkers(frame0, P0, Q);
%e1Coords = manuallyDetectMarkers(frame1, P1, Q);
e0Coords = undistortPoints(e0Coords, camParams0);
e1Coords = undistortPoints(e1Coords, camParams1);
% match ellipse candidates between cameras based on projection
E0 = projectOntoPointCloud(e0Coords, P0, Q);
E1 = projectOntoPointCloud(e1Coords, P1, Q);
matchedPairs = nan(size(E0, 1), 2);
nMatchedPairs = 0;
for j=1:size(E0, 1)
% should use pdist2 instead..
sqDists = sum((E1 - repmat(E0(j,:), size(E1, 1), 1)).^2, 2);
[minSqDist, minSqDistIdx] = min(sqDists);
if(minSqDist < 5^2)
nMatchedPairs = nMatchedPairs + 1;
matchedPairs(nMatchedPairs, :) = [j, minSqDistIdx];
end
end
matchedPairs = matchedPairs(1:nMatchedPairs, :);
% triangulate marker centers (lens correction has been performed)
E{i} = triangulate(e0Coords(matchedPairs(:, 1),:), e1Coords(matchedPairs(:, 2),:), camStereoParams);
% bring into initial alignment
[U,~,V] = svd(initialAlign(i).Rotation);
Ri = U*V';
Ti = initialAlign(i).Translation;
Eg{i} = E{i}*Ri' + repmat(Ti', size(E{i}, 1), 1);
end
% show found markers in initial alignment
figure;
hold('on');
for i=1:nSubScans
% fix Ri to be orthogonal
[U,~,V] = svd(initialAlign(i).Rotation);
Ri = U*V';
% bring point cloud into initial alignment
pc = pcread(fullfile(scanDir, initialAlign(i).FileName));
tform = affine3d([Ri' [0;0;0]; initialAlign(i).Translation' 1]);
pcg = pctransform(pc, tform);
pcshow(pcg);
xlabel('x');
ylabel('y');
zlabel('z');
plot3(Eg{i}(:,1), Eg{i}(:,2), Eg{i}(:,3), '.', 'MarkerSize', 15);
title('Initial Alignment');
end
% match markers between poses using initial alignment
Pg = {};
P = {};
for i=1:nSubScans
for j=1:size(Eg{i}, 1)
pg = Eg{i}(j,:);
p = E{i}(j,:);
matched = false;
for k=1:size(Pg, 2)
clusterCenter = mean(cat(1, Pg{:,k}), 1);
if(sum((pg - clusterCenter).^2) < 3^2)
% store in global frame
Pg{i,k} = pg;
% store in local frame
P{i,k} = p;
matched = true;
break;
end
end
% create new cluster
if(not(matched))
Pg{i,end+1} = pg;
P{i,end+1} = p;
end
end
end
% run optimization
alignment = groupwiseOrthogonalProcrustes(P, initialAlign);
% show found markers in optimized alignment
figure;
hold('on');
for i=1:nSubScans
Ri = alignment(i).Rotation;
Ti = alignment(i).Translation;
Ea = E{i}*Ri' + repmat(Ti', size(E{i}, 1), 1);
% bring point cloud into optimized alignment
pc = pcread(fullfile(scanDir, initialAlign(i).FileName));
tform = affine3d([Ri' [0;0;0]; initialAlign(i).Translation' 1]);
pcg = pctransform(pc, tform);
pcshow(pcg);
xlabel('x');
ylabel('y');
zlabel('z');
plot3(Ea(:,1), Ea(:,2), Ea(:,3), '.', 'MarkerSize', 15);
title('Optimized Alignment');
end
% write to ALN file
for i=1:length(alignment)
alignment(i).FileName = initialAlign(i).FileName;
end
writeMeshLabALN(alignment, strrep(alnFileName, '.aln', '_opt.aln'));
end
function e = autoDetectMarkers(frame, P, pointCloud)
% create mask based on morphology
bw = imbinarize(rgb2gray(frame));
cc = bwconncomp(bw);
rp = regionprops(cc, 'Area', 'Solidity', 'Eccentricity', 'Centroid');
idx = ([rp.Area] > 100 & [rp.Area] < 1000 & [rp.Solidity] > 0.9);
initialGuesses = cat(1, rp(idx).Centroid);
[e, ~] = detectMarkersSubpix(frame, initialGuesses, P, pointCloud);
figure;
imshow(frame);
hold('on');
plot(e(:,1), e(:,2), 'rx', 'MarkerSize', 15);
drawnow;
end
function e = manuallyDetectMarkers(frame, P, pointCloud)
e = [];
%edges = edge(rgb2gray(frame), 'Canny', [0.08 0.1], 2);
figure;
hold('on');
imshow(frame);
title('Close figure to end.');
set(gcf, 'pointer', 'crosshair');
set(gcf, 'WindowButtonDownFcn', @clickCallback);
uiwait;
function clickCallback(caller, ~)
p = get(gca, 'CurrentPoint');
p = p(1, 1:2);
[el, ~] = detectMarkersSubpix(frame, p, P, pointCloud);
e = [e; el(:, 1:2)];
if(not(isempty(el)))
figure(caller);
hold('on');
plot(el(1), el(2), 'rx', 'MarkerSize', 15);
end
end
end
function [e, conf] = detectMarkersSubpix(frame, initGuesses, P, Q)
% create mask based on morphology
bw = imbinarize(rgb2gray(frame));
cc = bwconncomp(bw);
labels = labelmatrix(cc);
% project point cloud into image
q = [Q ones(size(Q,1),1)]*P;
q = q./[q(:,3) q(:,3) q(:,3)];
e = zeros(size(initGuesses));
conf = zeros(size(initGuesses, 1), 1);
nMarkersFound = 0;
for i=1:size(initGuesses, 1)
labelId = labels(round(initGuesses(i,2)), round(initGuesses(i,1)));
labelMask = (labels == labelId);
labelMask = imdilate(labelMask, strel('disk', 3, 0));
if(sum(sum(labelMask)) < 10 || sum(sum(labelMask)) > 1000)
continue;
end
% determine 3D points that are part of the marker
% note: we should probably undistort labelMask
pointMask = false(size(q, 1), 1);
for j=1:size(q,1)
if(labelMask(round(q(j,2)), round(q(j,1))))
pointMask(j) = true;
end
end
if(sum(pointMask)) < 100
continue;
end
% project 3D points onto local plane
[~, sc, ~] = pca(Q(pointMask, :));
Qlocal = sc(:, 1:2);
% synthetic marker in high res. space
m = zeros(151, 151);
[x, y] = meshgrid(1:151, 1:151);
m((x(:)-76).^2 + (y(:)-76).^2 <= 50^2) = 1.0;
% relation between marker space (px) and true marker/local plane(mm)
% true marker diameter is 1.75mm
mScale = 101/1.75; %px/mm
mShift = 76; %px
% build homography from image to marker space
H = fitgeotrans(q(pointMask, 1:2), mScale*Qlocal+mShift, 'projective');
% bring image of marker into marker space
imMarkerSpace = imwarp(frame, H, 'OutputView', imref2d(size(m)));
imMarkerSpace = rgb2gray(im2double(imMarkerSpace));
%figure; imshowpair(imMarkerSpace, m);
% perform image registration
[opt, met] = imregconfig('monomodal');
T = imregtform(m, imMarkerSpace, 'translation', opt, met);
rege = imwarp(m, T, 'OutputView', imref2d(size(m)));
%figure; imshowpair(imMarkerSpace, rege);
% measure of correlation
confI = sum(sum(imMarkerSpace .* rege))/sqrt(sum(sum(imMarkerSpace) * sum(sum(rege))));
if confI<0.4
continue;
end
fprintf('Found marker with confidence: %f\n', confI);
% transform marker space coordinates (76,76) to frame space
el = T.transformPointsForward([76, 76]);
el = H.transformPointsInverse(el);
nMarkersFound = nMarkersFound+1;
e(nMarkersFound,:) = el;
conf(nMarkersFound) = confI;
end
e = e(1:nMarkersFound, :);
conf = conf(1:nMarkersFound);
end
function E = projectOntoPointCloud(e, P, pointCloud)
q = [pointCloud ones(size(pointCloud,1),1)]*P;
q = q(:,1:2)./[q(:,3) q(:,3)];
E = nan(size(e,1), 3);
for i=1:size(e, 1)
sqDists = sum((q - repmat(e(i,:), size(q, 1), 1)).^2, 2);
[minSqDist, minSqDistIdx] = min(sqDists);
if(minSqDist < 2^2)
E(i, :) = pointCloud(minSqDistIdx, :);
end
end
end