diff --git a/applications/gui2/CMakeLists.txt b/applications/gui2/CMakeLists.txt index 9cc2407305bba5062d8cfd1c71c226ad0f1b6028..60032ebebc0f2dddebc58dd8d70949007bbaf407 100644 --- a/applications/gui2/CMakeLists.txt +++ b/applications/gui2/CMakeLists.txt @@ -41,6 +41,7 @@ if (WITH_CERES) src/modules/calibration/extrinsic.cpp src/modules/calibration/intrinsic.cpp src/modules/calibration/stereo.cpp + src/views/calibration/widgets.cpp src/views/calibration/extrinsicview.cpp src/views/calibration/intrinsicview.cpp src/views/calibration/stereoview.cpp diff --git a/applications/gui2/src/modules/calibration/calibration.hpp b/applications/gui2/src/modules/calibration/calibration.hpp index e2e45e9e43528c27ed1fb8676f69611f3acb2812..307244cf13687ee427983c30a49929c7eeaa2555 100644 --- a/applications/gui2/src/modules/calibration/calibration.hpp +++ b/applications/gui2/src/modules/calibration/calibration.hpp @@ -20,21 +20,21 @@ public: void unset(unsigned int flag) { flags_ &= ~flag; } void reset() { flags_ = 0; } std::string name(int) const; - - int defaultFlags() const; - std::vector<int> list() const; - std::string explain(int) const; operator int() { return flags_; } + virtual int defaultFlags() const; + virtual std::vector<int> list() const; + virtual std::string explain(int) const; + private: int flags_ = 0; }; class OpenCVCalibrateFlagsStereo : public OpenCVCalibrateFlags { public: - int defaultFlags() const; - std::vector<int> list() const; - std::string explain(int) const; + int defaultFlags() const override; + std::vector<int> list() const override; + std::string explain(int) const override; }; /** @@ -129,7 +129,7 @@ public: int calibrated() { return state_->calibrated; } OpenCVCalibrateFlags& flags() { return state_->flags; }; - void resetFlags(); + int defaultFlags(); /** Reset calibration instance, discards drops all state. */ void reset(); @@ -168,19 +168,9 @@ public: float reprojectionError() { return state_->reprojection_error; } - /** Get sensor size from config (in mm) */ - float sensorWidth(); - float sensorHeight(); - - struct Values { - double fov_x; - double fov_y; - double f; - double pp_x; - double pp_y; - double aspect_ratio; - }; - Values values(double sensor_width, double sensor_height); + /** Get sensor size from config/previous calibration (in mm) */ + cv::Size2d sensorSize(); + void setSensorSize(cv::Size2d size); /** get current frame */ cv::cuda::GpuMat getFrame(); diff --git a/applications/gui2/src/modules/calibration/intrinsic.cpp b/applications/gui2/src/modules/calibration/intrinsic.cpp index 326568dfdc699cca491ed014c175151b22459805..2a39b8ceced33841b6d78d16c520df9b853b59ac 100644 --- a/applications/gui2/src/modules/calibration/intrinsic.cpp +++ b/applications/gui2/src/modules/calibration/intrinsic.cpp @@ -49,7 +49,7 @@ void IntrinsicCalibration::reset() { state_->object = std::make_unique<ChessboardObject>(); state_->channel = Channel::Left; state_->channel_alt = Channel::Left; - resetFlags(); + state_->flags.set(defaultFlags()); } void IntrinsicCalibration::start(ftl::data::FrameID id) { @@ -98,7 +98,7 @@ void IntrinsicCalibration::setChannel(Channel channel) { void IntrinsicCalibration::setChannel_(FrameSetPtr fs) { state_->channel_alt = state_->channel; if (fs == nullptr) { return; } - + auto& frame = (*fs)[state_->id.source()].cast<ftl::rgbd::Frame>(); if ((state_->channel== Channel::Left) && frame.has(Channel::LeftHighRes)) { state_->channel_alt = Channel::LeftHighRes; @@ -121,7 +121,7 @@ void IntrinsicCalibration::setChannel_(FrameSetPtr fs) { auto calib = frame.get<CalibrationData>(Channel::CalibrationData); if (calib.hasCalibration(state_->channel)) { auto intrinsic = calib.get(state_->channel).intrinsic; - state_->calib = CalibrationData::Intrinsic(intrinsic.matrix(size), intrinsic.distCoeffs.Mat(), size); + state_->calib = CalibrationData::Intrinsic(intrinsic, size); state_->calibrated = true; } } @@ -196,19 +196,19 @@ void IntrinsicCalibration::save() { setCalibration(frame, calib_data); } -void IntrinsicCalibration::resetFlags() { - // reset flags and get class defaults - state_->flags.reset(); - state_->flags.set(state_->flags.defaultFlags()); +int IntrinsicCalibration::defaultFlags() { + int flags = state_->flags.defaultFlags(); // load config flags for (int i : state_->flags.list()) { auto flag = get<bool>(state_->flags.name(i)); if (flag) { - if (*flag) state_->flags.set(i); - else state_->flags.unset(i); + if (*flag) flags |= i; + else flags &= (~i); } } + + return flags; } bool IntrinsicCalibration::isBusy() { @@ -224,20 +224,26 @@ void IntrinsicCalibration::run() { LOG(INFO) << state_->flags.name(f); } } - cv::Mat K = state_->calib.matrix(); + cv::Size2d ssize = sensorSize(); + cv::Mat K; cv::Mat distCoeffs; cv::Size size = state_->calib.resolution; - state_->calib.distCoeffs.Mat(12).copyTo(distCoeffs); + if (state_->flags.has(cv::CALIB_USE_INTRINSIC_GUESS)) { + // OpenCV seems to use these anyways? + K = state_->calib.matrix(); + state_->calib.distCoeffs.Mat(12).copyTo(distCoeffs); + } std::vector<cv::Mat> rvecs, tvecs; auto term = cv::TermCriteria (cv::TermCriteria::COUNT|cv::TermCriteria::EPS, state_->max_iter, 1.0e-6); - + state_->reprojection_error = cv::calibrateCamera( state_->points_object, state_->points, size, K, distCoeffs, rvecs, tvecs, state_->flags, term); - state_->calib = CalibrationData::Intrinsic(K, distCoeffs, size); + state_->calib = CalibrationData::Intrinsic(K, distCoeffs, size); + state_->calib.sensorSize = ssize; state_->calibrated = true; } catch (std::exception &e) { @@ -283,15 +289,6 @@ cv::cuda::GpuMat IntrinsicCalibration::getFrameUndistort() { return cv::cuda::GpuMat(); } - /*if (state_->map1.empty()) { - auto K = state_->calib.matrix(); - auto distCoeffs = state_->calib.distCoeffs.Mat(); - auto size = state_->calib.resolution; - cv::initUndistortRectifyMap - (K, distCoeffs, cv::noArray(), cv::noArray(), size, CV_16SC2, - state_->map1, state_->map2); - }*/ - auto im = getMat((*fs_current_)[state_->id.source()].cast<ftl::rgbd::Frame>(), state_->channel_alt); @@ -304,13 +301,20 @@ cv::cuda::GpuMat IntrinsicCalibration::getFrameUndistort() { return gpu; } -float IntrinsicCalibration::sensorWidth() { - return value("sensor_width", 0.0f); +cv::Size2d IntrinsicCalibration::sensorSize() { + if (state_->calib.sensorSize == cv::Size2d{0.0, 0.0}) { + double w = value("sensor_width", 0.0); + double h = value("sensor_height", 0.0); + return {w, h}; + } + else { + return state_->calib.sensorSize; + } }; -float IntrinsicCalibration::sensorHeight() { - return value("sensor_height", 0.0f); -}; +void IntrinsicCalibration::setSensorSize(cv::Size2d sz) { + state_->calib.sensorSize = sz; +} bool IntrinsicCalibration::hasChannel(Channel c) { if (fs_current_) { @@ -341,25 +345,6 @@ std::vector<cv::Point2f> IntrinsicCalibration::previousPoints() { return {}; } -IntrinsicCalibration::Values IntrinsicCalibration::values(double sensor_width, - double sensor_height) { - - Values result{ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; - if (!calibrated()) { - return result; - } - cv::Point2d pp; - cv::calibrationMatrixValues - (state_->calib.matrix(), state_->calib.resolution, - sensor_width, sensor_height, result.fov_x, result.fov_y, result.f, - pp, result.aspect_ratio); - - result.pp_x = pp.x; - result.pp_y = pp.y; - - return result; -} - ftl::calibration::CalibrationData::Intrinsic IntrinsicCalibration::calibration() { return state_->calib; } diff --git a/applications/gui2/src/views/calibration/intrinsicview.cpp b/applications/gui2/src/views/calibration/intrinsicview.cpp index 8c85dc82b08d265c749698261cab2798cbeff1dc..998c9773c2cdf0ec0ebd0e6f7264cefb344441e1 100644 --- a/applications/gui2/src/views/calibration/intrinsicview.cpp +++ b/applications/gui2/src/views/calibration/intrinsicview.cpp @@ -1,7 +1,7 @@ #include <sstream> #include "visualization.hpp" - +#include "widgets.hpp" #include "intrinsicview.hpp" #include "../../screen.hpp" @@ -85,8 +85,6 @@ class IntrinsicCalibrationView::CalibrationWindow : public FixedWindow { public: CalibrationWindow(nanogui::Widget* parent, IntrinsicCalibrationView* view); virtual void draw(NVGcontext* ctx) override; - double sensorWidth() { return sensor_width_->value(); } - double sensorHeight() { return sensor_width_->value(); } private: IntrinsicCalibrationView* view_; @@ -113,8 +111,8 @@ private: IntrinsicCalibration* ctrl_; nanogui::Button* bsave_; - nanogui::Widget* params_; - nanogui::Widget* dist_; + nanogui::Label* rms_; + ftl::gui2::IntrinsicDetails* info_; public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW @@ -301,12 +299,14 @@ IntrinsicCalibrationView::ControlWindow::ControlWindow(nanogui::Widget* parent, auto* bback_ = new nanogui::Button(buttons, "", ENTYPO_ICON_ARROW_LEFT); bback_->setFixedWidth(40); + bback_->setTooltip("Back to capture options"); bback_->setCallback([this, button = bback_](){ view_->setMode(Mode::CAPTURE_INIT); }); bsave_ = new nanogui::Button(buttons, "", ENTYPO_ICON_SAVE); bsave_->setFixedWidth(40); + bsave_->setTooltip("Save calibration"); bsave_->setEnabled(ctrl_->calibrated()); bsave_->setCallback([ctrl = ctrl_, view = view_](){ ctrl->save(); @@ -316,6 +316,7 @@ IntrinsicCalibrationView::ControlWindow::ControlWindow(nanogui::Widget* parent, bapply_ = new nanogui::Button(buttons, ""); bapply_->setFixedWidth(40); + bapply_->setTooltip("Apply distortion correction"); bapply_->setEnabled(ctrl_->calibrated()); bapply_->setFlags(nanogui::Button::Flags::ToggleButton); bapply_->setPushed(view_->undistort()); @@ -323,7 +324,6 @@ IntrinsicCalibrationView::ControlWindow::ControlWindow(nanogui::Widget* parent, view->setUndistort(v); }); - bresults_ = new nanogui::Button(buttons, "Details"); bresults_->setFixedWidth(120); @@ -354,7 +354,8 @@ void IntrinsicCalibrationView::ControlWindow::draw(NVGcontext* ctx) { bresults_->setEnabled(ctrl_->calibrated()); bsave_->setEnabled(ctrl_->calibrated()); bapply_->setEnabled(ctrl_->calibrated()); - bapply_->setIcon(ctrl_->calibrated() ? ENTYPO_ICON_EYE : ENTYPO_ICON_EYE); + bapply_->setIcon(view_->undistort() ? ENTYPO_ICON_EYE : ENTYPO_ICON_EYE); + bapply_->setPushed(view_->undistort()); updateCount(); FixedWindow::draw(ctx); } @@ -392,49 +393,28 @@ IntrinsicCalibrationView::CalibrationWindow::CalibrationWindow(nanogui::Widget* sensor->setLayout(grid_layout); new nanogui::Label(sensor, "Width"); - sensor_width_ = new nanogui::FloatBox<double>(sensor, ctrl_->sensorWidth()); + sensor_width_ = new nanogui::FloatBox<double>(sensor, ctrl_->sensorSize().width); sensor_width_->setEditable(true); sensor_width_->setFormat("[0-9]*\\.?[0-9]+"); sensor_width_->setUnits("mm"); + sensor_width_->setCallback([ctrl = this->ctrl_](double value) { + auto size = ctrl->sensorSize(); + ctrl->setSensorSize({value, size.height}); + }); new nanogui::Label(sensor, "Height"); - sensor_height_ = new nanogui::FloatBox<double>(sensor, ctrl_->sensorHeight()); + sensor_height_ = new nanogui::FloatBox<double>(sensor, ctrl_->sensorSize().height); sensor_height_->setEditable(true); sensor_height_->setFormat("[0-9]*\\.?[0-9]+"); sensor_height_->setUnits("mm"); + sensor_height_->setCallback([ctrl = this->ctrl_](double value) { + auto size = ctrl->sensorSize(); + ctrl->setSensorSize({size.width, value}); + }); // flags new nanogui::Label(this, "Flags"); - auto* flags = new nanogui::Widget(this); - flags->setLayout(new nanogui::BoxLayout - (nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 4)); - - for(int flag : ctrl_->flags().list()) { - auto* checkbox = new nanogui::CheckBox(flags, ctrl_->flags().name(flag), - [flag, this](bool state){ - if (state) { ctrl_->flags().set(flag); } - else { ctrl_->flags().unset(flag); } - }); - checkbox->setChecked(ctrl_->flags().has(flag)); - checkbox->setTooltip(ctrl_->flags().explain(flag)); - } - - // reset button - auto* reset = new nanogui::Button(this, "Reset flags"); - reset->setCallback([ctrl = ctrl_, flags](){ - ctrl->resetFlags(); - - // update widget - auto all_flags = ctrl->flags().list(); - for(size_t i = 0; i < all_flags.size(); i++) { - auto* checkbox = dynamic_cast<nanogui::CheckBox*>(flags->childAt(i)); - checkbox->setChecked(ctrl->flags().has(all_flags[i])); - if (all_flags[i] == cv::CALIB_USE_INTRINSIC_GUESS) { - checkbox->setEnabled(ctrl->calibrated()); - } - } - }); - + new ftl::gui2::OpenCVFlagWidget(this, &(ctrl_->flags()), ctrl_->defaultFlags()); status_ = new nanogui::Label(this, " "); bcalibrate_ = new nanogui::Button(this, "Run"); @@ -463,6 +443,7 @@ void IntrinsicCalibrationView::CalibrationWindow::draw(NVGcontext* ctx) { bcalibrate_->setEnabled(!ctrl_->isBusy() && (ctrl_->count() > 0)); if (calibrating_ && !ctrl_->isBusy()) { calibrating_ = false; + view_->setUndistort(true); view_->setMode(Mode::RESULTS); } FixedWindow::draw(ctx); @@ -475,24 +456,16 @@ IntrinsicCalibrationView::ResultWindow::ResultWindow(nanogui::Widget* parent, In FixedWindow(parent, "Results"), view_(view), ctrl_(view->ctrl_) { setLayout(new nanogui::BoxLayout - (nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 8 , 0)); + (nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 16 , 0)); (new nanogui::Button(buttonPanel(), "", ENTYPO_ICON_CROSS))->setCallback( [view = view_]() { view->setMode(Mode::VIDEO); }); - params_ = new nanogui::Widget(this); - nanogui::GridLayout *grid_layout = new nanogui::GridLayout - (nanogui::Orientation::Horizontal, 3, nanogui::Alignment::Fill, 8, 8); - grid_layout->setColAlignment - ({nanogui::Alignment::Maximum, nanogui::Alignment::Fill}); + rms_ = new nanogui::Label(this, ""); - params_->setLayout(grid_layout); - - dist_ = new nanogui::Widget(this); - dist_->setLayout(new nanogui::BoxLayout - (nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 8, 8)); + info_ = new ftl::gui2::IntrinsicDetails(this); bsave_ = new nanogui::Button(this, "Save"); bsave_->setCallback([button = bsave_, ctrl = ctrl_](){ @@ -507,83 +480,16 @@ void IntrinsicCalibrationView::ResultWindow::draw(NVGcontext* ctx) { } void IntrinsicCalibrationView::ResultWindow::update() { - while (params_->childCount() > 0) { - params_->removeChild(params_->childCount() - 1); - } - while (dist_->childCount() > 0) { - dist_->removeChild(dist_->childCount() - 1); - } - - auto sw = view_->wcalibration_->sensorWidth(); - auto sh = view_->wcalibration_->sensorHeight(); - auto values = ctrl_->values(sw, sh); - auto K = ctrl_->calibration().matrix(); - std::vector<double> D = ctrl_->calibration().distCoeffs.Mat(12); - auto fx = K.at<double>(0, 0); - auto fy = K.at<double>(1, 1); - - new nanogui::Label(params_, "Reprojection error:"); - new nanogui::Label(params_, to_string(ctrl_->reprojectionError())); - new nanogui::Label(params_, ""); - - new nanogui::Label(params_, "Focal length:"); - new nanogui::Label(params_, to_string(values.f) + " mm"); - new nanogui::Label(params_, - ((fx == fy) ? to_string(fx) + " px": ( - "(" + to_string(fx) + ", " - + to_string(fy) + ")"))); - - new nanogui::Label(params_, "Principal point:"); - new nanogui::Label(params_, - "(" + to_string(values.pp_x) + ", " + - to_string(values.pp_y) + ")"); - new nanogui::Label(params_, - "(" + to_string(K.at<double>(0, 2)) + ", " + - to_string(K.at<double>(1, 2)) + ")"); - - new nanogui::Label(params_, "Field of View (x):"); - new nanogui::Label(params_, to_string(values.fov_x) + "°"); - new nanogui::Label(params_, ""); - - new nanogui::Label(params_, "Field of View (y):"); - new nanogui::Label(params_, to_string(values.fov_y)+ "°"); - new nanogui::Label(params_, ""); - - new nanogui::Label(params_, "Aspect ratio:"); - new nanogui::Label(params_, to_string(values.aspect_ratio)); - new nanogui::Label(params_, ""); - - std::string pK; - std::string pP; - std::string pS; - if(D.size() >= 4) { - pK += "K1: " + to_string(D[0] ,3); - pK += ", K2: " + to_string(D[1] ,3); - pP += "P1: " + to_string(D[2], 3); - pP += "P1: " + to_string(D[3], 3); - } - if (D.size() >= 5) { - pK += ", K3: " + to_string(D[4], 3); - } - if (D.size() >= 8) { - pK += ", K4: " + to_string(D[5] ,3); - pK += ", K5: " + to_string(D[6] ,3); - pK += ", K6: " + to_string(D[7] ,3); + if (!isnan(ctrl_->reprojectionError())) { + rms_->setCaption("Reprojection error (RMS): " + to_string(ctrl_->reprojectionError())); + rms_->setVisible(true); } - if (D.size() >= 12) { - pS += "S1: " + to_string(D[8] ,3); - pS += ", S2: " + to_string(D[9] ,3); - pS += ", S3: " + to_string(D[10] ,3); - pS += ", S4: " + to_string(D[11] ,3); + else { + rms_->setVisible(false); } - - if (!pK.empty()) new nanogui::Label(dist_, pK); - if (!pP.empty()) new nanogui::Label(dist_, pP); - if (!pS.empty()) new nanogui::Label(dist_, pS); - + info_->update(ctrl_->calibration()); bsave_->setEnabled(true); bsave_->setCaption("Save"); - screen()->performLayout(); } //////////////////////////////////////////////////////////////////////////////// @@ -602,6 +508,7 @@ IntrinsicCalibrationView::IntrinsicCalibrationView(Screen* parent, wcalibration_ = new CalibrationWindow(screen(), this); wcalibration_->setFixedWidth(w); wresults_ = new ResultWindow(screen(), this); + wresults_->update(); screen()->performLayout(); setMode(Mode::CAPTURE_INIT); @@ -627,6 +534,7 @@ void IntrinsicCalibrationView::performLayout(NVGcontext *ctx) { wcalibration_->center(); wresults_->center(); imview_->setSize(size()); + View::performLayout(ctx); } void IntrinsicCalibrationView::draw(NVGcontext *ctx) { @@ -645,7 +553,9 @@ void IntrinsicCalibrationView::draw(NVGcontext *ctx) { } } View::draw(ctx); - drawChessboardCorners(ctx, imview_, ctrl_->previousPoints()); + if (ctrl_->capturing()) { + drawChessboardCorners(ctx, imview_, ctrl_->previousPoints()); + } } void IntrinsicCalibrationView::setMode(Mode m) { @@ -691,4 +601,5 @@ void IntrinsicCalibrationView::setMode(Mode m) { wresults_->update(); break; } + screen()->performLayout(); } diff --git a/applications/gui2/src/views/calibration/stereoview.cpp b/applications/gui2/src/views/calibration/stereoview.cpp index 921d1225896e5dcfa38517d5ae3d1ff68f16c38e..9433d816c09dd1902c878c4326d7501821aa2f93 100644 --- a/applications/gui2/src/views/calibration/stereoview.cpp +++ b/applications/gui2/src/views/calibration/stereoview.cpp @@ -1,9 +1,10 @@ #include <sstream> #include "visualization.hpp" - +#include "widgets.hpp" #include "stereoview.hpp" + #include "../../screen.hpp" #include "../../widgets/window.hpp" @@ -298,8 +299,8 @@ void StereoCalibrationView::ControlWindow::draw(NVGcontext* ctx) { } void StereoCalibrationView::ControlWindow::updateCount() { - /*txtnframes_->setCaption("Detected patterns: " + - std::to_string(ctrl_->calib().count()));*/ + txtnframes_->setCaption("Detected patterns: " + + std::to_string(ctrl_->count())); } //////////////////////////////////////////////////////////////////////////////// // Calibration Window @@ -326,35 +327,9 @@ StereoCalibrationView::CalibrationWindow::CalibrationWindow(nanogui::Widget* par auto* sensor = new nanogui::Widget(this); sensor->setLayout(grid_layout); - // flags new nanogui::Label(this, "Flags"); - auto* flags = new nanogui::Widget(this); - flags->setLayout(new nanogui::BoxLayout - (nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 4)); - - for(int flag : ctrl_->flags().list()) { - auto* checkbox = new nanogui::CheckBox(flags, ctrl_->flags().name(flag), - [flag, this](bool state){ - if (state) { ctrl_->flags().set(flag); } - else { ctrl_->flags().unset(flag); } - }); - checkbox->setChecked(ctrl_->flags().has(flag)); - checkbox->setTooltip(ctrl_->flags().explain(flag)); - } - - // reset button - auto* reset = new nanogui::Button(this, "Reset flags"); - reset->setCallback([ctrl = ctrl_, flags](){ - ctrl->resetFlags(); - - // update widget - auto all_flags = ctrl->flags().list(); - for(size_t i = 0; i < all_flags.size(); i++) { - auto* checkbox = dynamic_cast<nanogui::CheckBox*>(flags->childAt(i)); - checkbox->setChecked(ctrl->flags().has(all_flags[i])); - } - }); + new ftl::gui2::OpenCVFlagWidget(this, &(ctrl_->flags())); bcalibrate_ = new nanogui::Button(this, "Run"); bcalibrate_->setEnabled(false); diff --git a/applications/gui2/src/views/calibration/widgets.cpp b/applications/gui2/src/views/calibration/widgets.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d6732dd9f2b5014251ccd2c962c9dbb6dca8d0a9 --- /dev/null +++ b/applications/gui2/src/views/calibration/widgets.cpp @@ -0,0 +1,165 @@ +#include "widgets.hpp" + +#include <nanogui/label.h> +#include <nanogui/layout.h> +#include <nanogui/checkbox.h> + +#include <opencv2/calib3d.hpp> + +using ftl::gui2::OpenCVFlagWidget; +using ftl::gui2::OpenCVCalibrateFlags; + +template<typename T> +std::string to_string(T v, int precision = 2) { + std::stringstream stream; + stream << std::fixed << std::setprecision(precision) << v; + return stream.str(); +} + +OpenCVFlagWidget::OpenCVFlagWidget(nanogui::Widget* parent, OpenCVCalibrateFlags* flags, int defaultv) : + nanogui::Widget(parent), flags_(flags), defaults_(defaultv) { + + if (defaultv == -1) { + defaults_ = flags_->defaultFlags(); + } + + setLayout(new nanogui::BoxLayout + (nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 4)); + + reset(); +} + +void OpenCVFlagWidget::reset() { + while(childCount() > 0) { + removeChild(childCount() - 1); + } + + for(int flag : flags_->list()) { + auto* checkbox = new nanogui::CheckBox(this, flags_->name(flag), + [flag, this](bool state){ + if (state) { flags_->set(flag); } + else { flags_->unset(flag); } + }); + checkbox->setChecked(flags_->has(flag)); + checkbox->setTooltip(flags_->explain(flag)); + } + + // reset button + auto* reset = new nanogui::Button(this, "Reset flags"); + reset->setCallback([this](){ + + // update widget + auto all_flags = flags_->list(); + for(size_t i = 0; i < all_flags.size(); i++) { + auto* checkbox = dynamic_cast<nanogui::CheckBox*>(childAt(i)); + checkbox->setChecked(all_flags[i] & defaults_); + } + }); +} + +//////////////////////////////////////////////////////////////////////////////// + +using ftl::gui2::IntrinsicDetails; + +IntrinsicDetails::IntrinsicDetails(nanogui::Widget* parent) : + nanogui::Widget(parent) { + + setLayout(new nanogui::BoxLayout + (nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0 , 0)); + + params_ = new nanogui::Widget(this); + dist_ = new nanogui::Widget(this); + dist_->setLayout(new nanogui::BoxLayout + (nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 8)); +} + +void IntrinsicDetails::update(const ftl::calibration::CalibrationData::Intrinsic &values) { + while (params_->childCount() > 0) { + params_->removeChild(params_->childCount() - 1); + } + while (dist_->childCount() > 0) { + dist_->removeChild(dist_->childCount() - 1); + } + bool use_physical = values.sensorSize != cv::Size2d{0.0, 0.0}; + nanogui::GridLayout* grid_layout; + if (use_physical) { + grid_layout = new nanogui::GridLayout + (nanogui::Orientation::Horizontal, 3, nanogui::Alignment::Fill, 0, 8); + } + else { + grid_layout = new nanogui::GridLayout + (nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill, 0, 8); + } + grid_layout->setColAlignment + ({nanogui::Alignment::Maximum, nanogui::Alignment::Fill}); + params_->setLayout(grid_layout); + + auto sw = values.sensorSize.width; + auto sh = values.sensorSize.height; + auto K = values.matrix(); + auto imsize = values.resolution; + + double fovx; + double fovy; + double f; + cv::Point2d pp; + double ar; + cv::calibrationMatrixValues(K, imsize, sw, sh, fovx, fovy, f, pp, ar); + + new nanogui::Label(params_, "Image size:"); + if (use_physical) new nanogui::Label(params_, to_string(sw, 1) + std::string("x") + to_string(sh, 1)); + new nanogui::Label(params_, std::to_string(imsize.width) + std::string("x") + std::to_string(imsize.height)); + + new nanogui::Label(params_, "Focal length:"); + if (use_physical) new nanogui::Label(params_, to_string(f) + " mm"); + new nanogui::Label(params_, + ((values.fx == values.fy) ? to_string(values.fx) + " px": ( + "(" + to_string(values.fx) + ", " + + to_string(values.fy) + ")"))); + + new nanogui::Label(params_, "Principal point:"); + if (use_physical) new nanogui::Label(params_, + "(" + to_string(pp.x) + ", " + + to_string(pp.y) + ")"); + + new nanogui::Label(params_, + "(" + to_string(values.cx) + ", " + + to_string(values.cy) + ")"); + + new nanogui::Label(params_, "Field of View (x):"); + new nanogui::Label(params_, to_string(fovx) + "°"); + if (use_physical) new nanogui::Label(params_, ""); + + new nanogui::Label(params_, "Field of View (y):"); + new nanogui::Label(params_, to_string(fovy)+ "°"); + if (use_physical) new nanogui::Label(params_, ""); + + new nanogui::Label(params_, "Aspect ratio:"); + new nanogui::Label(params_, to_string(ar)); + if (use_physical) new nanogui::Label(params_, ""); + + std::string pK; + std::string pP; + std::string pS; + auto& D = values.distCoeffs; + + pK += "K1: " + to_string(D[0] ,3); + pK += ", K2: " + to_string(D[1] ,3); + pP += "P1: " + to_string(D[2], 3); + pP += ", P2: " + to_string(D[3], 3); + + pK += ", K3: " + to_string(D[4], 3); + + pK += ", K4: " + to_string(D[5] ,3); + pK += ", K5: " + to_string(D[6] ,3); + pK += ", K6: " + to_string(D[7] ,3); + + pS += "S1: " + to_string(D[8] ,3); + pS += ", S2: " + to_string(D[9] ,3); + pS += ", S3: " + to_string(D[10] ,3); + pS += ", S4: " + to_string(D[11] ,3); + + if (!pK.empty()) new nanogui::Label(dist_, pK); + if (!pP.empty()) new nanogui::Label(dist_, pP); + if (!pS.empty()) new nanogui::Label(dist_, pS); +} diff --git a/applications/gui2/src/views/calibration/widgets.hpp b/applications/gui2/src/views/calibration/widgets.hpp new file mode 100644 index 0000000000000000000000000000000000000000..381f2c9bc1a40274137d1fa621b99513eabf4dca --- /dev/null +++ b/applications/gui2/src/views/calibration/widgets.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include <nanogui/widget.h> + +#include <ftl/calibration/structures.hpp> + +#include "../../modules/calibration/calibration.hpp" + +namespace ftl { +namespace gui2 { + +class OpenCVFlagWidget : public nanogui::Widget { +public: + OpenCVFlagWidget(nanogui::Widget* parent, OpenCVCalibrateFlags* flags, int defaultv=-1); + void reset(); + void setDefaults(int v) { defaults_ = v; } + +private: + OpenCVCalibrateFlags* flags_; + int defaults_; +}; + +class IntrinsicDetails : public nanogui::Widget { +public: + IntrinsicDetails(nanogui::Widget* parent); + void update(const ftl::calibration::CalibrationData::Intrinsic &values); + +private: + nanogui::Widget* params_; + nanogui::Widget* dist_; +}; + +} +} \ No newline at end of file diff --git a/components/calibration/include/ftl/calibration/structures.hpp b/components/calibration/include/ftl/calibration/structures.hpp index 45217f82bd0b144d8974174f8b15fab8fe524277..c93f31404c6b06cc9fb0d82209b4516c10317147 100644 --- a/components/calibration/include/ftl/calibration/structures.hpp +++ b/components/calibration/include/ftl/calibration/structures.hpp @@ -45,6 +45,9 @@ struct CalibrationData { Intrinsic(const cv::Mat &K, cv::Size sz); Intrinsic(const cv::Mat &K, const cv::Mat &D, cv::Size sz); + /** New instance with scaled values for new resolution */ + Intrinsic(const Intrinsic& other, cv::Size sz); + /** Camera matrix */ cv::Mat matrix() const; /** Camera matrix (scaled) */ @@ -56,7 +59,11 @@ struct CalibrationData { double cx; double cy; DistortionCoefficients distCoeffs; - MSGPACK_DEFINE(resolution, fx, fy, cx, cy, distCoeffs); + + /** (optional) sensor size */ + cv::Size2d sensorSize; + + MSGPACK_DEFINE(resolution, fx, fy, cx, cy, distCoeffs, sensorSize); }; struct Extrinsic { Extrinsic(); diff --git a/components/calibration/src/structures.cpp b/components/calibration/src/structures.cpp index f0614cd13cd5063d5e253838f4fcfe44ff6340f8..755c44458c171c6692e0569d5fb691a994d43f97 100644 --- a/components/calibration/src/structures.cpp +++ b/components/calibration/src/structures.cpp @@ -56,6 +56,17 @@ CalibrationData::Intrinsic::Intrinsic(const cv::Mat &K, const cv::Mat &D, cv::Si D.copyTo(distCoeffs.Mat(D.cols)); } +CalibrationData::Intrinsic::Intrinsic(const CalibrationData::Intrinsic& other, cv::Size size) { + distCoeffs = DistortionCoefficients(other.distCoeffs); + sensorSize = other.sensorSize; + auto K = other.matrix(size); + fx = K.at<double>(0, 0); + fy = K.at<double>(1, 1); + cx = K.at<double>(0, 2); + cy = K.at<double>(1, 2); + resolution = size; +} + cv::Mat CalibrationData::Intrinsic::matrix() const { cv::Mat K(cv::Size(3, 3), CV_64FC1, cv::Scalar(0)); K.at<double>(0, 0) = fx; @@ -75,7 +86,7 @@ cv::Mat CalibrationData::Intrinsic::matrix(cv::Size size) const { CalibrationData::Extrinsic::Extrinsic() : rvec(0.0, 0.0, 0.0), tvec(0.0, 0.0, 0.0) {} CalibrationData::Extrinsic::Extrinsic(const cv::Mat &T) { - + if (T.type() != CV_64FC1) { throw ftl::exception("Input must be CV_64FC1"); } @@ -91,15 +102,15 @@ CalibrationData::Extrinsic::Extrinsic(const cv::Mat &T) { } CalibrationData::Extrinsic::Extrinsic(cv::InputArray R, cv::InputArray t) { - + if ((t.type() != CV_64FC1) || (R.type() != CV_64FC1)) { throw ftl::exception("R and t must be CV_64FC1"); } - + if ((t.size() != cv::Size(3, 1)) && (t.size() != cv::Size(1, 3))) { throw ftl::exception("t must be have size (3, 1) or (1, 3"); } - + if (R.isMat()) { const auto& rmat = R.getMat(); @@ -158,6 +169,7 @@ CalibrationData CalibrationData::readFile(const std::string &path) { (*it)["cx"] >> calib.intrinsic.cx; (*it)["cy"] >> calib.intrinsic.cy; (*it)["distCoeffs"] >> calib.intrinsic.distCoeffs.data_; + (*it)["sensorSize"] >> calib.intrinsic.sensorSize; (*it)["rvec"] >> calib.extrinsic.rvec; (*it)["tvec"] >> calib.extrinsic.tvec; @@ -176,7 +188,7 @@ void CalibrationData::writeFile(const std::string &path) const { fs << "enabled" << enabled; fs << "calibration" << "["; for (auto &[channel, data] : data_) { - fs << "{:" + fs << "{" << "channel" << int(channel) << "resolution" << data.intrinsic.resolution << "fx" << data.intrinsic.fx @@ -186,6 +198,7 @@ void CalibrationData::writeFile(const std::string &path) const { << "distCoeffs" << data.intrinsic.distCoeffs.data_ << "rvec" << data.extrinsic.rvec << "tvec" << data.extrinsic.tvec + << "sensorSize" << data.intrinsic.sensorSize << "}"; } fs << "]";