Subversion Repositories seema-scanner

Rev

Rev 215 | Rev 218 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
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);
11
 
211 jakw 12
calibration = readOpenCVXML(calibrationFileName);
13
 
216 jakw 14
% correct for Matlab 1-indexing in principle point coordinates
15
calibration.K0(1:2, 3) = calibration.K0(1:2, 3)+1;
16
calibration.K1(1:2, 3) = calibration.K1(1:2, 3)+1;
17
 
211 jakw 18
% full projection matrices in Matlab convention
19
P0 = transpose(calibration.K0*[eye(3) zeros(3,1)]);
20
P1 = transpose(calibration.K1*[calibration.R1 calibration.T1']);
21
 
22
% matlab cam params for undistortion
23
camParams0 = cameraParameters('IntrinsicMatrix', calibration.K0', 'RadialDistortion', calibration.k0([1 2 5]), 'TangentialDistortion', calibration.k0([3 4]));
24
camParams1 = cameraParameters('IntrinsicMatrix', calibration.K1', 'RadialDistortion', calibration.k1([1 2 5]), 'TangentialDistortion', calibration.k1([3 4]));
25
 
26
% matlab struct for triangulation
27
camStereoParams = stereoParameters(camParams0, camParams1, calibration.R1', calibration.T1');
28
 
214 jakw 29
nSubScans = length(initialAlign);
209 jakw 30
 
211 jakw 31
% 3D coordinates of markers in local camera frame
32
E = cell(nSubScans, 1);
33
 
34
% 3D coordinates of markers in global initial alignment
35
Eg = cell(size(E));
36
 
37
% find 3D markers coordinates 
209 jakw 38
for i=1:nSubScans
211 jakw 39
 
40
    % load point cloud
214 jakw 41
    pc = pcread(initialAlign(i).FileName);
211 jakw 42
    Q = pc.Location;
214 jakw 43
    idString = strsplit(initialAlign(i).FileName, {'.ply', '_'});
44
    idString = idString{end-1};
209 jakw 45
 
211 jakw 46
    % load white frames
214 jakw 47
    scanDir = strsplit(initialAlign(i).FileName, '/');
48
    scanDir = fullfile(scanDir{1:end-1});
211 jakw 49
    frame0 = imread(fullfile(scanDir, ['sequence_' idString], 'frames0_0.png'));
50
    frame1 = imread(fullfile(scanDir, ['sequence_' idString], 'frames1_0.png'));
51
 
215 jakw 52
    e0Coords = autoDetectMarkers(frame0, P0, Q);
53
    e1Coords = autoDetectMarkers(frame1, P1, Q);
209 jakw 54
 
215 jakw 55
    %e0Coords = manuallyDetectMarkers(frame0, P0, Q);
56
    %e1Coords = manuallyDetectMarkers(frame1, P1, Q);
210 jakw 57
 
211 jakw 58
    e0Coords = undistortPoints(e0Coords, camParams0);
59
    e1Coords = undistortPoints(e1Coords, camParams1);
60
 
61
    % match ellipse candidates between cameras based on projection
62
    E0 = projectOntoPointCloud(e0Coords, P0, Q);
63
    E1 = projectOntoPointCloud(e1Coords, P1, Q);
64
 
65
    matchedPairs = nan(size(E0, 1), 2);
66
    nMatchedPairs = 0;
67
    for j=1:size(E0, 1)
68
 
69
        % should use pdist2 instead..
70
        sqDists = sum((E1 - repmat(E0(j,:), size(E1, 1), 1)).^2, 2);
71
 
72
        [minSqDist, minSqDistIdx] = min(sqDists);
73
 
216 jakw 74
        if(minSqDist < 1^2)
211 jakw 75
            nMatchedPairs = nMatchedPairs + 1;
76
            matchedPairs(nMatchedPairs, :) = [j, minSqDistIdx];
77
        end
78
    end
79
    matchedPairs = matchedPairs(1:nMatchedPairs, :);
209 jakw 80
 
211 jakw 81
    % triangulate marker centers (lens correction has been performed)
214 jakw 82
    [E{i}, e] = triangulate(e0Coords(matchedPairs(:, 1),:), e1Coords(matchedPairs(:, 2),:), camStereoParams);
216 jakw 83
    E{i} = E{i}(e<1.0, :);
209 jakw 84
 
216 jakw 85
    % write point cloud with marker (debugging)
86
    pcDebug = pointCloud([pc.Location; E{i}], 'Color', [pc.Color; repmat([255, 0, 0], size(E{i}, 1), 1)]);
87
    pcwrite(pcDebug, 'pcDebug.ply');
88
 
89
    % bring markers into initial alignment
211 jakw 90
    [U,~,V] = svd(initialAlign(i).Rotation);
91
    Ri = U*V';
92
    Ti = initialAlign(i).Translation;
209 jakw 93
 
211 jakw 94
    Eg{i} = E{i}*Ri' + repmat(Ti', size(E{i}, 1), 1);
95
end
96
 
212 jakw 97
% show found markers in initial alignment
98
figure;
99
hold('on');
211 jakw 100
for i=1:nSubScans
101
    % fix Ri to be orthogonal
102
    [U,~,V] = svd(initialAlign(i).Rotation);
103
    Ri = U*V';
209 jakw 104
 
211 jakw 105
    % bring point cloud into initial alignment
215 jakw 106
    pc = pcread(initialAlign(i).FileName);
211 jakw 107
    tform = affine3d([Ri' [0;0;0]; initialAlign(i).Translation' 1]);
108
    pcg = pctransform(pc, tform);
212 jakw 109
 
211 jakw 110
    pcshow(pcg);
111
    xlabel('x');
112
    ylabel('y');
113
    zlabel('z');
212 jakw 114
 
115
    plot3(Eg{i}(:,1), Eg{i}(:,2), Eg{i}(:,3), '.', 'MarkerSize', 15);
213 jakw 116
    title('Initial Alignment');
204 jakw 117
end
118
 
212 jakw 119
% match markers between poses using initial alignment
120
Pg = {};
121
P = {};
122
for i=1:nSubScans
123
    for j=1:size(Eg{i}, 1)
124
        pg = Eg{i}(j,:);
125
        p = E{i}(j,:);
126
        matched = false;
127
        for k=1:size(Pg, 2)
128
            clusterCenter = mean(cat(1, Pg{:,k}), 1);
129
            if(sum((pg - clusterCenter).^2) < 3^2)
130
                % store in global frame
131
                Pg{i,k} = pg;
132
                % store in local frame
133
                P{i,k} = p;
134
                matched = true;
135
                break;
136
            end
137
        end
138
        % create new cluster
139
        if(not(matched))
140
            Pg{i,end+1} = pg;
141
            P{i,end+1} = p;
142
        end 
143
    end
144
end
211 jakw 145
 
212 jakw 146
% run optimization
147
alignment = groupwiseOrthogonalProcrustes(P, initialAlign);
148
 
213 jakw 149
% show found markers in optimized alignment
150
figure;
151
hold('on');
152
for i=1:nSubScans
153
    Ri = alignment(i).Rotation;
154
    Ti = alignment(i).Translation;
155
 
156
    Ea = E{i}*Ri' + repmat(Ti', size(E{i}, 1), 1);
157
 
158
    % bring point cloud into optimized alignment
215 jakw 159
    pc = pcread(initialAlign(i).FileName);
213 jakw 160
    tform = affine3d([Ri' [0;0;0]; initialAlign(i).Translation' 1]);
161
    pcg = pctransform(pc, tform);
162
 
163
    pcshow(pcg);
164
    xlabel('x');
165
    ylabel('y');
166
    zlabel('z');
167
 
168
    plot3(Ea(:,1), Ea(:,2), Ea(:,3), '.', 'MarkerSize', 15);
169
    title('Optimized Alignment');
170
end
171
 
172
% write to ALN file
212 jakw 173
for i=1:length(alignment)
174
    alignment(i).FileName = initialAlign(i).FileName;
209 jakw 175
end
176
 
214 jakw 177
writeMeshLabALN(alignment, strrep(alnFileName, '.aln', 'Optimized.aln'));
211 jakw 178
 
212 jakw 179
end
180
 
213 jakw 181
function e = autoDetectMarkers(frame, P, pointCloud)
212 jakw 182
 
211 jakw 183
    % create mask based on morphology
184
    bw = imbinarize(rgb2gray(frame));
185
    cc = bwconncomp(bw);
186
    rp = regionprops(cc, 'Area', 'Solidity', 'Eccentricity', 'Centroid');
213 jakw 187
    idx = ([rp.Area] > 100 & [rp.Area] < 1000 & [rp.Solidity] > 0.9);
211 jakw 188
 
213 jakw 189
    initialGuesses = cat(1, rp(idx).Centroid);
190
 
191
    [e, ~] = detectMarkersSubpix(frame, initialGuesses, P, pointCloud);
192
 
193
    figure; 
194
    imshow(frame);
195
        hold('on');
196
    plot(e(:,1), e(:,2), 'rx', 'MarkerSize', 15);
197
    drawnow;
211 jakw 198
end
199
 
213 jakw 200
function e = manuallyDetectMarkers(frame, P, pointCloud)
211 jakw 201
 
212 jakw 202
    e = [];
213 jakw 203
	%edges = edge(rgb2gray(frame), 'Canny', [0.08 0.1], 2);
212 jakw 204
 
211 jakw 205
    figure; 
212 jakw 206
    hold('on');
211 jakw 207
    imshow(frame);
212 jakw 208
    title('Close figure to end.');
209
    set(gcf, 'pointer', 'crosshair'); 
210
    set(gcf, 'WindowButtonDownFcn', @clickCallback);
211
 
212
    uiwait;
211 jakw 213
 
213 jakw 214
    function clickCallback(caller, ~)
212 jakw 215
 
216
        p = get(gca, 'CurrentPoint'); 
217
        p = p(1, 1:2);
211 jakw 218
 
213 jakw 219
        [el, ~] = detectMarkersSubpix(frame, p, P, pointCloud);
212 jakw 220
        e = [e; el(:, 1:2)];
213 jakw 221
 
222
        if(not(isempty(el)))
223
            figure(caller);
224
            hold('on');
225
            plot(el(1), el(2), 'rx', 'MarkerSize', 15);
226
        end
212 jakw 227
    end
228
 
229
end
211 jakw 230
 
212 jakw 231
function [e, conf] = detectMarkersSubpix(frame, initGuesses, P, Q)
211 jakw 232
 
212 jakw 233
    % create mask based on morphology
234
    bw = imbinarize(rgb2gray(frame));
235
    cc = bwconncomp(bw);
236
    labels = labelmatrix(cc);
211 jakw 237
 
212 jakw 238
    % project point cloud into image
239
    q = [Q ones(size(Q,1),1)]*P;
240
    q = q./[q(:,3) q(:,3) q(:,3)];
241
 
213 jakw 242
    e = zeros(size(initGuesses));
243
    conf = zeros(size(initGuesses, 1), 1);
244
 
245
    nMarkersFound = 0;
246
 
212 jakw 247
    for i=1:size(initGuesses, 1)
248
 
249
        labelId = labels(round(initGuesses(i,2)), round(initGuesses(i,1)));
250
        labelMask = (labels == labelId);
251
        labelMask = imdilate(labelMask, strel('disk', 3, 0));
252
 
213 jakw 253
        if(sum(sum(labelMask)) < 10 || sum(sum(labelMask)) > 1000)
254
            continue;
255
        end
256
 
212 jakw 257
        % determine 3D points that are part of the marker
213 jakw 258
        % note: we should probably undistort labelMask
212 jakw 259
        pointMask = false(size(q, 1), 1);
260
        for j=1:size(q,1)
215 jakw 261
            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)
262
                continue;
263
            end
264
 
212 jakw 265
            if(labelMask(round(q(j,2)), round(q(j,1))))
266
                pointMask(j) = true;
267
            end
268
        end
269
 
215 jakw 270
        if(sum(pointMask)) < 10
213 jakw 271
            continue;
272
        end
212 jakw 273
 
213 jakw 274
        % project 3D points onto local plane
275
        [~, sc, ~] = pca(Q(pointMask, :));
276
        Qlocal = sc(:, 1:2);
277
 
278
        % synthetic marker in high res. space
279
        m = zeros(151, 151);
280
        [x, y] = meshgrid(1:151, 1:151);
281
        m((x(:)-76).^2 + (y(:)-76).^2 <= 50^2) = 1.0;
282
 
283
        % relation between marker space (px) and true marker/local plane(mm)
284
        % true marker diameter is 1.75mm
214 jakw 285
        mScale = 101/1.8; %px/mm
213 jakw 286
        mShift = 76; %px
287
 
288
        % build homography from image to marker space
289
        H = fitgeotrans(q(pointMask, 1:2), mScale*Qlocal+mShift,  'projective');
290
 
291
        % bring image of marker into marker space
292
        imMarkerSpace = imwarp(frame, H, 'OutputView', imref2d(size(m)));
293
        imMarkerSpace = rgb2gray(im2double(imMarkerSpace));
294
 
295
        %figure; imshowpair(imMarkerSpace, m);
296
 
297
        % perform image registration
214 jakw 298
        % might be better off using subpixel image correlation
299
        [opt, met] = imregconfig('multimodal');
300
        T = imregtform(m, imMarkerSpace, 'translation', opt, met, 'DisplayOptimization', true);
213 jakw 301
 
302
        rege = imwarp(m, T, 'OutputView', imref2d(size(m)));
303
        %figure; imshowpair(imMarkerSpace, rege);
304
 
305
        % measure of correlation
306
        confI = sum(sum(imMarkerSpace .* rege))/sqrt(sum(sum(imMarkerSpace) * sum(sum(rege))));
307
 
308
        if confI<0.4
309
            continue;
310
        end
311
 
312
        fprintf('Found marker with confidence: %f\n', confI);
313
 
314
        % transform marker space coordinates (76,76) to frame space
315
        el = T.transformPointsForward([76, 76]);
316
        el = H.transformPointsInverse(el);
317
 
318
        nMarkersFound = nMarkersFound+1;
319
        e(nMarkersFound,:) = el;
320
        conf(nMarkersFound) = confI;
211 jakw 321
    end
322
 
213 jakw 323
    e = e(1:nMarkersFound, :);
324
    conf = conf(1:nMarkersFound);
211 jakw 325
end
326
 
212 jakw 327
function E = projectOntoPointCloud(e, P, pointCloud)
211 jakw 328
 
212 jakw 329
    q = [pointCloud ones(size(pointCloud,1),1)]*P;
211 jakw 330
    q = q(:,1:2)./[q(:,3) q(:,3)];
331
 
332
    E = nan(size(e,1), 3);
333
 
334
    for i=1:size(e, 1)
335
        sqDists = sum((q - repmat(e(i,:), size(q, 1), 1)).^2, 2);
336
 
216 jakw 337
        [sqDistsSorted, sortIdx] = sort(sqDists);
211 jakw 338
 
216 jakw 339
        neighbors = (sqDistsSorted < 4.0^2);
340
 
341
        distsSorted = sqrt(sqDistsSorted(neighbors));
342
        invDistsSorted = 1.0/distsSorted;
343
        sortIdx = sortIdx(neighbors);
344
 
345
        nNeighbors = sum(neighbors);
346
 
347
        if(nNeighbors >= 2)
348
            E(i, :) = 0;
349
            for j=1:nNeighbors
350
                E(i, :) = E(i, :) + invDistsSorted(j)/sum(invDistsSorted) * pointCloud(sortIdx(j), :);
351
            end
211 jakw 352
        end
353
 
354
    end    
355
end
356