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