11constexpr std::size_t minimumObservations = 30;
12constexpr std::size_t minimumPointsPerObservation = 12;
14bool isFinite(
const cv::Point2f& point)
16 return std::isfinite(point.x) && std::isfinite(point.y);
19bool isFinite(
const cv::Point3f& point)
21 return std::isfinite(point.x) && std::isfinite(point.y) && std::isfinite(point.z);
30 const std::vector<StereoObservation>& observations,
33 std::string& errorMessage)
const
48 errorMessage =
"Invalid fisheye calibration options.";
52 if (!validateObservations(observations, options.
common.
imageSize, errorMessage))
90 !calibrateStereo(observations, options, calculated.
leftCamera,
98 errorMessage =
"Unknown calibration mode.";
104 errorMessage =
"Calibration produced an invalid result.";
108 result = std::move(calculated);
111 catch (
const cv::Exception& exception)
113 errorMessage = std::string(
"OpenCV calibration error: ") + exception.what();
115 catch (
const std::exception& exception)
117 errorMessage = std::string(
"Calibration error: ") + exception.what();
124bool FisheyeCalibrationEngine::validateObservations(
125 const std::vector<StereoObservation>& observations,
126 const cv::Size& expectedImageSize,
127 std::string& errorMessage)
const
129 if (observations.size() < minimumObservations)
131 errorMessage =
"At least " + std::to_string(minimumObservations) +
132 " stereo observations are required.";
136 std::size_t expectedPointCount = 0;
137 std::vector<cv::Point3f> referenceObjectPoints;
138 for (std::size_t index = 0; index < observations.size(); ++index)
140 const StereoObservation& observation = observations[index];
141 const std::string prefix =
"Observation #" + std::to_string(index + 1) +
": ";
143 if (observation.imageSize != expectedImageSize)
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();
153 if (!observation.isValid())
155 errorMessage = prefix +
"has inconsistent point lists or an invalid timestamp delta.";
159 if (observation.objectPoints.size() < minimumPointsPerObservation)
161 errorMessage = prefix +
"contains fewer than " +
162 std::to_string(minimumPointsPerObservation) +
" calibration points.";
166 if (!std::isfinite(observation.leftTimestampSeconds) ||
167 !std::isfinite(observation.rightTimestampSeconds) ||
168 !std::isfinite(observation.timestampDeltaSeconds))
170 errorMessage = prefix +
"contains a non-finite timestamp.";
174 for (std::size_t pointIndex = 0; pointIndex < observation.objectPoints.size(); ++pointIndex)
176 if (!isFinite(observation.objectPoints[pointIndex]) ||
177 !isFinite(observation.leftImagePoints[pointIndex]) ||
178 !isFinite(observation.rightImagePoints[pointIndex]))
180 errorMessage = prefix +
"point #" + std::to_string(pointIndex + 1) +
181 " contains a non-finite coordinate.";
188 expectedPointCount = observation.objectPoints.size();
189 referenceObjectPoints = observation.objectPoints;
191 else if (observation.objectPoints.size() != expectedPointCount)
193 errorMessage = prefix +
"has " + std::to_string(observation.objectPoints.size()) +
194 " object points, expected " + std::to_string(expectedPointCount) +
".";
199 for (std::size_t pointIndex = 0; pointIndex < expectedPointCount; ++pointIndex)
201 if (cv::norm(observation.objectPoints[pointIndex] - referenceObjectPoints[pointIndex]) > 1
e-6)
203 errorMessage = prefix +
"uses object points different from observation #1.";
213bool FisheyeCalibrationEngine::calibrateMonocular(
214 const std::vector<StereoObservation>& observations,
216 const FisheyeCalibrationOptions& options,
217 CameraCalibrationResult& result,
218 std::string& errorMessage)
const
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))
228 const std::vector<std::vector<cv::Point2f>>& imagePoints =
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;
239 const double rms = cv::fisheye::calibrate(objectPoints, imagePoints, options.common.imageSize,
240 intrinsic, distortion, rotationVectors,
241 translationVectors, options.monocularFlags,
244 result = CameraCalibrationResult{};
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);
253 if (result.rotationVectors.size() != observations.size() ||
254 result.translationVectors.size() != observations.size())
256 errorMessage =
"OpenCV returned an incomplete set of monocular poses.";
260 result.perViewRms.reserve(observations.size());
261 for (std::size_t index = 0; index < observations.size(); ++index)
263 std::vector<cv::Point2f> projectedPoints;
264 cv::fisheye::projectPoints(objectPoints[index], projectedPoints,
265 result.rotationVectors[index], result.translationVectors[index],
267 if (projectedPoints.size() != imagePoints[index].size())
269 errorMessage =
"OpenCV returned an incomplete projected point set.";
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);
277 if(!result.isValid())
279 errorMessage =
"Monocular calibration produced invalid camera parameters.";
283 if(result.rotationVectors.size() != observations.size())
285 errorMessage =
"Monocular calibration did not produce one pose per observation.";
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
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))
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();
313 const double rms = cv::fisheye::stereoCalibrate(objectPoints, leftImagePoints, rightImagePoints,
314 leftK, leftD, rightK, rightD, options.common.imageSize,
315 rotation, translation, options.stereoFlags,
318 result = StereoCalibrationResult{};
319 result.R = rotation.clone();
320 result.T = translation.reshape(1, 3).clone();
322 if (!result.isValid())
324 errorMessage =
"Stereo calibration produced invalid extrinsic parameters.";
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
337 objectPoints.clear();
338 leftImagePoints.clear();
339 rightImagePoints.clear();
340 objectPoints.reserve(observations.size());
341 leftImagePoints.reserve(observations.size());
342 rightImagePoints.reserve(observations.size());
344 for (
const StereoObservation& observation : observations)
346 objectPoints.push_back(observation.objectPoints);
347 leftImagePoints.push_back(observation.leftImagePoints);
348 rightImagePoints.push_back(observation.rightImagePoints);
350 if (objectPoints.empty())
352 errorMessage =
"No calibration points are available.";
bool calibrate(const std::vector< StereoObservation > &observations, const FisheyeCalibrationOptions &options, CalibrationResult &result, std::string &errorMessage) const
CameraCalibrationResult leftCamera
StereoCalibrationResult stereo
CameraCalibrationResult rightCamera
CalibrationMode calibrationMode
CommonCalibrationOptions common