diff --git a/components/rgbd-sources/CMakeLists.txt b/components/rgbd-sources/CMakeLists.txt index 33f555f92e5eddf2c58d0385d411f0b267918426..5329ac9f2892c172af9cbc5f7fdc6b0e0e82360f 100644 --- a/components/rgbd-sources/CMakeLists.txt +++ b/components/rgbd-sources/CMakeLists.txt @@ -12,6 +12,7 @@ set(RGBDSRC # src/algorithms/rtcensus_sgm.cpp # src/algorithms/opencv_sgbm.cpp # src/algorithms/opencv_bm.cpp + src/cb_segmentation.cpp ) if (HAVE_REALSENSE) diff --git a/components/rgbd-sources/include/ftl/cb_segmentation.hpp b/components/rgbd-sources/include/ftl/cb_segmentation.hpp new file mode 100644 index 0000000000000000000000000000000000000000..75ec0bdc4c7c7c8c67ee77a05e808fb439961f8d --- /dev/null +++ b/components/rgbd-sources/include/ftl/cb_segmentation.hpp @@ -0,0 +1,117 @@ +#pragma once + +#include <opencv2/core.hpp> + +namespace ftl { + +/** + * @brief Codebook segmentation and depthmap filling. + * @param Input image width + * @param Input image height + * + * Codebook segmentation based on + * + * Kim, K., Chalidabhongse, T. H., Harwood, D., & Davis, L. (2005). + * Real-time foreground-background segmentation using codebook model. + * Real-Time Imaging. https://doi.org/10.1016/j.rti.2004.12.004 + * + * and fixed size codebook optimization in + * + * Rodriguez-Gomez, R., Fernandez-Sanchez, E. J., Diaz, J., & Ros, E. + * (2015). Codebook hardware implementation on FPGA for background + * subtraction. Journal of Real-Time Image Processing. + * https://doi.org/10.1007/s11554-012-0249-6 + * + * Additional modifications to include depth maps as part of the + * background model. + */ +class CBSegmentation { +public: + CBSegmentation(char codebook_size, size_t width, size_t height, float alpha, float beta, float epsilon, float sigma, int T_add, int T_del, int T_h); + + /** + * @brief Segment image. + * @param Input image (3-channels) + * @param Output Mat. Background pixels set to 0, foreground pixels > 0. + * + * @todo Template method on OpenCV type + */ + void apply(cv::Mat &in, cv::Mat &out, cv::Mat &depth, bool fill=false); + void apply(cv::Mat &in, cv::Mat &out); + +protected: + class Pixel { + public: + int idx; + float r; + float g; + float b; + float i; + int d; + long t; + Pixel(const int &index, const uchar *bgr, const int &depth, const long &time); + }; + + class Codeword { + public: + float r; + float g; + float b; + float i_min, i_max; + long f, lambda, p, q; + + float d_m; + float d_f; + float d_S; + + void set(CBSegmentation::Pixel &pixel); + void update(CBSegmentation::Pixel &pixel); + + bool colordiff(CBSegmentation::Pixel &pixel, float epsilon); + bool brightness(CBSegmentation::Pixel &pixel, float alpha, float beta); + bool depthdiff(CBSegmentation::Pixel &pixel, float sigma); + + inline int freq() { return f; } + inline long getLambda() { return lambda; } + inline long ctime() { return p; } + inline long atime() { return q; } + }; + + enum EntryType { H, M }; + + union Entry { + char size; + struct Data { + EntryType type; + CBSegmentation::Codeword cw; + } data ; + }; + + struct CompareEntry{ + bool operator()(const Entry &a,const Entry &b) const{ + return !((a.data.type == M && b.data.type == H) || + (a.data.cw.f < b.data.cw.f)); + } + }; + + bool processPixel(Pixel &px, Codeword *codeword=nullptr); + + size_t width_; + size_t height_; + size_t size_; + + int T_h_; + int T_add_; + int T_del_; + + float alpha_; + float beta_; + float epsilon_; + float sigma_; + +private: + long t_ = 1; + std::vector<Entry> cb_; +}; + +} \ No newline at end of file diff --git a/components/rgbd-sources/src/algorithms/fixstars_sgm.hpp b/components/rgbd-sources/src/algorithms/fixstars_sgm.hpp index 200775cf3f91c26cdd459935fdf171842118752a..95511cb2cdc9cde452d6ffe47ec4e3c9fb99c2f9 100644 --- a/components/rgbd-sources/src/algorithms/fixstars_sgm.hpp +++ b/components/rgbd-sources/src/algorithms/fixstars_sgm.hpp @@ -12,6 +12,8 @@ #include <opencv2/cudastereo.hpp> #include <ftl/configuration.hpp> +#include "ftl/cb_segmentation.hpp" + namespace ftl { namespace algorithms { diff --git a/components/rgbd-sources/src/cb_segmentation.cpp b/components/rgbd-sources/src/cb_segmentation.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f6f5fc2b2093b25fda6fd4554eb6e4bc04eef472 --- /dev/null +++ b/components/rgbd-sources/src/cb_segmentation.cpp @@ -0,0 +1,216 @@ +#include "ftl/cb_segmentation.hpp" + +#include<algorithm> +#include <math.h> + +using cv::Mat; +using cv::Vec3f, cv::Vec4f; + +using std::vector; +using std::min; +using std::max; +using std::pair; + +using namespace ftl; + +CBSegmentation::Pixel::Pixel(const int &index, const uchar *bgr, const int &depth, const long &time) { + idx = index; + r = bgr[2]; + g = bgr[1]; + b = bgr[0]; + i = sqrt(r*r + g*g + b*b); + d = depth; + t = time; +} + + +void CBSegmentation::Codeword::set(CBSegmentation::Pixel &px) { + r = px.r; + g = px.g; + b = px.b; + i_min = px.i; + i_max = px.i; + f = px.t; + lambda = px.t - 1; + p = px.t; + q = px.t; + + d_m = px.d; + d_S = 0.0; + d_f = 1.0; +} + +void CBSegmentation::Codeword::update(CBSegmentation::Pixel &px) { + r = (f * r + px.r) / (f + 1); + g = (f * g + px.g) / (f + 1); + b = (f * b + px.b) / (f + 1); + i_min = min(px.i, i_min); + i_max = max(px.i, i_max); + f = f + 1; + lambda = max(lambda, px.t - q); + q = px.t; + + if (false /*isValidDepth(px.d)*/) { // check value valid + float d_prev = d_m; + d_f = d_f + 1; + d_m = d_m + (px.d - d_m) / d_f; + d_S = d_S + (px.d - d_m) * (px.d - d_prev); + } +} + +// eq (2) and BSG +// +bool CBSegmentation::Codeword::colordiff(CBSegmentation::Pixel &px, float epsilon) { + float x_2 = px.r * px.r + px.g * px.g + px.b * px.b; + float v_2 = r*r + g*g + b*b; + float xv_2 = pow(px.r * r + px.g * g + px.b * b, 2); + float p_2 = xv_2 / v_2; + return sqrt(x_2 - p_2) < epsilon; +} + +// eq (3) +// note: ||x_t|| in the article is equal to I defined in +// "Algorithm for codebook construction" +// +bool CBSegmentation::Codeword::brightness(CBSegmentation::Pixel &px, float alpha, float beta) { + return true; + float i_low = alpha * i_max; + float i_hi = min(beta * i_max, i_min / alpha); + return (i_low <= px.i) && (px.i <= i_hi); +} + +CBSegmentation::CBSegmentation( + char codebook_size, size_t width, size_t height, + float alpha, float beta, float epsilon, float sigma, + int T_h, int T_add, int T_del) : + size_(codebook_size + 1), width_(width), height_(height), + alpha_(alpha), beta_(beta), epsilon_(epsilon), sigma_(sigma), + T_h_(T_h), T_add_(T_add), T_del_(T_del) { + + cb_ = vector<Entry>(width * height * size_); + for (size_t i = 0; i < cb_.size(); i += size_) { + cb_[i].size = 0; + } +} + +bool CBSegmentation::processPixel(CBSegmentation::Pixel &px, CBSegmentation::Codeword *codeword) { + char &size = cb_[size_ * px.idx].size; + size_t idx_begin = size_ * px.idx + 1; + + CBSegmentation::Entry::Data *start = &(cb_[idx_begin].data); + CBSegmentation::Entry::Data *entry = start; + + CBSegmentation::Entry::Data *lru = nullptr; + CBSegmentation::Entry::Data *lfu = nullptr; + + // TODO: benchmark sorting + + // if value is found (in M or H), loop exits early and no further maintenance + // is done. Maintenance may happen when codeword is not found and all entries + // are evaluated. + + for (int i = 0; i < size; i++) { + if (entry->type == M) { + // matching codeword, update and return + if (entry->cw.brightness(px, alpha_, beta_) && entry->cw.colordiff(px, epsilon_)) { + entry->cw.update(px); + codeword = &(entry->cw); + return true; + } + + // delete (move last to here) if not accessed for longer time than T_del + if ((px.t - entry->cw.atime()) > T_del_) { + size--; + *entry = *(start + size); + //std::sort( cb_.begin() + idx_begin, + // cb_.begin() + idx_begin + size, + // CompareEntry()); + continue; + } + + // update LFU + if (!lfu || lfu->cw.freq() > entry->cw.freq()) { + lfu = entry; + } + } + else if (entry->type == H) { + // matching codeword, update and return + if (entry->cw.brightness(px, alpha_, beta_) && entry->cw.colordiff(px, epsilon_)) { + entry->cw.update(px); + + // should be moved to M? if so, move and return true + if ((px.t - entry->cw.ctime()) > T_add_) { + entry->type = M; + //std::sort( cb_.begin() + idx_begin, + // cb_.begin() + idx_begin + size, + // CompareEntry()); + return true; + } + else { + return false; + } + } + + // delete if lambda lt T_h + if (entry->cw.getLambda() < T_h_) { + size--; + *entry = *(start + size); + continue; + } + + // update LRU + if (!lru || lru->cw.atime() > entry->cw.atime()) { + lru = entry; + } + } + } + + // not found, create new codeword (to empty position or lru h or lfu m) + // TODO: Should not prefer H codewords over M codewords? + if (size < (size_ - 1)) { + entry = start + size; + size++; + entry->type = H; + entry->cw.set(px); + } + else if (lru) { + lru->type = H; + lru->cw.set(px); + } + else { + lfu->type = H; + lfu->cw.set(px); + } + + // sort anyways (frequencies may have changed during earlier iterations) + //std::sort(cb_.begin() + idx_begin, cb_.begin() + idx_begin + size, CompareEntry()); + + return false; +} + +void CBSegmentation::apply(Mat &in, Mat &out) { + if ((out.rows != height_) || (out.cols != width_) + || (out.type() != CV_8UC1) || !out.isContinuous()) { + out = Mat(height_, width_, CV_8UC1, cv::Scalar(0)); + } + + // TODO: thread pool, queue N rows + #pragma omp parallel for + for (int y = 0; y < height_; ++y) { + size_t idx = y * width_; + uchar *ptr_in = in.ptr<uchar>(y); + uchar *ptr_out = out.ptr<uchar>(y); + + for (int x = 0; x < width_; ++x, ++idx, ptr_in += 3) { + auto px = Pixel(idx, ptr_in, 0, t_); + if(processPixel(px)) { + ptr_out[x] = 0; + } + else { + ptr_out[x] = 255; + } + } + } + + t_++; +}