* scribo/src/binarization/Makefile.am: Add new target.
* scribo/src/binarization/sauvola_ms.cc: New.
---
scribo/ChangeLog | 8 +
scribo/src/binarization/Makefile.am | 4 +-
scribo/src/binarization/sauvola_ms.cc | 406 +++++++++++++++++++++++++++++++++
3 files changed, 417 insertions(+), 1 deletions(-)
create mode 100644 scribo/src/binarization/sauvola_ms.cc
diff --git a/scribo/ChangeLog b/scribo/ChangeLog
index b04de6a..279e1fd 100644
--- a/scribo/ChangeLog
+++ b/scribo/ChangeLog
@@ -1,5 +1,13 @@
2009-11-03 Guillaume Lazzara <z(a)lrde.epita.fr>
+ First draft of multiscale Sauvola's binarization.
+
+ * scribo/src/binarization/Makefile.am: Add new target.
+
+ * scribo/src/binarization/sauvola_ms.cc: New.
+
+2009-11-03 Guillaume Lazzara <z(a)lrde.epita.fr>
+
Improve line pattern recognition routine.
* primitive/extract/lines_pattern.hh: Make it less tolerant.
diff --git a/scribo/src/binarization/Makefile.am b/scribo/src/binarization/Makefile.am
index ab982dc..531d4cf 100644
--- a/scribo/src/binarization/Makefile.am
+++ b/scribo/src/binarization/Makefile.am
@@ -21,10 +21,12 @@ include $(top_srcdir)/scribo/scribo.mk
bin_PROGRAMS = \
sauvola \
+ sauvola_ms \
sauvola_pgm \
simple
sauvola_SOURCES = sauvola.cc
+sauvola_ms_SOURCES = sauvola_ms.cc
sauvola_pgm_SOURCES = sauvola_pgm.cc
-simple_SOURCES = simple.cc
+simple_SOURCES = simple.cc
diff --git a/scribo/src/binarization/sauvola_ms.cc
b/scribo/src/binarization/sauvola_ms.cc
new file mode 100644
index 0000000..747bc11
--- /dev/null
+++ b/scribo/src/binarization/sauvola_ms.cc
@@ -0,0 +1,406 @@
+// 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.
+
+#include <mln/core/alias/neighb2d.hh>
+#include <mln/data/stretch.hh>
+#include <mln/debug/filename.hh>
+#include <mln/fun/i2v/array.hh>
+#include <mln/io/pbm/all.hh>
+#include <mln/io/pgm/all.hh>
+#include <mln/io/ppm/all.hh>
+#include <mln/literal/colors.hh>
+#include <mln/math/sqr.hh>
+#include <mln/subsampling/subsampling.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/debug/filename.hh>
+
+#include <scribo/core/macros.hh>
+#include <scribo/core/object_image.hh>
+#include <scribo/filter/objects_small.hh>
+#include <scribo/filter/objects_large.hh>
+
+#include <scribo/filter/objects_thin.hh>
+#include <scribo/filter/objects_thick.hh>
+
+#include <scribo/primitive/extract/objects.hh>
+#include <scribo/binarization/sauvola_threshold.hh>
+#include <scribo/debug/usage.hh>
+#include <scribo/debug/save_object_diff.hh>
+
+namespace mln
+{
+
+
+// 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)
+
+// }
+
+ template <typename I, typename T>
+ mln_ch_value(I, bool)
+ apply_sauvola(const I& intensity, const T& threshold, unsigned s)
+ {
+ mln_precondition(intensity.domain() == threshold.domain());
+
+ mln_ch_value(I, bool) output;
+ initialize(output, intensity);
+
+ mln_piter(I) p(intensity.domain());
+ for_all(p)
+ output(p) = (intensity(p) <= threshold(p / s));
+
+ return output;
+ }
+
+
+ template <typename I>
+ mln_concrete(I)
+ enlarge(const Image<I>& input_, unsigned ratio)
+ {
+ const I& input = exact(input_);
+ mln_precondition(input.is_valid());
+
+ mln_domain(I) bbox(input.domain().pmin() * ratio,
+ input.domain().pmax() * ratio
+ + (ratio - 1) * mln::down_right);
+
+ mln_concrete(I) output(bbox);
+
+ 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);
+ }
+
+ return output;
+ }
+
+ 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)
+ {
+ const I& input = exact(input_);
+
+ mln_precondition(input.is_valid());
+
+ typedef mln_ch_value(I, value::label_16) L;
+
+ dpoint2d none(0, 0);
+ I input_sub = mln::subsampling::subsampling(input, none, ratio);
+
+ image2d<value::int_u8>
+ t = scribo::binarization::sauvola_threshold(input_sub, w);
+
+ image2d<bool> b = apply_sauvola(input_sub, t, 1);
+
+ value::label_16 nb;
+ object_image(L) lbl = scribo::primitive::extract::objects(b, c8(), nb);
+ object_image(L) lbl_raw(lbl);
+
+ 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));
+
+ return lbl;
+ }
+
+
+ bool
+ check_args(int argc, char * argv[])
+ {
+ if (argc < 7)
+ return false;
+
+ int nb_scale = atoi(argv[3]);
+ int s = atoi(argv[4]);
+ 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)
+ {
+ std::cout << "s must be greater or equal to 1 and greater than q."
+ << 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;
+ }
+
+
+} // end of namespace mln;
+
+
+
+
+const char *args_desc[][2] =
+{
+ { "input.pgm", "A graylevel image." },
+ { "w", "Window size." },
+ { "nb_scale", "Number of scales (Common value: 3)." },
+ { "s", "First subsampling ratio (Common value: 2)." },
+ { "q", "Next subsampling ratio (Common value: 2)." },
+ { "<area threshold> (Common values: 200 800)", "Area
threshold" },
+ {0, 0}
+};
+
+
+
+
+int main(int argc, char *argv[])
+{
+ using namespace mln;
+ using namespace scribo;
+ using value::rgb8;
+ using value::int_u8;
+ using value::int_u16;
+ using value::label_16;
+
+ 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>",
+ args_desc, "A binary image.");
+
+ trace::entering("main");
+
+ mln::debug::internal::filename_prefix = argv[1];
+
+ // Window size.
+ unsigned w = atoi(argv[2]);
+
+ // First subsampling scale.
+ unsigned s = atoi(argv[4]);
+
+ // Number of subscales.
+ unsigned nb_subscale = atoi(argv[3]);
+
+ // Subscale step.
+ unsigned q = atoi(argv[5]);
+
+ 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_;
+
+ timer_.start();
+ I input_full;
+ io::pgm::load(input_full, argv[1]);
+ std::cout << "Image loaded - " << timer_ << std::endl;
+
+ timer_.restart();
+
+ util::array<object_image(L)> lbls;
+
+
+
+ // First subsampling 1/s
+
+// std::cout << "1/" << s << std::endl;
+ timer_.start();
+ I input = mln::subsampling::subsampling(input_full, none, s);
+
+ 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);
+
+ {
+ 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));
+ }
+
+ std::cout << "Scale 2 - 1/" << s << " Done - "
<< timer_ << 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;
+ }
+
+ std::cout << "--------" << std::endl;
+
+ // Constructing "scale image".
+ image2d<int_u8> e;
+ initialize(e, input);
+
+ data::fill(e, 0);
+
+ typedef object_image(L) obj_t;
+
+ for (int i = nb_subscale - 1; i >= 0; --i)
+ {
+ unsigned ratio = std::pow(q, i);
+
+ std::cout << "Scale " << 2 + i << " - 1/"
<< s * ratio << " merged" << std::endl;
+
+ 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);
+ }
+ }
+
+ std::cout << "--------" << std::endl;
+
+ /// Saving "scale image".
+ {
+ 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");
+ }
+
+
+ /// 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");
+
+
+ /// 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)
+ {
+ unsigned ratio = std::pow(q, i);
+ f(i) = ratio * w;
+ }
+
+ image2d<int_u16> wsize = data::transform(e_ext, f);
+ io::pgm::save(data::stretch(int_u8(), wsize), "wsize.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;
+
+ io::pgm::save(t, "t.pgm");
+
+
+ timer_.restart();
+
+ /// 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;
+
+
+ trace::exiting("main");
+}
--
1.5.6.5