iCub-main
Loading...
Searching...
No Matches
CalibrationWriter.cpp
Go to the documentation of this file.
1#include "CalibrationWriter.h"
2
3#include <cerrno>
4#include <cmath>
5#include <cstdio>
6#include <cstring>
7#include <fstream>
8#include <iomanip>
9#include <sstream>
10
11#include <opencv2/imgcodecs.hpp>
12#include <opencv2/imgproc.hpp>
13
14namespace
15{
16
17bool isFinite(const cv::Mat& matrix)
18{
19 return !matrix.empty() && cv::checkRange(matrix, true, nullptr);
20}
21
22bool isFinite(double value)
23{
24 return std::isfinite(value);
25}
26
27bool isValidCamera(const stereo_calib::CameraCalibrationResult& camera)
28{
29 return camera.isValid() && camera.K.type() == CV_64F && camera.D.type() == CV_64F &&
30 camera.K.at<double>(0, 0) > 0.0 && camera.K.at<double>(1, 1) > 0.0 &&
31 isFinite(camera.K) && isFinite(camera.D) && isFinite(camera.rms);
32}
33
34bool isValidStereo(const stereo_calib::StereoCalibrationResult& stereo)
35{
36 return stereo.isValid() && stereo.R.type() == CV_64F && stereo.T.type() == CV_64F &&
37 isFinite(stereo.R) && isFinite(stereo.T) && isFinite(stereo.rms);
38}
39
40bool isValidRectification(const stereo_calib::RectificationResult& rectification)
41{
42 return rectification.isValid() && rectification.R1.type() == CV_64F &&
43 rectification.R2.type() == CV_64F && rectification.P1.type() == CV_64F &&
44 rectification.P2.type() == CV_64F && rectification.Q.type() == CV_64F &&
45 isFinite(rectification.R1) && isFinite(rectification.R2) &&
46 isFinite(rectification.P1) && isFinite(rectification.P2) &&
47 isFinite(rectification.Q) && isFinite(rectification.balance) &&
48 isFinite(rectification.fovScale);
49}
50
51bool isValidStereoQuality(const stereo_calib::CalibrationQualityMetrics& quality)
52{
53 return quality.acceptedObservations > 0 && isFinite(quality.baseline) && quality.baseline > 0.0 &&
54 isFinite(quality.meanTimestampDeltaMs) && isFinite(quality.maxTimestampDeltaMs) &&
55 isFinite(quality.meanVerticalRectificationErrorPx) &&
56 isFinite(quality.medianVerticalRectificationErrorPx) &&
57 isFinite(quality.rmsVerticalRectificationErrorPx) &&
58 isFinite(quality.p95VerticalRectificationErrorPx) &&
59 isFinite(quality.maxVerticalRectificationErrorPx) &&
60 quality.meanTimestampDeltaMs >= 0.0 && quality.maxTimestampDeltaMs >= 0.0 &&
61 quality.meanVerticalRectificationErrorPx >= 0.0 &&
63 quality.rmsVerticalRectificationErrorPx >= 0.0 &&
64 quality.p95VerticalRectificationErrorPx >= 0.0 &&
66}
67
68double matrixValue(const cv::Mat& matrix, int index)
69{
70 return matrix.reshape(1, 1).at<double>(0, index);
71}
72
73void writeMatrix(std::ostream& output, const std::string& name, const cv::Mat& matrix)
74{
75 output << name << " (";
76 for (int index = 0; index < static_cast<int>(matrix.total()); ++index)
77 {
78 if (index != 0)
79 {
80 output << ' ';
81 }
82 output << matrixValue(matrix, index);
83 }
84 output << ")\n";
85}
86
87void writeCamera(std::ostream& output, const char* side,
89{
90 output << "[CAMERA_CALIBRATION_" << side << "]\n";
91 output << "projection fisheye\n";
92 output << "drawCenterCross 0\n\n";
93 output << "w " << camera.imageSize.width << "\n";
94 output << "h " << camera.imageSize.height << "\n";
95 output << "fx " << matrixValue(camera.K, 0) << "\n";
96 output << "fy " << matrixValue(camera.K, 4) << "\n";
97 output << "cx " << matrixValue(camera.K, 2) << "\n";
98 output << "cy " << matrixValue(camera.K, 5) << "\n";
99 output << "k1 " << matrixValue(camera.D, 0) << "\n";
100 output << "k2 " << matrixValue(camera.D, 1) << "\n";
101 output << "k3 " << matrixValue(camera.D, 2) << "\n";
102 output << "k4 " << matrixValue(camera.D, 3) << "\n\n";
103}
104
105std::string temporaryObservationFile(const std::string& filename)
106{
107 const std::size_t separator = filename.find_last_of("/\\");
108 const std::size_t extension = filename.find_last_of('.');
109 if (extension != std::string::npos &&
110 (separator == std::string::npos || extension > separator))
111 {
112 return filename.substr(0, extension) + ".tmp" + filename.substr(extension);
113 }
114 return filename + ".tmp.yml";
115}
116
117std::string joinPath(const std::string& directory, const std::string& filename)
118{
119 if (directory.empty() || directory.back() == '/' || directory.back() == '\\')
120 {
121 return directory + filename;
122 }
123 return directory + '/' + filename;
124}
125
126bool isValidObservation(const stereo_calib::StereoObservation& observation)
127{
128 if (!observation.isValid() || !isFinite(observation.leftTimestampSeconds) ||
129 !isFinite(observation.rightTimestampSeconds) ||
130 !isFinite(observation.timestampDeltaSeconds))
131 {
132 return false;
133 }
134 for (std::size_t index = 0; index < observation.objectPoints.size(); ++index)
135 {
136 const cv::Point3f& objectPoint = observation.objectPoints[index];
137 const cv::Point2f& leftPoint = observation.leftImagePoints[index];
138 const cv::Point2f& rightPoint = observation.rightImagePoints[index];
139 if (!isFinite(objectPoint.x) || !isFinite(objectPoint.y) || !isFinite(objectPoint.z) ||
140 !isFinite(leftPoint.x) || !isFinite(leftPoint.y) ||
141 !isFinite(rightPoint.x) || !isFinite(rightPoint.y))
142 {
143 return false;
144 }
145 }
146 return true;
147}
148
149} // namespace
150
151namespace stereo_calib
152{
153
154bool CalibrationWriter::write(const std::string& outputFile,
155 const CalibrationResult& result,
156 std::string& errorMessage) const
157{
158 errorMessage.clear();
159 if (outputFile.empty())
160 {
161 errorMessage = "Calibration output filename is empty.";
162 return false;
163 }
164 if (!validateResult(result, errorMessage))
165 {
166 return false;
167 }
168
169 const std::string temporaryFile = outputFile + ".tmp";
170 std::ofstream output(temporaryFile.c_str(), std::ios::out | std::ios::trunc);
171 if (!output.is_open())
172 {
173 errorMessage = "Could not open temporary calibration file '" + temporaryFile +
174 "': " + std::strerror(errno);
175 return false;
176 }
177
178 output << std::setprecision(17);
179 output << "# Generated by stereoCalib. Do not edit while calibration is running.\n";
180 switch (result.mode)
181 {
182 case CalibrationMode::MonocularLeft: output << "calibrationMode MonocularLeft\n\n"; break;
183 case CalibrationMode::MonocularRight: output << "calibrationMode MonocularRight\n\n"; break;
184 case CalibrationMode::MonocularBoth: output << "calibrationMode MonocularBoth\n\n"; break;
185 case CalibrationMode::StereoFull: output << "calibrationMode StereoFull\n\n"; break;
186 }
187
188 const bool writesLeftCamera = result.mode != CalibrationMode::MonocularRight;
189 const bool writesRightCamera = result.mode != CalibrationMode::MonocularLeft;
190 if (writesLeftCamera)
191 {
192 writeCamera(output, "LEFT", result.leftCamera);
193 }
194 if (writesRightCamera)
195 {
196 writeCamera(output, "RIGHT", result.rightCamera);
197 }
198
199 if (result.mode == CalibrationMode::StereoFull)
200 {
201 cv::Mat homogeneousTransform = cv::Mat::eye(4, 4, CV_64F);
202 result.stereo.R.copyTo(homogeneousTransform(cv::Rect(0, 0, 3, 3)));
203 result.stereo.T.reshape(1, 3).copyTo(homogeneousTransform(cv::Rect(3, 0, 1, 3)));
204
205 output << "[STEREO_DISPARITY]\n";
206 // This is deliberately left-to-right: Xright = R * Xleft + T.
207 writeMatrix(output, "HN", homogeneousTransform);
208 writeMatrix(output, "R", result.stereo.R);
209 writeMatrix(output, "T", result.stereo.T);
210 output << "\n[STEREO_RECTIFICATION]\n";
211 writeMatrix(output, "R1", result.rectification.R1);
212 writeMatrix(output, "R2", result.rectification.R2);
213 writeMatrix(output, "P1", result.rectification.P1);
214 writeMatrix(output, "P2", result.rectification.P2);
215 writeMatrix(output, "Q", result.rectification.Q);
216 output << "balance " << result.rectification.balance << "\n";
217 output << "fovScale " << result.rectification.fovScale << "\n";
218 output << "zeroDisparity " << (result.rectification.zeroDisparity ? 1 : 0) << "\n\n";
219 }
220
221 output << "[CALIBRATION_QUALITY]\n";
222 if (writesLeftCamera)
223 {
224 output << "leftRms " << result.leftCamera.rms << "\n";
225 }
226 if (writesRightCamera)
227 {
228 output << "rightRms " << result.rightCamera.rms << "\n";
229 }
230 if (result.mode == CalibrationMode::StereoFull)
231 {
232 output << "stereoRms " << result.stereo.rms << "\n";
233 output << "baseline " << result.quality.baseline << "\n";
234 output << "synchronizedPairs " << result.quality.synchronizedPairs << "\n";
235 output << "acceptedObservations " << result.quality.acceptedObservations << "\n";
236 output << "rejectedDetections " << result.quality.rejectedDetections << "\n";
237 output << "meanTimestampDeltaMs " << result.quality.meanTimestampDeltaMs << "\n";
238 output << "maxTimestampDeltaMs " << result.quality.maxTimestampDeltaMs << "\n";
239 output << "meanVerticalErrorPx " << result.quality.meanVerticalRectificationErrorPx << "\n";
240 output << "medianVerticalErrorPx " << result.quality.medianVerticalRectificationErrorPx << "\n";
241 output << "rmsVerticalErrorPx " << result.quality.rmsVerticalRectificationErrorPx << "\n";
242 output << "p95VerticalErrorPx " << result.quality.p95VerticalRectificationErrorPx << "\n";
243 output << "maxVerticalErrorPx " << result.quality.maxVerticalRectificationErrorPx << "\n";
244 }
245
246 output.flush();
247 if (!output.good())
248 {
249 errorMessage = "Failed while writing temporary calibration file '" + temporaryFile + "'.";
250 return false;
251 }
252 output.close();
253 if (output.fail())
254 {
255 errorMessage = "Could not close temporary calibration file '" + temporaryFile + "'.";
256 return false;
257 }
258
259 std::ifstream validation(temporaryFile.c_str());
260 if (!validation.good())
261 {
262 errorMessage = "Could not validate temporary calibration file '" + temporaryFile + "'.";
263 return false;
264 }
265 validation.close();
266 if (std::rename(temporaryFile.c_str(), outputFile.c_str()) != 0)
267 {
268 errorMessage = "Could not replace calibration file '" + outputFile + "' with temporary file: " +
269 std::strerror(errno);
270 return false;
271 }
272 return true;
273}
274
275bool CalibrationWriter::writeObservations(const std::string& outputFile,
276 const std::vector<StereoObservation>& observations,
277 std::string& errorMessage) const
278{
279 errorMessage.clear();
280 if (outputFile.empty())
281 {
282 errorMessage = "Observation output filename is empty.";
283 return false;
284 }
285 for (std::size_t index = 0; index < observations.size(); ++index)
286 {
287 if (!isValidObservation(observations[index]))
288 {
289 errorMessage = "Observation #" + std::to_string(index) + " is invalid.";
290 return false;
291 }
292 }
293
294 const std::string temporaryFile = temporaryObservationFile(outputFile);
295 try
296 {
297 cv::FileStorage storage(temporaryFile, cv::FileStorage::WRITE);
298 if (!storage.isOpened())
299 {
300 errorMessage = "Could not open temporary observation file '" + temporaryFile + "'.";
301 return false;
302 }
303
304 storage << "observations" << "[";
305 for (std::size_t index = 0; index < observations.size(); ++index)
306 {
307 const StereoObservation& observation = observations[index];
308 storage << "{";
309 storage << "index" << static_cast<int>(index);
310 storage << "imageWidth" << observation.imageSize.width;
311 storage << "imageHeight" << observation.imageSize.height;
312 storage << "leftTimestampSeconds" << observation.leftTimestampSeconds;
313 storage << "rightTimestampSeconds" << observation.rightTimestampSeconds;
314 storage << "timestampDeltaSeconds" << observation.timestampDeltaSeconds;
315 storage << "leftSequenceNumber" << std::to_string(observation.leftSequenceNumber);
316 storage << "rightSequenceNumber" << std::to_string(observation.rightSequenceNumber);
317 storage << "leftImageFilename" << observation.leftImageFilename;
318 storage << "rightImageFilename" << observation.rightImageFilename;
319 storage << "objectPoints" << cv::Mat(observation.objectPoints);
320 storage << "leftImagePoints" << cv::Mat(observation.leftImagePoints);
321 storage << "rightImagePoints" << cv::Mat(observation.rightImagePoints);
322 storage << "}";
323 }
324 storage << "]";
325 storage.release();
326
327 cv::FileStorage validation(temporaryFile, cv::FileStorage::READ);
328 const cv::FileNode serializedObservations = validation["observations"];
329 if (!validation.isOpened() || serializedObservations.type() != cv::FileNode::SEQ ||
330 serializedObservations.size() != observations.size())
331 {
332 errorMessage = "Could not validate temporary observation file '" + temporaryFile + "'.";
333 return false;
334 }
335 }
336 catch (const cv::Exception& exception)
337 {
338 errorMessage = std::string("Could not write observation file: ") + exception.what();
339 return false;
340 }
341 if (std::rename(temporaryFile.c_str(), outputFile.c_str()) != 0)
342 {
343 errorMessage = "Could not replace observation file '" + outputFile + "' with temporary file: " +
344 std::strerror(errno);
345 return false;
346 }
347 return true;
348}
349
350bool CalibrationWriter::writeImagePair(const std::string& outputDirectory,
351 std::size_t observationIndex,
352 const cv::Mat& leftImg,
353 const cv::Mat& rightImg,
354 std::string& leftFile,
355 std::string& rightFile,
356 std::string& errorMessage) const
357{
358 errorMessage.clear();
359 leftFile.clear();
360 rightFile.clear();
361 if (outputDirectory.empty() || leftImg.empty() || rightImg.empty())
362 {
363 errorMessage = "Cannot save an image pair without an output directory and two images.";
364 return false;
365 }
366 if (leftImg.size() != rightImg.size())
367 {
368 errorMessage = "Cannot save an image pair with different image sizes.";
369 return false;
370 }
371
372 std::ostringstream name;
373 name << std::setfill('0') << std::setw(4) << observationIndex;
374 leftFile = "left_" + name.str() + ".png";
375 rightFile = "right_" + name.str() + ".png";
376
377 cv::Mat leftToWrite;
378 cv::Mat rightToWrite;
379 try
380 {
381 if (leftImg.channels() == 3)
382 {
383 cv::cvtColor(leftImg, leftToWrite, cv::COLOR_RGB2BGR);
384 }
385 else
386 {
387 leftToWrite = leftImg.clone();
388 }
389 if (rightImg.channels() == 3)
390 {
391 cv::cvtColor(rightImg, rightToWrite, cv::COLOR_RGB2BGR);
392 }
393 else
394 {
395 rightToWrite = rightImg.clone();
396 }
397
398 if (!cv::imwrite(joinPath(outputDirectory, leftFile), leftToWrite))
399 {
400 errorMessage = "Could not write left calibration image '" + joinPath(outputDirectory, leftFile) + "'.";
401 return false;
402 }
403 if (!cv::imwrite(joinPath(outputDirectory, rightFile), rightToWrite))
404 {
405 errorMessage = "Could not write right calibration image '" + joinPath(outputDirectory, rightFile) + "'.";
406 return false;
407 }
408 }
409 catch (const cv::Exception& exception)
410 {
411 errorMessage = std::string("Could not write calibration images: ") + exception.what();
412 return false;
413 }
414 return true;
415}
416
417bool CalibrationWriter::validateResult(const CalibrationResult& result,
418 std::string& errorMessage) const
419{
420 errorMessage.clear();
421 switch (result.mode)
422 {
424 if (!isValidCamera(result.leftCamera))
425 {
426 errorMessage = "Monocular-left result does not contain valid left fisheye intrinsics.";
427 return false;
428 }
429 return true;
431 if (!isValidCamera(result.rightCamera))
432 {
433 errorMessage = "Monocular-right result does not contain valid right fisheye intrinsics.";
434 return false;
435 }
436 return true;
438 if (!isValidCamera(result.leftCamera) || !isValidCamera(result.rightCamera))
439 {
440 errorMessage = "Monocular-both result does not contain valid fisheye intrinsics for both cameras.";
441 return false;
442 }
443 return true;
445 if (!isValidCamera(result.leftCamera) || !isValidCamera(result.rightCamera) ||
446 !isValidStereo(result.stereo) || !isValidRectification(result.rectification) ||
447 !isValidStereoQuality(result.quality))
448 {
449 errorMessage = "Stereo result is missing valid intrinsics, transform, rectification, or quality metrics.";
450 return false;
451 }
452 return true;
453 }
454 errorMessage = "Unknown calibration mode.";
455 return false;
456}
457
458} // namespace stereo_calib
bool writeImagePair(const std::string &outputDirectory, std::size_t observationIndex, const cv::Mat &leftImg, const cv::Mat &rightImg, std::string &leftFile, std::string &rightFile, std::string &errorMessage) const
bool write(const std::string &outputFile, const CalibrationResult &result, std::string &errorMessage) const
bool writeObservations(const std::string &outputFile, const std::vector< StereoObservation > &observations, std::string &errorMessage) const
CameraCalibrationResult leftCamera
StereoCalibrationResult stereo
CameraCalibrationResult rightCamera
CalibrationQualityMetrics quality
std::vector< cv::Point2f > leftImagePoints
std::vector< cv::Point3f > objectPoints
std::vector< cv::Point2f > rightImagePoints