214 |
jakw |
1 |
function MSE = alignSubScansMarkers(calibrationFileName, alnFileName)
|
204 |
jakw |
2 |
%ALIGNSUBSCANSMARKERS Determines an exact alignment of sub scans (scans
|
|
|
3 |
% from e.g. one revolution of the rotation stage).
|
212 |
jakw |
4 |
% The method searches for circular white markers of a specific diameter.
|
204 |
jakw |
5 |
% White frames corresponding to each sub scan must be available.
|
209 |
jakw |
6 |
% A coarse alignment in the form of an aln-file must be provided.
|
204 |
jakw |
7 |
%
|
|
|
8 |
% 2017 Jakob Wilm, DTU
|
|
|
9 |
|
209 |
jakw |
10 |
initialAlign = readMeshLabALN(alnFileName);
|
237 |
jakw |
11 |
[alnFilePath, ~, ~] = fileparts(alnFileName);
|
209 |
jakw |
12 |
|
211 |
jakw |
13 |
calibration = readOpenCVXML(calibrationFileName);
|
|
|
14 |
|
216 |
jakw |
15 |
% correct for Matlab 1-indexing in principle point coordinates
|
|
|
16 |
calibration.K0(1:2, 3) = calibration.K0(1:2, 3)+1;
|
|
|
17 |
calibration.K1(1:2, 3) = calibration.K1(1:2, 3)+1;
|
|
|
18 |
|
211 |
jakw |
19 |
% full projection matrices in Matlab convention
|
|
|
20 |
P0 = transpose(calibration.K0*[eye(3) zeros(3,1)]);
|
|
|
21 |
P1 = transpose(calibration.K1*[calibration.R1 calibration.T1']);
|
|
|
22 |
|
|
|
23 |
% matlab cam params for undistortion
|
|
|
24 |
camParams0 = cameraParameters('IntrinsicMatrix', calibration.K0', 'RadialDistortion', calibration.k0([1 2 5]), 'TangentialDistortion', calibration.k0([3 4]));
|
|
|
25 |
camParams1 = cameraParameters('IntrinsicMatrix', calibration.K1', 'RadialDistortion', calibration.k1([1 2 5]), 'TangentialDistortion', calibration.k1([3 4]));
|
|
|
26 |
|
|
|
27 |
% matlab struct for triangulation
|
|
|
28 |
camStereoParams = stereoParameters(camParams0, camParams1, calibration.R1', calibration.T1');
|
|
|
29 |
|
214 |
jakw |
30 |
nSubScans = length(initialAlign);
|
241 |
jakw |
31 |
%nSubScans = 5;
|
209 |
jakw |
32 |
|
211 |
jakw |
33 |
% 3D coordinates of markers in local camera frame
|
|
|
34 |
E = cell(nSubScans, 1);
|
|
|
35 |
|
|
|
36 |
% 3D coordinates of markers in global initial alignment
|
|
|
37 |
Eg = cell(size(E));
|
|
|
38 |
|
|
|
39 |
% find 3D markers coordinates
|
209 |
jakw |
40 |
for i=1:nSubScans
|
211 |
jakw |
41 |
% load point cloud
|
237 |
jakw |
42 |
pcFileName = fullfile(alnFilePath, initialAlign(i).FileName);
|
|
|
43 |
pcFilePath = fileparts(pcFileName);
|
|
|
44 |
pc = pcread(pcFileName);
|
211 |
jakw |
45 |
Q = pc.Location;
|
214 |
jakw |
46 |
idString = strsplit(initialAlign(i).FileName, {'.ply', '_'});
|
|
|
47 |
idString = idString{end-1};
|
209 |
jakw |
48 |
|
211 |
jakw |
49 |
% load white frames
|
237 |
jakw |
50 |
frame0 = imread(fullfile(pcFilePath, ['sequence_' idString], 'frames0_0.png'));
|
|
|
51 |
frame1 = imread(fullfile(pcFilePath, ['sequence_' idString], 'frames1_0.png'));
|
211 |
jakw |
52 |
|
237 |
jakw |
53 |
e0Coords = autoDetectMarkers(frame0);
|
|
|
54 |
e1Coords = autoDetectMarkers(frame1);
|
209 |
jakw |
55 |
|
237 |
jakw |
56 |
%e0Coords = manuallyDetectMarkers(frame0);
|
|
|
57 |
%e1Coords = manuallyDetectMarkers(frame1);
|
210 |
jakw |
58 |
|
237 |
jakw |
59 |
%[e0Coords, conf0] = detectMarkersSubpix(frame0, e0Coords, P0, Q);
|
|
|
60 |
%[e1Coords, conf1] = detectMarkersSubpix(frame1, e1Coords, P1, Q);
|
|
|
61 |
|
|
|
62 |
if(length(e0Coords) < 1 || length(e1Coords) < 1)
|
|
|
63 |
continue;
|
|
|
64 |
end
|
|
|
65 |
|
|
|
66 |
% figure;
|
|
|
67 |
% subplot(1,2,1);
|
|
|
68 |
% imshow(frame0);
|
|
|
69 |
% hold('on');
|
|
|
70 |
% plot(e0Coords(:,1), e0Coords(:,2), 'rx', 'MarkerSize', 15);
|
|
|
71 |
% subplot(1,2,2);
|
|
|
72 |
% imshow(frame1);
|
|
|
73 |
% hold('on');
|
|
|
74 |
% plot(e1Coords(:,1), e1Coords(:,2), 'rx', 'MarkerSize', 15);
|
|
|
75 |
% drawnow;
|
|
|
76 |
|
241 |
jakw |
77 |
e0CoordsUndistort = undistortPointsFast(e0Coords, camParams0);
|
|
|
78 |
e1CoordsUndistort = undistortPointsFast(e1Coords, camParams1);
|
211 |
jakw |
79 |
|
|
|
80 |
% match ellipse candidates between cameras based on projection
|
241 |
jakw |
81 |
E0 = projectOntoPointCloud(e0CoordsUndistort, P0, Q);
|
|
|
82 |
E1 = projectOntoPointCloud(e1CoordsUndistort, P1, Q);
|
211 |
jakw |
83 |
|
|
|
84 |
matchedPairs = nan(size(E0, 1), 2);
|
|
|
85 |
nMatchedPairs = 0;
|
|
|
86 |
for j=1:size(E0, 1)
|
|
|
87 |
|
|
|
88 |
% should use pdist2 instead..
|
|
|
89 |
sqDists = sum((E1 - repmat(E0(j,:), size(E1, 1), 1)).^2, 2);
|
|
|
90 |
|
|
|
91 |
[minSqDist, minSqDistIdx] = min(sqDists);
|
|
|
92 |
|
241 |
jakw |
93 |
if(minSqDist < 2^2)
|
211 |
jakw |
94 |
nMatchedPairs = nMatchedPairs + 1;
|
|
|
95 |
matchedPairs(nMatchedPairs, :) = [j, minSqDistIdx];
|
|
|
96 |
end
|
|
|
97 |
end
|
|
|
98 |
matchedPairs = matchedPairs(1:nMatchedPairs, :);
|
209 |
jakw |
99 |
|
237 |
jakw |
100 |
figure;
|
|
|
101 |
subplot(1,2,1);
|
|
|
102 |
imshow(frame0);
|
|
|
103 |
hold('on');
|
|
|
104 |
plot(e0Coords(matchedPairs(:, 1),1), e0Coords(matchedPairs(:, 1),2), 'rx', 'MarkerSize', 15);
|
|
|
105 |
subplot(1,2,2);
|
|
|
106 |
imshow(frame1);
|
|
|
107 |
hold('on');
|
|
|
108 |
plot(e1Coords(matchedPairs(:, 2),1), e1Coords(matchedPairs(:, 2),2), 'rx', 'MarkerSize', 15);
|
|
|
109 |
drawnow;
|
|
|
110 |
|
238 |
jakw |
111 |
% % triangulate marker centers (lens correction has been performed)
|
|
|
112 |
% [E{i}, e] = triangulate(e0Coords(matchedPairs(:, 1),:), e1Coords(matchedPairs(:, 2),:), camStereoParams);
|
|
|
113 |
% E{i} = E{i}(e<3.0, :);
|
|
|
114 |
% display(e);
|
|
|
115 |
|
|
|
116 |
[E{i}, e] = detectMarkersStereoSubpix(frame0, frame1, E0(matchedPairs(:, 1), :), camStereoParams, pc);
|
218 |
jakw |
117 |
display(e);
|
209 |
jakw |
118 |
|
216 |
jakw |
119 |
% write point cloud with marker (debugging)
|
241 |
jakw |
120 |
%pcDebug = pointCloud([pc.Location; E{i}], 'Color', [pc.Color; repmat([255, 0, 0], size(E{i}, 1), 1)]);
|
|
|
121 |
%pcwrite(pcDebug, 'pcDebug.ply');
|
216 |
jakw |
122 |
|
|
|
123 |
% bring markers into initial alignment
|
211 |
jakw |
124 |
[U,~,V] = svd(initialAlign(i).Rotation);
|
|
|
125 |
Ri = U*V';
|
|
|
126 |
Ti = initialAlign(i).Translation;
|
209 |
jakw |
127 |
|
211 |
jakw |
128 |
Eg{i} = E{i}*Ri' + repmat(Ti', size(E{i}, 1), 1);
|
|
|
129 |
end
|
|
|
130 |
|
212 |
jakw |
131 |
% show found markers in initial alignment
|
|
|
132 |
figure;
|
|
|
133 |
hold('on');
|
211 |
jakw |
134 |
for i=1:nSubScans
|
|
|
135 |
% fix Ri to be orthogonal
|
|
|
136 |
[U,~,V] = svd(initialAlign(i).Rotation);
|
|
|
137 |
Ri = U*V';
|
241 |
jakw |
138 |
initialAlign(i).Rotation = Ri;
|
209 |
jakw |
139 |
|
211 |
jakw |
140 |
% bring point cloud into initial alignment
|
237 |
jakw |
141 |
pcFileName = fullfile(alnFilePath, initialAlign(i).FileName);
|
|
|
142 |
pc = pcread(pcFileName);
|
211 |
jakw |
143 |
tform = affine3d([Ri' [0;0;0]; initialAlign(i).Translation' 1]);
|
|
|
144 |
pcg = pctransform(pc, tform);
|
212 |
jakw |
145 |
|
211 |
jakw |
146 |
pcshow(pcg);
|
|
|
147 |
xlabel('x');
|
|
|
148 |
ylabel('y');
|
|
|
149 |
zlabel('z');
|
212 |
jakw |
150 |
|
|
|
151 |
plot3(Eg{i}(:,1), Eg{i}(:,2), Eg{i}(:,3), '.', 'MarkerSize', 15);
|
213 |
jakw |
152 |
title('Initial Alignment');
|
204 |
jakw |
153 |
end
|
|
|
154 |
|
212 |
jakw |
155 |
% match markers between poses using initial alignment
|
|
|
156 |
Pg = {};
|
|
|
157 |
P = {};
|
|
|
158 |
for i=1:nSubScans
|
|
|
159 |
for j=1:size(Eg{i}, 1)
|
|
|
160 |
pg = Eg{i}(j,:);
|
|
|
161 |
p = E{i}(j,:);
|
|
|
162 |
matched = false;
|
|
|
163 |
for k=1:size(Pg, 2)
|
|
|
164 |
clusterCenter = mean(cat(1, Pg{:,k}), 1);
|
|
|
165 |
if(sum((pg - clusterCenter).^2) < 3^2)
|
|
|
166 |
% store in global frame
|
|
|
167 |
Pg{i,k} = pg;
|
|
|
168 |
% store in local frame
|
|
|
169 |
P{i,k} = p;
|
|
|
170 |
matched = true;
|
|
|
171 |
break;
|
|
|
172 |
end
|
|
|
173 |
end
|
|
|
174 |
% create new cluster
|
|
|
175 |
if(not(matched))
|
|
|
176 |
Pg{i,end+1} = pg;
|
|
|
177 |
P{i,end+1} = p;
|
|
|
178 |
end
|
|
|
179 |
end
|
|
|
180 |
end
|
211 |
jakw |
181 |
|
212 |
jakw |
182 |
% run optimization
|
|
|
183 |
alignment = groupwiseOrthogonalProcrustes(P, initialAlign);
|
|
|
184 |
|
213 |
jakw |
185 |
% show found markers in optimized alignment
|
|
|
186 |
figure;
|
|
|
187 |
hold('on');
|
|
|
188 |
for i=1:nSubScans
|
218 |
jakw |
189 |
% fix Ri to be orthogonal
|
241 |
jakw |
190 |
%[U,~,V] = svd(alignment(i).Rotation);
|
|
|
191 |
%Ri = U*V';
|
|
|
192 |
Ri = alignment(i).Rotation;
|
213 |
jakw |
193 |
Ti = alignment(i).Translation;
|
|
|
194 |
|
|
|
195 |
Ea = E{i}*Ri' + repmat(Ti', size(E{i}, 1), 1);
|
|
|
196 |
|
|
|
197 |
% bring point cloud into optimized alignment
|
241 |
jakw |
198 |
pcFileName = fullfile(alnFilePath, initialAlign(i).FileName);
|
|
|
199 |
pc = pcread(pcFileName);
|
213 |
jakw |
200 |
tform = affine3d([Ri' [0;0;0]; initialAlign(i).Translation' 1]);
|
|
|
201 |
pcg = pctransform(pc, tform);
|
|
|
202 |
|
|
|
203 |
pcshow(pcg);
|
|
|
204 |
xlabel('x');
|
|
|
205 |
ylabel('y');
|
|
|
206 |
zlabel('z');
|
|
|
207 |
|
|
|
208 |
plot3(Ea(:,1), Ea(:,2), Ea(:,3), '.', 'MarkerSize', 15);
|
|
|
209 |
title('Optimized Alignment');
|
|
|
210 |
end
|
|
|
211 |
|
|
|
212 |
% write to ALN file
|
212 |
jakw |
213 |
for i=1:length(alignment)
|
|
|
214 |
alignment(i).FileName = initialAlign(i).FileName;
|
209 |
jakw |
215 |
end
|
|
|
216 |
|
214 |
jakw |
217 |
writeMeshLabALN(alignment, strrep(alnFileName, '.aln', 'Optimized.aln'));
|
211 |
jakw |
218 |
|
212 |
jakw |
219 |
end
|
|
|
220 |
|
213 |
jakw |
221 |
function e = autoDetectMarkers(frame, P, pointCloud)
|
212 |
jakw |
222 |
|
211 |
jakw |
223 |
% create mask based on morphology
|
236 |
jakw |
224 |
g = rgb2gray(frame);
|
237 |
jakw |
225 |
% g(g>254) = 0;
|
|
|
226 |
% bw = imbinarize(g, 'adaptive', 'Sensitivity', 10^(-50));
|
|
|
227 |
bw = imbinarize(g, 0.10);
|
211 |
jakw |
228 |
cc = bwconncomp(bw);
|
|
|
229 |
rp = regionprops(cc, 'Area', 'Solidity', 'Eccentricity', 'Centroid');
|
213 |
jakw |
230 |
idx = ([rp.Area] > 100 & [rp.Area] < 1000 & [rp.Solidity] > 0.9);
|
211 |
jakw |
231 |
|
237 |
jakw |
232 |
e = cat(1, rp(idx).Centroid);
|
213 |
jakw |
233 |
|
211 |
jakw |
234 |
end
|
|
|
235 |
|
213 |
jakw |
236 |
function e = manuallyDetectMarkers(frame, P, pointCloud)
|
211 |
jakw |
237 |
|
212 |
jakw |
238 |
e = [];
|
213 |
jakw |
239 |
%edges = edge(rgb2gray(frame), 'Canny', [0.08 0.1], 2);
|
212 |
jakw |
240 |
|
211 |
jakw |
241 |
figure;
|
212 |
jakw |
242 |
hold('on');
|
211 |
jakw |
243 |
imshow(frame);
|
212 |
jakw |
244 |
title('Close figure to end.');
|
|
|
245 |
set(gcf, 'pointer', 'crosshair');
|
|
|
246 |
set(gcf, 'WindowButtonDownFcn', @clickCallback);
|
|
|
247 |
|
|
|
248 |
uiwait;
|
211 |
jakw |
249 |
|
213 |
jakw |
250 |
function clickCallback(caller, ~)
|
212 |
jakw |
251 |
|
|
|
252 |
p = get(gca, 'CurrentPoint');
|
|
|
253 |
p = p(1, 1:2);
|
211 |
jakw |
254 |
|
237 |
jakw |
255 |
e = [e; p(:, 1:2)];
|
213 |
jakw |
256 |
|
|
|
257 |
if(not(isempty(el)))
|
|
|
258 |
figure(caller);
|
|
|
259 |
hold('on');
|
|
|
260 |
plot(el(1), el(2), 'rx', 'MarkerSize', 15);
|
|
|
261 |
end
|
212 |
jakw |
262 |
end
|
|
|
263 |
|
|
|
264 |
end
|
211 |
jakw |
265 |
|
212 |
jakw |
266 |
function [e, conf] = detectMarkersSubpix(frame, initGuesses, P, Q)
|
237 |
jakw |
267 |
% Detect a marker in a single frame by rectifying to the image and
|
|
|
268 |
% performing image registration.
|
211 |
jakw |
269 |
|
212 |
jakw |
270 |
% create mask based on morphology
|
236 |
jakw |
271 |
g = rgb2gray(frame);
|
|
|
272 |
g(g>254) = 0;
|
|
|
273 |
bw = imbinarize(g);
|
212 |
jakw |
274 |
cc = bwconncomp(bw);
|
|
|
275 |
labels = labelmatrix(cc);
|
211 |
jakw |
276 |
|
212 |
jakw |
277 |
% project point cloud into image
|
|
|
278 |
q = [Q ones(size(Q,1),1)]*P;
|
|
|
279 |
q = q./[q(:,3) q(:,3) q(:,3)];
|
|
|
280 |
|
213 |
jakw |
281 |
e = zeros(size(initGuesses));
|
|
|
282 |
conf = zeros(size(initGuesses, 1), 1);
|
|
|
283 |
|
|
|
284 |
nMarkersFound = 0;
|
|
|
285 |
|
212 |
jakw |
286 |
for i=1:size(initGuesses, 1)
|
|
|
287 |
|
|
|
288 |
labelId = labels(round(initGuesses(i,2)), round(initGuesses(i,1)));
|
|
|
289 |
labelMask = (labels == labelId);
|
|
|
290 |
labelMask = imdilate(labelMask, strel('disk', 3, 0));
|
|
|
291 |
|
213 |
jakw |
292 |
if(sum(sum(labelMask)) < 10 || sum(sum(labelMask)) > 1000)
|
|
|
293 |
continue;
|
|
|
294 |
end
|
|
|
295 |
|
212 |
jakw |
296 |
% determine 3D points that are part of the marker
|
213 |
jakw |
297 |
% note: we should probably undistort labelMask
|
212 |
jakw |
298 |
pointMask = false(size(q, 1), 1);
|
|
|
299 |
for j=1:size(q,1)
|
215 |
jakw |
300 |
if(round(q(j,2)) > size(labelMask, 1) || round(q(j,1)) > size(labelMask, 2) || round(q(j,2)) < 1 || round(q(j,1)) < 1)
|
|
|
301 |
continue;
|
|
|
302 |
end
|
|
|
303 |
|
212 |
jakw |
304 |
if(labelMask(round(q(j,2)), round(q(j,1))))
|
|
|
305 |
pointMask(j) = true;
|
|
|
306 |
end
|
|
|
307 |
end
|
|
|
308 |
|
215 |
jakw |
309 |
if(sum(pointMask)) < 10
|
213 |
jakw |
310 |
continue;
|
|
|
311 |
end
|
212 |
jakw |
312 |
|
213 |
jakw |
313 |
% project 3D points onto local plane
|
|
|
314 |
[~, sc, ~] = pca(Q(pointMask, :));
|
|
|
315 |
Qlocal = sc(:, 1:2);
|
|
|
316 |
|
|
|
317 |
% synthetic marker in high res. space
|
|
|
318 |
m = zeros(151, 151);
|
|
|
319 |
[x, y] = meshgrid(1:151, 1:151);
|
|
|
320 |
m((x(:)-76).^2 + (y(:)-76).^2 <= 50^2) = 1.0;
|
|
|
321 |
|
|
|
322 |
% relation between marker space (px) and true marker/local plane(mm)
|
|
|
323 |
% true marker diameter is 1.75mm
|
236 |
jakw |
324 |
mScale = 101/1.4; %px/mm
|
213 |
jakw |
325 |
mShift = 76; %px
|
|
|
326 |
|
|
|
327 |
% build homography from image to marker space
|
236 |
jakw |
328 |
H = fitgeotrans(q(pointMask, 1:2), mScale*Qlocal+mShift, 'projective');
|
|
|
329 |
%Hdlt = Hest_DLT([mScale*Qlocal+mShift, ones(size(Qlocal, 1), 1)]', q(pointMask,:)');
|
|
|
330 |
%H = projective2d(Hdlt');
|
213 |
jakw |
331 |
|
|
|
332 |
% bring image of marker into marker space
|
|
|
333 |
imMarkerSpace = imwarp(frame, H, 'OutputView', imref2d(size(m)));
|
|
|
334 |
imMarkerSpace = rgb2gray(im2double(imMarkerSpace));
|
|
|
335 |
|
|
|
336 |
%figure; imshowpair(imMarkerSpace, m);
|
|
|
337 |
|
|
|
338 |
% perform image registration
|
214 |
jakw |
339 |
% might be better off using subpixel image correlation
|
|
|
340 |
[opt, met] = imregconfig('multimodal');
|
218 |
jakw |
341 |
T = imregtform(m, imMarkerSpace, 'translation', opt, met, 'DisplayOptimization', false);
|
213 |
jakw |
342 |
|
|
|
343 |
rege = imwarp(m, T, 'OutputView', imref2d(size(m)));
|
|
|
344 |
%figure; imshowpair(imMarkerSpace, rege);
|
|
|
345 |
|
|
|
346 |
% measure of correlation
|
|
|
347 |
confI = sum(sum(imMarkerSpace .* rege))/sqrt(sum(sum(imMarkerSpace) * sum(sum(rege))));
|
236 |
jakw |
348 |
%confI = 1.0;
|
213 |
jakw |
349 |
|
|
|
350 |
if confI<0.4
|
|
|
351 |
continue;
|
|
|
352 |
end
|
|
|
353 |
|
|
|
354 |
fprintf('Found marker with confidence: %f\n', confI);
|
|
|
355 |
|
|
|
356 |
% transform marker space coordinates (76,76) to frame space
|
|
|
357 |
el = T.transformPointsForward([76, 76]);
|
|
|
358 |
el = H.transformPointsInverse(el);
|
|
|
359 |
|
|
|
360 |
nMarkersFound = nMarkersFound+1;
|
|
|
361 |
e(nMarkersFound,:) = el;
|
|
|
362 |
conf(nMarkersFound) = confI;
|
211 |
jakw |
363 |
end
|
|
|
364 |
|
213 |
jakw |
365 |
e = e(1:nMarkersFound, :);
|
|
|
366 |
conf = conf(1:nMarkersFound);
|
211 |
jakw |
367 |
end
|
|
|
368 |
|
238 |
jakw |
369 |
function [E, conf] = detectMarkersStereoSubpix(frame0, frame1, initGuesses, camStereoParams, pc)
|
|
|
370 |
% Detect a marker in stereo frame set by minimizing the difference to
|
|
|
371 |
% projected images of 3d marker
|
|
|
372 |
|
|
|
373 |
normals = pcnormals(pc, 6);
|
239 |
jakw |
374 |
|
|
|
375 |
frame0 = rgb2gray(frame0);
|
|
|
376 |
frame1 = rgb2gray(frame1);
|
238 |
jakw |
377 |
|
241 |
jakw |
378 |
nMarkers = size(initGuesses, 1);
|
238 |
jakw |
379 |
|
239 |
jakw |
380 |
E = zeros(nMarkers, 3);
|
|
|
381 |
conf = zeros(nMarkers, 1);
|
|
|
382 |
|
238 |
jakw |
383 |
for i=1:nMarkers
|
|
|
384 |
|
|
|
385 |
pI = initGuesses(i,:);
|
|
|
386 |
|
|
|
387 |
e0 = camStereoParams.CameraParameters1.worldToImage(eye(3,3), zeros(3,1), pI);
|
|
|
388 |
e1 = camStereoParams.CameraParameters2.worldToImage(camStereoParams.RotationOfCamera2, camStereoParams.TranslationOfCamera2, pI);
|
|
|
389 |
|
|
|
390 |
% center of support region
|
|
|
391 |
e0Center = round(e0);
|
|
|
392 |
e1Center = round(e1);
|
|
|
393 |
|
|
|
394 |
% find initial normal
|
|
|
395 |
idx = pc.findNearestNeighbors(pI, 1);
|
239 |
jakw |
396 |
nI = double(normals(idx, :));
|
238 |
jakw |
397 |
|
239 |
jakw |
398 |
margin = 25;
|
238 |
jakw |
399 |
|
|
|
400 |
[x,y] = meshgrid(e0Center(1)-margin:e0Center(1)+margin, e0Center(2)-margin:e0Center(2)+margin);
|
|
|
401 |
e0ImCoords = [x(:), y(:)];
|
|
|
402 |
|
|
|
403 |
[x,y] = meshgrid(e1Center(1)-margin:e1Center(1)+margin, e1Center(2)-margin:e1Center(2)+margin);
|
|
|
404 |
e1ImCoords = [x(:), y(:)];
|
|
|
405 |
|
241 |
jakw |
406 |
nCoords = length(x(:));
|
|
|
407 |
|
239 |
jakw |
408 |
e0UndistImCoords = undistortPointsFast(e0ImCoords, camStereoParams.CameraParameters1);
|
|
|
409 |
e0NormImCoords = camStereoParams.CameraParameters1.pointsToWorld(eye(3,3), [0, 0, 1], e0UndistImCoords);
|
241 |
jakw |
410 |
e0Hom = [e0NormImCoords, ones(nCoords, 1)];
|
239 |
jakw |
411 |
e1UndistImCoords = undistortPointsFast(e1ImCoords, camStereoParams.CameraParameters2);
|
|
|
412 |
e1NormImCoords = camStereoParams.CameraParameters2.pointsToWorld(eye(3,3), [0, 0, 1], e1UndistImCoords);
|
241 |
jakw |
413 |
e1Hom = [e1NormImCoords, ones(nCoords, 1)];
|
238 |
jakw |
414 |
|
241 |
jakw |
415 |
imVals0 = double(frame0(sub2ind(size(frame0), e0ImCoords(:,2), e0ImCoords(:,1))));
|
|
|
416 |
imVals1 = double(frame1(sub2ind(size(frame1), e1ImCoords(:,2), e1ImCoords(:,1))));
|
|
|
417 |
|
239 |
jakw |
418 |
x0 = [pI nI(1:2)/nI(3) 70.0 70.0];
|
238 |
jakw |
419 |
|
241 |
jakw |
420 |
% r = circleResiduals(x0);
|
|
|
421 |
% figure;
|
|
|
422 |
% subplot(2,2,1);
|
|
|
423 |
% imagesc(reshape(r(1:length(e0NormImCoords)), 2*margin+1, 2*margin+1), [-50 50]);
|
|
|
424 |
% subplot(2,2,2);
|
|
|
425 |
% imagesc(reshape(r(length(e0NormImCoords)+1:end), 2*margin+1, 2*margin+1), [-50 50]);
|
|
|
426 |
% drawnow;
|
240 |
jakw |
427 |
|
241 |
jakw |
428 |
%options = optimset('Algorithm', 'levenberg-marquardt', 'Display', 'final', 'OutputFcn', @out, 'MaxIter', 30, 'TolFun', 10^(-6), 'TolX', 0);
|
|
|
429 |
options = optimoptions('lsqnonlin', 'Display', 'final');
|
|
|
430 |
|
239 |
jakw |
431 |
[x, conf(i), ~] = lsqnonlin(@circleResiduals, x0, [], [], options);
|
238 |
jakw |
432 |
|
241 |
jakw |
433 |
% r = circleResiduals(x);
|
|
|
434 |
% subplot(2,2,3);
|
|
|
435 |
% imagesc(reshape(r(1:length(e0NormImCoords)), 2*margin+1, 2*margin+1), [-50 50]);
|
|
|
436 |
% subplot(2,2,4);
|
|
|
437 |
% imagesc(reshape(r(length(e0NormImCoords)+1:end), 2*margin+1, 2*margin+1), [-50 50]);
|
|
|
438 |
% drawnow;
|
240 |
jakw |
439 |
|
239 |
jakw |
440 |
E(i,:) = x(1:3);
|
|
|
441 |
|
|
|
442 |
end
|
|
|
443 |
|
|
|
444 |
function stop = out(x, optimValues, state)
|
238 |
jakw |
445 |
|
239 |
jakw |
446 |
% r = optimValues.residual;
|
|
|
447 |
%
|
|
|
448 |
% figure;
|
|
|
449 |
% subplot(1,2,1);
|
|
|
450 |
% imagesc(reshape(r(1:length(e0NormImCoords)), 2*margin+1, 2*margin+1), [-50 50]);
|
|
|
451 |
% subplot(1,2,2);
|
|
|
452 |
% imagesc(reshape(r(length(e0NormImCoords)+1:end), 2*margin+1, 2*margin+1), [-50 50]);
|
|
|
453 |
% drawnow;
|
|
|
454 |
%
|
|
|
455 |
% display(x);
|
238 |
jakw |
456 |
|
239 |
jakw |
457 |
stop = false;
|
238 |
jakw |
458 |
end
|
239 |
jakw |
459 |
|
238 |
jakw |
460 |
function r = circleResiduals(x)
|
|
|
461 |
|
239 |
jakw |
462 |
p0 = x(1:3);
|
|
|
463 |
p1 = x(1:3) * camStereoParams.RotationOfCamera2 + camStereoParams.TranslationOfCamera2;
|
|
|
464 |
n0 = [x(4:5) 1.0];
|
|
|
465 |
n0 = 1.0/norm(n0) * n0;
|
|
|
466 |
n1 = n0 * camStereoParams.RotationOfCamera2;
|
238 |
jakw |
467 |
|
239 |
jakw |
468 |
r = zeros(length(e0NormImCoords) + length(e1NormImCoords), 1);
|
238 |
jakw |
469 |
|
239 |
jakw |
470 |
% norminal radius of markers
|
|
|
471 |
markerRadius = 1.4/2.0; %mm
|
|
|
472 |
|
|
|
473 |
% half-width of ramp
|
241 |
jakw |
474 |
w = 0.1; %mm
|
|
|
475 |
|
|
|
476 |
% camera 0
|
|
|
477 |
t = (p0*n0')./(e0Hom*n0');
|
239 |
jakw |
478 |
|
241 |
jakw |
479 |
dVec = repmat(p0, nCoords, 1) - t.*e0Hom;
|
|
|
480 |
d = sqrt(sum(dVec.*dVec, 2));
|
239 |
jakw |
481 |
|
241 |
jakw |
482 |
% "saturated" ramp function for marker disc shape
|
|
|
483 |
weights = max(min(1.0, -1.0/(2*w)*(d-markerRadius)+0.5), 0.0);
|
239 |
jakw |
484 |
|
241 |
jakw |
485 |
r(1:nCoords) = x(6)*weights - imVals0;
|
238 |
jakw |
486 |
|
241 |
jakw |
487 |
% camera 1
|
|
|
488 |
t = (p1*n1')./(e1Hom*n1');
|
239 |
jakw |
489 |
|
241 |
jakw |
490 |
dVec = repmat(p1, nCoords, 1) - t.*e1Hom;
|
|
|
491 |
d = sqrt(sum(dVec.*dVec, 2));
|
|
|
492 |
|
|
|
493 |
% "saturated" ramp function for marker disc shape
|
|
|
494 |
weights = max(min(1.0, -1.0/(2*w)*(d-markerRadius)+0.5), 0.0);
|
|
|
495 |
|
|
|
496 |
r(nCoords+1:end) = x(7)*weights - imVals1;
|
238 |
jakw |
497 |
|
239 |
jakw |
498 |
% figure;
|
|
|
499 |
% subplot(1,2,1);
|
|
|
500 |
% imagesc(reshape(r(1:length(e0NormImCoords)), 2*margin+1, 2*margin+1), [-50 50]);
|
|
|
501 |
% subplot(1,2,2);
|
|
|
502 |
% imagesc(reshape(r(length(e0NormImCoords)+1:end), 2*margin+1, 2*margin+1), [-50 50]);
|
|
|
503 |
% drawnow;
|
|
|
504 |
|
238 |
jakw |
505 |
end
|
|
|
506 |
|
|
|
507 |
end
|
|
|
508 |
|
212 |
jakw |
509 |
function E = projectOntoPointCloud(e, P, pointCloud)
|
237 |
jakw |
510 |
% Project 2d point coordinates onto pointCloud to find corresponding 3d
|
|
|
511 |
% point coordinates.
|
211 |
jakw |
512 |
|
212 |
jakw |
513 |
q = [pointCloud ones(size(pointCloud,1),1)]*P;
|
211 |
jakw |
514 |
q = q(:,1:2)./[q(:,3) q(:,3)];
|
|
|
515 |
|
|
|
516 |
E = nan(size(e,1), 3);
|
|
|
517 |
|
|
|
518 |
for i=1:size(e, 1)
|
|
|
519 |
sqDists = sum((q - repmat(e(i,:), size(q, 1), 1)).^2, 2);
|
|
|
520 |
|
216 |
jakw |
521 |
[sqDistsSorted, sortIdx] = sort(sqDists);
|
211 |
jakw |
522 |
|
241 |
jakw |
523 |
% neighbors are points that project to within 10px
|
|
|
524 |
neighbors = (sqDistsSorted < 10.0^2);
|
216 |
jakw |
525 |
|
|
|
526 |
distsSorted = sqrt(sqDistsSorted(neighbors));
|
241 |
jakw |
527 |
invDistsSorted = 1.0./distsSorted;
|
216 |
jakw |
528 |
sortIdx = sortIdx(neighbors);
|
|
|
529 |
|
|
|
530 |
nNeighbors = sum(neighbors);
|
|
|
531 |
|
|
|
532 |
if(nNeighbors >= 2)
|
|
|
533 |
E(i, :) = 0;
|
|
|
534 |
for j=1:nNeighbors
|
|
|
535 |
E(i, :) = E(i, :) + invDistsSorted(j)/sum(invDistsSorted) * pointCloud(sortIdx(j), :);
|
|
|
536 |
end
|
211 |
jakw |
537 |
end
|
|
|
538 |
|
|
|
539 |
end
|
|
|
540 |
end
|
|
|
541 |
|
219 |
jakw |
542 |
function H = Hest_DLT(q1, q2)
|
|
|
543 |
% Estimate the homography between a set of point correspondences using the
|
|
|
544 |
% direct linear transform algorithm.
|
|
|
545 |
%
|
|
|
546 |
% Input:
|
|
|
547 |
% q1: 3xN matrix of homogenous point coordinates from camera 1.
|
|
|
548 |
% q2: 3xN matrix of corresponding points from camera 2.
|
|
|
549 |
% Output:
|
|
|
550 |
% H: 3x3 matrix. The Fundamental Matrix estimate.
|
|
|
551 |
%
|
|
|
552 |
% Note that N must be at least 4.
|
|
|
553 |
% See derivation in Aanaes, Lecture Notes on Computer Vision, 2011
|
|
|
554 |
|
|
|
555 |
% Normalize points
|
|
|
556 |
[T1,invT1] = normalizationMat(q1);
|
|
|
557 |
q1_tilde = T1*q1;
|
|
|
558 |
|
|
|
559 |
T2 = normalizationMat(q2);
|
|
|
560 |
q2_tilde = T2*q2;
|
|
|
561 |
|
|
|
562 |
% DLT estimation
|
|
|
563 |
N = size(q1_tilde,2);
|
|
|
564 |
assert(size(q2_tilde,2)==N);
|
|
|
565 |
|
|
|
566 |
B = zeros(3*N,9);
|
|
|
567 |
|
|
|
568 |
for i=1:N
|
|
|
569 |
q1i = q1_tilde(:,i);
|
|
|
570 |
q2i = q2_tilde(:,i);
|
|
|
571 |
q1_x = [0 -q1i(3) q1i(2); q1i(3) 0 -q1i(1); -q1i(2) q1i(1) 0];
|
|
|
572 |
biT = kron(q2i', q1_x);
|
|
|
573 |
B(3*(i-1)+1:3*i, :) = biT;
|
|
|
574 |
end
|
|
|
575 |
|
|
|
576 |
[U,S,~] = svd(B');
|
|
|
577 |
|
|
|
578 |
[~,idx] = min(diag(S));
|
|
|
579 |
h = U(:,idx);
|
|
|
580 |
|
|
|
581 |
H_tilde = reshape(h, 3, 3);
|
|
|
582 |
|
|
|
583 |
% Unnormalize H
|
|
|
584 |
H = invT1*H_tilde*T2;
|
|
|
585 |
|
|
|
586 |
% Arbitrarily chose scale
|
|
|
587 |
H = H * 1/H(3,3);
|
|
|
588 |
end
|
|
|
589 |
|
|
|
590 |
function [T,invT] = normalizationMat(q)
|
|
|
591 |
% Gives a normalization matrix for homogeneous coordinates
|
|
|
592 |
% such that T*q will have zero mean and unit variance.
|
|
|
593 |
% See Aanaes, Computer Vision Lecture Notes 2.8.2
|
|
|
594 |
%
|
|
|
595 |
% q: (M+1)xN matrix of N MD points in homogenous coordinates
|
|
|
596 |
%
|
|
|
597 |
% Extended to also efficiently compute the inverse matrix
|
|
|
598 |
% DTU, 2013, Jakob Wilm
|
|
|
599 |
|
|
|
600 |
[M,N] = size(q);
|
|
|
601 |
M = M-1;
|
|
|
602 |
|
|
|
603 |
mu = mean(q(1:M,:),2);
|
|
|
604 |
|
|
|
605 |
q_bar = q(1:M,:)-repmat(mu,1,N);
|
|
|
606 |
|
|
|
607 |
s = mean(sqrt(diag(q_bar'*q_bar)))/sqrt(2);
|
|
|
608 |
|
|
|
609 |
T = [eye(M)/s, -mu/s; zeros(1,M) 1];
|
|
|
610 |
|
|
|
611 |
invT = [eye(M)*s, mu; zeros(1,M) 1];
|
|
|
612 |
end
|