
* scribo/main.cc: Change usage message. * scribo/demat.hh: - Cleanup. - Remove too small components using level::transform. - Support text bboxes extraction. --- milena/sandbox/ChangeLog | 11 ++ milena/sandbox/scribo/demat.hh | 238 ++++++++++++++++++++++++++++++++++++--- milena/sandbox/scribo/main.cc | 2 +- 3 files changed, 231 insertions(+), 20 deletions(-) diff --git a/milena/sandbox/ChangeLog b/milena/sandbox/ChangeLog index 3727032..2ecca11 100644 --- a/milena/sandbox/ChangeLog +++ b/milena/sandbox/ChangeLog @@ -1,3 +1,14 @@ +2008-10-21 Guillaume Lazzara <z@lrde.epita.fr> + + Extract text bboxes for Scribo. + + * scribo/main.cc: Change usage message. + + * scribo/demat.hh: + - Cleanup. + - Remove too small components using level::transform. + - Support text bboxes extraction. + 2008-10-21 Ugo Jardonnet <ugo.jardonnet@lrde.epita.fr> Work on ICCVG04 classification method diff --git a/milena/sandbox/scribo/demat.hh b/milena/sandbox/scribo/demat.hh index 742143d..06f54e9 100644 --- a/milena/sandbox/scribo/demat.hh +++ b/milena/sandbox/scribo/demat.hh @@ -30,6 +30,7 @@ # include <mln/core/image/image2d.hh> +# include <mln/core/concept/function.hh> # include <mln/core/image/image_if.hh> # include <mln/core/image/sub_image.hh> # include <mln/core/image/cast_image.hh> @@ -38,6 +39,7 @@ # include <mln/core/routine/clone.hh> # include <mln/core/routine/ops.hh> # include <mln/core/site_set/p_vaccess.hh> +# include <mln/core/site_set/p_set.hh> # include <mln/accu/bbox.hh> @@ -54,6 +56,8 @@ # include <mln/estim/nsites.hh> +# include <mln/fun/i2v/array.hh> + # include <mln/io/pbm/load.hh> # include <mln/io/pgm/load.hh> # include <mln/io/pbm/save.hh> @@ -70,6 +74,8 @@ # include <mln/pw/all.hh> +# include <mln/set/inter.hh> + # include <mln/util/array.hh> # include <mln/value/int_u16.hh> @@ -84,27 +90,33 @@ namespace scribo using namespace mln; using value::int_u16; + /// Functions related to the table removal + /// \{ + + + /// Extract the components bboxes. util::array<box2d> component_boxes(const image2d<bool>& filter) { - int_u16 nlabels; image2d<int_u16> lbl = labeling::blobs(filter, c4(), nlabels); return labeling::compute(accu::meta::bbox(), lbl, nlabels); } + + /// Remove bboxes from an image. void erase_boxes(image2d<bool>& ima, const util::array<box2d>& boxes, unsigned bbox_larger) { for (unsigned i = 1; i <= boxes.nelements(); ++i) - level::paste(pw::cst(false) - | boxes[i].to_larger(bbox_larger), - ima); + level::paste((pw::cst(false) + | boxes[i].to_larger(bbox_larger)), ima); } + /// Find table bboxes and remove them from the image. std::pair<util::array<box2d>, util::array<box2d> > extract_tables(image2d<bool>& in, unsigned h, unsigned w, unsigned n) @@ -116,28 +128,190 @@ namespace scribo // Lignes verticales win::rectangle2d vwin(h, w); image2d<bool> vfilter = morpho::opening(in, vwin); - io::pbm::save(vfilter, "./table-vfilter.pbm"); + //io::pbm::save(vfilter, "./table-vfilter.pbm"); boxes_t vboxes = component_boxes(vfilter); erase_boxes(in, vboxes, n); // Lignes horizontales win::rectangle2d hwin(w, h); image2d<bool> hfilter = morpho::opening(in, hwin); - io::pbm::save(hfilter, "./table-hfilter.pbm"); + //io::pbm::save(hfilter, "./table-hfilter.pbm"); boxes_t hboxes = component_boxes(hfilter); erase_boxes(in, hboxes, n); + //io::pbm::save(in, "./table-filtered.pbm"); + return std::make_pair(vboxes, hboxes); } + /// \} + /// End of functions related to the table removal. + + + /// Function related to text extraction + /// \{ + + int_u16 + anc(const fun::i2v::array<int_u16>& f, unsigned i) + { + while (f(i) != i) + i = f(i); + return i; + } + + void do_curri(fun::i2v::array<int_u16>& f, int_u16 i) + { + if (f(i) != i) + f(i) = anc(f, f(i)); + } + + void - extract_text(image2d<bool>& in) + remove_small_comps_i2v(image2d<int_u16>& ima, image2d<int_u16>& lbl, + util::array<box2d>& cboxes, int_u16& ncomps, + unsigned min_comp_size) { - typedef image2d<int_u16> I; + ncomps = cboxes.nelements() - 1; + unsigned current = 1; + fun::i2v::array<int_u16> comps; + comps.resize(cboxes.nelements()); + comps(0) = 0; + + // Construct the transform function. + for (int i = 1; i < ncomps;) + { + // On aimerait avoir une routine qui nous le fait toute seule et qui + // soit optimisee. + if (estim::nsites(cboxes[i]) < min_comp_size) + { + comps(current) = 0; + + /// DEBUG + level::paste(pw::cst(false) | cboxes[i], ima); + /// DEBUG + + cboxes[i] = cboxes[ncomps]; + comps(ncomps) = i; + current = ncomps--; + } + else + { + comps(current) = i; + current = ++i; + //draw::box(ima, cboxes[i], 150u); + } + } + + //Relabel + lbl = level::transform(lbl, comps); + cboxes.resize(ncomps + 1); + } + + + /// Merge bboxes according to their left box neighbor. + util::array< accu::bbox<point2d> > + group_bboxes(fun::i2v::array<int_u16>& left, image2d<int_u16>& lbl, + util::array<box2d>& cboxes, unsigned ncomp) + { + // Currify left lookup table and compute text area bboxes. + util::array< accu::bbox<point2d> > tboxes; + tboxes.resize(ncomp + 1); + for (unsigned i = 1; i <= ncomp; ++i) + { + do_curri(left, i); + tboxes[left(i)].take(cboxes[i]); + } + + //Update labels + level::transform(lbl, left); + + return tboxes; + } + + bool + has_valid_left_link(const fun::i2v::array<int_u16>& left, unsigned j) + { + return left(j) == j; + } + + + + bool + is_valid_comp_neigh(const point2d& p, const point2d& c, unsigned dmax) + { + return (p.col() - c.col()) < dmax; + } + + + + /// Update the lookup table \p left if a neighbor is found on the right of + /// the current bbox. + void update_link(fun::i2v::array<int_u16>& left_link, image2d<int_u16>& lbl, + const point2d& p, const point2d& c, + unsigned i, unsigned dmax) + { + if (lbl.domain().has(p) && lbl(p) != 0 + && is_valid_comp_neigh(p, c, dmax) + && has_valid_left_link(left_link, lbl(p))) + left_link(lbl(p)) = i; + else if (!has_valid_left_link(left_link, lbl(p)) && lbl(p) != 0) + left_link(lbl(p)) = 0; + } + + + + /// Map each character bbox to its left bbox neighbor if possible. + /// Iterate to the right but link boxes to the left. + fun::i2v::array<int_u16> + link_character_bboxes(image2d<int_u16>& lbl, + const util::array<box2d>& cboxes, + unsigned ncomp, + unsigned bbox_distance) + { + fun::i2v::array<int_u16> left_link; + left_link.resize(ncomp + 1); + + for (unsigned i = 0; i <= ncomp; ++i) + left_link(i) = i; + + for (unsigned i = 1; i <= ncomp; ++i) + { + unsigned midcol = (cboxes[i].pmax().col() - cboxes[i].pmin().col()) / 2; + unsigned dmax = midcol + bbox_distance; + /// Box center => Routine? + point2d c (cboxes[i].pmin().row() + ((cboxes[i].pmax().row() - cboxes[i].pmin().row()) / 2), + cboxes[i].pmin().col() + midcol); + /// First point on the right of c + point2d p(c.row(), c.col() + 1); + + // Lemmings avec condition sur la distance en plus => Faire une version speciale? + while (lbl.domain().has(p) && (lbl(p) == 0 || lbl(p) == i) + && is_valid_comp_neigh(p, c, dmax)) + ++p.col(); + + update_link(left_link, lbl, p, c, i, dmax); + } + + return left_link; + } + + + + void + extract_text(image2d<bool>& in, unsigned bbox_distance, unsigned min_comp_size) + { + typedef int_u16 V; + typedef image2d<V> I; typedef util::array<box2d> boxes_t; - boxes_t tboxes = component_boxes(in); + // Find character bboxes. + V nlabels; + image2d<V> lbl = labeling::blobs(in, c4(), nlabels); + boxes_t cboxes = labeling::compute(accu::meta::bbox(), lbl, nlabels); + + std::cout << "nlabels = " << nlabels << std::endl; + //DEBUG PURPOSE //FIXME: don't know how to clone and convert to image<int> properly // \{ I ima(in.domain()); @@ -146,15 +320,41 @@ namespace scribo | (in | (pw::value(in) == pw::cst(true))).domain(), ima); // \} - for (int i = 1; i < tboxes.nelements(); ++i) - if (estim::nsites(tboxes[i]) < 40) - level::paste(pw::cst(false) | tboxes[i], ima); - else - draw::box(ima, tboxes[i], 150u); + //Remove small components. + int_u16 ncomp; + remove_small_comps_i2v(ima, lbl, cboxes, ncomp, min_comp_size); + + std::cout << "ncomp = " << ncomp << std::endl; + + //Link character bboxes to their left neighboor if possible. + fun::i2v::array<int_u16> left = + link_character_bboxes(lbl, cboxes, ncomp, bbox_distance); + //Merge character bboxes according to their left neighbor. + util::array< accu::bbox<point2d> > tboxes = group_bboxes(left, lbl, cboxes, ncomp); + + /// DEBUG PURPOSE + /// \{ + io::pgm::save(lbl, "./textlbl.pgm"); io::pgm::save(ima, "./text.pgm"); + + for (unsigned i = 1; i <= ncomp; ++i) + if (tboxes[i].is_valid()) + draw::box(ima, tboxes[i].to_result(), 254u); + + for (unsigned i = 1; i <= ncomp; ++i) + if (tboxes[i].is_valid()) + draw::box(in, tboxes[i].to_result(), true); + + io::pgm::save(ima, "./bbtext.pgm"); + io::pbm::save(in, "./intext.pgm"); + /// \} + } + /// \} + /// End of functions related to text extraction + } // end of namespace scribo::internal @@ -168,17 +368,17 @@ namespace scribo //Useful debug variables unsigned h = atoi(argv[2]); unsigned w = atoi(argv[3]); - unsigned n = atoi(argv[4]); + unsigned bbox_larger = atoi(argv[4]); + unsigned bbox_distance = atoi(argv[5]); + unsigned min_comp_size = atoi(argv[6]); //Load image image2d<bool> in; io::pbm::load(in, argv[1]); - internal::extract_tables(in, h, w, n); - - io::pbm::save(in, "./table-filtered.pbm"); + internal::extract_tables(in, h, w, bbox_larger); - internal::extract_text(in); + internal::extract_text(in, bbox_distance, min_comp_size); } } // end of namespace scribo diff --git a/milena/sandbox/scribo/main.cc b/milena/sandbox/scribo/main.cc index d49fee8..52aad5a 100644 --- a/milena/sandbox/scribo/main.cc +++ b/milena/sandbox/scribo/main.cc @@ -35,7 +35,7 @@ int main(int argc, char*argv[]) if (argc < 2) { - std::cout << argv[0] << " <image.pgm> <h> <w> <bbox_larger>" << std::endl; + std::cout << argv[0] << " <image.pgm> <h> <w> <bbox_larger> <bbox_distance> <min_comp_size>" << std::endl; return 1; } -- 1.5.6.5