diff --git a/applications/gui2/src/modules/calibration/calibration.hpp b/applications/gui2/src/modules/calibration/calibration.hpp index a55f3d77dbe78ca0bea068d6aca07f8dde236768..00b33b3ec5288c7f49469f8c51002e2a33efa372 100644 --- a/applications/gui2/src/modules/calibration/calibration.hpp +++ b/applications/gui2/src/modules/calibration/calibration.hpp @@ -262,6 +262,7 @@ public: int cameraCount(); std::string cameraName(int camera); + std::vector<std::string> cameraNames(); ftl::calibration::ExtrinsicCalibration& calib(); diff --git a/applications/gui2/src/modules/calibration/extrinsic.cpp b/applications/gui2/src/modules/calibration/extrinsic.cpp index 21246352da2d5ebf8686ad0b15b22ec5f57aa569..b0ced5bb10af804b3c3a1f999c4fa344bf2a69d6 100644 --- a/applications/gui2/src/modules/calibration/extrinsic.cpp +++ b/applications/gui2/src/modules/calibration/extrinsic.cpp @@ -121,6 +121,15 @@ std::string ExtrinsicCalibration::cameraName(int c) { ((camera.channel == Channel::Left) ? "Left" : "Right"); } +std::vector<std::string> ExtrinsicCalibration::cameraNames() { + std::vector<std::string> names; + names.reserve(cameraCount()); + for (int i = 0; i < cameraCount(); i++) { + names.push_back(cameraName(i)); + } + return names; +} + CalibrationData::Calibration ExtrinsicCalibration::calibration(int c) { return state_.calib.calibrationOptimized(c); } diff --git a/applications/gui2/src/views/calibration/extrinsicview.cpp b/applications/gui2/src/views/calibration/extrinsicview.cpp index 2b8be17b76664a700e65ab712ca017ce06e1dbba..c637ab249563d615984f2ab60f8c43862df5f65a 100644 --- a/applications/gui2/src/views/calibration/extrinsicview.cpp +++ b/applications/gui2/src/views/calibration/extrinsicview.cpp @@ -407,6 +407,9 @@ class ExtrinsicCalibrationView::ResultsWindow : public FixedWindow { public: ResultsWindow(nanogui::Widget* parent, ExtrinsicCalibrationView* view); virtual void draw(NVGcontext* ctx) override; + virtual void performLayout(NVGcontext* ctx); + //virtual nanogui::Vector2i preferredSize(NVGcontext* ctx) const override; + void update(); private: @@ -416,7 +419,7 @@ private: std::vector<ftl::calibration::CalibrationData::Calibration> calib_; std::vector<std::string> names_; - nanogui::TabWidget* tabs_; + nanogui::TabWidget* tabs_ = nullptr; public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW @@ -425,6 +428,9 @@ public: ExtrinsicCalibrationView::ResultsWindow::ResultsWindow(nanogui::Widget* parent, ExtrinsicCalibrationView* view) : FixedWindow(parent, "Results"), view_(view), ctrl_(view->ctrl_) { + setLayout(new nanogui::BoxLayout + (nanogui::Orientation::Vertical, nanogui::Alignment::Maximum)); + (new nanogui::Button(buttonPanel(), "", ENTYPO_ICON_CROSS))->setCallback( [view = view_]() { view->setMode(Mode::VIDEO); @@ -432,20 +438,35 @@ ExtrinsicCalibrationView::ResultsWindow::ResultsWindow(nanogui::Widget* parent, tabs_ = new nanogui::TabWidget(this); tabs_->createTab("Extrinsic"); +} +/*nanogui::Vector2i ExtrinsicCalibrationView::ResultsWindow::preferredSize(NVGcontext* ctx) const { + return {600, 400}; +}*/ + +void ExtrinsicCalibrationView::ResultsWindow::ResultsWindow::performLayout(NVGcontext* ctx) { setFixedSize({600, 400}); - tabs_->setFixedSize(fixedSize()); + tabs_->setFixedWidth(width()); + FixedWindow::performLayout(ctx); } void ExtrinsicCalibrationView::ResultsWindow::ResultsWindow::update() { calib_.resize(ctrl_->cameraCount()); while (tabs_->tabCount() > 1) { - tabs_->removeTab(tabs_->tabCount() - 1); + // bug in nanogui: incorrect assert in removeTab(int). + // workaround: use tabLabelAt() + tabs_->removeTab(tabs_->tabLabelAt(tabs_->tabCount() - 1)); } for (int i = 0; i < ctrl_->cameraCount(); i++) { calib_[i] = ctrl_->calibration(i); + // nanogui issue: too many tabs/long names header goes outside of widget + // use just idx for now auto* tab = tabs_->createTab(std::to_string(i)); + new nanogui::Label(tab, ctrl_->cameraName(i), "sans-bold", 18); + tab->setLayout(new nanogui::BoxLayout + (nanogui::Orientation::Vertical, nanogui::Alignment::Middle, 0, 8)); + auto* display = new IntrinsicDetails(tab); display->update(calib_[i].intrinsic); } @@ -471,6 +492,73 @@ static void drawText(NVGcontext* ctx, nanogui::Vector2f &pos, const std::string nvgText(ctx, pos.x() + 1, pos.y() + 1, text.c_str(), nullptr); } +//////////////////////////////////////////////////////////////////////////////// + +class StereoCalibrationImageView : public ftl::gui2::StereoImageView { +public: + using ftl::gui2::StereoImageView::StereoImageView; + virtual bool mouseButtonEvent(const nanogui::Vector2i &p, int button, bool down, int modifiers) override; + virtual void draw(NVGcontext* ctx) override; +private: + bool draw_line_ = false; + float row_image_ = 0; + +public: + EIGEN_MAKE_ALIGNED_OPERATOR_NEW; +}; + +bool StereoCalibrationImageView::mouseButtonEvent(const nanogui::Vector2i &p, int button, bool down, int modifiers) { + if (button == GLFW_MOUSE_BUTTON_1 && !down) { + float row = round(imageCoordinateAt(p.cast<float>()).y()); + if (row == row_image_ && draw_line_) { + draw_line_ = false; + } + else { + draw_line_ = true; + row_image_ = row; + } + } + return true; +} + +void StereoCalibrationImageView::draw(NVGcontext* ctx) { + StereoImageView::draw(ctx); + if (!draw_line_) { return; } + + // assumes vertical alignment (horizontal not tested) + CHECK(orientation() == nanogui::Orientation::Vertical); + int x = position().x(); + int y = position().y(); + int w = width(); + int h = left()->height(); + nanogui::Vector2f l = left()->positionForCoordinate({0.0f, row_image_}) + left()->position().cast<float>(); + nanogui::Vector2f r = right()->positionForCoordinate({0.0f, row_image_}) + right()->position().cast<float>(); + float swidth = std::max(1.0f, left()->scale()); + + for (auto& p : {l, r}) { + nvgScissor(ctx, x, y, w, h); + nvgBeginPath(ctx); + nvgMoveTo(ctx, x, p.y() - swidth*0.5f); // "half a pixel" shift + nvgLineTo(ctx, x + w, p.y() - swidth*0.5f); + nvgStrokeColor(ctx, nvgRGBA(255, 32, 32, (swidth == 1.0f) ? 255 : 96)); + nvgStrokeWidth(ctx, swidth); + nvgStroke(ctx); + + if (swidth*0.5f > 2.0f) { + nvgBeginPath(ctx); + nvgMoveTo(ctx, x, p.y() - swidth*0.5f); + nvgLineTo(ctx, x + w, p.y() - swidth*0.5f); + nvgStrokeColor(ctx, nvgRGBA(0, 0, 0, 196)); + nvgStrokeWidth(ctx, 1.0f); + nvgStroke(ctx); + } + nvgResetScissor(ctx); + y += h; + } +} + +//////////////////////////////////////////////////////////////////////////////// + ExtrinsicCalibrationView::ExtrinsicCalibrationView(Screen* widget, ExtrinsicCalibration* ctrl) : ftl::gui2::View(widget), ctrl_(ctrl), rows_(0) { @@ -483,7 +571,7 @@ ExtrinsicCalibrationView::ExtrinsicCalibrationView(Screen* widget, ExtrinsicCali // assumes all cameras stereo cameras, indexed in order for (int i = 0; i < ctrl_->cameraCount(); i += 2) { - new StereoImageView(frames_, nanogui::Orientation::Vertical); + new StereoCalibrationImageView(frames_, nanogui::Orientation::Vertical); } paused_ = false; wcontrol_ = new ControlWindow(screen(), this); @@ -505,7 +593,7 @@ void ExtrinsicCalibrationView::performLayout(NVGcontext* ctx) { nanogui::Vector2i fsize = { width()/(frames_->childCount()), height() }; for (int i = 0; i < frames_->childCount(); i++) { - auto* stereo = dynamic_cast<StereoImageView*>(frames_->childAt(i)); + auto* stereo = dynamic_cast<StereoCalibrationImageView*>(frames_->childAt(i)); stereo->setFixedSize(fsize); stereo->fit(); } diff --git a/applications/gui2/src/views/calibration/widgets.cpp b/applications/gui2/src/views/calibration/widgets.cpp index f907b2ce1a37ecb5f69504b6987f3d186ae361a7..80a4af5d5d4a43ff19b3bf206891af924de456aa 100644 --- a/applications/gui2/src/views/calibration/widgets.cpp +++ b/applications/gui2/src/views/calibration/widgets.cpp @@ -126,17 +126,23 @@ void IntrinsicDetails::update(const ftl::calibration::CalibrationData::Intrinsic "(" + to_string(values.cx) + ", " + to_string(values.cy) + ")"); + new nanogui::Widget(params_); + new nanogui::Label(params_, + "(" + to_string(100.0*(2.0*values.cx/double(imsize.width) - 1.0)) + "% , " + + to_string(100.0*(2.0*values.cy/double(imsize.height) - 1.0)) + "%)"); + if (use_physical) new nanogui::Widget(params_); + new nanogui::Label(params_, "Field of View (x):"); new nanogui::Label(params_, to_string(fovx) + "°"); - if (use_physical) new nanogui::Label(params_, ""); + if (use_physical) new nanogui::Widget(params_); new nanogui::Label(params_, "Field of View (y):"); new nanogui::Label(params_, to_string(fovy)+ "°"); - if (use_physical) new nanogui::Label(params_, ""); + if (use_physical) new nanogui::Widget(params_); new nanogui::Label(params_, "Aspect ratio:"); new nanogui::Label(params_, to_string(ar)); - if (use_physical) new nanogui::Label(params_, ""); + if (use_physical) new nanogui::Widget(params_); std::string pK; std::string pP; diff --git a/applications/gui2/src/widgets/imageview.cpp b/applications/gui2/src/widgets/imageview.cpp index 3562577975d3d882b3599e6efbbc693811bb5c8f..2137b1ce9f8124c65234952dfaaa93605323bbb5 100644 --- a/applications/gui2/src/widgets/imageview.cpp +++ b/applications/gui2/src/widgets/imageview.cpp @@ -538,6 +538,24 @@ StereoImageView::StereoImageView(nanogui::Widget* parent, nanogui::Orientation o right_->setFixedScale(true); } + +nanogui::Vector2f StereoImageView::imageCoordinateAt(const nanogui::Vector2f& p) const { + + nanogui::Vector2f pos = position().cast<float>(); + nanogui::Vector2f posr = pos + right_->position().cast<float>(); + + bool is_right = + ((p.x() >= posr.x()) && (orientation_ == nanogui::Orientation::Horizontal)) || + ((p.y() >= posr.y()) && (orientation_ == nanogui::Orientation::Vertical)); + + if (is_right) { + return right_->imageCoordinateAt(p - right_->position().cast<float>()); + } + else { + return left_->imageCoordinateAt(p); + } +} + bool StereoImageView::mouseMotionEvent(const nanogui::Vector2i &p, const nanogui::Vector2i &rel, int button, int modifiers) { if ((button & (1 << GLFW_MOUSE_BUTTON_RIGHT)) != 0) { nanogui::Vector2f posl = left_->imageCoordinateAt(p.cast<float>()); @@ -554,7 +572,7 @@ bool StereoImageView::mouseMotionEvent(const nanogui::Vector2i &p, const nanogui } return false; } -#include <loguru.hpp> + bool StereoImageView::scrollEvent(const nanogui::Vector2i& p, const nanogui::Vector2f& rel) { // synchronized zoom diff --git a/applications/gui2/src/widgets/imageview.hpp b/applications/gui2/src/widgets/imageview.hpp index 740152cdf15183a2aed2440c53ea52edb1059fa3..0209636fafd0049e4fcc8304db2b0240f15c8f2d 100644 --- a/applications/gui2/src/widgets/imageview.hpp +++ b/applications/gui2/src/widgets/imageview.hpp @@ -217,7 +217,10 @@ public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW }; -/** Two ImageViews with synchronized zoom and pan. */ +/** Two ImageViews with synchronized zoom and pan. Widget split in two equal + * size sections (left and right). With vertical orientation right is the lower + * image. +*/ class StereoImageView : public nanogui::Widget { public: StereoImageView(nanogui::Widget* parent, nanogui::Orientation orientation = nanogui::Orientation::Horizontal); @@ -232,6 +235,11 @@ public: FTLImageView* left() { return left_; } FTLImageView* right() { return right_; } + /** get image coordinate at given widget coordinate */ + nanogui::Vector2f imageCoordinateAt(const nanogui::Vector2f& position) const; + + nanogui::Orientation orientation() { return orientation_; } + void fit(); void bindLeft(GLuint id) { left_->texture().free(); left_->bindImage(id); } diff --git a/components/calibration/src/extrinsic.cpp b/components/calibration/src/extrinsic.cpp index 99bbf4036d954200abc0ff184d12f65a648495ad..d32961040338efec217048c1017b84588b12d443 100644 --- a/components/calibration/src/extrinsic.cpp +++ b/components/calibration/src/extrinsic.cpp @@ -356,6 +356,7 @@ double calibratePair(const cv::Mat &K1, const cv::Mat &D1, unsigned int ExtrinsicCalibration::addCamera(const CalibrationData::Intrinsic &c) { unsigned int idx = calib_.size(); calib_.push_back({c, {}}); + calib_optimized_.push_back(calib_.back()); is_calibrated_.push_back(false); return idx; } @@ -363,6 +364,7 @@ unsigned int ExtrinsicCalibration::addCamera(const CalibrationData::Intrinsic &c unsigned int ExtrinsicCalibration::addCamera(const CalibrationData::Calibration &c) { unsigned int idx = calib_.size(); calib_.push_back(c); + calib_optimized_.push_back(calib_.back()); is_calibrated_.push_back(true); return idx; } @@ -370,7 +372,9 @@ unsigned int ExtrinsicCalibration::addCamera(const CalibrationData::Calibration unsigned int ExtrinsicCalibration::addStereoCamera(const CalibrationData::Intrinsic &c1, const CalibrationData::Intrinsic &c2) { unsigned int idx = calib_.size(); calib_.push_back({c1, {}}); + calib_optimized_.push_back(calib_.back()); calib_.push_back({c2, {}}); + calib_optimized_.push_back(calib_.back()); is_calibrated_.push_back(false); is_calibrated_.push_back(false); mask_.insert({idx, idx + 1}); @@ -380,7 +384,9 @@ unsigned int ExtrinsicCalibration::addStereoCamera(const CalibrationData::Intrin unsigned int ExtrinsicCalibration::addStereoCamera(const CalibrationData::Calibration &c1, const CalibrationData::Calibration &c2) { unsigned int idx = calib_.size(); calib_.push_back({c1.intrinsic, c1.extrinsic}); + calib_optimized_.push_back(calib_.back()); calib_.push_back({c2.intrinsic, c2.extrinsic}); + calib_optimized_.push_back(calib_.back()); is_calibrated_.push_back(c1.extrinsic.valid()); is_calibrated_.push_back(c2.extrinsic.valid()); mask_.insert({idx, idx + 1}); @@ -673,9 +679,9 @@ double ExtrinsicCalibration::optimize() { LOG(INFO) << "fix distortion: " << (options_.fix_distortion ? "yes" : "no"); ba.run(options_); - LOG(INFO) << "removed points: " << ba.removeObservations(1.0); + LOG(INFO) << "removed points: " << ba.removeObservations(2.0); ba.run(options_); - LOG(INFO) << "removed points: " << ba.removeObservations(0.67); + LOG(INFO) << "removed points: " << ba.removeObservations(1.0); ba.run(options_); calib_optimized_.resize(calib_.size()); diff --git a/components/calibration/src/optimize.cpp b/components/calibration/src/optimize.cpp index 65e10fb512a83503e8178cd5c818c5e4f5983448..a7ff076238bfdadaf835bc1db5c644f5f0b566d3 100644 --- a/components/calibration/src/optimize.cpp +++ b/components/calibration/src/optimize.cpp @@ -445,7 +445,7 @@ void BundleAdjustment::addPoints(const vector<vector<Point2d>>& observations, st void BundleAdjustment::_setCameraParametrization(ceres::Problem &problem, const BundleAdjustment::Options &options) { - vector<int> constant_camera_parameters; + std::set<int> constant_camera_parameters; // apply options for (size_t i = 0; i < cameras_.size(); i++) { @@ -453,6 +453,9 @@ void BundleAdjustment::_setCameraParametrization(ceres::Problem &problem, const cameras_[i]->data[Camera::Parameter::K4] = 0.0; cameras_[i]->data[Camera::Parameter::K5] = 0.0; cameras_[i]->data[Camera::Parameter::K6] = 0.0; + constant_camera_parameters.insert(Camera::Parameter::K4); + constant_camera_parameters.insert(Camera::Parameter::K5); + constant_camera_parameters.insert(Camera::Parameter::K6); } if (options.zero_distortion) { cameras_[i]->data[Camera::Parameter::K1] = 0.0; @@ -468,33 +471,33 @@ void BundleAdjustment::_setCameraParametrization(ceres::Problem &problem, const // set extrinsic paramters constant for all cameras if (!options.optimize_motion) { - constant_camera_parameters.push_back(Camera::Parameter::Q1); - constant_camera_parameters.push_back(Camera::Parameter::Q2); - constant_camera_parameters.push_back(Camera::Parameter::Q3); - constant_camera_parameters.push_back(Camera::Parameter::Q4); - constant_camera_parameters.push_back(Camera::Parameter::TX); - constant_camera_parameters.push_back(Camera::Parameter::TY); - constant_camera_parameters.push_back(Camera::Parameter::TZ); + constant_camera_parameters.insert(Camera::Parameter::Q1); + constant_camera_parameters.insert(Camera::Parameter::Q2); + constant_camera_parameters.insert(Camera::Parameter::Q3); + constant_camera_parameters.insert(Camera::Parameter::Q4); + constant_camera_parameters.insert(Camera::Parameter::TX); + constant_camera_parameters.insert(Camera::Parameter::TY); + constant_camera_parameters.insert(Camera::Parameter::TZ); } // set intrinsic parameters constant for all cameras if (!options.optimize_intrinsic || options.fix_focal) { - constant_camera_parameters.push_back(Camera::Parameter::F); + constant_camera_parameters.insert(Camera::Parameter::F); } if (!options.optimize_intrinsic || options.fix_principal_point) { - constant_camera_parameters.push_back(Camera::Parameter::CX); - constant_camera_parameters.push_back(Camera::Parameter::CY); + constant_camera_parameters.insert(Camera::Parameter::CX); + constant_camera_parameters.insert(Camera::Parameter::CY); } if (!options.optimize_intrinsic || options.fix_distortion) { - constant_camera_parameters.push_back(Camera::Parameter::K1); - constant_camera_parameters.push_back(Camera::Parameter::K2); - constant_camera_parameters.push_back(Camera::Parameter::K3); - constant_camera_parameters.push_back(Camera::Parameter::K4); - constant_camera_parameters.push_back(Camera::Parameter::K5); - constant_camera_parameters.push_back(Camera::Parameter::K6); - constant_camera_parameters.push_back(Camera::Parameter::P1); - constant_camera_parameters.push_back(Camera::Parameter::P2); + constant_camera_parameters.insert(Camera::Parameter::K1); + constant_camera_parameters.insert(Camera::Parameter::K2); + constant_camera_parameters.insert(Camera::Parameter::K3); + constant_camera_parameters.insert(Camera::Parameter::K4); + constant_camera_parameters.insert(Camera::Parameter::K5); + constant_camera_parameters.insert(Camera::Parameter::K6); + constant_camera_parameters.insert(Camera::Parameter::P1); + constant_camera_parameters.insert(Camera::Parameter::P2); } if (!options.optimize_motion && !options.optimize_intrinsic) { @@ -504,14 +507,14 @@ void BundleAdjustment::_setCameraParametrization(ceres::Problem &problem, const } } else { - std::unordered_set<int> fix_extrinsic( + std::set<int> fix_extrinsic( options.fix_camera_extrinsic.begin(), options.fix_camera_extrinsic.end()); - std::unordered_set<int> fix_intrinsic( + std::set<int> fix_intrinsic( options.fix_camera_extrinsic.begin(), options.fix_camera_extrinsic.end()); for (size_t i = 0; i < cameras_.size(); i++) { - std::unordered_set<int> constant_parameters( + std::set<int> constant_parameters( constant_camera_parameters.begin(), constant_camera_parameters.end());