diff --git a/applications/gui2/src/modules/calibration/extrinsic.cpp b/applications/gui2/src/modules/calibration/extrinsic.cpp index a12aa4b93e2bf4f117847859938f590d753ac1eb..6b006ab2aa74d98da43441b6ae1df1d09afad0b2 100644 --- a/applications/gui2/src/modules/calibration/extrinsic.cpp +++ b/applications/gui2/src/modules/calibration/extrinsic.cpp @@ -299,23 +299,23 @@ void ExtrinsicCalibration::run() { try { state_.calib.run(); LOG(INFO) << "Before:"; // DEBUG INFO - for (int c1 = 0; c1 < cameraCount(); c1++) { - for (int c2 = c1; c2 < cameraCount(); c2++) { + for (int c1 = 0; c1 < cameraCount(); c1 += 2) { + auto t1 = state_.calib.calibration(c1).extrinsic.tvec; - auto t2 = state_.calib.calibration(c2).extrinsic.tvec; + auto t2 = state_.calib.calibration(c1+1).extrinsic.tvec; - LOG(INFO) << "baseline (" << c1 << ", " << c2 << "): " + LOG(INFO) << "baseline (" << c1 << ", " << c1+1 << "): " << cv::norm(t1, t2); - }} + } LOG(INFO) << "After:"; // DEBUG INFO - for (int c1 = 0; c1 < cameraCount(); c1++) { - for (int c2 = c1; c2 < cameraCount(); c2++) { - auto t1 = state_.calib.calibrationOptimized(c1).extrinsic.tvec; - auto t2 = state_.calib.calibrationOptimized(c2).extrinsic.tvec; + for (int c1 = 0; c1 < cameraCount(); c1 += 2) { - LOG(INFO) << "baseline (" << c1 << ", " << c2 << "): " + auto t1 = state_.calib.calibration(c1).extrinsic.tvec; + auto t2 = state_.calib.calibration(c1+1).extrinsic.tvec; + + LOG(INFO) << "baseline (" << c1 << ", " << c1+1 << "): " << cv::norm(t1, t2); - }} + } // Rectification maps for visualization; stereo cameras assumed // if non-stereo cameras added visualization/grouping (by index) diff --git a/applications/gui2/src/screen.cpp b/applications/gui2/src/screen.cpp index 5ec71e4c224e89253aff5acfc9b35bb7415364ee..fe7747306860f942bb33fa83512fa4326fa7cf5f 100644 --- a/applications/gui2/src/screen.cpp +++ b/applications/gui2/src/screen.cpp @@ -45,8 +45,7 @@ Screen::Screen() : toolbar_->setFixedSize({toolbar_->width(), s[1]}); toolbar_->setPosition({0, 0}); if (active_view_) { - active_view_->setFixedSize(viewSize()); - // active_view_->setFixedSize({s[0], s[1]}); + active_view_->setSize(viewSize(s)); } performLayout(); }); @@ -98,10 +97,15 @@ void Screen::redraw() { glfwPostEmptyEvent(); } +nanogui::Vector2i Screen::viewSize(const nanogui::Vector2i &ws) { + return {ws.x() - toolbar_->width(), ws.y()}; +} + nanogui::Vector2i Screen::viewSize() { - return {width() - toolbar_->width(), height()}; + return viewSize(size()); } + void Screen::showError(const std::string&title, const std::string& msg) { if (msgerror_) { return; } msgerror_ = new nanogui::MessageDialog @@ -208,4 +212,4 @@ void Screen::drawAll() { } last_draw_time_ = now; nanogui::Screen::drawAll(); -} \ No newline at end of file +} diff --git a/applications/gui2/src/screen.hpp b/applications/gui2/src/screen.hpp index b46f6dc464eae00f9f93c9834c95e285a08dae8a..a5e47d6a3fab057a8e8633a012f53511f7dc6c25 100644 --- a/applications/gui2/src/screen.hpp +++ b/applications/gui2/src/screen.hpp @@ -73,13 +73,14 @@ public: nanogui::Color getColor(const std::string &name); void setColor(const std::string &name, const nanogui::Color &c); - nanogui::Vector2i viewSize(); - // Implement in View or Screen? Add ID (address of creating instance) // to each error to prevent spam? /** Show error message popup */ void showError(const std::string& title, const std::string &msg); + nanogui::Vector2i viewSize(const nanogui::Vector2i &ws); + nanogui::Vector2i viewSize(); + private: Module* addModule_(const std::string &name, Module* ptr); diff --git a/applications/gui2/src/view.cpp b/applications/gui2/src/view.cpp index 9b14660197b7fd3fd83f09e7d5ee02040d56cddc..67297b6b439a7d59bb6617a65ce8914b40a47d1a 100644 --- a/applications/gui2/src/view.cpp +++ b/applications/gui2/src/view.cpp @@ -5,7 +5,6 @@ using ftl::gui2::View; -View::View(Screen* screen) : nanogui::Widget(screen) { - setFixedSize(screen->viewSize()); - screen_ = screen; +View::View(Screen* screen) : nanogui::Widget(screen), screen_(screen) { + setSize(screen_->viewSize()); } diff --git a/components/calibration/include/ftl/calibration/extrinsic.hpp b/components/calibration/include/ftl/calibration/extrinsic.hpp index f68ad4f7f4cdb7ba6cb622e095bdce5fec72c49e..58f954fa25e573340217efea4bd02938c06c4400 100644 --- a/components/calibration/include/ftl/calibration/extrinsic.hpp +++ b/components/calibration/include/ftl/calibration/extrinsic.hpp @@ -1,5 +1,7 @@ #pragma once +#include <ftl/utility/msgpack.hpp> + #include <ftl/calibration/visibility.hpp> #include <ftl/calibration/structures.hpp> #include <ftl/calibration/optimize.hpp> @@ -89,6 +91,8 @@ public: /** Get all points. See Points struct. */ const std::vector<Points>& all() { return points_; } + + protected: bool hasCamera(unsigned int c); void setCamera(unsigned int c); @@ -208,6 +212,9 @@ private: // TODO: add map {c1,c2} for existing calibration which is used if available. // std::shared_ptr<std::string> status_; + + float threshold_bad_ = 0.25; // theshold for point to be skipped (cm) + float threhsold_warning_ = 0.1; // theshold for warning message (%) }; diff --git a/components/calibration/include/ftl/calibration/structures.hpp b/components/calibration/include/ftl/calibration/structures.hpp index ab8544763a4c5c9f650e3ad08ff9dd5889da7b6e..e0eeb2fcea82b491637b580680d0398813f90137 100644 --- a/components/calibration/include/ftl/calibration/structures.hpp +++ b/components/calibration/include/ftl/calibration/structures.hpp @@ -77,6 +77,8 @@ struct CalibrationData { void set(const cv::Mat &T); void set(cv::InputArray R, cv::InputArray t); + Extrinsic inverse() const; + /** get as a 4x4 matrix */ cv::Mat matrix() const; /** get 3x3 rotation matrix */ diff --git a/components/calibration/src/extrinsic.cpp b/components/calibration/src/extrinsic.cpp index e3c3fbf80c37909c77ffeb70f8bf2e5b52bd268a..971ca74ec0b59bfc8d45a4be5fad05ff3c079433 100644 --- a/components/calibration/src/extrinsic.cpp +++ b/components/calibration/src/extrinsic.cpp @@ -318,7 +318,6 @@ double calibratePair(const cv::Mat &K1, const cv::Mat &D1, double s = ftl::calibration::optimizeScale(object_points, points_out); t = t * s; - //transform::inverse(R, t); auto params1 = Camera(K1, D1, cv::Mat::eye(3, 3, CV_64FC1), cv::Mat::zeros(3, 1, CV_64FC1), {0, 0}); auto params2 = Camera(K2, D2, R, t, {0, 0}); @@ -416,8 +415,10 @@ void ExtrinsicCalibration::calculatePairPoses() { auto object = points().getObject(0); cv::Mat R, t; std::vector<cv::Point3d> points3d; - calibratePair(K1, distCoeffs1, K2, distCoeffs2, pts[0], pts[1], object, - R, t, points3d, true); + double rms = calibratePair(K1, distCoeffs1, K2, distCoeffs2, + pts[0], pts[1], object, R, t, points3d, true); + + LOG(INFO) << "Pair error " << rms; points().setTriangulatedPoints(c1, c2, points3d); @@ -487,6 +488,28 @@ static std::vector<bool> visibility(unsigned int ncameras, uint64_t visible) { return res; } +static cv::Point3d absdiff(const std::vector<double> &xs, const std::vector<double> &ys, const std::vector<double> &zs) { + double minx = INFINITY; + double maxx = -INFINITY; + for (auto x : xs) { + minx = std::min(minx, x); + maxx = std::max(maxx, x); + } + double miny = INFINITY; + double maxy = -INFINITY; + for (auto y : ys) { + miny = std::min(miny, y); + maxy = std::max(maxy, y); + } + double minz = INFINITY; + double maxz = -INFINITY; + for (auto z : zs) { + minz = std::min(minz, z); + maxz = std::max(maxz, z); + } + return {abs(minx - maxx), abs(miny - maxy), abs(minz - maxz)}; +} + double ExtrinsicCalibration::optimize() { BundleAdjustment ba; @@ -495,8 +518,9 @@ double ExtrinsicCalibration::optimize() { unsigned int ncameras = calib_.size(); for (const auto& c : calib_) { - cameras.push_back(Camera(c)); - cameras.back().setDistortion(cv::Mat(cv::Size{12, 1}, CV_64FC1, 0.0)); + auto cam = c; + cameras.push_back(Camera(cam)); + //cameras.back().setDistortion(cv::Mat(cv::Size{12, 1}, CV_64FC1, 0.0)); } for (auto &c : cameras ) { ba.addCamera(c); @@ -507,7 +531,6 @@ double ExtrinsicCalibration::optimize() { // inverse transformations, as points are transformed from camera to world // (instead the other way around by parameters in cameras[]). - std::vector<cv::Point3d> pointsw; std::vector<cv::Mat> T; for (const auto &c : cameras) { T.push_back(c.extrinsicMatrixInverse()); @@ -521,13 +544,21 @@ double ExtrinsicCalibration::optimize() { // suited as they can be easily interpreted as flat arrays or // multi-dimensional. + int n_points_bad = 0; + int n_points_missing = 0; + int n_points = 0; + for (const auto& itm : points_.all()) { auto sz = points_.getObject(itm.object).size(); auto vis = visibility(ncameras, itm.cameras); for (unsigned int i = 0; i < sz; i++) { - // observation and triangulated coordinates - std::vector<cv::Point2d> obs(ncameras, {0, 0}); + n_points++; + + // observation and triangulated coordinates; Use {NAN, NAN} for + // non-visible points (if those are used by mistake, Ceres will + // fail with error message). + std::vector<cv::Point2d> obs(ncameras, {NAN, NAN}); std::vector<double> px; std::vector<double> py; std::vector<double> pz; @@ -547,10 +578,12 @@ double ExtrinsicCalibration::optimize() { std::sort(px.begin(), px.end()); std::sort(py.begin(), py.end()); std::sort(pz.begin(), pz.end()); + cv::Point3d p; unsigned int n = px.size(); unsigned int m = n / 2; if (m == 0) { + n_points_missing++; break; // not triangulated (see earlier steps) // TODO: triangulate here @@ -559,18 +592,42 @@ double ExtrinsicCalibration::optimize() { // mean of two points if number of points even cv::Point3d p1 = {px[m - 1], py[m - 1], pz[m - 1]}; cv::Point3d p2 = {px[m], py[m], pz[m]}; - pointsw.push_back((p1 + p2)/2.0); + p = (p1 + p2)/2.0; } else { - pointsw.push_back({px[m], py[m], pz[m]}); + p = {px[m], py[m], pz[m]}; } - ba.addPoint(vis, obs, pointsw.back()); + if (cv::norm(absdiff(px, py, pz)) > threshold_bad_) { + n_points_bad++; + continue; + } + + ba.addPoint(vis, obs, p); } } + if (float(n_points_bad)/float(n_points - n_points_missing) > threhsold_warning_) { + // print wanrning message; calibration possibly fails if triangulation + // was very low quality (more than 10% bad points) + LOG(ERROR) << "Large variation in "<< n_points_bad << " " + "triangulated points. Are initial intrinsic parameters " + "good?"; + } + + if (float(n_points_missing)/float(n_points - n_points_bad) > threhsold_warning_) { + // low number of points; most points only visible in pairs? + LOG(WARNING) << "Large number of points skipped. Are there enough " + "visible points between stereo camera pairs? (TODO: " + "implement necessary triangulation after pair " + "calibration)"; + } + updateStatus_("Bundle adjustment"); options_.verbose = true; + for (unsigned int i = 0; i < cameras.size(); i++) { + LOG(INFO) << "RMS (camera " << i << ": " << ba.reprojectionError(i); + } ba.run(options_); calib_optimized_.resize(calib_.size()); @@ -580,6 +637,7 @@ double ExtrinsicCalibration::optimize() { calib_optimized_[i] = calib_[i]; calib_optimized_[i].intrinsic.set(intr.matrix(), intr.distCoeffs.Mat(), intr.resolution); calib_optimized_[i].extrinsic.set(extr.rvec, extr.tvec); + LOG(INFO) << "RMS (camera " << i << ": " << ba.reprojectionError(i); } return ba.reprojectionError(); diff --git a/components/calibration/src/optimize.cpp b/components/calibration/src/optimize.cpp index 7b3030363298081281d155a899a987e6c1ece9e0..da7680e7e84a07b690f3febc4f724cd9fffefdae 100644 --- a/components/calibration/src/optimize.cpp +++ b/components/calibration/src/optimize.cpp @@ -38,6 +38,7 @@ struct ReprojectionError { * * 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 */ explicit ReprojectionError(double observed_x, double observed_y) : observed_x(observed_x), observed_y(observed_y) {} @@ -78,8 +79,6 @@ struct ReprojectionError { const T r4 = r2*r2; const T r6 = r4*r2; - // see also https://github.com/opencv/opencv/blob/b698d0a6ee12342a87b8ad739d908fd8d7ff1fca/modules/calib3d/src/calibration.cpp#L774 - // Radial distortion const T cdist = T(1.0) + k1*r2 + k2*r4 + k3*r6; // Radial distortion: rational model @@ -91,8 +90,8 @@ struct ReprojectionError { 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_x = f*x/*xd*/ + cx; + T predicted_y = f*y/*yd*/ + cy; // Error: the difference between the predicted and observed position residuals[0] = predicted_x - T(observed_x); diff --git a/components/calibration/src/parameters.cpp b/components/calibration/src/parameters.cpp index 5549f75bded8fa9efdb279a60901a199b038ab50..2c9c2f1a37c55350845f12bc4410c552bb95cefb 100644 --- a/components/calibration/src/parameters.cpp +++ b/components/calibration/src/parameters.cpp @@ -169,7 +169,7 @@ Mat Camera::extrinsicMatrix() const { } Mat Camera::extrinsicMatrixInverse() const { - return extrinsicMatrix().inv(); + return transform::inverse(extrinsicMatrix()); } //////////////////////////////////////////////////////////////////////////////// diff --git a/components/calibration/src/structures.cpp b/components/calibration/src/structures.cpp index ce67daec3950cf36cd79df215e56d99bbabdc282..a7c49886067784ac4ffc07da611d6eb34eac96c1 100644 --- a/components/calibration/src/structures.cpp +++ b/components/calibration/src/structures.cpp @@ -91,15 +91,14 @@ void CalibrationData::Extrinsic::set(const cv::Mat& T) { if (T.type() != CV_64FC1) { throw ftl::exception("Input must be CV_64FC1"); } - if (ftl::calibration::validate::pose(T)) { + if (!ftl::calibration::validate::pose(T)) { throw ftl::exception("T is not a valid transform matrix"); } cv::Rodrigues(T(cv::Rect(0, 0, 3, 3)), rvec); tvec[0] = T.at<double>(0, 3); tvec[1] = T.at<double>(1, 3); - tvec[2] = T.at<double>(3, 3); - tvec[3] = 1.0; + tvec[2] = T.at<double>(2, 3); } void CalibrationData::Extrinsic::set(cv::InputArray R, cv::InputArray t) { @@ -156,6 +155,12 @@ cv::Mat CalibrationData::Extrinsic::matrix() const { return T; } + CalibrationData::Extrinsic CalibrationData::Extrinsic::inverse() const { + cv::Mat T = matrix(); + ftl::calibration::transform::inverse(T); + return CalibrationData::Extrinsic(T); +} + cv::Mat CalibrationData::Extrinsic::rmat() const { cv::Mat R(cv::Size(3, 3), CV_64FC1, cv::Scalar(0)); cv::Rodrigues(rvec, R); diff --git a/components/common/cpp/include/ftl/utility/msgpack.hpp b/components/common/cpp/include/ftl/utility/msgpack.hpp index ca95fd90ddfb85004d3e07646a909ba625a1aed0..3e13df14462ea4edc3210a6c0b8aae55ca6e5649 100644 --- a/components/common/cpp/include/ftl/utility/msgpack.hpp +++ b/components/common/cpp/include/ftl/utility/msgpack.hpp @@ -149,7 +149,7 @@ struct object_with_zone<cv::Vec<T, SIZE>> { }; //////////////////////////////////////////////////////////////////////////////// -// cv::Point_ and cv::Point3_ +// cv::Point_ template<typename T> struct pack<cv::Point_<T>> { @@ -190,6 +190,9 @@ struct object_with_zone<cv::Point_<T>> { } }; +//////////////////////////////////////////////////////////////////////////////// +// cv::Point3_ + template<typename T> struct pack<cv::Point3_<T>> { template <typename Stream>