diff --git a/applications/gui/src/main.cpp b/applications/gui/src/main.cpp index ea45a23ce6c883e49f9f87340f6458723a6c893d..c93f64381b9fefb968e1f2424813afd4b01e45d4 100644 --- a/applications/gui/src/main.cpp +++ b/applications/gui/src/main.cpp @@ -28,6 +28,113 @@ using ftl::rgbd::Source; nanogui::ImageView *view; };*/ +class StatisticsImage { +private: + std::vector<float> data_; + cv::Size size_; + int n_; + +public: + StatisticsImage(cv::Size size) { + size_ = size; + data_ = std::vector<float>(size.width * size.height * 2, 0.0f); + n_ = 0; + } + + void update(const cv::Mat &in); + void getStdDev(cv::Mat &out); + void getMean(cv::Mat &out); +}; + +void StatisticsImage::update(const cv::Mat &in) { + DCHECK(in.type() == CV_32F); + DCHECK(in.size() == size_); + // Knuth's method + + n_++; + for (int i = 0; i < size_.width * size_.height; i++) { + float x = ((float*) in.data)[i]; + if (x > 30.0f || x < 0.001) { continue; } // invalid value + float &m = data_[2*i]; + float &S = data_[2*i+1]; + float m_prev = m; + m = m + (x - m) / n_; + S = S + (x - m) * (x - m_prev); + } +} + +void StatisticsImage::getStdDev(cv::Mat &in) { + in = cv::Mat(size_, CV_32F, 0.0f); + + for (int i = 0; i < size_.width * size_.height; i++) { + float &m = data_[2*i]; + float &S = data_[2*i+1]; + ((float*) in.data)[i] = sqrt(S / n_); + } +} + +void StatisticsImage::getMean(cv::Mat &in) { + in = cv::Mat(size_, CV_32F, 0.0f); + + for (int i = 0; i < size_.width * size_.height; i++) { + ((float*) in.data)[i] = data_[2*i]; + } +} + +class StatisticsImageNSamples { +private: + std::vector<cv::Mat> samples_; + cv::Size size_; + int i_; + int n_; + +public: + StatisticsImageNSamples(cv::Size size, int n) { + size_ = size; + samples_ = std::vector<cv::Mat>(n); + i_ = 0; + n_ = n; + } + + void update(const cv::Mat &in); + void getStdDev(cv::Mat &out); + void getMean(cv::Mat &out); +}; + +void StatisticsImageNSamples::update(const cv::Mat &in) { + DCHECK(in.type() == CV_32F); + DCHECK(in.size() == size_); + + i_ = (i_ + 1) % n_; + in.copyTo(samples_[i_]); +} + +void StatisticsImageNSamples::getStdDev(cv::Mat &in) { + // Knuth's method + cv::Mat mat_m(size_, CV_32F, 0.0f); + cv::Mat mat_S(size_, CV_32F, 0.0f); + + float n = 0.0f; + for (int i_sample = (i_ + 1) % n_; i_sample != i_; i_sample = (i_sample + 1) % n_) { + n += 1.0f; + for (int i = 0; i < size_.width * size_.height; i++) { + float &x = ((float*) samples_[i_sample].data)[i]; + float &m = ((float*) mat_m.data)[i]; + float &S = ((float*) mat_S.data)[i]; + float m_prev = m; + + if (x > 30.0 || x < 0.001) continue; + + m = m + (x - m) / n; + S = S + (x - m) * (x - m_prev); + } + } + + mat_S.copyTo(in); + cv::sqrt(in, in); +} + +void StatisticsImageNSamples::getMean(cv::Mat &in) {} namespace { constexpr char const *const defaultImageViewVertexShader = @@ -211,6 +318,8 @@ class FTLApplication : public nanogui::Screen { } } + StatisticsImageNSamples *stats_ = nullptr; + virtual void draw(NVGcontext *ctx) { using namespace Eigen; @@ -230,21 +339,40 @@ class FTLApplication : public nanogui::Screen { src_->grab(); src_->getFrames(rgb, depth); - if (swindow_->getDepth()) { - if (depth.rows > 0) { + if (!stats_ && depth.rows > 0) { + stats_ = new StatisticsImageNSamples(depth.size(), 25); + } + + if (stats_ && depth.rows > 0) { stats_->update(depth); } + + cv::Mat tmp; + + using ftl::gui::SourceWindow; + switch(swindow_->getMode()) { + case SourceWindow::Mode::depth: + if (depth.rows == 0) { break; } imageSize = Vector2f(depth.cols,depth.rows); - cv::Mat idepth; - depth.convertTo(idepth, CV_8U, 255.0f / 10.0f); // TODO(nick) - applyColorMap(idepth, idepth, cv::COLORMAP_JET); - texture_.update(idepth); + depth.convertTo(tmp, CV_8U, 255.0f / 10.0f); // TODO(nick) + applyColorMap(tmp, tmp, cv::COLORMAP_JET); + texture_.update(tmp); mImageID = texture_.texture(); - } - } else { - if (rgb.rows > 0) { + break; + + case SourceWindow::Mode::stddev: + if (depth.rows == 0) { break; } + imageSize = Vector2f(depth.cols, depth.rows); + stats_->getStdDev(tmp); + tmp.convertTo(tmp, CV_8U, 50.0); + applyColorMap(tmp, tmp, cv::COLORMAP_HOT); + texture_.update(tmp); + mImageID = texture_.texture(); + break; + + default: + if (rgb.rows == 0) { break; } imageSize = Vector2f(rgb.cols,rgb.rows); texture_.update(rgb); mImageID = texture_.texture(); - } } } diff --git a/applications/gui/src/src_window.cpp b/applications/gui/src/src_window.cpp index 62fbb7265f84bf03ffbe536aa30a2d9d8131828a..1aa1bd353c177a511392ff08cde101e9834a8f34 100644 --- a/applications/gui/src/src_window.cpp +++ b/applications/gui/src/src_window.cpp @@ -1,6 +1,8 @@ #include "src_window.hpp" #include <nanogui/imageview.h> +#include <nanogui/textbox.h> +#include <nanogui/slider.h> #include <nanogui/combobox.h> #include <nanogui/label.h> #include <nanogui/opengl.h> @@ -158,37 +160,43 @@ SourceWindow::SourceWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl) setLayout(new nanogui::GroupLayout()); using namespace nanogui; - - depth_ = false; - src_ = ftl::create<Source>(ctrl->getRoot(), "source", ctrl->getNet()); + + mode_ = Mode::rgb; + src_ = ftl::create<Source>(ctrl->getRoot(), "source", ctrl->getNet()); //Widget *tools = new Widget(this); - // tools->setLayout(new BoxLayout(Orientation::Horizontal, - // Alignment::Middle, 0, 6)); + // tools->setLayout(new BoxLayout(Orientation::Horizontal, + // Alignment::Middle, 0, 6)); - new Label(this, "Select source","sans-bold"); - available_ = ctrl->getNet()->findAll<string>("list_streams"); - auto select = new ComboBox(this, available_); - select->setCallback([this,select](int ix) { - LOG(INFO) << "Change source: " << ix; - src_->set("uri", available_[ix]); - }); + new Label(this, "Select source","sans-bold"); + available_ = ctrl->getNet()->findAll<string>("list_streams"); + auto select = new ComboBox(this, available_); + select->setCallback([this,select](int ix) { + LOG(INFO) << "Change source: " << ix; + src_->set("uri", available_[ix]); +}); ctrl->getNet()->onConnect([this,select](ftl::net::Peer *p) { - available_ = ctrl_->getNet()->findAll<string>("list_streams"); - select->setItems(available_); + available_ = ctrl_->getNet()->findAll<string>("list_streams"); + select->setItems(available_); }); - auto depth = new Button(this, "Depth"); - depth->setFlags(Button::ToggleButton); - depth->setChangeCallback([this](bool state) { - //image_->setDepth(state); - depth_ = state; - }); + auto button_rgb = new Button(this, "RGB (left)"); + button_rgb->setFlags(Button::RadioButton); + button_rgb->setPushed(true); + button_rgb->setChangeCallback([this](bool state) { mode_ = Mode::rgb; }); + + auto button_depth = new Button(this, "Depth"); + button_depth->setFlags(Button::RadioButton); + button_depth->setChangeCallback([this](bool state) { mode_ = Mode::depth; }); + + auto button_stddev = new Button(this, "Standard Deviation (25 frames)"); + button_stddev->setFlags(Button::RadioButton); + button_stddev->setChangeCallback([this](bool state) { mode_ = Mode::stddev; }); #ifdef HAVE_LIBARCHIVE - auto snapshot = new Button(this, "Snapshot"); - snapshot->setCallback([this] { + auto button_snapshot = new Button(this, "Snapshot"); + button_snapshot->setCallback([this] { try { char timestamp[18]; std::time_t t=std::time(NULL); diff --git a/applications/gui/src/src_window.hpp b/applications/gui/src/src_window.hpp index 6258267c62fcec81c688a89a9361c4c58d166938..e7aa192133eebd7ec306c41be1d84d30a8861538 100644 --- a/applications/gui/src/src_window.hpp +++ b/applications/gui/src/src_window.hpp @@ -18,16 +18,18 @@ namespace gui { */ class SourceWindow : public nanogui::Window { public: + enum class Mode { rgb, depth, stddev }; SourceWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl); ~SourceWindow(); ftl::rgbd::Source *getSource() const { return src_; } - bool getDepth() const { return depth_; } + bool getDepth() const { return mode_ == Mode::depth; } + Mode getMode() { return mode_; } private: ftl::ctrl::Master *ctrl_; ftl::rgbd::Source *src_; - bool depth_; + Mode mode_; VirtualCameraView *image_; std::vector<std::string> available_;