Subversion Repositories seema-scanner

Rev

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