iCub-main
Loading...
Searching...
No Matches
PinholeCalibrationEngine.cpp
Go to the documentation of this file.
2
3namespace
4{
5 constexpr std::size_t minimumObservations = 30;
6 constexpr std::size_t minimumPointsPerObservation = 12;
7
8 bool isFinite(const cv::Point2f& point)
9 {
10 return std::isfinite(point.x) && std::isfinite(point.y);
11 }
12
13 bool isFinite(const cv::Point3f& point)
14 {
15 return std::isfinite(point.x) && std::isfinite(point.y) && std::isfinite(point.z);
16 }
17} // namespace
18
19namespace stereo_calib
20{
22 const std::vector<StereoObservation>& observations,
23 const PinholeCalibrationOptions& options,
24 CalibrationResult& result,
25 std::string& errorMessage) const
26 {
27 result = CalibrationResult{};
28 errorMessage.clear();
29
30 CalibrationResult calculated;
31 calculated.model = CameraModel::Pinhole;
34 calculated.mode = options.common.calibrationMode;
35
36 try
37 {
38 if(!options.isValid())
39 {
40 errorMessage = "Invalid pinhole calibration options.";
41 return false;
42 }
43
44 if(!validateObservations(observations, options.common.imageSize, errorMessage))
45 {
46 return false;
47 }
48
49 switch (options.common.calibrationMode)
50 {
52 if(!calibrateMonocular(observations,
54 options,
55 calculated.leftCamera,
56 errorMessage))
57 {
58 return false;
59 }
60 break;
62 if(!calibrateMonocular(
63 observations,
65 options,
66 calculated.rightCamera,
67 errorMessage))
68 {
69 return false;
70 }
71 break;
73 if (!calibrateMonocular(observations, CameraSide::Left, options,
74 calculated.leftCamera, errorMessage) ||
75 !calibrateMonocular(observations, CameraSide::Right, options,
76 calculated.rightCamera, errorMessage))
77 {
78 return false;
79 }
80 break;
81
83 if (!calibrateMonocular(observations, CameraSide::Left, options,
84 calculated.leftCamera, errorMessage) ||
85 !calibrateMonocular(observations, CameraSide::Right, options,
86 calculated.rightCamera, errorMessage) ||
87 !calibrateStereo(observations, options, calculated.leftCamera,
88 calculated.rightCamera, calculated.stereo, errorMessage))
89 {
90 return false;
91 }
92 break;
93 default:
94 errorMessage = "Unknown calibration mode.";
95 return false;
96 break;
97 }
98
99 if (!calculated.isValid())
100 {
101 errorMessage = "Calibration produced an invalid result.";
102 return false;
103 }
104
105 result = std::move(calculated);
106 return true;
107 }
108 catch(const cv::Exception& e)
109 {
110 errorMessage = std::string("OpenCV exception during calibration: ") + e.what();
111 }
112 catch(const std::exception& e)
113 {
114 errorMessage = std::string("Standard exception during calibration: ") + e.what();
115 }
116 catch(...)
117 {
118 errorMessage = "Unknown exception during calibration.";
119 }
120
121 result = CalibrationResult{};
122 return false;
123 }
124
125 bool PinholeCalibrationEngine::validateObservations(
126 const std::vector<StereoObservation>& observations,
127 const cv::Size& expectedImageSize,
128 std::string& errorMessage) const
129 {
130 if (observations.size() < minimumObservations)
131 {
132 errorMessage = "At least " + std::to_string(minimumObservations) +
133 " stereo observations are required.";
134 return false;
135 }
136
137 std::size_t expectedPointCount = 0;
138 std::vector<cv::Point3f> referenceObjectPoints;
139 for (std::size_t index = 0; index < observations.size(); ++index)
140 {
141 const StereoObservation& observation = observations[index];
142 const std::string prefix = "Observation #" + std::to_string(index + 1) + ": ";
143
144 if (observation.imageSize != expectedImageSize)
145 {
146 std::ostringstream message;
147 message << prefix << "image size is " << observation.imageSize.width << "x"
148 << observation.imageSize.height << ", expected " << expectedImageSize.width
149 << "x" << expectedImageSize.height << ".";
150 errorMessage = message.str();
151 return false;
152 }
153
154 if (!observation.isValid())
155 {
156 errorMessage = prefix + "has inconsistent point lists or an invalid timestamp delta.";
157 return false;
158 }
159
160 if (observation.objectPoints.size() < minimumPointsPerObservation)
161 {
162 errorMessage = prefix + "contains fewer than " +
163 std::to_string(minimumPointsPerObservation) + " calibration points.";
164 return false;
165 }
166
167 if (!std::isfinite(observation.leftTimestampSeconds) ||
168 !std::isfinite(observation.rightTimestampSeconds) ||
169 !std::isfinite(observation.timestampDeltaSeconds))
170 {
171 errorMessage = prefix + "contains a non-finite timestamp.";
172 return false;
173 }
174
175 for (std::size_t pointIndex = 0; pointIndex < observation.objectPoints.size(); ++pointIndex)
176 {
177 if (!isFinite(observation.objectPoints[pointIndex]) ||
178 !isFinite(observation.leftImagePoints[pointIndex]) ||
179 !isFinite(observation.rightImagePoints[pointIndex]))
180 {
181 errorMessage = prefix + "point #" + std::to_string(pointIndex + 1) +
182 " contains a non-finite coordinate.";
183 return false;
184 }
185 }
186
187 if (index == 0)
188 {
189 expectedPointCount = observation.objectPoints.size();
190 referenceObjectPoints = observation.objectPoints;
191 }
192 else if (observation.objectPoints.size() != expectedPointCount)
193 {
194 errorMessage = prefix + "has " + std::to_string(observation.objectPoints.size()) +
195 " object points, expected " + std::to_string(expectedPointCount) + ".";
196 return false;
197 }
198 else
199 {
200 for (std::size_t pointIndex = 0; pointIndex < expectedPointCount; ++pointIndex)
201 {
202 if (cv::norm(observation.objectPoints[pointIndex] - referenceObjectPoints[pointIndex]) > 1e-6)
203 {
204 errorMessage = prefix + "uses object points different from observation #1.";
205 return false;
206 }
207 }
208 }
209 }
210
211 return true;
212 }
213
214 bool PinholeCalibrationEngine::calibrateMonocular(
215 const std::vector<StereoObservation>& observations,
216 CameraSide cameraSide,
217 const PinholeCalibrationOptions& options,
218 CameraCalibrationResult& result,
219 std::string& errorMessage) const
220 {
221 std::vector<std::vector<cv::Point3f>> objectPoints;
222 std::vector<std::vector<cv::Point2f>> leftImagePoints;
223 std::vector<std::vector<cv::Point2f>> rightImagePoints;
224 if (!extractCalibrationPoints(observations, objectPoints, leftImagePoints, rightImagePoints, errorMessage))
225 {
226 return false;
227 }
228
229 const std::vector<std::vector<cv::Point2f>>& imagePoints =
230 cameraSide == CameraSide::Left ? leftImagePoints : rightImagePoints;
231 cv::Mat intrinsic = cv::Mat::eye(3, 3, CV_64F);
232 if(options.cameraFocalLengthGuess > 0.0 && (options.monocularFlags & cv::CALIB_USE_INTRINSIC_GUESS))
233 {
234 intrinsic.at<double>(0, 0) = options.cameraFocalLengthGuess;
235 intrinsic.at<double>(1, 1) = options.cameraFocalLengthGuess;
236 intrinsic.at<double>(0, 2) = options.common.imageSize.width * 0.5;
237 intrinsic.at<double>(1, 2) = options.common.imageSize.height * 0.5;
238 }
239 // OpenCV's standard pinhole calibration vector contains k3 as its
240 // fifth entry. Keep it fixed to zero during optimization, then expose
241 // only the four coefficients consumed by camCalib.
242 cv::Mat distortion = cv::Mat::zeros(5, 1, CV_64F);
243 std::vector<cv::Mat> rotationVectors;
244 std::vector<cv::Mat> translationVectors;
245
246 const double rms = cv::calibrateCamera(objectPoints, imagePoints, options.common.imageSize,
247 intrinsic, distortion, rotationVectors,
248 translationVectors, options.monocularFlags,
249 options.criteria);
250
251 result = CameraCalibrationResult{};
252 result.model = CameraModel::Pinhole;
253 result.imageSize = options.common.imageSize;
254 result.K = intrinsic.clone();
255 result.D = distortion.reshape(1, 5).rowRange(0, 4).clone();
256 result.rotationVectors = std::move(rotationVectors);
257 result.translationVectors = std::move(translationVectors);
258 result.rms = rms;
259
260 if (result.rotationVectors.size() != observations.size() ||
261 result.translationVectors.size() != observations.size())
262 {
263 errorMessage = "OpenCV returned an incomplete set of monocular poses.";
264 return false;
265 }
266
267 result.perViewRms.reserve(observations.size());
268 for (std::size_t index = 0; index < observations.size(); ++index)
269 {
270 std::vector<cv::Point2f> projectedPoints;
271 cv::projectPoints(objectPoints[index],
272 result.rotationVectors[index],
273 result.translationVectors[index],
274 result.K, result.D,
275 projectedPoints);
276 if (projectedPoints.size() != imagePoints[index].size())
277 {
278 errorMessage = "OpenCV returned an incomplete projected point set.";
279 return false;
280 }
281 const double viewRms = cv::norm(projectedPoints, imagePoints[index], cv::NORM_L2) /
282 std::sqrt(static_cast<double>(projectedPoints.size()));
283 result.perViewRms.push_back(viewRms);
284 }
285
286 if (!result.isValid())
287 {
288 errorMessage = "Monocular calibration produced invalid camera parameters.";
289 return false;
290 }
291 return true;
292 }
293
294 bool PinholeCalibrationEngine::calibrateStereo(
295 const std::vector<StereoObservation>& observations,
296 const PinholeCalibrationOptions& options,
297 const CameraCalibrationResult& leftCamera,
298 const CameraCalibrationResult& rightCamera,
299 StereoCalibrationResult& result,
300 std::string& errorMessage) const
301 {
302 std::vector<std::vector<cv::Point3f>> objectPoints;
303 std::vector<std::vector<cv::Point2f>> leftImagePoints;
304 std::vector<std::vector<cv::Point2f>> rightImagePoints;
305 if (!extractCalibrationPoints(observations, objectPoints, leftImagePoints, rightImagePoints, errorMessage))
306 {
307 return false;
308 }
309
310 cv::Mat leftK = leftCamera.K.clone();
311 cv::Mat rightK = rightCamera.K.clone();
312 cv::Mat leftD = leftCamera.D.clone();
313 cv::Mat rightD = rightCamera.D.clone();
314 cv::Mat Rotation, Translation, Essential, Fundamental;
315
316 const double rms = cv::stereoCalibrate(
317 objectPoints, leftImagePoints, rightImagePoints,
318 leftK, leftD, rightK, rightD,
319 options.common.imageSize, Rotation, Translation, Essential, Fundamental,
320 options.stereoFlags, options.criteria);
321
322 result = StereoCalibrationResult{};
323 result.R = Rotation.clone();
324 result.T = Translation.reshape(1, 3).clone();
325 result.rms = rms;
326
327 if (!result.isValid())
328 {
329 errorMessage = "Stereo calibration produced an invalid result.";
330 return false;
331 }
332 return true;
333 }
334
335 bool PinholeCalibrationEngine::extractCalibrationPoints(
336 const std::vector<StereoObservation>& observations,
337 std::vector<std::vector<cv::Point3f>>& objectPoints,
338 std::vector<std::vector<cv::Point2f>>& leftImagePoints,
339 std::vector<std::vector<cv::Point2f>>& rightImagePoints,
340 std::string& errorMessage) const
341 {
342
343 objectPoints.clear();
344 leftImagePoints.clear();
345 rightImagePoints.clear();
346 objectPoints.reserve(observations.size());
347 leftImagePoints.reserve(observations.size());
348 rightImagePoints.reserve(observations.size());
349
350 for (const StereoObservation& observation : observations)
351 {
352 objectPoints.push_back(observation.objectPoints);
353 leftImagePoints.push_back(observation.leftImagePoints);
354 rightImagePoints.push_back(observation.rightImagePoints);
355 }
356 if (objectPoints.empty())
357 {
358 errorMessage = "No calibration points are available.";
359 return false;
360 }
361 return true;
362 }
363}
bool calibrate(const std::vector< StereoObservation > &observations, const PinholeCalibrationOptions &options, CalibrationResult &result, std::string &errorMessage) const
CameraCalibrationResult leftCamera
StereoCalibrationResult stereo
CameraCalibrationResult rightCamera