7#include <opencv2/calib3d.hpp>
8#include <opencv2/imgproc.hpp>
13#include <yarp/os/Bottle.h>
14#include <yarp/os/LogStream.h>
15#include <yarp/os/Value.h>
19using yarp::os::Bottle;
20using yarp::os::Searchable;
21using yarp::sig::ImageOf;
22using yarp::sig::PixelRgb;
25 : previousImageSize(-1, -1)
27 , needInitialization(true)
30 , drawEpipolars(false)
31 , epipolarLineStep(48)
49bool FisheyeCalibTool::loadCameraCalibration(
const Searchable& group,
50 CameraCalibration& camera,
51 const std::string& groupName)
53 const char* required[] = {
"w",
"h",
"fx",
"fy",
"cx",
"cy",
"k1",
"k2",
"k3",
"k4"};
54 for (
const char* key : required)
56 if (!group.check(key))
58 yError() <<
"Stereo rectification: missing" << key <<
"in" << groupName;
63 camera.calibrationSize = cv::Size(group.find(
"w").asInt32(), group.find(
"h").asInt32());
64 if (camera.calibrationSize.width <= 0 || camera.calibrationSize.height <= 0)
66 yError() <<
"Stereo rectification: invalid calibration size in" << groupName;
70 camera.K = cv::Mat::eye(3, 3, CV_64F);
71 camera.K.at<
double>(0, 0) = group.find(
"fx").asFloat64();
72 camera.K.at<
double>(1, 1) = group.find(
"fy").asFloat64();
73 camera.K.at<
double>(0, 2) = group.find(
"cx").asFloat64();
74 camera.K.at<
double>(1, 2) = group.find(
"cy").asFloat64();
76 camera.D = cv::Mat::zeros(4, 1, CV_64F);
77 camera.D.at<
double>(0, 0) = group.find(
"k1").asFloat64();
78 camera.D.at<
double>(1, 0) = group.find(
"k2").asFloat64();
79 camera.D.at<
double>(2, 0) = group.find(
"k3").asFloat64();
80 camera.D.at<
double>(3, 0) = group.find(
"k4").asFloat64();
84bool FisheyeCalibTool::loadStereoCalibration(
const Searchable& config)
86 if (!config.check(
"STEREO_DISPARITY"))
88 yError() <<
"Stereo rectification: [STEREO_DISPARITY] group not found";
92 const Bottle& stereo = config.findGroup(
"STEREO_DISPARITY");
93 if (!stereo.check(
"HN"))
95 yError() <<
"Stereo rectification: HN is missing from [STEREO_DISPARITY]";
99 const Bottle* homogeneous = stereo.find(
"HN").asList();
100 if (homogeneous ==
nullptr || homogeneous->size() != 16)
102 yError() <<
"Stereo rectification: HN must contain a 4x4 homogeneous transform";
106 rotation = cv::Mat::eye(3, 3, CV_64F);
107 translation = cv::Mat::zeros(3, 1, CV_64F);
108 for (
int row = 0; row < 3; ++row)
110 for (
int col = 0; col < 3; ++col)
112 rotation.at<
double>(row, col) = homogeneous->get(row * 4 + col).asFloat64();
114 translation.at<
double>(row, 0) = homogeneous->get(row * 4 + 3).asFloat64();
117 if (cv::norm(translation) <= 1
e-12)
119 yError() <<
"Stereo rectification: translation from HN is zero";
127 groupName = config.check(
"group", yarp::os::Value(
"")).asString();
128 if (groupName !=
"CAMERA_CALIBRATION_LEFT" && groupName !=
"CAMERA_CALIBRATION_RIGHT")
130 yError() <<
"Stereo rectification requires --group CAMERA_CALIBRATION_LEFT or CAMERA_CALIBRATION_RIGHT";
133 if (!config.check(
"CAMERA_CALIBRATION_LEFT") || !config.check(
"CAMERA_CALIBRATION_RIGHT"))
135 yError() <<
"Stereo rectification requires both camera calibration groups";
139 const Bottle& leftGroup = config.findGroup(
"CAMERA_CALIBRATION_LEFT");
140 const Bottle& rightGroup = config.findGroup(
"CAMERA_CALIBRATION_RIGHT");
141 if (!loadCameraCalibration(leftGroup, leftCamera,
"CAMERA_CALIBRATION_LEFT")
142 || !loadCameraCalibration(rightGroup, rightCamera,
"CAMERA_CALIBRATION_RIGHT")
143 || !loadStereoCalibration(config))
148 const Bottle& selectedGroup = (groupName ==
"CAMERA_CALIBRATION_LEFT") ? leftGroup : rightGroup;
149 const yarp::os::Value defaultBalance = config.check(
"rectifyAlpha", yarp::os::Value(0.0));
150 balance = config.check(
"balance", selectedGroup.check(
"balance", defaultBalance)).asFloat64();
151 balance = std::max(0.0, std::min(1.0, balance));
152 fovScale = config.check(
"fovScale", selectedGroup.check(
"fovScale", yarp::os::Value(1.0))).asFloat64();
155 yError() <<
"Fisheye rectification: fovScale must be greater than zero";
158 drawEpipolars = config.check(
"drawEpipolars", yarp::os::Value(
false)).asBool();
159 epipolarLineStep = config.check(
"epipolarLineStep", yarp::os::Value(48)).asInt32();
160 epipolarLineStep = epipolarLineStep > 0 ? epipolarLineStep : 1;
161 previousImageSize = cv::Size(-1, -1);
162 needInitialization =
true;
163 yDebug() <<
"FisheyeCalibTool configured with balance:" << balance <<
"fovScale:" << fovScale
164 <<
"drawEpipolars:" << drawEpipolars <<
"epipolarLineStep:" << epipolarLineStep;
168cv::Mat FisheyeCalibTool::scaledCameraMatrix(
const CameraCalibration& camera,
169 const cv::Size& imageSize)
const
171 const double scaleX =
static_cast<double>(imageSize.width) / camera.calibrationSize.width;
172 const double scaleY =
static_cast<double>(imageSize.height) / camera.calibrationSize.height;
173 cv::Mat scaled = camera.K.clone();
174 scaled.at<
double>(0, 0) *= scaleX;
175 scaled.at<
double>(0, 2) *= scaleX;
176 scaled.at<
double>(1, 1) *= scaleY;
177 scaled.at<
double>(1, 2) *= scaleY;
181bool FisheyeCalibTool::initializeMaps(
const cv::Size& imageSize)
183 const cv::Mat leftK = scaledCameraMatrix(leftCamera, imageSize);
184 const cv::Mat rightK = scaledCameraMatrix(rightCamera, imageSize);
189 cv::Mat disparityToDepth;
190 cv::fisheye::stereoRectify(leftK, leftCamera.D, rightK, rightCamera.D, imageSize,
191 rotation, translation, leftR, rightR, leftP, rightP,
192 disparityToDepth, cv::fisheye::CALIB_ZERO_DISPARITY,
193 imageSize, balance, fovScale);
195 if (leftR.empty() || rightR.empty() || leftP.empty() || rightP.empty())
197 yError() <<
"Fisheye rectification: stereoRectify returned empty matrices";
201 const double leftFocal = leftP.at<
double>(0, 0);
202 const double rightFocal = rightP.at<
double>(0, 0);
203 if (!std::isfinite(leftFocal) || !std::isfinite(rightFocal)
204 || leftFocal < 1.0 || rightFocal < 1.0)
206 yWarning() <<
"Fisheye rectification: stereoRectify returned an invalid focal length"
207 << leftFocal << rightFocal
208 <<
"- using a projection matrix derived from the original intrinsics";
209 cv::Mat commonProjection = cv::Mat::eye(3, 3, CV_64F);
210 commonProjection.at<
double>(0, 0) = 0.5 * (leftK.at<
double>(0, 0) + rightK.at<
double>(0, 0));
211 commonProjection.at<
double>(1, 1) = 0.5 * (leftK.at<
double>(1, 1) + rightK.at<
double>(1, 1));
212 commonProjection.at<
double>(0, 2) = 0.5 * (leftK.at<
double>(0, 2) + rightK.at<
double>(0, 2));
213 commonProjection.at<
double>(1, 2) = 0.5 * (leftK.at<
double>(1, 2) + rightK.at<
double>(1, 2));
214 leftP = commonProjection;
215 rightP = commonProjection;
218 if(groupName ==
"CAMERA_CALIBRATION_LEFT")
220 cv::fisheye::initUndistortRectifyMap(leftK, leftCamera.D, leftR, leftP,
221 imageSize, CV_16SC2, leftMap1, leftMap2);
223 else if(groupName ==
"CAMERA_CALIBRATION_RIGHT")
225 cv::fisheye::initUndistortRectifyMap(rightK, rightCamera.D, rightR, rightP,
226 imageSize, CV_16SC2, rightMap1, rightMap2);
229 previousImageSize = imageSize;
230 needInitialization =
false;
231 return (!leftMap1.empty() && !leftMap2.empty()) || (!rightMap1.empty() && !rightMap2.empty());
234void FisheyeCalibTool::drawEpipolarLines(cv::Mat& image)
const
236 const cv::Scalar lineColor(0, 255, 0);
237 for (
int y = 0;
y < image.rows;
y += epipolarLineStep)
239 cv::line(image, cv::Point(0,
y), cv::Point(image.cols - 1,
y), lineColor, 1, cv::LINE_AA);
245 const cv::Size imageSize(in.width(), in.height());
246 if (needInitialization || imageSize != previousImageSize)
248 if (!initializeMaps(imageSize))
250 yError() <<
"Fisheye calibration: unable to initialize maps";
256 out.resize(in.width(), in.height());
257 cv::Mat input(in.height(), in.width(), CV_8UC3, in.getRawImage(), in.getRowSize());
258 cv::Mat output(
out.height(),
out.width(), CV_8UC3,
out.getRawImage(),
out.getRowSize());
259 const cv::Mat& activeMap1 = (groupName ==
"CAMERA_CALIBRATION_LEFT") ? leftMap1 : rightMap1;
260 const cv::Mat& activeMap2 = (groupName ==
"CAMERA_CALIBRATION_LEFT") ? leftMap2 : rightMap2;
261 cv::remap(input, output, activeMap1, activeMap2, cv::INTER_LINEAR, cv::BORDER_CONSTANT);
264 drawEpipolarLines(output);