last-svn-commit-19-gc6851fd Optimize Sauvola's multiscale binarization.

* core/init_integral_image.hh: New. * src/binarization/sauvola_ms.cc: Optimize and make it more robust. * canvas/integral_browsing.hh: New canvas to browse and compute data in an integral image. * binarization/internal/first_pass_functor.hh: New functor to be used in the integral browsing. * binarization/sauvola_threshold.hh: Add new overloads. * subsampling/integral_single_image.hh: Subsample an image and compute integral images at the same time. --- scribo/ChangeLog | 20 + scribo/binarization/internal/first_pass_functor.hh | 141 +++ scribo/binarization/sauvola_threshold.hh | 318 +++--- scribo/canvas/integral_browsing.hh | 412 ++++++++ scribo/core/init_integral_image.hh | 96 ++ scribo/src/binarization/sauvola_ms.cc | 1072 ++++++++++++++++---- scribo/subsampling/integral_single_image.hh | 403 ++++++++ 7 files changed, 2103 insertions(+), 359 deletions(-) create mode 100644 scribo/binarization/internal/first_pass_functor.hh create mode 100644 scribo/canvas/integral_browsing.hh create mode 100644 scribo/core/init_integral_image.hh create mode 100644 scribo/subsampling/integral_single_image.hh diff --git a/scribo/ChangeLog b/scribo/ChangeLog index 4fed85c..9eac19c 100644 --- a/scribo/ChangeLog +++ b/scribo/ChangeLog @@ -1,3 +1,23 @@ +2009-12-04 Guillaume Lazzara <z@lrde.epita.fr> + + Optimize Sauvola's multiscale binarization. + + * core/init_integral_image.hh: New. + + * src/binarization/sauvola_ms.cc: Optimize and make it more + robust. + + * canvas/integral_browsing.hh: New canvas to browse and compute + data in an integral image. + + * binarization/internal/first_pass_functor.hh: New functor to be + used in the integral browsing. + + * binarization/sauvola_threshold.hh: Add new overloads. + + * subsampling/integral_single_image.hh: Subsample an image and + compute integral images at the same time. + 2009-11-03 Guillaume Lazzara <z@lrde.epita.fr> Add a new example in Scribo. diff --git a/scribo/binarization/internal/first_pass_functor.hh b/scribo/binarization/internal/first_pass_functor.hh new file mode 100644 index 0000000..13fc62a --- /dev/null +++ b/scribo/binarization/internal/first_pass_functor.hh @@ -0,0 +1,141 @@ +// Copyright (C) 2009 EPITA Research and Development Laboratory (LRDE) +// +// This file is part of Olena. +// +// Olena is free software: you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation, version 2 of the License. +// +// Olena is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Olena. If not, see <http://www.gnu.org/licenses/>. +// +// As a special exception, you may use this file as part of a free +// software project without restriction. Specifically, if other files +// instantiate templates or use macros or inline functions from this +// file, or you compile this file and link it with other files to produce +// an executable, this file does not by itself cause the resulting +// executable to be covered by the GNU General Public License. This +// exception does not however invalidate any other reasons why the +// executable file might be covered by the GNU General Public License. + +#ifndef SCRIBO_BINARIZATION_INTERNAL_FIRST_PASS_FUNCTOR_HH +# define SCRIBO_BINARIZATION_INTERNAL_FIRST_PASS_FUNCTOR_HH + +# include <mln/value/int_u8.hh> + +# include <scribo/binarization/sauvola_threshold.hh> + + +// #include <mln/border/adjust.hh> + +namespace scribo +{ + + namespace binarization + { + + namespace internal + { + + using namespace mln; + + + unsigned my_find_root(image2d<unsigned>& parent, unsigned x) + { + if (parent.element(x) == x) + return x; + return parent.element(x) = my_find_root(parent, + parent.element(x)); + } + + + template <typename I> + struct first_pass_functor + { + const I& input; + mln_fwd_pixter(const I) pxl; + double res; + image2d<unsigned> parent; + image2d<unsigned> card; + image2d<bool> msk; + image2d<value::int_u8> t_sub; + + unsigned n_nbhs; + util::array<int> dp; + + static const double one_k = 1 - 0.34; + static const double k_R = 0.34 / 128.0; + + first_pass_functor(const I& input) + : input(input), + pxl(input) + { + res = 0; + pxl.start(); + + initialize(t_sub, input); + initialize(parent, input); + initialize(msk, input); + extension::fill(msk, false); + + initialize(card, input); + data::fill(card, 1); + + dp = negative_offsets_wrt(input, c4()); + n_nbhs = dp.nelements(); + } + + void exec(double mean, double stddev) + { + mln_precondition(pxl.is_valid()); + + unsigned p = pxl.offset(); + + // Use an inlined and developed version of sauvola's + // threshold formula. + value::int_u8 t_p = mean * (one_k + k_R * stddev); +// value::int_u8 t_p = sauvola_threshold_formula(mean, stddev); + + msk.element(p) = input.element(p) < t_p; + t_sub.element(p) = t_p; + if (! msk.element(p)) + { + pxl.next(); + return; + } + parent.element(p) = p; + for (unsigned i = 0; i < n_nbhs; ++i) + { + unsigned n = p + dp[i]; + if (! msk.element(n)) + continue; + unsigned r = my_find_root(parent, n); + if (r != p) + { + parent.element(r) = p; + card.element(p) += card.element(r); + } + } + + pxl.next(); // next pixel + } + + void finalize() + { + mln_assertion(! pxl.is_valid()); + } + }; + + + } // end of namespace scribo::binarization::internal + + } // end of namespace scribo::binarization + +} // end of namespace scribo + +#endif // SCRIBO_BINARIZATION_INTERNAL_FIRST_PASS_FUNCTOR_HH diff --git a/scribo/binarization/sauvola_threshold.hh b/scribo/binarization/sauvola_threshold.hh index 9a9e439..857de1f 100644 --- a/scribo/binarization/sauvola_threshold.hh +++ b/scribo/binarization/sauvola_threshold.hh @@ -44,10 +44,7 @@ # include <mln/pw/all.hh> # include <mln/core/routine/duplicate.hh> - -// Forward declaration. -namespace mln { template <typename T> class integral_image; } - +# include <scribo/core/init_integral_image.hh> namespace scribo { @@ -60,7 +57,7 @@ namespace scribo /*! \brief Compute a threshold image with Sauvola's algorithm. \input[in] input An image. - \input[in] window_size The window size.x + \input[in] window_size The window size. \input[out] simple The sum of all intensities of \p input. \input[out] squared The sum of all squared intensities of \p input. @@ -68,11 +65,11 @@ namespace scribo \return An image of local thresholds. */ - template <typename I, typename T> + template <typename I, typename J> mln_ch_value(I, value::int_u8) sauvola_threshold(const Image<I>& input, unsigned window_size, - integral_image<T>& simple, - integral_image<T>& squared); + Image<J>& simple, + Image<J>& squared); /// \overload @@ -106,36 +103,63 @@ namespace scribo }; - unsigned long long square_(const value::int_u8& val) + /*! \brief compute Sauvola's threshold applying directly the formula. + + \param[in] m_x_y Mean value. + \param[in] s_x_y Standard deviation. + \param[in] k Control the threshold value in the local + window. The higher, the lower the threshold + form the local mean m(x, y). + \param[in] R Maximum value of the standard deviation (128 + for grayscale documents). + + \return A threshold. + */ + inline + double + sauvola_threshold_formula(const double m_x_y, const double s_x_y, + const double k, const double R) { - unsigned long long v = static_cast<unsigned long long>(val); - return v * v; + return m_x_y * (1.0 + k * ((s_x_y / R) - 1.0)); } - unsigned long long identity_(const value::int_u8& val) + + /// \overload + // + inline + double + sauvola_threshold_formula(double m_x_y, double s_x_y) { - return static_cast<unsigned long long>(val); + // Badekas et al. said 0.34 was best. + const double k = 0.34; + + // 128 is best for grayscale documents. + const double R = 128; + + return sauvola_threshold_formula(m_x_y, s_x_y, k, R); } - /// \brief Compute a point wise threshold according Sauvola's - /// binarization. - /// - /// \param[in] p A site. - /// \param[in] simple An integral image of mean values. - /// \param[in] squared An integral image of squared mean values. - /// \param[in] win_width Window width. - /// \param[in] k Control the threshold value in the local - /// window. The higher, the lower the threshold - /// form the local mean m(x, y). - /// \param[in] R Maximum value of the standard deviation (128 - /// for grayscale documents). - - template <typename P, typename T> + /*! \brief Compute a point wise threshold according Sauvola's + binarization. + + \param[in] p A site. + \param[in] simple An integral image of mean values. + \param[in] squared An integral image of squared mean values. + \param[in] win_width Window width. + \param[in] k Control the threshold value in the local + window. The higher, the lower the threshold + form the local mean m(x, y). + \param[in] R Maximum value of the standard deviation (128 + for grayscale documents). + + \return A threshold. + */ + template <typename P, typename J> double compute_sauvola_threshold(const P& p, - const integral_image<T>& simple, - const integral_image<T>& squared, + const J& simple, + const J& squared, int win_width, double k, double R) { mln_precondition(simple.nrows() == squared.nrows()); @@ -146,142 +170,116 @@ namespace scribo int row_min = std::max(0, p.row() - w_2); int col_min = std::max(0, p.col() - w_2); - int row_max = std::min(simple.nrows() - 1, p.row() + w_2); - int col_max = std::min(simple.ncols() - 1, p.col() + w_2); + + int row_max = std::min(static_cast<int>(simple.nrows()) - 1, + p.row() + w_2); + int col_max = std::min(static_cast<int>(simple.ncols()) - 1, + p.col() + w_2); + + +// std::cout << "sauvola threshold : " +// << simple.domain() << " - " +// << row_max << " - " +// << col_max << std::endl; double wh = (row_max - row_min + 1) * (col_max - col_min + 1); // Mean. - double m_x_y_tmp = (simple(row_max, col_max) - + simple(row_min, col_min) - - simple(row_max, col_min) - - simple(row_min, col_max)); + double m_x_y_tmp = (simple.at_(row_max, col_max) + + simple.at_(row_min, col_min) + - simple.at_(row_max, col_min) + - simple.at_(row_min, col_max)); double m_x_y = m_x_y_tmp / wh; // Standard deviation. - double s_x_y_tmp = (squared(row_max, col_max) - + squared(row_min, col_min) - - squared(row_max, col_min) - - squared(row_min, col_max)); + double s_x_y_tmp = (squared.at_(row_max, col_max) + + squared.at_(row_min, col_min) + - squared.at_(row_max, col_min) + - squared.at_(row_min, col_max)); double s_x_y = std::sqrt((s_x_y_tmp - (m_x_y_tmp * m_x_y_tmp) / wh) / (wh - 1.f)); // Thresholding. - double t_x_y = m_x_y * (1.0 + k * ((s_x_y / R) - 1.0)); + double t_x_y = sauvola_threshold_formula(m_x_y, s_x_y, k, R); return t_x_y; } - template <typename P, typename T> + template <typename P, typename J> double - compute_sauvola_threshold(const P& p, - const integral_image<T>& simple, - const integral_image<T>& squared, - int win_width) + compute_sauvola_threshold_single_image(const P& p, + const J& integral, + int win_width) { - // Badekas et al. said 0.34 was best. - const double k = 0.34; - - // 128 is best for grayscale documents. - const double R = 128; - - return compute_sauvola_threshold(p, simple, squared, win_width, k, R); - } - - - } // end of namespace scribo::binarization::internal - - } // end of namespace scribo::binarization + // Window half width. + int w_2 = win_width >> 1; -} // end of namespace scribo + int row_min = std::max(0, p.row() - w_2); + int col_min = std::max(0, p.col() - w_2); + //FIXME: offset (-4) should be replaced by the number of + //padding pixels. + int row_max = std::min(static_cast<int>(integral.nrows()) - 1, + p.row() + w_2); + int col_max = std::min(static_cast<int>(integral.ncols()) - 1, + p.col() + w_2); -namespace mln -{ - template <typename T> - class integral_image - { - public: +// std::cout << "sauvola threshold : " +// << simple.domain() << " - " +// << row_max << " - " +// << col_max << std::endl; - integral_image() - : img_(0), nrows_(0), ncols_(0) - {} - - template<class F> - integral_image(const image2d<T>& i, F func) - : img_(0), - nrows_(0), - ncols_(0) - { - init_(i, func); - } + double wh = (row_max - row_min + 1) * (col_max - col_min + 1); - template<class F> - void init_(const image2d<T>& i, F func) - { - nrows_ = i.nrows(); - ncols_ = i.ncols(); + // Mean. + double m_x_y_tmp = (integral.at_(row_max, col_max).first() + + integral.at_(row_min, col_min).first() + - integral.at_(row_max, col_min).first() + - integral.at_(row_min, col_max).first()); - img_ = (unsigned long long**)malloc(sizeof(unsigned long long*) * nrows_); - for (int n = 0; n < nrows_; ++n) - img_[n] = (unsigned long long*)malloc(sizeof(unsigned long long) * ncols_); + double m_x_y = m_x_y_tmp / wh; - img_[0][0] = func(i.at_(0, 0)); + // Standard deviation. + double s_x_y_tmp = (integral.at_(row_max, col_max).second() + + integral.at_(row_min, col_min).second() + - integral.at_(row_max, col_min).second() + - integral.at_(row_min, col_max).second()); - for (int row = 1; row < nrows_; ++row) - img_[row][0] = (*this)(row - 1, 0) + func(i.at_(row, 0)); + double s_x_y = std::sqrt((s_x_y_tmp - (m_x_y_tmp * m_x_y_tmp) / wh) / (wh - 1.f)); - for (int col = 1; col < ncols_; ++col) - img_[0][col] = (*this)(0, col - 1) - + func(i.at_(0, col)); + // Thresholding. + double t_x_y = m_x_y * (1.0 + 0.14 * ((s_x_y / 128) - 1.0)); - for (int row = 1; row < nrows_; ++row) - for (int col = 1; col < ncols_; ++col) - img_[row][col] = (*this)(row - 1, col) - + (*this)(row, col - 1) - - (*this)(row - 1, col - 1) - + func(i.at_(row, col)); - } + return t_x_y; + } - ~integral_image() - { - for (int n = 0; n < nrows_; ++n) - free(img_[n]); - free(img_); - } - bool is_valid() const - { - return img_ != 0; - } + template <typename P, typename J> + double + compute_sauvola_threshold(const P& p, + const J& simple, + const J& squared, + int win_width) + { + // Badekas et al. said 0.34 was best. + const double k = 0.34; - unsigned long long operator()(int row, int col) const - { - return img_[row][col]; - } + // 128 is best for grayscale documents. + const double R = 128; - int nrows() const - { - return nrows_; - } + return compute_sauvola_threshold(p, simple, squared, win_width, k, R); + } - int ncols() const - { - return ncols_; - } + } // end of namespace scribo::binarization::internal - private: - unsigned long long** img_; - int nrows_; - int ncols_; - }; + } // end of namespace scribo::binarization -} // end of namespace mln +} // end of namespace scribo @@ -300,23 +298,25 @@ namespace scribo namespace generic { - template <typename I, typename T> + template <typename I, typename J> inline mln_concrete(I) - sauvola_threshold(const I& input, unsigned window_size, - integral_image<T>& simple, - integral_image<T>& squared) + sauvola_threshold(const Image<I>& input_, unsigned window_size, + Image<J>& simple_, + Image<J>& squared_) { trace::entering("scribo::binarization::impl::generic::sauvola_threshold"); - typedef mln_value(I) V; - typedef mln_site(I) P; + const I& input = exact(input_); + J& simple = exact(simple_); + J& squared = exact(squared_); - // Compute the sum of all intensities of input - simple.init_(input, internal::identity_); + mln_assertion(input.is_valid()); + mln_assertion(simple.is_valid()); + mln_assertion(squared.is_valid()); - // Compute the sum of all squared intensities of input - squared.init_(input, internal::square_); + typedef mln_value(I) V; + typedef mln_site(I) P; // Savaula Algorithm with I.I. @@ -340,24 +340,24 @@ namespace scribo - template <typename I, typename T> + template <typename I, typename J> inline mln_concrete(I) sauvola_threshold_gl(const I& input, unsigned window_size, - integral_image<T>& simple, - integral_image<T>& squared) + Image<J>& simple, + Image<J>& squared) { return impl::generic::sauvola_threshold(input, window_size, simple, squared); } - template <typename I, typename T> + template <typename I, typename J> inline mln_ch_value(I, value::int_u8) sauvola_threshold_rgb8(const I& input, unsigned window_size, - integral_image<T>& simple, - integral_image<T>& squared) + Image<J>& simple, + Image<J>& squared) { trace::entering("scribo::binarization::impl::sauvola_threshold_rgb8"); @@ -384,36 +384,36 @@ namespace scribo namespace internal { - template <unsigned n, typename I, typename T> + template <unsigned n, typename I, typename J> inline mln_ch_value(I, value::int_u<n>) sauvola_threshold_dispatch(const value::int_u<n>&, const I& input, unsigned window_size, - integral_image<T>& simple, - integral_image<T>& squared) + J& simple, + J& squared) { return impl::sauvola_threshold_gl(input, window_size, simple, squared); } - template <typename I, typename T> + template <typename I, typename J> inline mln_ch_value(I, value::int_u8) sauvola_threshold_dispatch(const value::rgb8&, const I& input, unsigned window_size, - integral_image<T>& simple, - integral_image<T>& squared) + J& simple, + J& squared) { return impl::sauvola_threshold_rgb8(input, window_size, simple, squared); } - template <typename I, typename T> + template <typename I, typename J> inline mln_ch_value(I, value::int_u8) sauvola_threshold_dispatch(const mln_value(I)&, const I& input, unsigned window_size, - integral_image<T>& simple, - integral_image<T>& squared) + J& simple, + J& squared) { // No dispatch for this kind of value type. mlc_abort(I)::check(); @@ -427,11 +427,11 @@ namespace scribo - template <typename I, typename T> + template <typename I, typename J> mln_ch_value(I, value::int_u8) sauvola_threshold(const Image<I>& input, unsigned window_size, - integral_image<T>& simple, - integral_image<T>& squared) + Image<J>& simple, + Image<J>& squared) { trace::entering("scribo::binarization::sauvola_threshold"); @@ -441,8 +441,9 @@ namespace scribo typedef mln_value(I) value_t; mln_ch_value(I, value::int_u8) output = internal::sauvola_threshold_dispatch(value_t(), exact(input), - window_size, simple, - squared); + window_size, + exact(simple), + exact(squared)); trace::exiting("scribo::text::ppm2pbm"); return output; @@ -454,7 +455,10 @@ namespace scribo mln_ch_value(I, value::int_u8) sauvola_threshold(const Image<I>& input, unsigned window_size) { - mln::integral_image<value::int_u8> simple, squared; + mln_ch_value(I, double) + simple = init_integral_image(input, scribo::internal::identity_), + squared = init_integral_image(input, scribo::internal::square_); + return sauvola_threshold(input, window_size, simple, squared); } diff --git a/scribo/canvas/integral_browsing.hh b/scribo/canvas/integral_browsing.hh new file mode 100644 index 0000000..814733f --- /dev/null +++ b/scribo/canvas/integral_browsing.hh @@ -0,0 +1,412 @@ +// Copyright (C) 2009 EPITA Research and Development Laboratory (LRDE) +// +// This file is part of Olena. +// +// Olena is free software: you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation, version 2 of the License. +// +// Olena is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Olena. If not, see <http://www.gnu.org/licenses/>. +// +// As a special exception, you may use this file as part of a free +// software project without restriction. Specifically, if other files +// instantiate templates or use macros or inline functions from this +// file, or you compile this file and link it with other files to produce +// an executable, this file does not by itself cause the resulting +// executable to be covered by the GNU General Public License. This +// exception does not however invalidate any other reasons why the +// executable file might be covered by the GNU General Public License. + + +#ifndef SCRIBO_CANVAS_INTEGRAL_BROWSING_HH +# define SCRIBO_CANVAS_INTEGRAL_BROWSING_HH + +# include <mln/core/image/image2d.hh> +# include <mln/util/couple.hh> + +namespace scribo +{ + + namespace canvas + { + using namespace mln; + + + template <typename F> + void integral_browsing(const image2d<util::couple<double, double> >& ima, + unsigned step, + unsigned w, unsigned h, + F& functor); + + +# ifndef MLN_INCLUDE_ONLY + + + namespace internal + { + + inline + void compute_stats(// in + double sum, double sum_2, unsigned n, + // out + double& mean, double& stddev) + { + mean = sum / n; + stddev = std::sqrt(sum_2 / n - mean * mean); + // unbias version: + // stddev = std::sqrt((sum_2 - n * mean * mean) / (n - 1)); + } + + } // end of namespace scribo::canvas::internal + + + + + template <typename F> + void integral_browsing(const image2d<util::couple<double, double> >& ima, + unsigned step, + unsigned w, unsigned h, + F& functor) + { + typedef util::couple<double, double> V; + typedef const V* Ptr; + Ptr a_ima, b_ima, c_ima, d_ima; + + const unsigned + nrows = ima.nrows(), + ncols = ima.ncols(), + row_0 = step / 2, + col_0 = step / 2; + + const unsigned + offset_down = ima.delta_index(dpoint2d(step, 0)), + offset_ante = ima.delta_index(dpoint2d(0, -w)), + offset_below = ima.delta_index(dpoint2d(+h, 0)); + + const unsigned + max_row_top = h/2, + max_row_mid = nrows - 1 - h/2, + max_col_left = w/2, + max_col_mid = ncols - 1 - w/2, + + step_2 = step * step, + + h_top = row_0 + h/2 + 1, + w_left = col_0 + w/2 + 1; + + unsigned row, col; + + for (col = col_0; col <= max_col_mid; col += step) ; + unsigned w_right = ncols - col + w/2; + + Ptr + d_tl_start, d_tr_start, + b_ml_start = 0, d_ml_start = 0, b_mr_start = 0, d_mr_start = 0, + b_bl_start = 0, d_bl_start = 0, b_br_start = 0, d_br = 0; + + double mean, stddev; + + + // ------------------------------- + // T (top) + // ------------------------------- + + const unsigned + delta_start_left = step * w_left, + delta_start_right = step * w_right, + step_w = step * w; + unsigned + size_tl_start = h_top * w_left, + size_tl, + delta_size_tl = h_top * step, + size_tc = h_top * w, + delta_size_tr = h_top * step, + size_tr_start = h_top * w_right, + size_tr; + + d_tl_start = & ima.at_(row_0 + h/2, col_0 + w/2); + d_tr_start = & ima.at_(row_0 + h/2, ncols - 1); + + for (row = row_0; row <= max_row_top; row += step) + { + + // TL (top left) + + d_ima = d_tl_start; + size_tl = size_tl_start; + + for (col = col_0; col <= max_col_left; col += step) + { + // D + internal::compute_stats(d_ima->first(), + d_ima->second(), + size_tl, + mean, stddev); + functor.exec(mean, stddev); + d_ima += step; + size_tl += delta_size_tl; + } + + delta_size_tl += step_2; + size_tl_start += delta_start_left; + d_tl_start += offset_down; + + // TC (top center) + + c_ima = d_ima + offset_ante; + + for (; col <= max_col_mid; col += step) + { + // D - C + internal::compute_stats(d_ima->first() - c_ima->first(), + d_ima->second() - c_ima->second(), + size_tc, + mean, stddev); + functor.exec(mean, stddev); + c_ima += step; + d_ima += step; + } + + size_tc += step_w; + + // TR (top right) + + d_ima = d_tr_start; + double + d_sum = d_ima->first(), + d_sum_2 = d_ima->second(); + size_tr = size_tr_start; + + for (; col < ncols; col += step) + { + // D* - C + internal::compute_stats(d_sum - c_ima->first(), + d_sum_2 - c_ima->second(), + size_tr, + mean, stddev); + functor.exec(mean, stddev); + c_ima += step; + size_tr -= delta_size_tr; + } + + delta_size_tr += step_2; + size_tr_start += delta_start_right; + d_tr_start += offset_down; + + } + + + + // ------------------------------- + // (M) middle + // ------------------------------- + + + const unsigned + size_ml_start = h * w_left, + h_step = h * step, + size_mc = w * h; + unsigned + size_ml, + size_mr_start = h * w_right, + size_mr; + + if (row <= max_row_mid) + { + b_ml_start = & ima.at_(row - h/2 - 1, col_0 + w/2); + d_ml_start = b_ml_start + offset_below; + b_mr_start = & ima.at_(row - h/2 - 1, ncols - 1); + d_mr_start = b_mr_start + offset_below; + } + + for (; row <= max_row_mid; row += step) + { + + // ML (middle left) + + size_ml = size_ml_start; + b_ima = b_ml_start; + d_ima = d_ml_start; + + for (col = col_0; col <= max_col_left; col += step) + { + // D - B + internal::compute_stats(d_ima->first() - b_ima->first(), + d_ima->second() - b_ima->second(), + size_ml, + mean, stddev); + functor.exec(mean, stddev); + b_ima += step; + d_ima += step; + size_ml += h_step; + } + + b_ml_start += offset_down; + d_ml_start += offset_down; + + // MC (middle center) + + a_ima = b_ima + offset_ante; + c_ima = d_ima + offset_ante; + + for (; col <= max_col_mid; col += step) + { + // D + A - B - C + internal::compute_stats((d_ima->first() - b_ima->first()) + (a_ima->first() - c_ima->first()), + (d_ima->second() - b_ima->second()) + (a_ima->second() - c_ima->second()), + size_mc, + mean, stddev); + functor.exec(mean, stddev); + a_ima += step; + b_ima += step; + c_ima += step; + d_ima += step; + } + + // MR (middle right) + + size_mr = size_mr_start; + b_ima = b_mr_start; + d_ima = d_mr_start; + double + d_b_sum = d_ima->first() - b_ima->first(), + d_b_sum_2 = d_ima->second() - b_ima->second(); + + for (; col < ncols; col += step) + { + // D* + A - B* - C + internal::compute_stats(d_b_sum + (a_ima->first() - c_ima->first()), + d_b_sum_2 + (a_ima->second() - c_ima->second()), + size_mr, + mean, stddev); + functor.exec(mean, stddev); + a_ima += step; + c_ima += step; + size_mr -= h_step; + } + + b_mr_start += offset_down; + d_mr_start += offset_down; + } + + + + // ------------------------------- + // B (bottom) + // ------------------------------- + + unsigned + size_bl_start = (nrows - row + h/2) * w_left, + size_bl, + delta_size_bl = (nrows - row + h/2) * step, + size_bc = (nrows - row + h/2) * w, + size_br_start = (nrows - row + h/2) * w_right, + delta_size_br = (nrows - row + h/2) * step, + size_br; + + if (row < nrows) + { + b_bl_start = & ima.at_(row - h/2 - 1, col_0 + w/2); + d_bl_start = & ima.at_(nrows - 1, col_0 + w/2); + b_br_start = & ima.at_(row - h/2 - 1, ncols - 1); + d_br = & ima.at_(nrows - 1, ncols - 1); + } + + for (; row < nrows; row += step) + { + + // BL (bottom left) + + size_bl = size_bl_start; + b_ima = b_bl_start; + d_ima = d_bl_start; + + for (col = col_0; col <= max_col_left; col += step) + { + // D* - B + internal::compute_stats(d_ima->first() - b_ima->first(), + d_ima->second() - b_ima->second(), + size_bl, + mean, stddev); + functor.exec(mean, stddev); + b_ima += step; + d_ima += step; + size_bl += delta_size_bl; + } + + delta_size_bl -= step_2; + size_bl_start -= delta_start_left; + b_bl_start += offset_down; + + // BC (bottom center) + + a_ima = b_ima + offset_ante; + c_ima = d_ima + offset_ante; + + for (; col <= max_col_mid; col += step) + { + // D* + A - B - C* + internal::compute_stats((d_ima->first() - b_ima->first()) + (a_ima->first() - c_ima->first()), + (d_ima->second() - b_ima->second()) + (a_ima->second() - c_ima->second()), + size_bc, + mean, stddev); +// std::cout << (d_ima->second() - b_ima->second()) + (a_ima->second() - c_ima->second()) << std::endl; + +// std::cout << d_ima->second() << " - " << b_ima->second() << " - " +// << a_ima->second() << " - " << c_ima->second() << std::endl; +// std::cout << d_ima->first() << " - " << b_ima->first() << " - " +// << a_ima->first() << " - " << c_ima->first() << std::endl; + functor.exec(mean, stddev); + a_ima += step; + b_ima += step; + c_ima += step; + d_ima += step; + } + + size_bc -= step_w; + + // BR (bottom right) + + size_br = size_br_start; + b_ima = b_br_start; + d_ima = d_br; + double + d_b_sum = d_ima->first() - b_ima->first(), + d_b_sum_2 = d_ima->second() - b_ima->second(); + + for (; col < ncols; col += step) + { + // D* + A - B* - C* + internal::compute_stats(d_b_sum + (a_ima->first() - c_ima->first()), + d_b_sum_2 + (a_ima->second() - c_ima->second()), + size_br, + mean, stddev); + functor.exec(mean, stddev); + a_ima += step; + c_ima += step; + size_br -= delta_size_br; + } + + delta_size_br -= step_2; + size_br_start -= delta_start_right; + b_br_start += offset_down; + } + + functor.finalize(); + } + +# endif // ! MLN_INCLUDE_ONLY + + + } // end of namespace scribo::canvas + +} // end of namespace mln + + +#endif // ! SCRIBO_CANVAS_INTEGRAL_BROWSING_HH diff --git a/scribo/core/init_integral_image.hh b/scribo/core/init_integral_image.hh new file mode 100644 index 0000000..ec19070 --- /dev/null +++ b/scribo/core/init_integral_image.hh @@ -0,0 +1,96 @@ +// Copyright (C) 2009 EPITA Research and Development Laboratory (LRDE) +// +// This file is part of Olena. +// +// Olena is free software: you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation, version 2 of the License. +// +// Olena is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Olena. If not, see <http://www.gnu.org/licenses/>. +// +// As a special exception, you may use this file as part of a free +// software project without restriction. Specifically, if other files +// instantiate templates or use macros or inline functions from this +// file, or you compile this file and link it with other files to produce +// an executable, this file does not by itself cause the resulting +// executable to be covered by the GNU General Public License. This +// exception does not however invalidate any other reasons why the +// executable file might be covered by the GNU General Public License. + +#ifndef SCRIBO_CORE_INIT_INTEGRAL_IMAGE_HH +# define SCRIBO_CORE_INIT_INTEGRAL_IMAGE_HH + +/// \file +/// +/// + +# include <mln/core/image/image2d.hh> + +namespace scribo +{ + using namespace mln; + + + namespace internal + { + + double square_(const double& val) + { + double v = static_cast<double>(val); + return v * v; + } + + double identity_(const double& val) + { + return static_cast<double>(val); + } + + } // end of namespace scribo::internal + + + template <typename I, typename F> + mln_ch_value(I,double) + init_integral_image(const Image<I>& input_, F& func) + { + trace::entering("scribo::init_integral_image"); + + const I& input = exact(input_); + mln_precondition(input.is_valid()); + mln_precondition(input.domain().pmin() == literal::origin); + + mln_ch_value(I,double) output; + initialize(output, input); + + unsigned + nrows_ = input.nrows(), + ncols_ = input.ncols(); + + output.at_(0,0) = func(input.at_(0, 0)); + + for (unsigned row = 1; row < nrows_; ++row) + output.at_(row,0) = output.at_(row - 1, 0) + func(input.at_(row, 0)); + + for (unsigned col = 1; col < ncols_; ++col) + output.at_(0,col) = output.at_(0, col - 1) + + func(input.at_(0, col)); + + for (unsigned row = 1; row < nrows_; ++row) + for (unsigned col = 1; col < ncols_; ++col) + output.at_(row, col) = output.at_(row - 1, col) + + output.at_(row, col - 1) + - output.at_(row - 1, col - 1) + + func(input.at_(row, col)); + + trace::exiting("scribo::init_integral_image"); + return output; + } + +} // end of namespace scribo + +#endif // ! SCRIBO_CORE_INIT_INTEGRAL_IMAGE_HH diff --git a/scribo/src/binarization/sauvola_ms.cc b/scribo/src/binarization/sauvola_ms.cc index 747bc11..8a07dcc 100644 --- a/scribo/src/binarization/sauvola_ms.cc +++ b/scribo/src/binarization/sauvola_ms.cc @@ -25,6 +25,11 @@ #include <mln/core/alias/neighb2d.hh> #include <mln/data/stretch.hh> +#include <mln/data/paste.hh> +// #include <mln/debug/iota.hh> +// #include <mln/debug/quiet.hh> +// #include <mln/debug/println.hh> +// #include <mln/debug/println_with_border.hh> #include <mln/debug/filename.hh> #include <mln/fun/i2v/array.hh> #include <mln/io/pbm/all.hh> @@ -32,17 +37,30 @@ #include <mln/io/ppm/all.hh> #include <mln/literal/colors.hh> #include <mln/math/sqr.hh> -#include <mln/subsampling/subsampling.hh> +#include <mln/math/abs.hh> + +#include <mln/subsampling/antialiased.hh> + #include <mln/transform/influence_zone_geodesic.hh> #include <mln/util/timer.hh> #include <mln/value/int_u16.hh> #include <mln/value/int_u8.hh> #include <mln/value/label_16.hh> #include <mln/value/rgb8.hh> +#include <mln/border/equalize.hh> +#include <mln/border/mirror.hh> +#include <mln/border/adjust.hh> #include <mln/debug/filename.hh> +#include <mln/core/box_runend_piter.hh> +#include <mln/core/box_runstart_piter.hh> + +#include <scribo/subsampling/integral_single_image.hh> +//#include <scribo/subsampling/integral.hh> + #include <scribo/core/macros.hh> #include <scribo/core/object_image.hh> + #include <scribo/filter/objects_small.hh> #include <scribo/filter/objects_large.hh> @@ -50,155 +68,737 @@ #include <scribo/filter/objects_thick.hh> #include <scribo/primitive/extract/objects.hh> + #include <scribo/binarization/sauvola_threshold.hh> +#include <scribo/binarization/internal/first_pass_functor.hh> + +#include <scribo/canvas/integral_browsing.hh> + #include <scribo/debug/usage.hh> #include <scribo/debug/save_object_diff.hh> + namespace mln { + using value::int_u8; -// FIXME: do not use scribo's filters since they use object images. -// Object images computes object bounding boxes which are not needed here. -// -// template <typename L> -// mln_concrete(L) -// filter_small_objects(const Image<L>& objects, -// const mln_value(L)& nobjects, unsigned min_size) -// { -// card = labeling::compute(card_t(), objects, objects.nlabels()); - -// mln_concrete(L) output; -// initialize(output, objects); -// fun::i2v::array<bool> f(nobjects.next(), 0); -// for (unsigned i = 0; i < card.size(); ++i) + unsigned my_find_root(image2d<unsigned>& parent, unsigned x) + { + if (parent.element(x) == x) + return x; + return parent.element(x) = my_find_root(parent, + parent.element(x)); + } -// } - template <typename I, typename T> - mln_ch_value(I, bool) - apply_sauvola(const I& intensity, const T& threshold, unsigned s) + image2d<int_u8> + compute_t_n_and_e_2(const image2d<int_u8>& sub, image2d<int_u8>& e_2, + unsigned lambda_min, unsigned lambda_max, + unsigned q, unsigned i, unsigned w, + const image2d<util::couple<double,double> >& integral_sum_sum_2) + // lambdas: limits of component cardinality at this scale { - mln_precondition(intensity.domain() == threshold.domain()); + typedef image2d<int_u8> I; + typedef point2d P; - mln_ch_value(I, bool) output; - initialize(output, intensity); + util::timer tt; + float t_; - mln_piter(I) p(intensity.domain()); - for_all(p) - output(p) = (intensity(p) <= threshold(p / s)); + unsigned ratio = std::pow(q, i - 2); // Ratio in comparison to e_2 + + + tt.restart(); + + unsigned w_2 = w / 2 * ratio; + + // 1st pass + scribo::binarization::internal::first_pass_functor< image2d<int_u8> > + f(sub); + scribo::canvas::integral_browsing(integral_sum_sum_2, + ratio, + w_2, w_2, + f); + +// debug::println("mask", f.msk); +// debug::println("parent", f.parent); +// debug::println("card", f.card); + + t_ = tt; + if (mln::debug::quiet) + std::cout << "1st pass - " << t_ << std::endl; + + tt.restart(); + + { + util::array<mln_value_(I) *> ptr(ratio); + unsigned nrows = geom::nrows(e_2); + + mln_box_runend_piter_(I) sp(sub.domain()); // Backward. + unsigned ncols = sp.run_length(); + for_all(sp) + { + unsigned p = &sub(sp) - sub.buffer(); // Offset + P site = sp; + + { + P tmp = site * ratio; + + // FIXME: to be removed! + if (tmp.row() + ratio >= nrows) + ptr.resize(nrows - tmp.row()); + + ptr(0) = &e_2(tmp); + // FIXME: pointers could just be updated with an offset. + for (unsigned j = 1; j < ptr.size(); ++j) + { + tmp[0] += 1; + ptr(j) = & e_2(tmp); + } + } + + for (unsigned j = 0; j < ncols; ++j) + { + if (f.msk.element(p)) + { + + mln_site_(I) sq = site * ratio; + + if (f.parent.element(p) == p) + { + // test over the component cardinality + f.msk.element(p) = f.card.element(p) > lambda_min + && f.card.element(p) < lambda_max; + + if (f.msk.element(p) && e_2(sq) == 0u) + { + for (unsigned l = 0; l < ptr.size(); ++l) + std::memset(ptr(l), i, ratio * sizeof(mln_value_(I))); +// debug(sq) = i; + } + + } + else + { + // Propagation + f.msk.element(p) = f.msk.element(f.parent.element(p)); + + if (f.msk.element(p) && e_2(sq) == 0u) + { + for (unsigned l = 0; l < ptr.size(); ++l) + std::memset(ptr(l), i, ratio * sizeof(mln_value_(I))); +// debug(sq) = i; + } + + } + } + + for (unsigned l = 0; l < ptr.size(); ++l) + ptr(l) -= ratio; + + --site[1]; + --p; + } + + } - return output; + t_ = tt; + if (mln::debug::quiet) + std::cout << "2nd pass - " << t_ << std::endl; + +// io::pgm::save(e_2, mln::debug::filename("e.pgm", i)); +// io::pgm::save(debug, mln::debug::filename("debug.pgm", i)); +// debug::println(msk); +// io::pbm::save(f.msk, mln::debug::filename("mask.pbm", i)); +// io::pgm::save(data::stretch(int_u8(), card), mln::debug::filename("card.pgm")); + } // end of 2nd pass + +// io::pgm::save(f.t_sub, mln::debug::filename("t.pgm", i)); + return f.t_sub; } - template <typename I> - mln_concrete(I) - enlarge(const Image<I>& input_, unsigned ratio) + + template <typename I, typename J, typename K> + mln_ch_value(I, bool) + binarize_generic(const I& in, const J& e2, const util::array<K>& t_ima, + unsigned s) { - const I& input = exact(input_); - mln_precondition(input.is_valid()); + mln_ch_value(I,bool) out; + initialize(out, in); - mln_domain(I) bbox(input.domain().pmin() * ratio, - input.domain().pmax() * ratio - + (ratio - 1) * mln::down_right); + typedef const mln_value(K)* ptr_type; - mln_concrete(I) output(bbox); + ptr_type ptr_t[5]; + ptr_t[2] = & t_ima[2].at_(0, 0); + ptr_t[3] = & t_ima[3].at_(0, 0); + ptr_t[4] = & t_ima[4].at_(0, 0); - mln_site(I) first_p = input.domain().pmin(); - for (def::coord j = geom::min_col(input); - j <= geom::max_col(input); ++j) - for (def::coord i = geom::min_row(input); - i <= geom::max_row(input); ++i) - { - point2d p1(i, j); - point2d p2(first_p[0] + i * ratio, first_p[1] + j * ratio); - output(p2) = input(p1); - output(p2 + mln::right) = input(p1); - output(p2 + mln::down_right) = input(p1); - output(p2 + mln::down) = input(p1); + const mln_value(J)* ptr_e2 = & e2.at_(0, 0); + const mln_value(I)* ptr__in = & in.at_(0, 0); + bool* ptr__out = & out.at_(0, 0); + + + // Since we iterate from a smaller image in the largest ones and + // image at scale 1 does not always have a size which can be + // divided by (4*s), some sites in the border may not be processed + // and we must skip them. + unsigned more_offset = in.border() - ((4 * s) - in.ncols() % (4 * s)); + + if (more_offset == (4 * s)) + more_offset = 0; // No offset needed. + + const int + nrows4 = t_ima[4].nrows(), ncols4 = t_ima[4].ncols(), + + + delta1 = in.delta_index(dpoint2d(+1, -(s - 1))), + delta1b = in.delta_index(dpoint2d(+1, -(s + s - 1))), + delta1c = in.delta_index(dpoint2d(-(s + s - 1), +1)), + delta1d = in.delta_index(dpoint2d(+1, -(s * 4 - 1))), + delta1e = in.delta_index(dpoint2d(-(s * 4 - 1), +1)), + delta1f = in.delta_index(dpoint2d(-(s - 1), +1)), + + delta2 = t_ima[2].delta_index(dpoint2d(+1, -1)), + delta2b = t_ima[2].delta_index(dpoint2d(+1, -3)), + delta2c = t_ima[2].delta_index(dpoint2d(-3, +1)), + + delta3 = t_ima[3].delta_index(dpoint2d(+1, -1)), + + eor1 = in.delta_index(dpoint2d(+4 * s, - in.ncols() - in.border())) + more_offset, + eor2 = t_ima[2].delta_index(dpoint2d(+4,- t_ima[2].ncols())), + eor3 = t_ima[3].delta_index(dpoint2d(+2,- t_ima[3].ncols())), + eor4 = t_ima[4].delta_index(dpoint2d(+1,- t_ima[4].ncols())); + + mln_value(J) threshold; + for (int row4 = 0; row4 < nrows4; ++row4) + { + for (int col4 = 0; col4 < ncols4; ++col4) + { + // top left 1 + { + threshold = *ptr_t[*ptr_e2]; + { + for (unsigned i = 1; i < s; ++i) + { + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + + *ptr__out = *ptr__in < threshold; + ptr__out += delta1; ptr__in += delta1; + } + + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + *ptr__out = *ptr__in < threshold; + ptr__out += delta1f; ptr__in += delta1f; + } + + ++ptr_t[2]; ++ptr_e2; + threshold = *ptr_t[*ptr_e2]; + { + for (unsigned i = 1; i < s; ++i) + { + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + + *ptr__out = *ptr__in < threshold; + ptr__out += delta1; ptr__in += delta1; + } + + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + *ptr__out = *ptr__in < threshold; + ptr__out += delta1b; ptr__in += delta1b; + } + + ptr_t[2] += delta2; ptr_e2 += delta2; + threshold = *ptr_t[*ptr_e2]; + { + for (unsigned i = 1; i < s; ++i) + { + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + + *ptr__out = *ptr__in < threshold; + ptr__out += delta1; ptr__in += delta1; + } + + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + *ptr__out = *ptr__in < threshold; + ptr__out += delta1f; ptr__in += delta1f; + + } + + ++ptr_t[2]; ++ptr_e2; + threshold = *ptr_t[*ptr_e2]; + { + for (unsigned i = 1; i < s; ++i) + { + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + + *ptr__out = *ptr__in < threshold; + ptr__out += delta1; ptr__in += delta1; + } + + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + *ptr__out = *ptr__in < threshold; + ptr__out += delta1c; ptr__in += delta1c; + } + + ptr_t[2] -= delta2; ptr_e2 -= delta2; + } + + // top right 1 + ptr_t[3] += 1; + { + threshold = *ptr_t[*ptr_e2]; + { + for (unsigned i = 1; i < s; ++i) + { + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + + *ptr__out = *ptr__in < threshold; + ptr__out += delta1; ptr__in += delta1; + } + + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + *ptr__out = *ptr__in < threshold; + ptr__out += delta1f; ptr__in += delta1f; + } + + ++ptr_t[2]; ++ptr_e2; + threshold = *ptr_t[*ptr_e2]; + { + for (unsigned i = 1; i < s; ++i) + { + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + + *ptr__out = *ptr__in < threshold; + ptr__out += delta1; ptr__in += delta1; + } + + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + *ptr__out = *ptr__in < threshold; + ptr__out += delta1b; ptr__in += delta1b; + } + + ptr_t[2] += delta2; ptr_e2 += delta2; + threshold = *ptr_t[*ptr_e2]; + { + for (unsigned i = 1; i < s; ++i) + { + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + + *ptr__out = *ptr__in < threshold; + ptr__out += delta1; ptr__in += delta1; + } + + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + *ptr__out = *ptr__in < threshold; + ptr__out += delta1f; ptr__in += delta1f; + } + + ++ptr_t[2]; ++ptr_e2; + threshold = *ptr_t[*ptr_e2]; + { + for (unsigned i = 1; i < s; ++i) + { + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + + *ptr__out = *ptr__in < threshold; + ptr__out += delta1; ptr__in += delta1; + } + + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + *ptr__out = *ptr__in < threshold; + ptr__out += delta1d; ptr__in += delta1d; + } + + ptr_t[2] += delta2b; ptr_e2 += delta2b; + } + + // bot left 1 + ptr_t[3] += delta3; + { + threshold = *ptr_t[*ptr_e2]; + { + for (unsigned i = 1; i < s; ++i) + { + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + + *ptr__out = *ptr__in < threshold; + ptr__out += delta1; ptr__in += delta1; + } + + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + *ptr__out = *ptr__in < threshold; + ptr__out += delta1f; ptr__in += delta1f; + } + + ++ptr_t[2]; ++ptr_e2; + threshold = *ptr_t[*ptr_e2]; + { + for (unsigned i = 1; i < s; ++i) + { + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + + *ptr__out = *ptr__in < threshold; + ptr__out += delta1; ptr__in += delta1; + } + + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + *ptr__out = *ptr__in < threshold; + ptr__out += delta1b; ptr__in += delta1b; + } + + ptr_t[2] += delta2; ptr_e2 += delta2; + threshold = *ptr_t[*ptr_e2]; + { + for (unsigned i = 1; i < s; ++i) + { + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + + *ptr__out = *ptr__in < threshold; + ptr__out += delta1; ptr__in += delta1; + } + + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + *ptr__out = *ptr__in < threshold; + ptr__out += delta1f; ptr__in += delta1f; + } + + ++ptr_t[2]; ++ptr_e2; + threshold = *ptr_t[*ptr_e2]; + { + for (unsigned i = 1; i < s; ++i) + { + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + + *ptr__out = *ptr__in < threshold; + ptr__out += delta1; ptr__in += delta1; + } + + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + *ptr__out = *ptr__in < threshold; + ptr__out += delta1c; ptr__in += delta1c; + } + + ptr_t[2] -= delta2; ptr_e2 -= delta2; + } + + // bot right 1 + ptr_t[3] += 1; + { + threshold = *ptr_t[*ptr_e2]; + { + for (unsigned i = 1; i < s; ++i) + { + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + + *ptr__out = *ptr__in < threshold; + ptr__out += delta1; ptr__in += delta1; + } + + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + *ptr__out = *ptr__in < threshold; + ptr__out += delta1f; ptr__in += delta1f; + } + + ++ptr_t[2]; ++ptr_e2; + threshold = *ptr_t[*ptr_e2]; + { + for (unsigned i = 1; i < s; ++i) + { + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + + *ptr__out = *ptr__in < threshold; + ptr__out += delta1; ptr__in += delta1; + } + + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + *ptr__out = *ptr__in < threshold; + ptr__out += delta1b; ptr__in += delta1b; + } + + ptr_t[2] += delta2; ptr_e2 += delta2; + threshold = *ptr_t[*ptr_e2]; + { + for (unsigned i = 1; i < s; ++i) + { + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + + *ptr__out = *ptr__in < threshold; + ptr__out += delta1; ptr__in += delta1; + } + + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + *ptr__out = *ptr__in < threshold; + ptr__out += delta1f; ptr__in += delta1f; + } + + ++ptr_t[2]; ++ptr_e2; + threshold = *ptr_t[*ptr_e2]; + { + for (unsigned i = 1; i < s; ++i) + { + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + + *ptr__out = *ptr__in < threshold; + ptr__out += delta1; ptr__in += delta1; + } + + for (unsigned j = 1; j < s; ++j) + { + *ptr__out = *ptr__in < threshold; + ++ptr__out; ++ptr__in; + } + *ptr__out = *ptr__in < threshold; + ptr__out += delta1e; ptr__in += delta1e; + } + } + + // bot right -> next top left + ptr_t[2] += delta2c; ptr_e2 += delta2c; + ptr_t[3] = ptr_t[3] - delta3; + ptr_t[4] += 1; } - return output; + // eof -> next bof + ptr__out += eor1; ptr__in += eor1; + ptr_t[2] += eor2; ptr_e2 += eor2; + ptr_t[3] += eor3; + ptr_t[4] += eor4; + } + + +// mln::debug::println(out); + + return out; + } + + + + + unsigned sub(unsigned nbr, unsigned down_scaling) + { + return (nbr + down_scaling - 1) / down_scaling; } template <typename I> - object_image(mln_ch_value(I, value::label_16)) - get_objects(const Image<I>& input_, - unsigned min_size, unsigned max_size, - unsigned ratio, unsigned w, unsigned scale) + util::array<util::couple<mln_domain(I), unsigned> > + compute_sub_domains(const I& ima, unsigned n_scales, unsigned s) { - const I& input = exact(input_); + util::array<util::couple<unsigned, unsigned> > n(n_scales + 2); - mln_precondition(input.is_valid()); + n(1) = make::couple(ima.nrows(), ima.ncols()); + n(2) = make::couple(sub(n(1).first(), s), + sub(n(1).second(), s)); + for (unsigned i = 3; i <= n_scales + 1; ++i) + n(i) = make::couple(sub(n(i - 1).first(), 2), + sub(n(i - 1).second(), 2)); - typedef mln_ch_value(I, value::label_16) L; - dpoint2d none(0, 0); - I input_sub = mln::subsampling::subsampling(input, none, ratio); + util::array<util::couple<mln_domain(I), unsigned> > out(n.size()); + out(0) = make::couple(make::box2d(1,1), 1u); + out(1) = make::couple(make::box2d(ima.nrows(), ima.ncols()), 2u); + out(n_scales + 1) = make::couple(make::box2d(n(n_scales + 1).first(), + n(n_scales + 1).second()), + 1u); - image2d<value::int_u8> - t = scribo::binarization::sauvola_threshold(input_sub, w); + for (unsigned i = n_scales; i > 1; --i) + out(i) = make::couple(make::box2d(2 * out(i + 1).first().nrows(), + 2 * out(i + 1).first().ncols()), + 2 * out(i + 1).second()); - image2d<bool> b = apply_sauvola(input_sub, t, 1); + out(1).second() = std::max(out(2).first().ncols() * s - ima.ncols(), + out(2).first().nrows() * s - ima.nrows()); - value::label_16 nb; - object_image(L) lbl = scribo::primitive::extract::objects(b, c8(), nb); - object_image(L) lbl_raw(lbl); +// out(1).second() = std::max(ima.ncols() % (4 * s), +// ima.nrows() % (4 * s)); - if (min_size > 0) - lbl = scribo::filter::objects_small(lbl, min_size); - if (max_size > 0) - lbl = scribo::filter::objects_large(lbl, max_size); - scribo::debug::save_object_diff(lbl_raw, lbl, - mln::debug::filename("filter_diff.ppm", scale + 2)); +// out(2).first().ncols() * s - ima.ncols(), +// out(2).first().nrows() * s - ima.nrows() ); - return lbl; + return out; } + bool check_args(int argc, char * argv[]) { - if (argc < 7) + if (argc < 5 || argc > 7) return false; - int nb_scale = atoi(argv[3]); - int s = atoi(argv[4]); - int q = atoi(argv[5]); +// int nb_scale = atoi(argv[3]); + int s = atoi(argv[3]); +// int q = atoi(argv[5]); - if (q < 2) - { - std::cout << "q must be greater than 2." << std::endl; - return false; - } - if (s < 1 || s < q) +// if (q < 2) +// { +// std::cout << "q must be greater than 2." << std::endl; +// return false; +// } + if (s < 1 || s > 3)// || s < q) { - std::cout << "s must be greater or equal to 1 and greater than q." + std::cout << "s must be set to 2 or 3." << std::endl; return false; } - if (nb_scale < 1) - { - std::cout << "Not enough scales." << std::endl; - return false; - } - if ((argc - 7) != (nb_scale - 1)) - { - std::cout << "Not enough area threshold." - << "There must be nb_scale - 1 thresholds." - << std::endl; - return false; - } +// if (nb_scale < 1) +// { +// std::cout << "Not enough scales." << std::endl; +// return false; +// } + +// if ((argc - 7) != (nb_scale - 1)) +// { +// std::cout << "Not enough area threshold." +// << "There must be nb_scale - 1 thresholds." +// << std::endl; +// return false; +// } return true; } + void data_rand(image2d<unsigned>& e2) + { + unsigned v = 2; + mln_piter_(box2d) p(e2.domain()); + for_all(p) + { + e2(p) = v++; + if (v == 5) v = 2; + } + } + + } // end of namespace mln; @@ -207,11 +807,10 @@ namespace mln const char *args_desc[][2] = { { "input.pgm", "A graylevel image." }, - { "w", "Window size." }, - { "nb_scale", "Number of scales (Common value: 3)." }, + { "w", "Window size. (Common value: 51)" }, { "s", "First subsampling ratio (Common value: 2)." }, - { "q", "Next subsampling ratio (Common value: 2)." }, - { "<area threshold> (Common values: 200 800)", "Area threshold" }, + { "min_area", "Minimum object area (relative to scale 2) (Common value: 200)" }, + { "debug", "Display debug/bench data if set to 1" }, {0, 0} }; @@ -229,11 +828,10 @@ int main(int argc, char *argv[]) typedef image2d<label_16> L; - if (!check_args(argc, argv)) return scribo::debug::usage(argv, - "Binarization of a color image based on Sauvola's algorithm.", - "input.pgm w nb_scale s q <area thresholds>", + "Multi-Scale Binarization of a color image based on Sauvola's algorithm.", + "input.pgm w s area_thresholds output.pbm [debug]", args_desc, "A binary image."); trace::entering("main"); @@ -244,163 +842,233 @@ int main(int argc, char *argv[]) unsigned w = atoi(argv[2]); // First subsampling scale. - unsigned s = atoi(argv[4]); + unsigned s = atoi(argv[3]); // Number of subscales. - unsigned nb_subscale = atoi(argv[3]); + unsigned nb_subscale = 3;//atoi(argv[3]); // Subscale step. - unsigned q = atoi(argv[5]); + unsigned q = 2;//atoi(argv[5]); + + mln::debug::quiet = true; - std::cout << "Running Sauvola_ms with w = " << w - << ", s = " << s - << ", nb_subscale = " << nb_subscale - << ", q = " << q - << std::endl; + if (argc == 7) + mln::debug::quiet = ! atoi(argv[6]); + + + if (mln::debug::quiet) + std::cout << "Running Sauvola_ms with w = " << w + << ", s = " << s + << ", nb_subscale = " << nb_subscale + << ", q = " << q + << std::endl; typedef image2d<value::int_u8> I; dpoint2d none(0, 0); - mln::util::timer timer_; + mln::util::timer + timer_, + sauvola_timer_;; + + // Tmp variable used for timer results; + float t_; - timer_.start(); I input_full; io::pgm::load(input_full, argv[1]); - std::cout << "Image loaded - " << timer_ << std::endl; - timer_.restart(); + { + unsigned max_dim = math::max(input_full.ncols() / 2, + input_full.nrows() / 2); + if (w > max_dim) + { + std::cout << "------------------" << std::endl; + std::cout << "The window is too large! Image size is only " + << input_full.nrows() << "x" << input_full.ncols() + << std::endl + << "Window size must not exceed " << max_dim + << std::endl; + return 1; + } + } - util::array<object_image(L)> lbls; +// I input_full(9,9); +// mln::debug::iota(input_full); + sauvola_timer_.start(); + unsigned lambda_min = atoi(argv[4]); + unsigned lambda_max = lambda_min * q; // * atoi(argv[7]) - // First subsampling 1/s -// std::cout << "1/" << s << std::endl; - timer_.start(); - I input = mln::subsampling::subsampling(input_full, none, s); + util::array<I> t_ima; + + // Make sure t_ima indexes start from 2. + { + I dummy(1,1); + for (unsigned i = 0; i < nb_subscale + 2; ++i) + t_ima.append(dummy); + } - integral_image<int_u8> simple, squared; - image2d<int_u8> - t_1 = scribo::binarization::sauvola_threshold(input, w, simple, squared); - label_16 nb1; - image2d<bool> b_1 = apply_sauvola(input, t_1, 1); + image2d<int_u8> e_2; + util::array<I> sub_ima; + + // Make sure sub_ima indexes start from 2. { - lbls.append(primitive::extract::objects(b_1, c8(), nb1)); - object_image(L) lbl_raw(lbls[0]); - lbls[0] = filter::objects_large(lbls[0], atoi(argv[6])); - scribo::debug::save_object_diff(lbl_raw, lbls[0], - mln::debug::filename("filter_diff.ppm", 2)); + I dummy(1,1); + sub_ima.append(dummy); + sub_ima.append(dummy); } - std::cout << "Scale 2 - 1/" << s << " Done - " << timer_ << std::endl; + timer_.restart(); + util::array<util::couple<box2d, unsigned> > + sub_domains = compute_sub_domains(input_full, nb_subscale, s); + if (mln::debug::quiet) + std::cout << "adjusting input border to " << sub_domains(1).second() + << std::endl; - // Additional subscales. - for (unsigned i = 1; i < nb_subscale; ++i) - { - unsigned ratio = std::pow(q, i); -// std::cout << "Scale " << 2 + i << " - 1/" << s * ratio << std::endl; - timer_.restart(); - unsigned - min_size = atoi(argv[5 + i]) / ratio, - max_size; - if ((3 + i) == static_cast<unsigned>(argc)) - max_size = 0; // Last subscale, so not max size limit. - else - max_size = atoi(argv[6 + i]) / ratio; - - lbls.append(get_objects(input, min_size, max_size, ratio, w, i)); - std::cout << "Scale " << 2 + i - << " - 1/" << s * ratio - << " Done - " << timer_ << std::endl; - } + border::adjust(input_full, sub_domains(1).second()); + border::mirror(input_full); - std::cout << "--------" << std::endl; +// mln::debug::println_with_border(input_full); - // Constructing "scale image". - image2d<int_u8> e; - initialize(e, input); + t_ = timer_; + if (mln::debug::quiet) + std::cout << "sub domains computed and adjust input border size - " + << t_ << std::endl; - data::fill(e, 0); + // Resize input and compute integral images. + timer_.restart(); + image2d<util::couple<double,double> > integral_sum_sum_2; - typedef object_image(L) obj_t; + if (mln::debug::quiet) + std::cout << "sub_domain(2).domain() == " << sub_domains(2).first() << std::endl; - for (int i = nb_subscale - 1; i >= 0; --i) - { - unsigned ratio = std::pow(q, i); + sub_ima.append(scribo::subsampling::integral(input_full, s, + integral_sum_sum_2, + sub_domains[2].first(), + sub_domains[2].second())); - std::cout << "Scale " << 2 + i << " - 1/" << s * ratio << " merged" << std::endl; +// mln::debug::println(integral_sum_sum_2); - mln_piter_(obj_t) p(lbls[i].domain()); - for_all(p) - if (lbls[i](p) != 0 && e(p * ratio) == 0) - { - box2d b(p * ratio, p * ratio + mln::down_right * (ratio - 1)); - data::fill((e | b).rw(), i + 1); - } - } + t_ = timer_; + if (mln::debug::quiet) + std::cout << "subsampling 1 -> 2 And integral images - " << t_ + << " - nsites = " + << input_full.domain().nsites() << " -> " + << sub_ima[2].domain().nsites() << " - " + << input_full.domain() << " -> " + << sub_ima[2].domain() << std::endl; - std::cout << "--------" << std::endl; - /// Saving "scale image". + for (unsigned i = 3; i <= nb_subscale + 1; ++i) { - mln::fun::i2v::array<value::int_u8> f(nb_subscale + 1, 0); - for (unsigned i = 1; i <= nb_subscale; ++i) - f(i) = 255 / nb_subscale * i; - - io::pgm::save(e, "e_raw.pgm"); - io::pgm::save(data::transform(e, f), "e.pgm"); + timer_.restart(); + sub_ima.append(mln::subsampling::antialiased(sub_ima[i - 1], q, none, + sub_domains[i].first(), + sub_domains[i].second())); + t_ = timer_; + if (mln::debug::quiet) + std::cout << "subsampling " << (i - 1) << " -> " << i + << " - " << t_ + << " - nsites = " + << sub_ima[i].domain().nsites() << " - " + << sub_ima[i].domain() + << std::endl; } - /// Saving influence zone scale image. - image2d<int_u8> - e_ext = transform::influence_zone_geodesic(e, c8(), mln_max(unsigned)); - io::pgm::save(e_ext, "e_ext.pgm"); + initialize(e_2, sub_ima[2]); + data::fill(e_2, 0u); + // Compute threshold image. - /// Creating window size image - std::cout << "Creating window size image..." << std::endl; - mln::fun::i2v::array<value::int_u16> f(nb_subscale, 0); - for (unsigned i = 0; i < nb_subscale; ++i) + // Highest scale -> no maximum component size. { - unsigned ratio = std::pow(q, i); - f(i) = ratio * w; + timer_.restart(); + int i = sub_ima.size() - 1; + unsigned ratio = std::pow(q, i - 2); // Ratio compared to e_2 + t_ima[i] = compute_t_n_and_e_2(sub_ima[i], e_2, + lambda_min / ratio, + mln_max(unsigned), + q, i, w, integral_sum_sum_2); + + t_ = timer_; + if (mln::debug::quiet) + std::cout << "Scale " << i + << " - 1/" << s * ratio + << " compute t_n and update e - " << t_ << std::endl; } - image2d<int_u16> wsize = data::transform(e_ext, f); - io::pgm::save(data::stretch(int_u8(), wsize), "wsize.pgm"); + // Other scales -> maximum and minimum component size. + { + for (int i = sub_ima.size() - 2; i > 2; --i) + { + timer_.restart(); + unsigned ratio = std::pow(q, i - 2); // Ratio compared to e_2 + t_ima[i] = compute_t_n_and_e_2(sub_ima[i], e_2, + lambda_min / ratio, + lambda_max / ratio, + q, i, w, integral_sum_sum_2); + + t_ = timer_; + if (mln::debug::quiet) + std::cout << "Scale " << i + << " - 1/" << s * ratio + << " compute t_n and update e - " << t_ << std::endl; + } + } + // Lowest scale -> no minimum component size. + { + timer_.restart(); + t_ima[2] = compute_t_n_and_e_2(sub_ima[2], e_2, 0, lambda_max, + 1, 2, w, integral_sum_sum_2); + t_ = timer_; + if (mln::debug::quiet) + std::cout << "Scale " << 2 + << " - 1/" << s + << " compute t_n and update e - " << t_ << std::endl; + + } + + if (mln::debug::quiet) + std::cout << "--------" << std::endl; +// io::pgm::save(e_2, mln::debug::filename("e.pgm")); - /// Constructing threshold image - image2d<int_u8> t; - initialize(t, input); - std::cout << "Computing threshold image..." << std::endl; timer_.restart(); - mln_piter_(L) p(input.domain()); - for_all(p) - t(p) = binarization::internal::compute_sauvola_threshold(p, - simple, - squared, - wsize(p)); - std::cout << "Compute t Done - " << timer_ << std::endl; + e_2 = transform::influence_zone_geodesic(e_2, c8()); + t_ = timer_; + if (mln::debug::quiet) + std::cout << "influence zone - " << t_ << std::endl; - io::pgm::save(t, "t.pgm"); - timer_.restart(); +// Saving influence zone scale image. +// io::pgm::save(e_2, mln::debug::filename("e_ext.pgm")); +// io::pbm::save(bin, argv[8]); +// io::pgm::save(t, mln::debug::filename("t.pgm")); - /// Applying threshold image and save. - io::pbm::save(apply_sauvola(input_full, t, s), argv[argc - 1]); - std::cout << "sauvola applied and saved Done - " << timer_ << std::endl; +// for (unsigned i = 2; i < t_ima.size(); ++i) +// io::pgm::save(t_ima[i], mln::debug::filename("t.pgm", i)); - trace::exiting("main"); + timer_.restart(); + image2d<bool> out_new = binarize_generic(input_full, e_2, t_ima, s); +// image2d<bool> out_new = binarize(input_full, e_2, t_ima); + t_ = timer_; + if (mln::debug::quiet) + std::cout << "Compute bin - " << t_ << std::endl; + + t_ = sauvola_timer_; + if (mln::debug::quiet) + std::cout << "Sauvola : " << t_ << std::endl; + io::pbm::save(out_new, argv[5]); } + diff --git a/scribo/subsampling/integral_single_image.hh b/scribo/subsampling/integral_single_image.hh new file mode 100644 index 0000000..07b391d --- /dev/null +++ b/scribo/subsampling/integral_single_image.hh @@ -0,0 +1,403 @@ +// Copyright (C) 2009 EPITA Research and Development Laboratory (LRDE) +// +// This file is part of Olena. +// +// Olena is free software: you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation, version 2 of the License. +// +// Olena is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Olena. If not, see <http://www.gnu.org/licenses/>. +// +// As a special exception, you may use this file as part of a free +// software project without restriction. Specifically, if other files +// instantiate templates or use macros or inline functions from this +// file, or you compile this file and link it with other files to produce +// an executable, this file does not by itself cause the resulting +// executable to be covered by the GNU General Public License. This +// exception does not however invalidate any other reasons why the +// executable file might be covered by the GNU General Public License. + +#ifndef SCRIBO_SUBSAMPLING_INTEGRAL_HH +# define SCRIBO_SUBSAMPLING_INTEGRAL_HH + +/// \file +/// +/// Both subsampling and integral image computation. + +#include <mln/core/concept/image.hh> +#include <mln/metal/equal.hh> +#include <mln/extension/fill.hh> +#include <mln/debug/println.hh> +#include <mln/debug/println_with_border.hh> + + + +namespace scribo +{ + + namespace subsampling + { + using namespace mln; + + + /*! \brief Subsample an image and compute tow integral images. + + \param[in] input An image of Scalar. + \param[in] scale The scale factor. + \param[in] integral_sum Integral image of mean values. + \param[in] integral_sum_2 Integral image of squared mean values. + \param[in] output_domain The domain of the subscaled image. + \param[in] border_thickness Border of the integral and subscaled images. + + \p integral_sum, \p integral_sum_2 and output image have the same domain. + The output domain is set with \p output_domain. + + */ + template <typename I, typename J> + inline + mln_concrete(I) + integral(const Image<I>& input, unsigned scale, + Image<J>& integral_sum, Image<J>& integral_sum_2, + const mln_domain(I)& output_domain, unsigned border_thickness); + + /*! \overload + + The output domain is defined as follows : + + for_each(dimension) + input.domain()[dimension] = (N + scale - 1) / scale + + where N is the number of elements in one run in a given dimension. + + The border thickness is set to mln::border::thickness. + */ + template <typename I, typename J> + inline + mln_concrete(I) + integral(const Image<I>& input, unsigned scale, + Image<J>& integral_sum, Image<J>& integral_sum_2); + + + +# ifndef MLN_INCLUDE_ONLY + + + // Implementation. + + namespace impl + { + + template <typename I, typename J> + inline + mln_concrete(I) + integral_3(const Image<I>& input_, + Image<J>& integral_sum_sum_2_, + const mln_domain(I)& output_domain, + unsigned border_thickness) + { + trace::entering("subsampling::impl::integral_3"); + + const unsigned scale = 3; + + const I& input = exact(input_); + J& integral_sum_sum_2 = exact(integral_sum_sum_2_); + const unsigned area = scale * scale; + + mln_precondition(input.is_valid()); + mln_precondition(input.domain().pmin() == literal::origin); + mln_precondition(scale > 1); + + typedef mln_value(I) V; + typedef mln_sum(V) S; + typedef mln_value(J) V2; + typedef mln_site(I) P; + + mln_concrete(I) sub(output_domain, border_thickness); + V* p_sub = sub.buffer(); + + integral_sum_sum_2.init_(output_domain, border_thickness); + V2* p_integ = integral_sum_sum_2.buffer(); + + const unsigned up = sub.delta_index(dpoint2d(-1, 0)); + + const unsigned nrows = 3 * output_domain.nrows(); + const unsigned ncols = 3 * output_domain.ncols(); + + unsigned row = 0; + + unsigned b_offset = sub.delta_index(dpoint2d(border_thickness, + border_thickness)); + p_sub += b_offset; + p_integ += b_offset; + { + S h_sum = 0, h_sum_2 = 0; + const V* ptr1 = & input.at_(row, 0); + const V* ptr2 = & input.at_(row + 1, 0); + const V* ptr3 = & input.at_(row + 2, 0); + for (unsigned col = 0; col < ncols; col += scale) + { + S sum; + sum = *ptr1 + *(ptr1 + 1) + *(ptr1 + 2); + sum += *ptr2 + *(ptr2 + 1) + *(ptr2 + 2); + sum += *ptr3 + *(ptr3 + 1) + *(ptr3 + 2); + ptr1 += 3; + ptr2 += 3; + ptr3 += 3; + + S val = sum / area; + *p_sub++ = val; + + h_sum += val; + h_sum_2 += val * val; + + // exception + p_integ->first() = h_sum; + p_integ->second() = h_sum_2; + + p_integ += 1; + } + } + + unsigned b_next = 2 * border_thickness; + + p_sub += b_next; + p_integ += b_next; + + for (row += scale; row < nrows; row += scale) + { + S h_sum = 0, h_sum_2 = 0; + const V* ptr1 = & input.at_(row, 0); + const V* ptr2 = & input.at_(row + 1, 0); + const V* ptr3 = & input.at_(row + 2, 0); + for (unsigned col = 0; col < ncols; col += scale) + { + S sum; + sum = *ptr1 + *(ptr1 + 1) + *(ptr1 + 2); + sum += *ptr2 + *(ptr2 + 1) + *(ptr2 + 2); + sum += *ptr3 + *(ptr3 + 1) + *(ptr3 + 2); + ptr1 += 3; + ptr2 += 3; + ptr3 += 3; + + S val = sum / area; + *p_sub++ = val; + + h_sum += val; + h_sum_2 += val * val; + + p_integ->first() = h_sum + (p_integ + up)->first(); + p_integ->second() = h_sum_2 + (p_integ + up)->second(); + + p_integ += 1; + } + + p_sub += b_next; + p_integ += b_next; + } + + trace::exiting("subsampling::impl::integral_3"); + return sub; + } + + + template <typename I, typename J> + inline + mln_concrete(I) + integral_2(const Image<I>& input_, + Image<J>& integral_sum_sum_2_, + const mln_domain(I)& output_domain, + unsigned border_thickness) + { + trace::entering("subsampling::impl::integral_2"); + + const unsigned scale = 2; + + const I& input = exact(input_); + J& integral_sum_sum_2 = exact(integral_sum_sum_2_); + const unsigned area = scale * scale; + + typedef mln_value(I) V; + typedef mln_sum(V) S; + typedef mln_site(I) P; + typedef mln_value(J) V2; + + mlc_bool(P::dim == 2)::check(); + mln_precondition(input.is_valid()); + mln_precondition(input.domain().pmin() == literal::origin); + mln_precondition(scale > 1); + + mln_concrete(I) sub(output_domain, border_thickness); + V* p_sub = sub.buffer(); + + integral_sum_sum_2.init_(output_domain, border_thickness); + V2* p_integ = integral_sum_sum_2.buffer(); + + const unsigned up = sub.delta_index(dpoint2d(-1, 0)); + + const unsigned nrows = 2 * output_domain.nrows(); + const unsigned ncols = 2 * output_domain.ncols(); + + extension::fill(sub, 0); + + unsigned b_offset = sub.delta_index(dpoint2d(border_thickness, + border_thickness)); + p_sub += b_offset; + p_integ += b_offset; + + unsigned row = 0; + { + S h_sum = 0, h_sum_2 = 0; + const V* ptr1 = & input.at_(row, 0); + const V* ptr2 = & input.at_(row + 1, 0); + for (unsigned col = 0; col < ncols; col += scale) + { + S sum; + sum = *ptr1 + *(ptr1 + 1); + sum += *ptr2 + *(ptr2 + 1); + ptr1 += 2; + ptr2 += 2; + + S val = sum / area; + *p_sub++ = val; + + h_sum += val; + h_sum_2 += val * val; + + // exception + p_integ->first() = h_sum; + p_integ->second() = h_sum_2; + + p_integ += 1; + } + } + + unsigned b_next = 2 * border_thickness; + + p_sub += b_next; + p_integ += b_next; + + for (row += scale; row < nrows; row += scale) + { + S h_sum = 0, h_sum_2 = 0; + const V* ptr1 = & input.at_(row, 0); + const V* ptr2 = & input.at_(row + 1, 0); + for (unsigned col = 0; col < ncols; col += scale) + { + S sum; + sum = *ptr1 + *(ptr1 + 1); + sum += *ptr2 + *(ptr2 + 1); + ptr1 += 2; + ptr2 += 2; + + S val = sum / area; + *p_sub++ = val; + + h_sum += val; + h_sum_2 += val * val; + + p_integ->first() = h_sum + (p_integ + up)->first(); + p_integ->second() = h_sum_2 + (p_integ + up)->second(); + + p_integ += 1; + } + + p_sub += b_next; + p_integ += b_next; + } + + trace::exiting("subsampling::impl::integral_2"); + return sub; + } + + + template <typename I, typename J> + inline + mln_concrete(I) + integral(const Image<I>& input, unsigned scale, + Image<J>& integral_sum_sum_2, + const mln_domain(I)& output_domain, unsigned border_thickness) + { + // mln_precondition(input.nrows() % scale == 0); + // mln_precondition(input.ncols() % scale == 0); + if (scale == 3) + return integral_3(input, integral_sum_sum_2, + output_domain, border_thickness); + else if (scale == 2) + return integral_2(input, integral_sum_sum_2, + output_domain, border_thickness); + else + std::cerr << "NYI!" << std::endl; + + typedef mln_concrete(I) output_t; + return output_t(); + } + + + } // end of namespace mln::subsampling::impl + + + + + // Facades. + + template <typename I, typename J> + inline + mln_concrete(I) + integral(const Image<I>& input_, unsigned scale, + Image<J>& integral_sum_sum_2, + const mln_domain(I)& output_domain, unsigned border_thickness) + { + trace::entering("subsampling::integral"); + + const I& input = exact(input_); + + mln_precondition(input.is_valid()); + mln_precondition(input.domain().pmin() == literal::origin); + mln_precondition(scale > 1); + + mln_concrete(I) + output = impl::integral(input, scale, integral_sum_sum_2, + output_domain, border_thickness); + + trace::exiting("subsampling::integral"); + return output; + } + + template <typename I, typename J> + inline + mln_concrete(I) + integral(const Image<I>& input_, unsigned scale, + Image<J>& integral_sum_sum_2) + { + trace::entering("subsampling::integral"); + + const I& input = exact(input_); + + mln_precondition(input.is_valid()); + mln_precondition(input.domain().pmin() == literal::origin); + mln_precondition(scale > 1); + + box<mln_site(I)> b = make::box2d((input.nrows() + scale - 1) / scale, + (input.ncols() + scale - 1) / scale); + mln_concrete(I) output; + output = integral(input_, scale, integral_sum_sum_2, + b, mln::border::thickness); + + trace::exiting("subsampling::integral"); + return output; + } + +# endif // ! MLN_INCLUDE_ONLY + + } // end of namespace scribo::subsampling + +} // end of namespace scribo + + +#endif // ! SCRIBO_SUBSAMPLING_INTEGRAL_HH -- 1.5.6.5
participants (1)
-
Guillaume Lazzara