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
27double matrixValue(const cv::Mat& matrix, int index)
28{
29 return matrix.reshape(1, 1).at<double>(0, index);
30}
31
32void writeMatrix(std::ostream& output, const std::string& name, const cv::Mat& matrix)
33{
34 output << name << " (";
35 for (int index = 0; index < static_cast<int>(matrix.total()); ++index)
36 {
37 if (index != 0)
38 {
39 output << ' ';
40 }
41 output << matrixValue(matrix, index);
42 }
43 output << ")\n";
44}
45
46void writeCamera(std::ostream& output, const char* side, std::string projection,
48{
49 output << "[CAMERA_CALIBRATION_" << side << "]\n";
50 output << "projection " << projection << "\n";
51 output << "drawCenterCross 0\n\n";
52 output << "w " << camera.imageSize.width << "\n";
53 output << "h " << camera.imageSize.height << "\n";
54 output << "fx " << matrixValue(camera.K, 0) << "\n";
55 output << "fy " << matrixValue(camera.K, 4) << "\n";
56 output << "cx " << matrixValue(camera.K, 2) << "\n";
57 output << "cy " << matrixValue(camera.K, 5) << "\n";
58 if (projection == "fisheye")
59 {
60 output << "k1 " << matrixValue(camera.D, 0) << "\n";
61 output << "k2 " << matrixValue(camera.D, 1) << "\n";
62 output << "k3 " << matrixValue(camera.D, 2) << "\n";
63 output << "k4 " << matrixValue(camera.D, 3) << "\n\n";
64 }
65 else if (projection == "pinhole")
66 {
67 output << "k1 " << matrixValue(camera.D, 0) << "\n";
68 output << "k2 " << matrixValue(camera.D, 1) << "\n";
69 output << "p1 " << matrixValue(camera.D, 2) << "\n";
70 output << "p2 " << matrixValue(camera.D, 3) << "\n\n";
71 }
72 else
73 {
74 output << "# Unknown projection type '" << projection
75 << "'; distortion coefficients not written.\n\n";
76 }
77}
78
79std::string temporaryObservationFile(const std::string& filename)
80{
81 const std::size_t separator = filename.find_last_of("/\\");
82 const std::size_t extension = filename.find_last_of('.');
83 if (extension != std::string::npos &&
84 (separator == std::string::npos || extension > separator))
85 {
86 return filename.substr(0, extension) + ".tmp" + filename.substr(extension);
87 }
88 return filename + ".tmp.yml";
89}
90
91std::string joinPath(const std::string& directory, const std::string& filename)
92{
93 if (directory.empty() || directory.back() == '/' || directory.back() == '\\')
94 {
95 return directory + filename;
96 }
97 return directory + '/' + filename;
98}
99
100bool isValidObservation(const stereo_calib::StereoObservation& observation)
101{
102 if (!observation.isValid() || !isFinite(observation.leftTimestampSeconds) ||
103 !isFinite(observation.rightTimestampSeconds) ||
104 !isFinite(observation.timestampDeltaSeconds))
105 {
106 return false;
107 }
108 for (std::size_t index = 0; index < observation.objectPoints.size(); ++index)
109 {
110 const cv::Point3f& objectPoint = observation.objectPoints[index];
111 const cv::Point2f& leftPoint = observation.leftImagePoints[index];
112 const cv::Point2f& rightPoint = observation.rightImagePoints[index];
113 if (!isFinite(objectPoint.x) || !isFinite(objectPoint.y) || !isFinite(objectPoint.z) ||
114 !isFinite(leftPoint.x) || !isFinite(leftPoint.y) ||
115 !isFinite(rightPoint.x) || !isFinite(rightPoint.y))
116 {
117 return false;
118 }
119 }
120 return true;
121}
122
123} // namespace
124
125namespace stereo_calib
126{
127
128bool CalibrationWriter::write(const std::string& outputFile,
129 const CalibrationResult& result,
130 std::string& errorMessage) const
131{
132 errorMessage.clear();
133 if (outputFile.empty())
134 {
135 errorMessage = "Calibration output filename is empty.";
136 return false;
137 }
138 if (!validateResult(result, errorMessage))
139 {
140 return false;
141 }
142
143 const std::string temporaryFile = outputFile + ".tmp";
144 std::ofstream output(temporaryFile.c_str(), std::ios::out | std::ios::trunc);
145 if (!output.is_open())
146 {
147 errorMessage = "Could not open temporary calibration file '" + temporaryFile +
148 "': " + std::strerror(errno);
149 return false;
150 }
151
152 output << std::setprecision(17);
153 output << "# Generated by stereoCalib. Do not edit while calibration is running.\n";
154 switch (result.mode)
155 {
156 case CalibrationMode::MonocularLeft: output << "calibrationMode MonocularLeft\n\n"; break;
157 case CalibrationMode::MonocularRight: output << "calibrationMode MonocularRight\n\n"; break;
158 case CalibrationMode::MonocularBoth: output << "calibrationMode MonocularBoth\n\n"; break;
159 case CalibrationMode::StereoFull: output << "calibrationMode StereoFull\n\n"; break;
160 }
161
162 const bool writesLeftCamera = result.mode != CalibrationMode::MonocularRight;
163 const bool writesRightCamera = result.mode != CalibrationMode::MonocularLeft;
164 const std::string projection = (result.model == CameraModel::Pinhole) ? "pinhole" : "fisheye";
165 if (writesLeftCamera)
166 {
167 writeCamera(output, "LEFT", projection, result.leftCamera);
168 }
169 if (writesRightCamera)
170 {
171 writeCamera(output, "RIGHT", projection, result.rightCamera);
172 }
173
174 if (result.mode == CalibrationMode::StereoFull)
175 {
176 cv::Mat homogeneousTransform = cv::Mat::eye(4, 4, CV_64F);
177 result.stereo.R.copyTo(homogeneousTransform(cv::Rect(0, 0, 3, 3)));
178 result.stereo.T.reshape(1, 3).copyTo(homogeneousTransform(cv::Rect(3, 0, 1, 3)));
179
180 output << "[STEREO_DISPARITY]\n";
181 // This is deliberately left-to-right: Xright = R * Xleft + T.
182 writeMatrix(output, "HN", homogeneousTransform);
183 writeMatrix(output, "R", result.stereo.R);
184 writeMatrix(output, "T", result.stereo.T);
185 }
186
187 output << "[CALIBRATION_QUALITY]\n";
188 if (writesLeftCamera)
189 {
190 output << "leftRms " << result.leftCamera.rms << "\n";
191 }
192 if (writesRightCamera)
193 {
194 output << "rightRms " << result.rightCamera.rms << "\n";
195 }
196 if (result.mode == CalibrationMode::StereoFull)
197 {
198 output << "stereoRms " << result.stereo.rms << "\n";
199 output << "baseline " << result.quality.baseline << "\n";
200 output << "synchronizedPairs " << result.quality.synchronizedPairs << "\n";
201 output << "acceptedObservations " << result.quality.acceptedObservations << "\n";
202 output << "rejectedDetections " << result.quality.rejectedDetections << "\n";
203 output << "meanTimestampDeltaMs " << result.quality.meanTimestampDeltaMs << "\n";
204 output << "maxTimestampDeltaMs " << result.quality.maxTimestampDeltaMs << "\n";
205 }
206
207 output.flush();
208 if (!output.good())
209 {
210 errorMessage = "Failed while writing temporary calibration file '" + temporaryFile + "'.";
211 return false;
212 }
213 output.close();
214 if (output.fail())
215 {
216 errorMessage = "Could not close temporary calibration file '" + temporaryFile + "'.";
217 return false;
218 }
219
220 std::ifstream validation(temporaryFile.c_str());
221 if (!validation.good())
222 {
223 errorMessage = "Could not validate temporary calibration file '" + temporaryFile + "'.";
224 return false;
225 }
226 validation.close();
227 if (std::rename(temporaryFile.c_str(), outputFile.c_str()) != 0)
228 {
229 errorMessage = "Could not replace calibration file '" + outputFile + "' with temporary file: " +
230 std::strerror(errno);
231 return false;
232 }
233 return true;
234}
235
236bool CalibrationWriter::writeObservations(const std::string& outputFile,
237 const std::vector<StereoObservation>& observations,
238 std::string& errorMessage) const
239{
240 errorMessage.clear();
241 if (outputFile.empty())
242 {
243 errorMessage = "Observation output filename is empty.";
244 return false;
245 }
246 for (std::size_t index = 0; index < observations.size(); ++index)
247 {
248 if (!isValidObservation(observations[index]))
249 {
250 errorMessage = "Observation #" + std::to_string(index) + " is invalid.";
251 return false;
252 }
253 }
254
255 const std::string temporaryFile = temporaryObservationFile(outputFile);
256 try
257 {
258 cv::FileStorage storage(temporaryFile, cv::FileStorage::WRITE);
259 if (!storage.isOpened())
260 {
261 errorMessage = "Could not open temporary observation file '" + temporaryFile + "'.";
262 return false;
263 }
264
265 storage << "observations" << "[";
266 for (std::size_t index = 0; index < observations.size(); ++index)
267 {
268 const StereoObservation& observation = observations[index];
269 storage << "{";
270 storage << "index" << static_cast<int>(index);
271 storage << "imageWidth" << observation.imageSize.width;
272 storage << "imageHeight" << observation.imageSize.height;
273 storage << "leftTimestampSeconds" << observation.leftTimestampSeconds;
274 storage << "rightTimestampSeconds" << observation.rightTimestampSeconds;
275 storage << "timestampDeltaSeconds" << observation.timestampDeltaSeconds;
276 storage << "leftSequenceNumber" << std::to_string(observation.leftSequenceNumber);
277 storage << "rightSequenceNumber" << std::to_string(observation.rightSequenceNumber);
278 storage << "leftImageFilename" << observation.leftImageFilename;
279 storage << "rightImageFilename" << observation.rightImageFilename;
280 storage << "objectPoints" << cv::Mat(observation.objectPoints);
281 storage << "leftImagePoints" << cv::Mat(observation.leftImagePoints);
282 storage << "rightImagePoints" << cv::Mat(observation.rightImagePoints);
283 storage << "}";
284 }
285 storage << "]";
286 storage.release();
287
288 cv::FileStorage validation(temporaryFile, cv::FileStorage::READ);
289 const cv::FileNode serializedObservations = validation["observations"];
290 if (!validation.isOpened() || serializedObservations.type() != cv::FileNode::SEQ ||
291 serializedObservations.size() != observations.size())
292 {
293 errorMessage = "Could not validate temporary observation file '" + temporaryFile + "'.";
294 return false;
295 }
296 }
297 catch (const cv::Exception& exception)
298 {
299 errorMessage = std::string("Could not write observation file: ") + exception.what();
300 return false;
301 }
302 if (std::rename(temporaryFile.c_str(), outputFile.c_str()) != 0)
303 {
304 errorMessage = "Could not replace observation file '" + outputFile + "' with temporary file: " +
305 std::strerror(errno);
306 return false;
307 }
308 return true;
309}
310
311bool CalibrationWriter::writeImagePair(const std::string& outputDirectory,
312 std::size_t observationIndex,
313 const cv::Mat& leftImg,
314 const cv::Mat& rightImg,
315 std::string& leftFile,
316 std::string& rightFile,
317 std::string& errorMessage) const
318{
319 errorMessage.clear();
320 leftFile.clear();
321 rightFile.clear();
322 if (outputDirectory.empty() || leftImg.empty() || rightImg.empty())
323 {
324 errorMessage = "Cannot save an image pair without an output directory and two images.";
325 return false;
326 }
327 if (leftImg.size() != rightImg.size())
328 {
329 errorMessage = "Cannot save an image pair with different image sizes.";
330 return false;
331 }
332
333 std::ostringstream name;
334 name << std::setfill('0') << std::setw(4) << observationIndex;
335 leftFile = "left_" + name.str() + ".png";
336 rightFile = "right_" + name.str() + ".png";
337
338 cv::Mat leftToWrite;
339 cv::Mat rightToWrite;
340 try
341 {
342 if (leftImg.channels() == 3)
343 {
344 cv::cvtColor(leftImg, leftToWrite, cv::COLOR_RGB2BGR);
345 }
346 else
347 {
348 leftToWrite = leftImg.clone();
349 }
350 if (rightImg.channels() == 3)
351 {
352 cv::cvtColor(rightImg, rightToWrite, cv::COLOR_RGB2BGR);
353 }
354 else
355 {
356 rightToWrite = rightImg.clone();
357 }
358
359 if (!cv::imwrite(joinPath(outputDirectory, leftFile), leftToWrite))
360 {
361 errorMessage = "Could not write left calibration image '" + joinPath(outputDirectory, leftFile) + "'.";
362 return false;
363 }
364 if (!cv::imwrite(joinPath(outputDirectory, rightFile), rightToWrite))
365 {
366 errorMessage = "Could not write right calibration image '" + joinPath(outputDirectory, rightFile) + "'.";
367 return false;
368 }
369 }
370 catch (const cv::Exception& exception)
371 {
372 errorMessage = std::string("Could not write calibration images: ") + exception.what();
373 return false;
374 }
375 return true;
376}
377
378bool CalibrationWriter::validateResult(const CalibrationResult& result,
379 std::string& errorMessage) const
380{
381 errorMessage.clear();
382 if (!result.isValid())
383 {
384 errorMessage = "Cannot serialize an invalid calibration result";
385 return false;
386 }
387 return true;
388}
389
390} // 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