11#include <opencv2/imgcodecs.hpp>
12#include <opencv2/imgproc.hpp>
17bool isFinite(
const cv::Mat& matrix)
19 return !matrix.empty() && cv::checkRange(matrix,
true,
nullptr);
22bool isFinite(
double value)
24 return std::isfinite(value);
27double matrixValue(
const cv::Mat& matrix,
int index)
29 return matrix.reshape(1, 1).at<
double>(0, index);
32void writeMatrix(std::ostream& output,
const std::string& name,
const cv::Mat& matrix)
34 output << name <<
" (";
35 for (
int index = 0; index < static_cast<int>(matrix.total()); ++index)
41 output << matrixValue(matrix, index);
46void writeCamera(std::ostream& output,
const char* side, std::string projection,
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")
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";
65 else if (projection ==
"pinhole")
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";
74 output <<
"# Unknown projection type '" << projection
75 <<
"'; distortion coefficients not written.\n\n";
79std::string temporaryObservationFile(
const std::string& filename)
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))
86 return filename.substr(0, extension) +
".tmp" + filename.substr(extension);
88 return filename +
".tmp.yml";
91std::string joinPath(
const std::string& directory,
const std::string& filename)
108 for (std::size_t index = 0; index < observation.
objectPoints.size(); ++index)
110 const cv::Point3f& objectPoint = observation.
objectPoints[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))
130 std::string& errorMessage)
const
132 errorMessage.clear();
133 if (outputFile.empty())
135 errorMessage =
"Calibration output filename is empty.";
138 if (!validateResult(result, errorMessage))
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())
147 errorMessage =
"Could not open temporary calibration file '" + temporaryFile +
148 "': " + std::strerror(errno);
152 output << std::setprecision(17);
153 output <<
"# Generated by stereoCalib. Do not edit while calibration is running.\n";
165 if (writesLeftCamera)
167 writeCamera(output,
"LEFT", projection, result.
leftCamera);
169 if (writesRightCamera)
171 writeCamera(output,
"RIGHT", projection, result.
rightCamera);
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)));
180 output <<
"[STEREO_DISPARITY]\n";
182 writeMatrix(output,
"HN", homogeneousTransform);
183 writeMatrix(output,
"R", result.
stereo.
R);
184 writeMatrix(output,
"T", result.
stereo.
T);
187 output <<
"[CALIBRATION_QUALITY]\n";
188 if (writesLeftCamera)
192 if (writesRightCamera)
198 output <<
"stereoRms " << result.
stereo.
rms <<
"\n";
210 errorMessage =
"Failed while writing temporary calibration file '" + temporaryFile +
"'.";
216 errorMessage =
"Could not close temporary calibration file '" + temporaryFile +
"'.";
220 std::ifstream validation(temporaryFile.c_str());
221 if (!validation.good())
223 errorMessage =
"Could not validate temporary calibration file '" + temporaryFile +
"'.";
227 if (std::rename(temporaryFile.c_str(), outputFile.c_str()) != 0)
229 errorMessage =
"Could not replace calibration file '" + outputFile +
"' with temporary file: " +
230 std::strerror(errno);
237 const std::vector<StereoObservation>& observations,
238 std::string& errorMessage)
const
240 errorMessage.clear();
241 if (outputFile.empty())
243 errorMessage =
"Observation output filename is empty.";
246 for (std::size_t index = 0; index < observations.size(); ++index)
248 if (!isValidObservation(observations[index]))
250 errorMessage =
"Observation #" + std::to_string(index) +
" is invalid.";
255 const std::string temporaryFile = temporaryObservationFile(outputFile);
258 cv::FileStorage storage(temporaryFile, cv::FileStorage::WRITE);
259 if (!storage.isOpened())
261 errorMessage =
"Could not open temporary observation file '" + temporaryFile +
"'.";
265 storage <<
"observations" <<
"[";
266 for (std::size_t index = 0; index < observations.size(); ++index)
270 storage <<
"index" <<
static_cast<int>(index);
271 storage <<
"imageWidth" << observation.
imageSize.width;
272 storage <<
"imageHeight" << observation.
imageSize.height;
280 storage <<
"objectPoints" << cv::Mat(observation.
objectPoints);
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())
293 errorMessage =
"Could not validate temporary observation file '" + temporaryFile +
"'.";
297 catch (
const cv::Exception& exception)
299 errorMessage = std::string(
"Could not write observation file: ") + exception.what();
302 if (std::rename(temporaryFile.c_str(), outputFile.c_str()) != 0)
304 errorMessage =
"Could not replace observation file '" + outputFile +
"' with temporary file: " +
305 std::strerror(errno);
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
319 errorMessage.clear();
322 if (outputDirectory.empty() || leftImg.empty() || rightImg.empty())
324 errorMessage =
"Cannot save an image pair without an output directory and two images.";
327 if (leftImg.size() != rightImg.size())
329 errorMessage =
"Cannot save an image pair with different image sizes.";
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";
339 cv::Mat rightToWrite;
342 if (leftImg.channels() == 3)
344 cv::cvtColor(leftImg, leftToWrite, cv::COLOR_RGB2BGR);
348 leftToWrite = leftImg.clone();
350 if (rightImg.channels() == 3)
352 cv::cvtColor(rightImg, rightToWrite, cv::COLOR_RGB2BGR);
356 rightToWrite = rightImg.clone();
359 if (!cv::imwrite(joinPath(outputDirectory, leftFile), leftToWrite))
361 errorMessage =
"Could not write left calibration image '" + joinPath(outputDirectory, leftFile) +
"'.";
364 if (!cv::imwrite(joinPath(outputDirectory, rightFile), rightToWrite))
366 errorMessage =
"Could not write right calibration image '" + joinPath(outputDirectory, rightFile) +
"'.";
370 catch (
const cv::Exception& exception)
372 errorMessage = std::string(
"Could not write calibration images: ") + exception.what();
379 std::string& errorMessage)
const
381 errorMessage.clear();
384 errorMessage =
"Cannot serialize an invalid calibration result";
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
std::size_t synchronizedPairs
std::size_t acceptedObservations
double meanTimestampDeltaMs
double maxTimestampDeltaMs
std::size_t rejectedDetections
CameraCalibrationResult leftCamera
StereoCalibrationResult stereo
CameraCalibrationResult rightCamera
CalibrationQualityMetrics quality
double timestampDeltaSeconds
int64_t leftSequenceNumber
std::string leftImageFilename
int64_t rightSequenceNumber
std::vector< cv::Point2f > leftImagePoints
std::vector< cv::Point3f > objectPoints
std::string rightImageFilename
double rightTimestampSeconds
double leftTimestampSeconds
std::vector< cv::Point2f > rightImagePoints