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);
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);
36 return stereo.
isValid() && stereo.
R.type() == CV_64F && stereo.
T.type() == CV_64F &&
37 isFinite(stereo.
R) && isFinite(stereo.
T) && isFinite(stereo.
rms);
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) &&
68double matrixValue(
const cv::Mat& matrix,
int index)
70 return matrix.reshape(1, 1).at<
double>(0, index);
73void writeMatrix(std::ostream& output,
const std::string& name,
const cv::Mat& matrix)
75 output << name <<
" (";
76 for (
int index = 0; index < static_cast<int>(matrix.total()); ++index)
82 output << matrixValue(matrix, index);
87void writeCamera(std::ostream& output,
const char* side,
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";
105std::string temporaryObservationFile(
const std::string& filename)
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))
112 return filename.substr(0, extension) +
".tmp" + filename.substr(extension);
114 return filename +
".tmp.yml";
117std::string joinPath(
const std::string& directory,
const std::string& filename)
134 for (std::size_t index = 0; index < observation.
objectPoints.size(); ++index)
136 const cv::Point3f& objectPoint = observation.
objectPoints[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))
156 std::string& errorMessage)
const
158 errorMessage.clear();
159 if (outputFile.empty())
161 errorMessage =
"Calibration output filename is empty.";
164 if (!validateResult(result, errorMessage))
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())
173 errorMessage =
"Could not open temporary calibration file '" + temporaryFile +
174 "': " + std::strerror(errno);
178 output << std::setprecision(17);
179 output <<
"# Generated by stereoCalib. Do not edit while calibration is running.\n";
190 if (writesLeftCamera)
192 writeCamera(output,
"LEFT", result.
leftCamera);
194 if (writesRightCamera)
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)));
205 output <<
"[STEREO_DISPARITY]\n";
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";
221 output <<
"[CALIBRATION_QUALITY]\n";
222 if (writesLeftCamera)
226 if (writesRightCamera)
232 output <<
"stereoRms " << result.
stereo.
rms <<
"\n";
249 errorMessage =
"Failed while writing temporary calibration file '" + temporaryFile +
"'.";
255 errorMessage =
"Could not close temporary calibration file '" + temporaryFile +
"'.";
259 std::ifstream validation(temporaryFile.c_str());
260 if (!validation.good())
262 errorMessage =
"Could not validate temporary calibration file '" + temporaryFile +
"'.";
266 if (std::rename(temporaryFile.c_str(), outputFile.c_str()) != 0)
268 errorMessage =
"Could not replace calibration file '" + outputFile +
"' with temporary file: " +
269 std::strerror(errno);
276 const std::vector<StereoObservation>& observations,
277 std::string& errorMessage)
const
279 errorMessage.clear();
280 if (outputFile.empty())
282 errorMessage =
"Observation output filename is empty.";
285 for (std::size_t index = 0; index < observations.size(); ++index)
287 if (!isValidObservation(observations[index]))
289 errorMessage =
"Observation #" + std::to_string(index) +
" is invalid.";
294 const std::string temporaryFile = temporaryObservationFile(outputFile);
297 cv::FileStorage storage(temporaryFile, cv::FileStorage::WRITE);
298 if (!storage.isOpened())
300 errorMessage =
"Could not open temporary observation file '" + temporaryFile +
"'.";
304 storage <<
"observations" <<
"[";
305 for (std::size_t index = 0; index < observations.size(); ++index)
309 storage <<
"index" <<
static_cast<int>(index);
310 storage <<
"imageWidth" << observation.
imageSize.width;
311 storage <<
"imageHeight" << observation.
imageSize.height;
319 storage <<
"objectPoints" << cv::Mat(observation.
objectPoints);
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())
332 errorMessage =
"Could not validate temporary observation file '" + temporaryFile +
"'.";
336 catch (
const cv::Exception& exception)
338 errorMessage = std::string(
"Could not write observation file: ") + exception.what();
341 if (std::rename(temporaryFile.c_str(), outputFile.c_str()) != 0)
343 errorMessage =
"Could not replace observation file '" + outputFile +
"' with temporary file: " +
344 std::strerror(errno);
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
358 errorMessage.clear();
361 if (outputDirectory.empty() || leftImg.empty() || rightImg.empty())
363 errorMessage =
"Cannot save an image pair without an output directory and two images.";
366 if (leftImg.size() != rightImg.size())
368 errorMessage =
"Cannot save an image pair with different image sizes.";
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";
378 cv::Mat rightToWrite;
381 if (leftImg.channels() == 3)
383 cv::cvtColor(leftImg, leftToWrite, cv::COLOR_RGB2BGR);
387 leftToWrite = leftImg.clone();
389 if (rightImg.channels() == 3)
391 cv::cvtColor(rightImg, rightToWrite, cv::COLOR_RGB2BGR);
395 rightToWrite = rightImg.clone();
398 if (!cv::imwrite(joinPath(outputDirectory, leftFile), leftToWrite))
400 errorMessage =
"Could not write left calibration image '" + joinPath(outputDirectory, leftFile) +
"'.";
403 if (!cv::imwrite(joinPath(outputDirectory, rightFile), rightToWrite))
405 errorMessage =
"Could not write right calibration image '" + joinPath(outputDirectory, rightFile) +
"'.";
409 catch (
const cv::Exception& exception)
411 errorMessage = std::string(
"Could not write calibration images: ") + exception.what();
418 std::string& errorMessage)
const
420 errorMessage.clear();
426 errorMessage =
"Monocular-left result does not contain valid left fisheye intrinsics.";
433 errorMessage =
"Monocular-right result does not contain valid right fisheye intrinsics.";
440 errorMessage =
"Monocular-both result does not contain valid fisheye intrinsics for both cameras.";
447 !isValidStereoQuality(result.
quality))
449 errorMessage =
"Stereo result is missing valid intrinsics, transform, rectification, or quality metrics.";
454 errorMessage =
"Unknown calibration mode.";
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
double rmsVerticalRectificationErrorPx
std::size_t synchronizedPairs
double medianVerticalRectificationErrorPx
std::size_t acceptedObservations
double meanTimestampDeltaMs
double maxVerticalRectificationErrorPx
double p95VerticalRectificationErrorPx
double maxTimestampDeltaMs
double meanVerticalRectificationErrorPx
std::size_t rejectedDetections
RectificationResult rectification
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