From 42a68cc03c11332f7acd0fb332c5db43cf3d073b Mon Sep 17 00:00:00 2001 From: Sebastian Hahta <joseha@utu.fi> Date: Fri, 7 Aug 2020 12:47:23 +0300 Subject: [PATCH] ugly hacks for stereo camera in calibration --- .../include/ftl/calibration/optimize.hpp | 7 +- components/calibration/src/extrinsic.cpp | 33 ++- components/calibration/src/optimize.cpp | 216 ++++++++++++------ 3 files changed, 173 insertions(+), 83 deletions(-) diff --git a/components/calibration/include/ftl/calibration/optimize.hpp b/components/calibration/include/ftl/calibration/optimize.hpp index 5a2911732..77fd1b6d6 100644 --- a/components/calibration/include/ftl/calibration/optimize.hpp +++ b/components/calibration/include/ftl/calibration/optimize.hpp @@ -15,7 +15,7 @@ #include <opencv2/core/core.hpp> -// BundleAdjustment uses Point3d instances via double* +// BundleAdjustment uses Point3d instances with cast to double* static_assert(sizeof(cv::Point2d) == 2*sizeof(double)); static_assert(std::is_standard_layout<cv::Point2d>()); static_assert(sizeof(cv::Point3d) == 3*sizeof(double)); @@ -28,7 +28,7 @@ namespace calibration { * Camera paramters (Ceres) */ struct Camera { - Camera() {} + Camera() { data[Parameter::Q1] = 1.0; } Camera(const cv::Mat& K, const cv::Mat& D, const cv::Mat& R, const cv::Mat& tvec, cv::Size size); Camera(const CalibrationData::Calibration& calib); @@ -66,8 +66,6 @@ struct Camera { const static int n_parameters = 18; const static int n_distortion_parameters = 8; - - bool quaternion = false; double data[n_parameters] = {0.0}; enum Parameter { @@ -261,6 +259,7 @@ private: std::vector<Camera*> cameras_; std::vector<BundleAdjustment::Point> points_; std::vector<BundleAdjustment::Object> objects_; + Camera dummy_camera_; // contains identity rotation+translation }; #endif diff --git a/components/calibration/src/extrinsic.cpp b/components/calibration/src/extrinsic.cpp index a1381fd47..cb8e82b33 100644 --- a/components/calibration/src/extrinsic.cpp +++ b/components/calibration/src/extrinsic.cpp @@ -635,10 +635,23 @@ double ExtrinsicCalibration::optimize() { cameras.reserve(calib_.size()); unsigned int ncameras = calib_.size(); - for (const auto& c : calib_) { - auto camera = c; - T.push_back(c.extrinsic.inverse().matrix()); - cameras.push_back(Camera(camera)); + for (unsigned i = 0; i < calib_.size(); i++) { + const auto& calib = calib_[i]; + T.push_back(calib.extrinsic.inverse().matrix()); + if (i%2 == 1) { + // Ugly hack to use stereo camera parametrization in + // BundleAdjustment (would require significant changes to implement + // properly). Use l->r extrinsics for right camera (always odd index) + auto cam = calib; + cam.extrinsic = CalibrationData::Extrinsic + (calib.extrinsic.matrix() * calib_[i - 1].extrinsic.inverse().matrix()); + + cameras.push_back(Camera(cam)); + } + else { + // left camera (even index), use common extrinsic parameters + cameras.push_back(Camera(calib)); + } } for (auto &c : cameras) { // BundleAdjustment stores pointers; do not resize cameras vector @@ -748,6 +761,8 @@ double ExtrinsicCalibration::optimize() { int n_removed = 0; for (const auto& t : prune_observations_) { + // Doesn't work without using reprojection error to with stereocam + // hacks ... n_removed += ba.removeObservations(t); if (float(n_removed)/float(n_points) > threhsold_warning_) { LOG(WARNING) << "significant number of observations removed"; @@ -767,7 +782,15 @@ double ExtrinsicCalibration::optimize() { auto intr = cameras[i].intrinsic(); calib_optimized_[i] = calib_[i]; calib_optimized_[i].intrinsic.set(intr.matrix(), intr.distCoeffs.Mat(), intr.resolution); - calib_optimized_[i].extrinsic.set(cameras[i].rvec(), cameras[i].tvec()); + + if (i%2 == 1) { + // HACK!! + cv::Mat T = cameras[i].extrinsicMatrix() * cameras[i - 1].extrinsicMatrix(); + calib_optimized_[i].extrinsic.set(T); + } + else { + calib_optimized_[i].extrinsic.set(cameras[i].rvec(), cameras[i].tvec()); + } } rmse_total_ = ba.reprojectionError(); diff --git a/components/calibration/src/optimize.cpp b/components/calibration/src/optimize.cpp index 400058725..8f8089cb6 100644 --- a/components/calibration/src/optimize.cpp +++ b/components/calibration/src/optimize.cpp @@ -99,12 +99,14 @@ void Camera::setDistortion(const Mat& D) { } Camera::Camera(const Mat &K, const Mat &D, const Mat &R, const Mat &tvec, cv::Size sz) { + data[Parameter::Q1] = 1.0; setIntrinsic(K, D, sz); if (!R.empty()) { setRotation(R); } if (!tvec.empty()) { setTranslation(tvec); } } Camera::Camera(const ftl::calibration::CalibrationData::Calibration& calib) { + data[Parameter::Q1] = 1.0; setIntrinsic(calib.intrinsic.matrix(), calib.intrinsic.distCoeffs.Mat(), calib.intrinsic.resolution); setExtrinsic(calib.extrinsic.matrix()(cv::Rect(0, 0, 3, 3)), cv::Mat(calib.extrinsic.tvec)); } @@ -186,21 +188,67 @@ Mat Camera::extrinsicMatrixInverse() const { //////////////////////////////////////////////////////////////////////////////// +/* +* Camera model has 18 parameters (with quaternion rotation): +* +* - rotation and translation: q1, q2, q3, q4, tx, ty, tx +* - focal length: f (fx == fy assumed) +* - pricipal point: cx, cy +* - distortion coefficients: k1, k2, k3, k4, k5, k6, p1, p2 +* +* Camera model documented in +* https://docs.opencv.org/master/d9/d0c/group__calib3d.html +* https://github.com/opencv/opencv/blob/b698d0a6ee12342a87b8ad739d908fd8d7ff1fca/modules/calib3d/src/calibration.cpp#L774 +*/ +template<typename T> +void projectPoint_(const T* const camera, const T* const point, T* const projected) { + + T p[3]; + ceres::QuaternionRotatePoint(camera + Camera::Parameter::ROTATION, point, p); + + p[0] += camera[Camera::Parameter::TX]; + p[1] += camera[Camera::Parameter::TY]; + p[2] += camera[Camera::Parameter::TZ]; + + T x = p[0]/p[2]; + T y = p[1]/p[2]; + + // Intrinsic parameters + const T& f = camera[Camera::Parameter::F]; + const T& cx = camera[Camera::Parameter::CX]; + const T& cy = camera[Camera::Parameter::CY]; + + // Distortion parameters + const T& k1 = camera[Camera::Parameter::K1]; + const T& k2 = camera[Camera::Parameter::K2]; + const T& k3 = camera[Camera::Parameter::K3]; + const T& k4 = camera[Camera::Parameter::K4]; + const T& k5 = camera[Camera::Parameter::K5]; + const T& k6 = camera[Camera::Parameter::K6]; + const T& p1 = camera[Camera::Parameter::P1]; + const T& p2 = camera[Camera::Parameter::P2]; + + const T r2 = x*x + y*y; + const T r4 = r2*r2; + const T r6 = r4*r2; + + // Radial distortion + const T cdist = T(1.0) + k1*r2 + k2*r4 + k3*r6; + // Radial distortion: rational model + const T icdist = T(1.0)/(T(1.0) + k4*r2 + k5*r4 + k6*r6); + // Tangential distortion + const T pdistx = (T(2.0)*x*y)*p1 + (r2 + T(2.0)*x*x)*p2; + const T pdisty = (r2 + T(2.0)*y*y)*p1 + (T(2.0)*x*y)*p2; + // Apply distortion + const T xd = x*cdist*icdist + pdistx; + const T yd = y*cdist*icdist + pdisty; + // Projected point position + projected[0] = f*xd + cx; + projected[1] = f*yd + cy; +} + struct ReprojectionError { - /** - * Reprojection error. - * - * Camera model has _CAMERA_PARAMETERS parameters: - * - * - rotation and translation: q1, q2, q3, q4, tx, ty, tx - * - focal length: f (fx == fy assumed) - * - pricipal point: cx, cy - * - distortion coefficients: k1, k2, k3, k4, k5, k6, p1, p2 - * - * Camera model documented in - * https://docs.opencv.org/master/d9/d0c/group__calib3d.html - * https://github.com/opencv/opencv/blob/b698d0a6ee12342a87b8ad739d908fd8d7ff1fca/modules/calib3d/src/calibration.cpp#L774 - */ + /** Reprojection error */ explicit ReprojectionError(double observed_x, double observed_y) : observed_x(observed_x), observed_y(observed_y) {} @@ -209,53 +257,12 @@ struct ReprojectionError { const T* const point, T* residuals) const { - T p[3]; - ceres::QuaternionRotatePoint(camera + Camera::Parameter::ROTATION, point, p); - - - p[0] += camera[Camera::Parameter::TX]; - p[1] += camera[Camera::Parameter::TY]; - p[2] += camera[Camera::Parameter::TZ]; - - T x = p[0] / p[2]; - T y = p[1] / p[2]; - - // Intrinsic parameters - const T& f = camera[Camera::Parameter::F]; - const T& cx = camera[Camera::Parameter::CX]; - const T& cy = camera[Camera::Parameter::CY]; - - // Distortion parameters - const T& k1 = camera[Camera::Parameter::K1]; - const T& k2 = camera[Camera::Parameter::K2]; - const T& k3 = camera[Camera::Parameter::K3]; - const T& k4 = camera[Camera::Parameter::K4]; - const T& k5 = camera[Camera::Parameter::K5]; - const T& k6 = camera[Camera::Parameter::K6]; - const T& p1 = camera[Camera::Parameter::P1]; - const T& p2 = camera[Camera::Parameter::P2]; - - const T r2 = x*x + y*y; - const T r4 = r2*r2; - const T r6 = r4*r2; - - // Radial distortion - const T cdist = T(1.0) + k1*r2 + k2*r4 + k3*r6; - // Radial distortion: rational model - const T icdist = T(1.0)/(T(1.0) + k4*r2 + k5*r4 + k6*r6); - // Tangential distortion - const T pdistx = (T(2.0)*x*y)*p1 + (r2 + T(2.0)*x*x)*p2; - const T pdisty = (r2 + T(2.0)*y*y)*p1 + (T(2.0)*x*y)*p2; - // Apply distortion - const T xd = x*cdist*icdist + pdistx; - const T yd = y*cdist*icdist + pdisty; - // Projected point position - T predicted_x = f*xd + cx; - T predicted_y = f*yd + cy; + T predicted[2]; + projectPoint_(camera, point, predicted); // Error: the difference between the predicted and observed position - residuals[0] = predicted_x - T(observed_x); - residuals[1] = predicted_y - T(observed_y); + residuals[0] = predicted[0] - T(observed_x); + residuals[1] = predicted[1] - T(observed_y); return true; } @@ -271,15 +278,60 @@ struct ReprojectionError { new ReprojectionError(observed.x, observed.y))); } - double observed_x; - double observed_y; + const double observed_x; + const double observed_y; }; -static ReprojectionError reproject_ = ReprojectionError(0.0, 0.0); +struct ReprojectionErrorStereo { + /** Reprojection error for stereo camera pairs. Second camera's extrinsic + * parameters are rotation and translation from first camera to second. + */ + + explicit ReprojectionErrorStereo(double observed_x, double observed_y) + : observed_x(observed_x), observed_y(observed_y) {} + + template<typename T> + bool operator()(const T* const camera1, const T* const camera2, + const T* const point, + T* residuals) const { + + T p[3]; + T predicted[2]; + ceres::QuaternionRotatePoint(camera1 + Camera::Parameter::ROTATION, point, p); + p[0] += camera1[Camera::Parameter::TX]; + p[1] += camera1[Camera::Parameter::TY]; + p[2] += camera1[Camera::Parameter::TZ]; + + projectPoint_(camera2, p, predicted); + + // Error: the difference between the predicted and observed position + residuals[0] = predicted[0] - T(observed_x); + residuals[1] = predicted[1] - T(observed_y); + + return true; + } + + static ceres::CostFunction* Create( const double observed_x, + const double observed_y) { + return (new ceres::AutoDiffCostFunction<ReprojectionErrorStereo, 2, Camera::n_parameters, Camera::n_parameters, 3>( + new ReprojectionErrorStereo(observed_x, observed_y))); + } + + static ceres::CostFunction* Create( const Point2d &observed) { + return (new ceres::AutoDiffCostFunction<ReprojectionErrorStereo, 2, Camera::n_parameters, Camera::n_parameters, 3>( + new ReprojectionErrorStereo(observed.x, observed.y))); + } + + const double observed_x; + const double observed_y; +}; cv::Point2d ftl::calibration::projectPoint(const Camera& camera, const cv::Point3d& point) { cv::Point2d out; - reproject_(static_cast<const double* const>(camera.data), reinterpret_cast<const double* const>(&(point.x)), reinterpret_cast<double*>(&(out.x))); + projectPoint_<double>( + static_cast<const double* const>(camera.data), + reinterpret_cast<const double* const>(&(point.x)), + reinterpret_cast<double*>(&(out.x))); return out; } @@ -567,7 +619,6 @@ void BundleAdjustment::_setCameraParametrization(ceres::Problem &problem, const camera_parameterization = new ceres::SubsetParameterization(Camera::n_parameters, params); } - problem.SetParameterization(getCameraPtr(i), camera_parameterization); } } @@ -585,19 +636,35 @@ void BundleAdjustment::_buildBundleAdjustmentProblem(ceres::Problem &problem, co loss_function = new ceres::CauchyLoss(1.0); } + // dummy camera here as a quick hack so both cameras can use same cost + // function so setCameraParametrization above works as expected for (auto& point : points_) { - for (size_t i = 0; i < point.observations.size(); i++) { - if (!point.visibility[i]) { continue; } + for (size_t i = 0; i < cameras_.size(); i++) { ceres::CostFunction* cost_function = - ReprojectionError::Create(point.observations[i]); + ReprojectionErrorStereo::Create(point.observations[i]); - problem.AddResidualBlock(cost_function, - loss_function, - getCameraPtr(i), - &(point.point.x) - ); + if (!point.visibility[i]) { continue; } + if (i%2 == 0) { + // left camera + problem.AddResidualBlock(cost_function, + loss_function, + &(dummy_camera_.data[0]), + getCameraPtr(i), + &(point.point.x) + ); + } + else { + // right camera + problem.AddResidualBlock(cost_function, + loss_function, + getCameraPtr(i - 1), + getCameraPtr(i), + &(point.point.x) + ); + } } } + problem.SetParameterBlockConstant(&(dummy_camera_.data[0])); _setCameraParametrization(problem, options); @@ -620,7 +687,7 @@ void BundleAdjustment::run(const BundleAdjustment::Options &bundle_adjustment_op _buildProblem(problem, bundle_adjustment_options); ceres::Solver::Options options; - options.linear_solver_type = ceres::DENSE_SCHUR; + options.linear_solver_type = ceres::DENSE_SCHUR; // http://ceres-solver.org/solving_faqs.html options.minimizer_progress_to_stdout = bundle_adjustment_options.verbose; if (bundle_adjustment_options.max_iter > 0) { @@ -668,7 +735,8 @@ int BundleAdjustment::removeObservations(double threshold) { } error_total /= n_points; - if (n_points <= 1) { continue; } // TODO: remove observation completely + // should remove (counted in mean error if left) + if (n_points <= 1) { continue; } for (unsigned int c = 0; c < cameras_.size(); c++) { if (!point.visibility[c]) { continue; } -- GitLab