181 |
jakw |
1 |
//
|
|
|
2 |
// Two Frequency Phase Shifting using the Heterodyne Principle
|
|
|
3 |
//
|
|
|
4 |
// This implementation follows "Reich, Ritter, Thesing, White light heterodyne principle for 3D-measurement", SPIE (1997)
|
|
|
5 |
// Different from the paper, it uses only two different frequencies.
|
|
|
6 |
//
|
|
|
7 |
// The number of periods in the primary (higher frequency) can in principle be chosen freely, but small changes can have a considerable impact on quality.
|
|
|
8 |
// The number of phase shifts can be chosen freely (min. 3), and higher values reduce the effects of image noise. They also allow us to filter bad points based on energy at non-primary frequencies.
|
|
|
9 |
//
|
|
|
10 |
|
128 |
jakw |
11 |
#include "AlgorithmPhaseShiftTwoFreq.h"
|
4 |
jakw |
12 |
#include <math.h>
|
|
|
13 |
|
|
|
14 |
#include "cvtools.h"
|
|
|
15 |
|
|
|
16 |
#ifndef M_PI
|
|
|
17 |
#define M_PI 3.14159265358979323846
|
|
|
18 |
#endif
|
|
|
19 |
|
143 |
jakw |
20 |
static unsigned int nStepsPrimary = 16; // number of shifts/steps in primary
|
|
|
21 |
static unsigned int nStepsSecondary = 8; // number of shifts/steps in secondary
|
133 |
jakw |
22 |
static float periodPrimary = 48; // primary period
|
4 |
jakw |
23 |
|
41 |
jakw |
24 |
// Algorithm
|
4 |
jakw |
25 |
static cv::Mat computePhaseVector(unsigned int length, float phase, float pitch){
|
|
|
26 |
|
|
|
27 |
cv::Mat phaseVector(length, 1, CV_8UC3);
|
|
|
28 |
//phaseVector.setTo(0);
|
|
|
29 |
|
|
|
30 |
const float pi = M_PI;
|
|
|
31 |
|
|
|
32 |
// Loop through vector
|
|
|
33 |
for(int i=0; i<phaseVector.rows; i++){
|
|
|
34 |
// Amplitude of channels
|
71 |
jakw |
35 |
float amp = 0.5*(1+cos(2*pi*i/pitch - phase));
|
4 |
jakw |
36 |
phaseVector.at<cv::Vec3b>(i, 0) = cv::Vec3b(255.0*amp,255.0*amp,255.0*amp);
|
|
|
37 |
}
|
|
|
38 |
|
|
|
39 |
return phaseVector;
|
|
|
40 |
}
|
|
|
41 |
|
128 |
jakw |
42 |
AlgorithmPhaseShiftTwoFreq::AlgorithmPhaseShiftTwoFreq(unsigned int _screenCols, unsigned int _screenRows) : Algorithm(_screenCols, _screenRows){
|
4 |
jakw |
43 |
|
72 |
jakw |
44 |
// Set N
|
118 |
jakw |
45 |
N = 2+nStepsPrimary+nStepsSecondary;
|
72 |
jakw |
46 |
|
74 |
jakw |
47 |
// Determine the secondary (wider) period
|
118 |
jakw |
48 |
float pSecondary = (screenCols*periodPrimary)/(screenCols-periodPrimary);
|
74 |
jakw |
49 |
|
70 |
jakw |
50 |
// all on pattern
|
|
|
51 |
cv::Mat allOn(1, screenCols, CV_8UC3, cv::Scalar::all(255));
|
|
|
52 |
patterns.push_back(allOn);
|
|
|
53 |
|
|
|
54 |
// all off pattern
|
|
|
55 |
cv::Mat allOff(1, screenCols, CV_8UC3, cv::Scalar::all(0));
|
|
|
56 |
patterns.push_back(allOff);
|
|
|
57 |
|
4 |
jakw |
58 |
// Precompute encoded patterns
|
|
|
59 |
const float pi = M_PI;
|
|
|
60 |
|
74 |
jakw |
61 |
// Primary encoding patterns
|
118 |
jakw |
62 |
for(unsigned int i=0; i<nStepsPrimary; i++){
|
|
|
63 |
float phase = 2.0*pi/nStepsPrimary * i;
|
|
|
64 |
float pitch = periodPrimary;
|
70 |
jakw |
65 |
cv::Mat patternI(1,1,CV_8U);
|
|
|
66 |
patternI = computePhaseVector(screenCols, phase, pitch);
|
|
|
67 |
patterns.push_back(patternI.t());
|
|
|
68 |
}
|
4 |
jakw |
69 |
|
74 |
jakw |
70 |
// Secondary encoding patterns
|
118 |
jakw |
71 |
for(unsigned int i=0; i<nStepsSecondary; i++){
|
|
|
72 |
float phase = 2.0*pi/nStepsSecondary * i;
|
74 |
jakw |
73 |
float pitch = pSecondary;
|
72 |
jakw |
74 |
cv::Mat patternI(1,1,CV_8U);
|
70 |
jakw |
75 |
patternI = computePhaseVector(screenCols, phase, pitch);
|
|
|
76 |
patterns.push_back(patternI.t());
|
4 |
jakw |
77 |
}
|
|
|
78 |
|
72 |
jakw |
79 |
|
4 |
jakw |
80 |
}
|
|
|
81 |
|
128 |
jakw |
82 |
cv::Mat AlgorithmPhaseShiftTwoFreq::getEncodingPattern(unsigned int depth){
|
4 |
jakw |
83 |
return patterns[depth];
|
|
|
84 |
}
|
|
|
85 |
|
|
|
86 |
|
167 |
jakw |
87 |
//// Absolute phase from 3 frames
|
|
|
88 |
//static cv::Mat getPhase(const cv::Mat I1, const cv::Mat I2, const cv::Mat I3){
|
4 |
jakw |
89 |
|
167 |
jakw |
90 |
// cv::Mat_<float> I1_(I1);
|
|
|
91 |
// cv::Mat_<float> I2_(I2);
|
|
|
92 |
// cv::Mat_<float> I3_(I3);
|
70 |
jakw |
93 |
|
167 |
jakw |
94 |
// cv::Mat phase;
|
70 |
jakw |
95 |
|
167 |
jakw |
96 |
// // One call approach
|
|
|
97 |
// cv::phase(2.0*I1_-I3_-I2_, sqrt(3.0)*(I2_-I3_), phase);
|
|
|
98 |
// return phase;
|
70 |
jakw |
99 |
|
167 |
jakw |
100 |
//}
|
70 |
jakw |
101 |
|
|
|
102 |
// Phase unwrapping by means of a phase cue
|
128 |
jakw |
103 |
static cv::Mat unwrapWithCue(const cv::Mat up, const cv::Mat upCue, float nPhases){
|
70 |
jakw |
104 |
|
4 |
jakw |
105 |
const float pi = M_PI;
|
|
|
106 |
|
70 |
jakw |
107 |
// Determine number of jumps
|
128 |
jakw |
108 |
cv::Mat P = (upCue*nPhases-up)/(2.0*pi);
|
4 |
jakw |
109 |
|
70 |
jakw |
110 |
// Round to integers
|
|
|
111 |
P.convertTo(P, CV_8U);
|
|
|
112 |
P.convertTo(P, CV_32F);
|
4 |
jakw |
113 |
|
70 |
jakw |
114 |
// Add to phase
|
|
|
115 |
cv::Mat upUnwrapped = up + P*2*pi;
|
4 |
jakw |
116 |
|
70 |
jakw |
117 |
// Scale to range [0; 2pi]
|
|
|
118 |
upUnwrapped *= 1.0/nPhases;
|
|
|
119 |
|
|
|
120 |
return upUnwrapped;
|
4 |
jakw |
121 |
}
|
|
|
122 |
|
70 |
jakw |
123 |
// Absolute phase and magnitude from N frames
|
128 |
jakw |
124 |
static std::vector<cv::Mat> getDFTComponents(const std::vector<cv::Mat> frames){
|
70 |
jakw |
125 |
|
|
|
126 |
unsigned int N = frames.size();
|
|
|
127 |
|
71 |
jakw |
128 |
// std::vector<cv::Mat> framesReverse = frames;
|
|
|
129 |
// std::reverse(framesReverse.begin(), framesReverse.end());
|
70 |
jakw |
130 |
|
|
|
131 |
// DFT approach
|
|
|
132 |
cv::Mat I;
|
|
|
133 |
cv::merge(frames, I);
|
|
|
134 |
unsigned int w = I.cols;
|
|
|
135 |
unsigned int h = I.rows;
|
|
|
136 |
I = I.reshape(1, h*w);
|
|
|
137 |
I.convertTo(I, CV_32F);
|
|
|
138 |
cv::Mat fI;
|
|
|
139 |
cv::dft(I, fI, cv::DFT_ROWS + cv::DFT_COMPLEX_OUTPUT);
|
|
|
140 |
fI = fI.reshape(N*2, h);
|
|
|
141 |
|
|
|
142 |
std::vector<cv::Mat> fIcomp;
|
|
|
143 |
cv::split(fI, fIcomp);
|
|
|
144 |
|
|
|
145 |
return fIcomp;
|
|
|
146 |
|
|
|
147 |
}
|
|
|
148 |
|
128 |
jakw |
149 |
void AlgorithmPhaseShiftTwoFreq::get3DPoints(SMCalibrationParameters calibration, const std::vector<cv::Mat>& frames0, const std::vector<cv::Mat>& frames1, std::vector<cv::Point3f>& Q, std::vector<cv::Vec3b>& color){
|
4 |
jakw |
150 |
|
70 |
jakw |
151 |
const float pi = M_PI;
|
|
|
152 |
|
|
|
153 |
assert(frames0.size() == N);
|
|
|
154 |
assert(frames1.size() == N);
|
|
|
155 |
|
|
|
156 |
int frameRows = frames0[0].rows;
|
|
|
157 |
int frameCols = frames0[0].cols;
|
|
|
158 |
|
178 |
jakw |
159 |
// Rectifying homographies (rotation+projections)
|
|
|
160 |
cv::Size frameSize(frameCols, frameRows);
|
|
|
161 |
cv::Mat R, T;
|
|
|
162 |
// stereoRectify segfaults unless R is double precision
|
|
|
163 |
cv::Mat(calibration.R1).convertTo(R, CV_64F);
|
|
|
164 |
cv::Mat(calibration.T1).convertTo(T, CV_64F);
|
|
|
165 |
cv::Mat R0, R1, P0, P1, QRect;
|
|
|
166 |
cv::stereoRectify(calibration.K0, calibration.k0, calibration.K1, calibration.k1, frameSize, R, T, R0, R1, P0, P1, QRect, 0);
|
|
|
167 |
|
|
|
168 |
// Interpolation maps (lens distortion and rectification)
|
|
|
169 |
cv::Mat map0X, map0Y, map1X, map1Y;
|
|
|
170 |
cv::initUndistortRectifyMap(calibration.K0, calibration.k0, R0, P0, frameSize, CV_32F, map0X, map0Y);
|
|
|
171 |
cv::initUndistortRectifyMap(calibration.K1, calibration.k1, R1, P1, frameSize, CV_32F, map1X, map1Y);
|
|
|
172 |
|
179 |
jakw |
173 |
// Gray-scale and remap
|
178 |
jakw |
174 |
std::vector<cv::Mat> frames0Rect(N);
|
|
|
175 |
std::vector<cv::Mat> frames1Rect(N);
|
167 |
jakw |
176 |
for(unsigned int i=0; i<N; i++){
|
178 |
jakw |
177 |
cv::Mat temp;
|
|
|
178 |
cv::cvtColor(frames0[i], temp, CV_BayerBG2GRAY);
|
|
|
179 |
cv::remap(temp, frames0Rect[i], map0X, map0Y, CV_INTER_LINEAR);
|
|
|
180 |
cv::cvtColor(frames1[i], temp, CV_BayerBG2GRAY);
|
|
|
181 |
cv::remap(temp, frames1Rect[i], map1X, map1Y, CV_INTER_LINEAR);
|
70 |
jakw |
182 |
}
|
|
|
183 |
|
|
|
184 |
// Decode camera0
|
178 |
jakw |
185 |
std::vector<cv::Mat> frames0Primary(frames0Rect.begin()+2, frames0Rect.begin()+2+nStepsPrimary);
|
|
|
186 |
std::vector<cv::Mat> frames0Secondary(frames0Rect.begin()+2+nStepsPrimary, frames0Rect.end());
|
76 |
jakw |
187 |
std::vector<cv::Mat> F0Primary = getDFTComponents(frames0Primary);
|
74 |
jakw |
188 |
cv::Mat up0Primary;
|
76 |
jakw |
189 |
cv::phase(F0Primary[2], -F0Primary[3], up0Primary);
|
|
|
190 |
//cv::Mat up0Secondary = getPhase(frames0Secondary[0], frames0Secondary[1], frames0Secondary[2]);
|
|
|
191 |
std::vector<cv::Mat> F0Secondary = getDFTComponents(frames0Secondary);
|
|
|
192 |
cv::Mat up0Secondary;
|
|
|
193 |
cv::phase(F0Secondary[2], -F0Secondary[3], up0Secondary);
|
74 |
jakw |
194 |
cv::Mat up0Equivalent = up0Primary - up0Secondary;
|
128 |
jakw |
195 |
up0Equivalent = cvtools::modulo(up0Equivalent, 2.0*pi);
|
118 |
jakw |
196 |
cv::Mat up0 = unwrapWithCue(up0Primary, up0Equivalent, (float)screenCols/periodPrimary);
|
128 |
jakw |
197 |
up0 *= screenCols/(2.0*pi);
|
|
|
198 |
cv::Mat amplitude0;
|
|
|
199 |
cv::magnitude(F0Primary[2], -F0Primary[3], amplitude0);
|
70 |
jakw |
200 |
|
|
|
201 |
// Decode camera1
|
178 |
jakw |
202 |
std::vector<cv::Mat> frames1Primary(frames1Rect.begin()+2, frames1Rect.begin()+2+nStepsPrimary);
|
|
|
203 |
std::vector<cv::Mat> frames1Secondary(frames1Rect.begin()+2+nStepsPrimary, frames1Rect.end());
|
76 |
jakw |
204 |
std::vector<cv::Mat> F1Primary = getDFTComponents(frames1Primary);
|
74 |
jakw |
205 |
cv::Mat up1Primary;
|
76 |
jakw |
206 |
cv::phase(F1Primary[2], -F1Primary[3], up1Primary);
|
|
|
207 |
//cv::Mat up1Secondary = getPhase(frames1Secondary[0], frames1Secondary[1], frames1Secondary[2]);
|
|
|
208 |
std::vector<cv::Mat> F1Secondary = getDFTComponents(frames1Secondary);
|
|
|
209 |
cv::Mat up1Secondary;
|
|
|
210 |
cv::phase(F1Secondary[2], -F1Secondary[3], up1Secondary);
|
74 |
jakw |
211 |
cv::Mat up1Equivalent = up1Primary - up1Secondary;
|
128 |
jakw |
212 |
up1Equivalent = cvtools::modulo(up1Equivalent, 2.0*pi);
|
118 |
jakw |
213 |
cv::Mat up1 = unwrapWithCue(up1Primary, up1Equivalent, (float)screenCols/periodPrimary);
|
128 |
jakw |
214 |
up1 *= screenCols/(2.0*pi);
|
|
|
215 |
cv::Mat amplitude1;
|
|
|
216 |
cv::magnitude(F1Primary[2], -F1Primary[3], amplitude1);
|
70 |
jakw |
217 |
|
180 |
jakw |
218 |
cvtools::writeMat(up0Primary, "up0Primary.mat", "up0Primary");
|
|
|
219 |
cvtools::writeMat(up0Secondary, "up0Secondary.mat", "up0Secondary");
|
|
|
220 |
cvtools::writeMat(up0Equivalent, "up0Equivalent.mat", "up0Equivalent");
|
|
|
221 |
cvtools::writeMat(up0, "up0.mat", "up0");
|
|
|
222 |
cvtools::writeMat(amplitude0, "amplitude0.mat", "amplitude0");
|
71 |
jakw |
223 |
|
118 |
jakw |
224 |
// color debayer and remap
|
178 |
jakw |
225 |
cv::Mat color0, color1;
|
121 |
jakw |
226 |
// frames0[0].convertTo(color0Rect, CV_8UC1, 1.0/256.0);
|
178 |
jakw |
227 |
cv::cvtColor(frames0[0], color0, CV_BayerBG2RGB);
|
|
|
228 |
cv::remap(color0, color0, map0X, map0Y, CV_INTER_LINEAR);
|
70 |
jakw |
229 |
|
121 |
jakw |
230 |
// frames1[0].convertTo(color1Rect, CV_8UC1, 1.0/256.0);
|
178 |
jakw |
231 |
cv::cvtColor(frames1[0], color1, CV_BayerBG2RGB);
|
|
|
232 |
cv::remap(color1, color1, map1X, map1Y, CV_INTER_LINEAR);
|
118 |
jakw |
233 |
|
178 |
jakw |
234 |
//cvtools::writeMat(color0, "color0.mat", "color0");
|
|
|
235 |
//cvtools::writeMat(color1, "color1.mat", "color1");
|
118 |
jakw |
236 |
|
70 |
jakw |
237 |
// Occlusion masks
|
178 |
jakw |
238 |
cv::Mat occlusion0, occlusion1;
|
|
|
239 |
cv::subtract(frames0Rect[0], frames0Rect[1], occlusion0);
|
|
|
240 |
occlusion0 = (occlusion0 > 25) & (occlusion0 < 250);
|
|
|
241 |
cv::subtract(frames1Rect[0], frames1Rect[1], occlusion1);
|
|
|
242 |
occlusion1 = (occlusion1 > 25) & (occlusion1 < 250);
|
70 |
jakw |
243 |
|
128 |
jakw |
244 |
// Threshold on energy at primary frequency
|
178 |
jakw |
245 |
occlusion0 = occlusion0 & (amplitude0 > 5.0*nStepsPrimary);
|
|
|
246 |
occlusion1 = occlusion1 & (amplitude1 > 5.0*nStepsPrimary);
|
128 |
jakw |
247 |
|
178 |
jakw |
248 |
//cvtools::writeMat(occlusion1, "occlusion1.mat", "occlusion1");
|
|
|
249 |
//cvtools::writeMat(occlusion1, "occlusion1.mat", "occlusion1");
|
74 |
jakw |
250 |
|
70 |
jakw |
251 |
// Erode occlusion masks
|
74 |
jakw |
252 |
cv::Mat strel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(5,5));
|
178 |
jakw |
253 |
cv::erode(occlusion0, occlusion0, strel);
|
|
|
254 |
cv::erode(occlusion1, occlusion1, strel);
|
70 |
jakw |
255 |
|
71 |
jakw |
256 |
// Threshold on gradient of phase
|
|
|
257 |
cv::Mat edges0;
|
178 |
jakw |
258 |
cv::Sobel(up0, edges0, -1, 1, 1, 5);
|
|
|
259 |
occlusion0 = occlusion0 & (abs(edges0) < 150);
|
71 |
jakw |
260 |
|
|
|
261 |
cv::Mat edges1;
|
178 |
jakw |
262 |
cv::Sobel(up1, edges1, -1, 1, 1, 5);
|
|
|
263 |
occlusion1 = occlusion1 & (abs(edges1) < 150);
|
71 |
jakw |
264 |
|
74 |
jakw |
265 |
//cvtools::writeMat(edges0, "edges0.mat", "edges0");
|
|
|
266 |
//cvtools::writeMat(edges1, "edges1.mat", "edges1");
|
71 |
jakw |
267 |
|
70 |
jakw |
268 |
// Match phase maps
|
|
|
269 |
int frameRectRows = map0X.rows;
|
|
|
270 |
int frameRectCols = map0X.cols;
|
|
|
271 |
|
|
|
272 |
// camera0 against camera1
|
178 |
jakw |
273 |
std::vector<cv::Vec2f> q0, q1;
|
70 |
jakw |
274 |
for(int row=0; row<frameRectRows; row++){
|
|
|
275 |
for(int col=0; col<frameRectCols; col++){
|
|
|
276 |
|
178 |
jakw |
277 |
if(!occlusion0.at<char>(row,col))
|
70 |
jakw |
278 |
continue;
|
|
|
279 |
|
178 |
jakw |
280 |
float up0i = up0.at<float>(row,col);
|
|
|
281 |
for(int col1=0; col1<up1.cols-1; col1++){
|
70 |
jakw |
282 |
|
178 |
jakw |
283 |
if(!occlusion1.at<char>(row,col1) || !occlusion1.at<char>(row,col1+1))
|
70 |
jakw |
284 |
continue;
|
|
|
285 |
|
178 |
jakw |
286 |
float up1Left = up1.at<float>(row,col1);
|
|
|
287 |
float up1Right = up1.at<float>(row,col1+1);
|
70 |
jakw |
288 |
|
74 |
jakw |
289 |
if((up1Left <= up0i) && (up0i <= up1Right) && (up0i-up1Left < 1) && (up1Right-up0i < 1)){
|
70 |
jakw |
290 |
|
|
|
291 |
float col1i = col1 + (up0i-up1Left)/(up1Right-up1Left);
|
|
|
292 |
|
178 |
jakw |
293 |
q0.push_back(cv::Point2f(col, row));
|
|
|
294 |
q1.push_back(cv::Point2f(col1i, row));
|
71 |
jakw |
295 |
|
|
|
296 |
break;
|
70 |
jakw |
297 |
}
|
|
|
298 |
}
|
|
|
299 |
}
|
|
|
300 |
}
|
|
|
301 |
|
|
|
302 |
|
178 |
jakw |
303 |
int nMatches = q0.size();
|
74 |
jakw |
304 |
|
70 |
jakw |
305 |
if(nMatches < 1){
|
|
|
306 |
Q.resize(0);
|
|
|
307 |
color.resize(0);
|
|
|
308 |
|
|
|
309 |
return;
|
|
|
310 |
}
|
|
|
311 |
|
|
|
312 |
// Retrieve color information
|
|
|
313 |
color.resize(nMatches);
|
|
|
314 |
for(int i=0; i<nMatches; i++){
|
|
|
315 |
|
178 |
jakw |
316 |
cv::Vec3b c0 = color0.at<cv::Vec3b>(q0[i][1], q0[i][0]);
|
|
|
317 |
cv::Vec3b c1 = color1.at<cv::Vec3b>(q1[i][1], q1[i][0]);
|
70 |
jakw |
318 |
|
|
|
319 |
color[i] = 0.5*c0 + 0.5*c1;
|
|
|
320 |
}
|
|
|
321 |
|
|
|
322 |
// Triangulate points
|
|
|
323 |
cv::Mat QMatHomogenous, QMat;
|
178 |
jakw |
324 |
cv::triangulatePoints(P0, P1, q0, q1, QMatHomogenous);
|
70 |
jakw |
325 |
cvtools::convertMatFromHomogeneous(QMatHomogenous, QMat);
|
|
|
326 |
|
|
|
327 |
// Undo rectification
|
|
|
328 |
cv::Mat R0Inv;
|
|
|
329 |
cv::Mat(R0.t()).convertTo(R0Inv, CV_32F);
|
|
|
330 |
QMat = R0Inv*QMat;
|
|
|
331 |
|
|
|
332 |
cvtools::matToPoints3f(QMat, Q);
|
|
|
333 |
|
4 |
jakw |
334 |
}
|