* 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(a)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(a)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