Subversion Repositories seema-scanner

Rev

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

Rev Author Line No. Line
209 jakw 1
function MSE = alignSubScansMarkers(calibrationFileName, scanDir, 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
whiteFrameDirs = dir(fullfile(scanDir, 'sequence_*'));
12
 
13
assert(length(whiteFrameDirs) == length(initialAlign));
14
 
211 jakw 15
calibration = readOpenCVXML(calibrationFileName);
16
 
17
% full projection matrices in Matlab convention
18
P0 = transpose(calibration.K0*[eye(3) zeros(3,1)]);
19
P1 = transpose(calibration.K1*[calibration.R1 calibration.T1']);
20
 
21
% matlab cam params for undistortion
22
camParams0 = cameraParameters('IntrinsicMatrix', calibration.K0', 'RadialDistortion', calibration.k0([1 2 5]), 'TangentialDistortion', calibration.k0([3 4]));
23
camParams1 = cameraParameters('IntrinsicMatrix', calibration.K1', 'RadialDistortion', calibration.k1([1 2 5]), 'TangentialDistortion', calibration.k1([3 4]));
24
 
25
% matlab struct for triangulation
26
camStereoParams = stereoParameters(camParams0, camParams1, calibration.R1', calibration.T1');
27
 
209 jakw 28
nSubScans = length(whiteFrameDirs);
29
 
211 jakw 30
% ellipse detection settings
31
ep = struct('minMajorAxis', 25, 'maxMajorAxis', 30, 'minAspectRatio', 0.4, 'randomize', 0, 'smoothStddev', 2);
32
 
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
 
42
    % load point cloud
43
    pc = pcread(fullfile(scanDir, initialAlign(i).FileName));
44
    Q = pc.Location;
45
    idString = initialAlign(i).FileName(12:end-4);
209 jakw 46
 
211 jakw 47
    % load white frames
48
    frame0 = imread(fullfile(scanDir, ['sequence_' idString], 'frames0_0.png'));
49
    frame1 = imread(fullfile(scanDir, ['sequence_' idString], 'frames1_0.png'));
50
 
212 jakw 51
    %e0Coords = autoDetectMarkers(frame0, ep);
52
    %e1Coords = autoDetectMarkers(frame1, ep);
209 jakw 53
 
212 jakw 54
    e0Coords = manuallyDetectMarkers(frame0, ep, P0, Q);
55
    e1Coords = manuallyDetectMarkers(frame1, ep, P1, Q);
210 jakw 56
 
211 jakw 57
    e0Coords = undistortPoints(e0Coords, camParams0);
58
    e1Coords = undistortPoints(e1Coords, camParams1);
59
 
60
    % match ellipse candidates between cameras based on projection
61
    E0 = projectOntoPointCloud(e0Coords, P0, Q);
62
    E1 = projectOntoPointCloud(e1Coords, P1, Q);
63
 
64
    matchedPairs = nan(size(E0, 1), 2);
65
    nMatchedPairs = 0;
66
    for j=1:size(E0, 1)
67
 
68
        % should use pdist2 instead..
69
        sqDists = sum((E1 - repmat(E0(j,:), size(E1, 1), 1)).^2, 2);
70
 
71
        [minSqDist, minSqDistIdx] = min(sqDists);
72
 
73
        if(minSqDist < 5^2)
74
            nMatchedPairs = nMatchedPairs + 1;
75
            matchedPairs(nMatchedPairs, :) = [j, minSqDistIdx];
76
        end
77
    end
78
    matchedPairs = matchedPairs(1:nMatchedPairs, :);
209 jakw 79
 
211 jakw 80
    % triangulate marker centers (lens correction has been performed)
81
    E{i} = triangulate(e0Coords(matchedPairs(:, 1),:), e1Coords(matchedPairs(:, 2),:), camStereoParams);
209 jakw 82
 
211 jakw 83
    % bring into initial alignment
84
    [U,~,V] = svd(initialAlign(i).Rotation);
85
    Ri = U*V';
86
    Ti = initialAlign(i).Translation;
209 jakw 87
 
211 jakw 88
    Eg{i} = E{i}*Ri' + repmat(Ti', size(E{i}, 1), 1);
89
end
90
 
212 jakw 91
% show found markers in initial alignment
92
figure;
93
hold('on');
211 jakw 94
for i=1:nSubScans
95
    % fix Ri to be orthogonal
96
    [U,~,V] = svd(initialAlign(i).Rotation);
97
    Ri = U*V';
209 jakw 98
 
211 jakw 99
    % bring point cloud into initial alignment
100
    pc = pcread(fullfile(scanDir, initialAlign(i).FileName));
101
    tform = affine3d([Ri' [0;0;0]; initialAlign(i).Translation' 1]);
102
    pcg = pctransform(pc, tform);
212 jakw 103
 
211 jakw 104
    pcshow(pcg);
105
    xlabel('x');
106
    ylabel('y');
107
    zlabel('z');
212 jakw 108
 
109
    plot3(Eg{i}(:,1), Eg{i}(:,2), Eg{i}(:,3), '.', 'MarkerSize', 15);
204 jakw 110
end
111
 
212 jakw 112
% match markers between poses using initial alignment
113
Pg = {};
114
P = {};
115
for i=1:nSubScans
116
    for j=1:size(Eg{i}, 1)
117
        pg = Eg{i}(j,:);
118
        p = E{i}(j,:);
119
        matched = false;
120
        for k=1:size(Pg, 2)
121
            clusterCenter = mean(cat(1, Pg{:,k}), 1);
122
            if(sum((pg - clusterCenter).^2) < 3^2)
123
                % store in global frame
124
                Pg{i,k} = pg;
125
                % store in local frame
126
                P{i,k} = p;
127
                matched = true;
128
                break;
129
            end
130
        end
131
        % create new cluster
132
        if(not(matched))
133
            Pg{i,end+1} = pg;
134
            P{i,end+1} = p;
135
        end 
136
    end
137
end
211 jakw 138
 
212 jakw 139
% run optimization
140
alignment = groupwiseOrthogonalProcrustes(P, initialAlign);
141
 
142
for i=1:length(alignment)
143
    alignment(i).FileName = initialAlign(i).FileName;
209 jakw 144
end
145
 
212 jakw 146
writeMeshLabALN(alignment, strrep(alnFileName, '.aln', '_opt.aln'));
211 jakw 147
 
212 jakw 148
end
149
 
150
function e = autoDetectMarkers(frame, ep)
151
 
211 jakw 152
    % create mask based on morphology
153
    bw = imbinarize(rgb2gray(frame));
154
    cc = bwconncomp(bw);
155
    rp = regionprops(cc, 'Area', 'Solidity', 'Eccentricity', 'Centroid');
156
    idx = find([rp.Area] > 100 & [rp.Area] < 1000 & [rp.Solidity] > 0.9);
212 jakw 157
    mask = ismember(labelmatrix(cc), idx);
211 jakw 158
    mask = imdilate(mask, strel('disk', 20, 0));
159
 
160
    % detect ellipses within mask
161
    edges = edge(rgb2gray(frame), 'Canny', [0.08 0.1], 2);
162
    edges(~mask) = 0;
163
    ep.numBest = 10; 
212 jakw 164
    el = ellipseDetection(edges, ep);
165
 
166
    e = el(:, 1:2);
211 jakw 167
end
168
 
212 jakw 169
function e = manuallyDetectMarkers(frame, ep, P, pointCloud)
211 jakw 170
 
212 jakw 171
    e = [];
172
	edges = edge(rgb2gray(frame), 'Canny', [0.08 0.1], 2);
173
 
211 jakw 174
    figure; 
212 jakw 175
    hold('on');
211 jakw 176
    imshow(frame);
212 jakw 177
    title('Close figure to end.');
178
    set(gcf, 'pointer', 'crosshair'); 
179
    set(gcf, 'WindowButtonDownFcn', @clickCallback);
180
 
181
    uiwait;
211 jakw 182
 
212 jakw 183
    function clickCallback(~, ~)
184
 
185
        p = get(gca, 'CurrentPoint'); 
186
        p = p(1, 1:2);
187
 
188
%         % create mask around selected point
189
%         mask = false(size(frame, 1), size(frame, 2));
190
% 
191
%         mask(round(p(2)), round(p(1))) = true;
192
%         mask = imdilate(mask, strel('disk', 100, 0));
193
% 
194
%         % detect ellipses within mask
195
%         edgesI = edges;
196
%         edgesI(~mask) = 0;
197
% 
198
%         ep.numBest = 1; 
199
%         el = ellipseDetection(edgesI, ep);
200
% 
201
%         ellipse(el(:,3), el(:,4), el(:,5)*pi/180, el(:,1), el(:,2), 'r'); 
202
%         
203
%         e = [e; el(:, 1:2)];
211 jakw 204
 
212 jakw 205
        [el, conf] = detectMarkersSubpix(frame, p, P, pointCloud)
206
        e = [e; el(:, 1:2)];
207
    end
208
 
209
end
211 jakw 210
 
212 jakw 211
function [e, conf] = detectMarkersSubpix(frame, initGuesses, P, Q)
211 jakw 212
 
212 jakw 213
    % create mask based on morphology
214
    bw = imbinarize(rgb2gray(frame));
215
    cc = bwconncomp(bw);
216
    labels = labelmatrix(cc);
211 jakw 217
 
212 jakw 218
    % project point cloud into image
219
    q = [Q ones(size(Q,1),1)]*P;
220
    q = q./[q(:,3) q(:,3) q(:,3)];
221
 
222
    for i=1:size(initGuesses, 1)
223
 
224
        labelId = labels(round(initGuesses(i,2)), round(initGuesses(i,1)));
225
        labelMask = (labels == labelId);
226
        labelMask = imdilate(labelMask, strel('disk', 3, 0));
227
 
228
        % determine 3D points that are part of the marker
229
        pointMask = false(size(q, 1), 1);
230
        for j=1:size(q,1)
231
            if(labelMask(round(q(j,2)), round(q(j,1))))
232
                pointMask(j) = true;
233
            end
234
        end
235
 
236
        % build homography
237
        H = fitgeotrans(Q(pointMask, :), q(pointMask, :), 'projective');
238
 
211 jakw 239
    end
240
 
212 jakw 241
    e = initGuesses;
242
    conf = 1.0;
243
 
211 jakw 244
end
245
 
212 jakw 246
function E = projectOntoPointCloud(e, P, pointCloud)
211 jakw 247
 
212 jakw 248
    q = [pointCloud ones(size(pointCloud,1),1)]*P;
211 jakw 249
    q = q(:,1:2)./[q(:,3) q(:,3)];
250
 
251
    E = nan(size(e,1), 3);
252
 
253
    for i=1:size(e, 1)
254
        sqDists = sum((q - repmat(e(i,:), size(q, 1), 1)).^2, 2);
255
 
256
        [minSqDist, minSqDistIdx] = min(sqDists);
257
 
258
        if(minSqDist < 2^2)
259
 
212 jakw 260
            E(i, :) = pointCloud(minSqDistIdx, :);
211 jakw 261
 
262
        end
263
 
264
    end    
265
end
266