182 |
jakw |
1 |
//
|
|
|
2 |
// Three 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 |
//
|
188 |
jakw |
6 |
// The number of periods in the First and Second frequencies can be chosen freely, but small changes can have a considerable impact on quality.
|
|
|
7 |
// 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-First frequencies.
|
182 |
jakw |
8 |
//
|
|
|
9 |
|
|
|
10 |
|
128 |
jakw |
11 |
#include "AlgorithmPhaseShiftThreeFreq.h"
|
4 |
jakw |
12 |
#include <math.h>
|
|
|
13 |
|
|
|
14 |
#include "cvtools.h"
|
182 |
jakw |
15 |
#include "algorithmtools.h"
|
4 |
jakw |
16 |
|
|
|
17 |
#ifndef M_PI
|
|
|
18 |
#define M_PI 3.14159265358979323846
|
|
|
19 |
#endif
|
|
|
20 |
|
188 |
jakw |
21 |
static unsigned int nStepsFirst = 8; // number of shifts/steps in First
|
|
|
22 |
static unsigned int nStepsSecond = 8; // number of shifts/steps in Second
|
|
|
23 |
static unsigned int nStepsThird = 8; // number of shifts/steps in Third
|
|
|
24 |
static float nPeriodsFirst = 24; // First period
|
|
|
25 |
static float nPeriodsSecond = 30; // First period
|
4 |
jakw |
26 |
|
128 |
jakw |
27 |
AlgorithmPhaseShiftThreeFreq::AlgorithmPhaseShiftThreeFreq(unsigned int _screenCols, unsigned int _screenRows) : Algorithm(_screenCols, _screenRows){
|
4 |
jakw |
28 |
|
72 |
jakw |
29 |
// Set N
|
188 |
jakw |
30 |
N = 2+nStepsFirst+nStepsSecond+nStepsThird;
|
72 |
jakw |
31 |
|
188 |
jakw |
32 |
// Determine the Third period to fulfill the heterodyne condition
|
|
|
33 |
float nPeriodsThird = 1 + 2*nPeriodsSecond - nPeriodsFirst;
|
182 |
jakw |
34 |
|
70 |
jakw |
35 |
// all on pattern
|
|
|
36 |
cv::Mat allOn(1, screenCols, CV_8UC3, cv::Scalar::all(255));
|
|
|
37 |
patterns.push_back(allOn);
|
|
|
38 |
|
|
|
39 |
// all off pattern
|
|
|
40 |
cv::Mat allOff(1, screenCols, CV_8UC3, cv::Scalar::all(0));
|
|
|
41 |
patterns.push_back(allOff);
|
|
|
42 |
|
4 |
jakw |
43 |
// Precompute encoded patterns
|
|
|
44 |
const float pi = M_PI;
|
|
|
45 |
|
188 |
jakw |
46 |
// First encoding patterns
|
|
|
47 |
for(unsigned int i=0; i<nStepsFirst; i++){
|
|
|
48 |
float phase = 2.0*pi/nStepsFirst * i;
|
|
|
49 |
float pitch = screenCols/nPeriodsFirst;
|
70 |
jakw |
50 |
cv::Mat patternI(1,1,CV_8U);
|
|
|
51 |
patternI = computePhaseVector(screenCols, phase, pitch);
|
|
|
52 |
patterns.push_back(patternI.t());
|
|
|
53 |
}
|
4 |
jakw |
54 |
|
188 |
jakw |
55 |
// Second encoding patterns
|
|
|
56 |
for(unsigned int i=0; i<nStepsSecond; i++){
|
|
|
57 |
float phase = 2.0*pi/nStepsSecond * i;
|
|
|
58 |
float pitch = screenCols/nPeriodsSecond;
|
72 |
jakw |
59 |
cv::Mat patternI(1,1,CV_8U);
|
70 |
jakw |
60 |
patternI = computePhaseVector(screenCols, phase, pitch);
|
|
|
61 |
patterns.push_back(patternI.t());
|
4 |
jakw |
62 |
}
|
188 |
jakw |
63 |
// Third encoding patterns
|
|
|
64 |
for(unsigned int i=0; i<nStepsThird; i++){
|
|
|
65 |
float phase = 2.0*pi/nStepsThird * i;
|
|
|
66 |
float pitch = screenCols/nPeriodsThird;
|
128 |
jakw |
67 |
cv::Mat patternI(1,1,CV_8U);
|
|
|
68 |
patternI = computePhaseVector(screenCols, phase, pitch);
|
|
|
69 |
patterns.push_back(patternI.t());
|
|
|
70 |
}
|
4 |
jakw |
71 |
|
|
|
72 |
}
|
|
|
73 |
|
128 |
jakw |
74 |
cv::Mat AlgorithmPhaseShiftThreeFreq::getEncodingPattern(unsigned int depth){
|
4 |
jakw |
75 |
return patterns[depth];
|
|
|
76 |
}
|
|
|
77 |
|
128 |
jakw |
78 |
void AlgorithmPhaseShiftThreeFreq::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 |
79 |
|
70 |
jakw |
80 |
const float pi = M_PI;
|
|
|
81 |
|
|
|
82 |
assert(frames0.size() == N);
|
|
|
83 |
assert(frames1.size() == N);
|
|
|
84 |
|
|
|
85 |
int frameRows = frames0[0].rows;
|
|
|
86 |
int frameCols = frames0[0].cols;
|
|
|
87 |
|
179 |
jakw |
88 |
// Rectifying homographies (rotation+projections)
|
|
|
89 |
cv::Size frameSize(frameCols, frameRows);
|
|
|
90 |
cv::Mat R, T;
|
|
|
91 |
// stereoRectify segfaults unless R is double precision
|
|
|
92 |
cv::Mat(calibration.R1).convertTo(R, CV_64F);
|
|
|
93 |
cv::Mat(calibration.T1).convertTo(T, CV_64F);
|
|
|
94 |
cv::Mat R0, R1, P0, P1, QRect;
|
|
|
95 |
cv::stereoRectify(calibration.K0, calibration.k0, calibration.K1, calibration.k1, frameSize, R, T, R0, R1, P0, P1, QRect, 0);
|
|
|
96 |
|
|
|
97 |
// Interpolation maps (lens distortion and rectification)
|
|
|
98 |
cv::Mat map0X, map0Y, map1X, map1Y;
|
|
|
99 |
cv::initUndistortRectifyMap(calibration.K0, calibration.k0, R0, P0, frameSize, CV_32F, map0X, map0Y);
|
|
|
100 |
cv::initUndistortRectifyMap(calibration.K1, calibration.k1, R1, P1, frameSize, CV_32F, map1X, map1Y);
|
|
|
101 |
|
|
|
102 |
|
|
|
103 |
// Gray-scale and remap
|
|
|
104 |
std::vector<cv::Mat> frames0Rect(N);
|
|
|
105 |
std::vector<cv::Mat> frames1Rect(N);
|
167 |
jakw |
106 |
for(unsigned int i=0; i<N; i++){
|
179 |
jakw |
107 |
cv::Mat temp;
|
|
|
108 |
cv::cvtColor(frames0[i], temp, CV_BayerBG2GRAY);
|
|
|
109 |
cv::remap(temp, frames0Rect[i], map0X, map0Y, CV_INTER_LINEAR);
|
|
|
110 |
cv::cvtColor(frames1[i], temp, CV_BayerBG2GRAY);
|
|
|
111 |
cv::remap(temp, frames1Rect[i], map1X, map1Y, CV_INTER_LINEAR);
|
70 |
jakw |
112 |
}
|
|
|
113 |
|
|
|
114 |
// Decode camera0
|
188 |
jakw |
115 |
std::vector<cv::Mat> frames0First(frames0Rect.begin()+2, frames0Rect.begin()+2+nStepsFirst);
|
|
|
116 |
std::vector<cv::Mat> frames0Second(frames0Rect.begin()+2+nStepsFirst, frames0Rect.end()-nStepsThird);
|
|
|
117 |
std::vector<cv::Mat> frames0Third(frames0Rect.end()-nStepsThird, frames0Rect.end());
|
129 |
jakw |
118 |
|
188 |
jakw |
119 |
std::vector<cv::Mat> F0First = getDFTComponents(frames0First);
|
|
|
120 |
cv::Mat up0First;
|
|
|
121 |
cv::phase(F0First[2], -F0First[3], up0First);
|
|
|
122 |
std::vector<cv::Mat> F0Second = getDFTComponents(frames0Second);
|
|
|
123 |
cv::Mat up0Second;
|
|
|
124 |
cv::phase(F0Second[2], -F0Second[3], up0Second);
|
|
|
125 |
std::vector<cv::Mat> F0Third = getDFTComponents(frames0Third);
|
|
|
126 |
cv::Mat up0Third;
|
|
|
127 |
cv::phase(F0Third[2], -F0Third[3], up0Third);
|
128 |
jakw |
128 |
|
189 |
jakw |
129 |
cv::Mat up0EquivalentFS = up0First - up0Second;
|
|
|
130 |
up0EquivalentFS = cvtools::modulo(up0EquivalentFS, 2.0*pi);
|
182 |
jakw |
131 |
|
189 |
jakw |
132 |
cv::Mat up0EquivalentST = up0Second - up0Third;
|
|
|
133 |
up0EquivalentST = cvtools::modulo(up0EquivalentST, 2.0*pi);
|
182 |
jakw |
134 |
|
189 |
jakw |
135 |
cv::Mat up0EquivalentFST = up0EquivalentFS - up0EquivalentST;
|
|
|
136 |
up0EquivalentFST = cvtools::modulo(up0EquivalentFST, 2.0*pi);
|
182 |
jakw |
137 |
|
188 |
jakw |
138 |
// TODO: we should use backward phase unwrapping (Song Zhang term)...
|
|
|
139 |
|
189 |
jakw |
140 |
cv::Mat up0 = unwrapWithCue(up0First, up0EquivalentFST, (float)screenCols/nPeriodsFirst);
|
128 |
jakw |
141 |
up0 *= screenCols/(2.0*pi);
|
|
|
142 |
cv::Mat amplitude0;
|
188 |
jakw |
143 |
cv::magnitude(F0First[2], -F0First[3], amplitude0);
|
70 |
jakw |
144 |
|
|
|
145 |
// Decode camera1
|
188 |
jakw |
146 |
std::vector<cv::Mat> frames1First(frames1Rect.begin()+2, frames1Rect.begin()+2+nStepsFirst);
|
|
|
147 |
std::vector<cv::Mat> frames1Second(frames1Rect.begin()+2+nStepsFirst, frames1Rect.end()-nStepsThird);
|
|
|
148 |
std::vector<cv::Mat> frames1Third(frames1Rect.end()-nStepsThird, frames1Rect.end());
|
129 |
jakw |
149 |
|
188 |
jakw |
150 |
std::vector<cv::Mat> F1First = getDFTComponents(frames1First);
|
|
|
151 |
cv::Mat up1First;
|
|
|
152 |
cv::phase(F1First[2], -F1First[3], up1First);
|
|
|
153 |
std::vector<cv::Mat> F1Second = getDFTComponents(frames1Second);
|
|
|
154 |
cv::Mat up1Second;
|
|
|
155 |
cv::phase(F1Second[2], -F1Second[3], up1Second);
|
|
|
156 |
std::vector<cv::Mat> F1Third = getDFTComponents(frames1Third);
|
|
|
157 |
cv::Mat up1Third;
|
|
|
158 |
cv::phase(F1Third[2], -F1Third[3], up1Third);
|
128 |
jakw |
159 |
|
189 |
jakw |
160 |
cv::Mat up1EquivalentFS = up1First - up1Second;
|
|
|
161 |
up1EquivalentFS = cvtools::modulo(up1EquivalentFS, 2.0*pi);
|
182 |
jakw |
162 |
|
188 |
jakw |
163 |
cv::Mat up1EquivalentST = up1Second - up1Third;
|
182 |
jakw |
164 |
up1EquivalentST = cvtools::modulo(up1EquivalentST, 2.0*pi);
|
|
|
165 |
|
189 |
jakw |
166 |
cv::Mat up1EquivalentFST = up1EquivalentFS - up1EquivalentST;
|
|
|
167 |
up1EquivalentFST = cvtools::modulo(up1EquivalentFST, 2.0*pi);
|
182 |
jakw |
168 |
|
189 |
jakw |
169 |
cv::Mat up1 = unwrapWithCue(up1First, up1EquivalentFST, (float)screenCols/nPeriodsFirst);
|
128 |
jakw |
170 |
up1 *= screenCols/(2.0*pi);
|
|
|
171 |
cv::Mat amplitude1;
|
188 |
jakw |
172 |
cv::magnitude(F1First[2], -F1First[3], amplitude1);
|
70 |
jakw |
173 |
|
185 |
jakw |
174 |
#ifdef QT_DEBUG
|
188 |
jakw |
175 |
cvtools::writeMat(up0First, "up0First.mat", "up0First");
|
|
|
176 |
cvtools::writeMat(up0Second, "up0Second.mat", "up0Second");
|
|
|
177 |
cvtools::writeMat(up0Third, "up0Third.mat", "up0Third");
|
189 |
jakw |
178 |
cvtools::writeMat(up0EquivalentFS, "up0EquivalentFS.mat", "up0EquivalentFS");
|
|
|
179 |
cvtools::writeMat(up0EquivalentST, "up0EquivalentST.mat", "up0EquivalentST");
|
|
|
180 |
cvtools::writeMat(up0EquivalentFST, "up0EquivalentFST.mat", "up0EquivalentFST");
|
182 |
jakw |
181 |
cvtools::writeMat(up0, "up0.mat", "up0");
|
|
|
182 |
cvtools::writeMat(up1, "up1.mat", "up1");
|
|
|
183 |
cvtools::writeMat(amplitude0, "amplitude0.mat", "amplitude0");
|
71 |
jakw |
184 |
|
182 |
jakw |
185 |
cvtools::writeMat(amplitude0, "amplitude0.mat", "amplitude0");
|
|
|
186 |
cvtools::writeMat(amplitude1, "amplitude1.mat", "amplitude1");
|
|
|
187 |
#endif
|
70 |
jakw |
188 |
|
187 |
jakw |
189 |
// color debayer and remap
|
179 |
jakw |
190 |
cv::Mat color0, color1;
|
|
|
191 |
cv::cvtColor(frames0[0], color0, CV_BayerBG2RGB);
|
187 |
jakw |
192 |
cv::remap(color0, color0, map0X, map0Y, CV_INTER_LINEAR);
|
|
|
193 |
|
179 |
jakw |
194 |
cv::cvtColor(frames1[0], color1, CV_BayerBG2RGB);
|
187 |
jakw |
195 |
cv::remap(color1, color1, map1X, map1Y, CV_INTER_LINEAR);
|
70 |
jakw |
196 |
|
185 |
jakw |
197 |
#ifdef QT_DEBUG
|
182 |
jakw |
198 |
cvtools::writeMat(color0, "color0.mat", "color0");
|
|
|
199 |
cvtools::writeMat(color1, "color1.mat", "color1");
|
|
|
200 |
#endif
|
70 |
jakw |
201 |
|
|
|
202 |
// Occlusion masks
|
179 |
jakw |
203 |
cv::Mat occlusion0, occlusion1;
|
|
|
204 |
cv::subtract(frames0Rect[0], frames0Rect[1], occlusion0);
|
|
|
205 |
occlusion0 = (occlusion0 > 5) & (occlusion0 < 250);
|
|
|
206 |
cv::subtract(frames1Rect[0], frames1Rect[1], occlusion1);
|
|
|
207 |
occlusion1 = (occlusion1 > 5) & (occlusion1 < 250);
|
70 |
jakw |
208 |
|
71 |
jakw |
209 |
// Threshold on gradient of phase
|
|
|
210 |
cv::Mat edges0;
|
179 |
jakw |
211 |
cv::Sobel(up0, edges0, -1, 1, 1, 5);
|
|
|
212 |
occlusion0 = occlusion0 & (abs(edges0) < 150);
|
71 |
jakw |
213 |
cv::Mat edges1;
|
179 |
jakw |
214 |
cv::Sobel(up1, edges1, -1, 1, 1, 5);
|
|
|
215 |
occlusion1 = occlusion1 & (abs(edges1) < 150);
|
71 |
jakw |
216 |
|
185 |
jakw |
217 |
#ifdef QT_DEBUG
|
182 |
jakw |
218 |
cvtools::writeMat(edges0, "edges0.mat", "edges0");
|
|
|
219 |
cvtools::writeMat(edges1, "edges1.mat", "edges1");
|
|
|
220 |
#endif
|
71 |
jakw |
221 |
|
187 |
jakw |
222 |
#ifdef QT_DEBUG
|
|
|
223 |
cvtools::writeMat(occlusion0, "occlusion0.mat", "occlusion0");
|
|
|
224 |
cvtools::writeMat(occlusion1, "occlusion1.mat", "occlusion1");
|
|
|
225 |
#endif
|
|
|
226 |
|
70 |
jakw |
227 |
// Match phase maps
|
|
|
228 |
int frameRectRows = map0X.rows;
|
|
|
229 |
int frameRectCols = map0X.cols;
|
|
|
230 |
|
|
|
231 |
// camera0 against camera1
|
179 |
jakw |
232 |
std::vector<cv::Vec2f> q0, q1;
|
70 |
jakw |
233 |
for(int row=0; row<frameRectRows; row++){
|
|
|
234 |
for(int col=0; col<frameRectCols; col++){
|
|
|
235 |
|
179 |
jakw |
236 |
if(!occlusion0.at<char>(row,col))
|
70 |
jakw |
237 |
continue;
|
|
|
238 |
|
179 |
jakw |
239 |
float up0i = up0.at<float>(row,col);
|
|
|
240 |
for(int col1=0; col1<up1.cols-1; col1++){
|
70 |
jakw |
241 |
|
179 |
jakw |
242 |
if(!occlusion1.at<char>(row,col1) || !occlusion1.at<char>(row,col1+1))
|
70 |
jakw |
243 |
continue;
|
|
|
244 |
|
179 |
jakw |
245 |
float up1Left = up1.at<float>(row,col1);
|
|
|
246 |
float up1Right = up1.at<float>(row,col1+1);
|
70 |
jakw |
247 |
|
189 |
jakw |
248 |
if((up1Left <= up0i) && (up0i <= up1Right) && (up0i-up1Left < 1.0) && (up1Right-up0i < 1.0) && (up1Right-up1Left > 0.1)){
|
70 |
jakw |
249 |
|
|
|
250 |
float col1i = col1 + (up0i-up1Left)/(up1Right-up1Left);
|
|
|
251 |
|
179 |
jakw |
252 |
q0.push_back(cv::Point2f(col, row));
|
|
|
253 |
q1.push_back(cv::Point2f(col1i, row));
|
71 |
jakw |
254 |
|
|
|
255 |
break;
|
70 |
jakw |
256 |
}
|
|
|
257 |
}
|
|
|
258 |
}
|
|
|
259 |
}
|
|
|
260 |
|
179 |
jakw |
261 |
int nMatches = q0.size();
|
70 |
jakw |
262 |
|
|
|
263 |
if(nMatches < 1){
|
|
|
264 |
Q.resize(0);
|
|
|
265 |
color.resize(0);
|
|
|
266 |
|
|
|
267 |
return;
|
|
|
268 |
}
|
|
|
269 |
|
|
|
270 |
// Retrieve color information
|
|
|
271 |
color.resize(nMatches);
|
|
|
272 |
for(int i=0; i<nMatches; i++){
|
|
|
273 |
|
179 |
jakw |
274 |
cv::Vec3b c0 = color0.at<cv::Vec3b>(q0[i][1], q0[i][0]);
|
|
|
275 |
cv::Vec3b c1 = color1.at<cv::Vec3b>(q1[i][1], q1[i][0]);
|
70 |
jakw |
276 |
|
|
|
277 |
color[i] = 0.5*c0 + 0.5*c1;
|
|
|
278 |
}
|
|
|
279 |
|
|
|
280 |
// Triangulate points
|
|
|
281 |
cv::Mat QMatHomogenous, QMat;
|
179 |
jakw |
282 |
cv::triangulatePoints(P0, P1, q0, q1, QMatHomogenous);
|
70 |
jakw |
283 |
cvtools::convertMatFromHomogeneous(QMatHomogenous, QMat);
|
|
|
284 |
|
|
|
285 |
// Undo rectification
|
|
|
286 |
cv::Mat R0Inv;
|
|
|
287 |
cv::Mat(R0.t()).convertTo(R0Inv, CV_32F);
|
|
|
288 |
QMat = R0Inv*QMat;
|
|
|
289 |
|
|
|
290 |
cvtools::matToPoints3f(QMat, Q);
|
|
|
291 |
|
4 |
jakw |
292 |
}
|