
--- scribo/sandbox/icdar_13_table/Makefile | 9 +- scribo/sandbox/icdar_13_table/README_ROLAND | 18 +++ scribo/sandbox/icdar_13_table/TODO | 2 - scribo/sandbox/icdar_13_table/src/new.cc | 208 +++++++++++++++++++++++--- 4 files changed, 209 insertions(+), 28 deletions(-) create mode 100644 scribo/sandbox/icdar_13_table/README_ROLAND diff --git a/scribo/sandbox/icdar_13_table/Makefile b/scribo/sandbox/icdar_13_table/Makefile index b9600a4..19743e1 100644 --- a/scribo/sandbox/icdar_13_table/Makefile +++ b/scribo/sandbox/icdar_13_table/Makefile @@ -1,21 +1,26 @@ CCACHE= CC=g++ -CFLAGS=-Wall -Werror -O3 -DHAVE_TESSERACT_3 -DNDEBUG +CFLAGS=-Wall -Werror -O3 -DHAVE_TESSERACT_3 -DNDEBUG -g CLIBS=-I../../../milena/ -I../../ -I/usr/include/poppler CLEAN=*.o output/* log final.xml SRC=src/new.cc +SRC_OLD=src/main.cc OUTPUT=table +OUTPUT_OLD=old all: table table: $(CCACHE) $(CC) $(CFLAGS) $(CLIBS) $(SRC) -ltesseract -lpoppler-cpp -o $(OUTPUT) +old: + $(CCACHE) $(CC) $(CFLAGS) $(CLIBS) $(SRC_OLD) -ltesseract -lpoppler-cpp -o $(OUTPUT_OLD) + clean: rm -rf $(CLEAN) mrproper: clean - rm -f $(OUTPUT) + rm -f $(OUTPUT) $(OUTPUT_OLD) .PHONY: table clean mrproper diff --git a/scribo/sandbox/icdar_13_table/README_ROLAND b/scribo/sandbox/icdar_13_table/README_ROLAND new file mode 100644 index 0000000..89b70dc --- /dev/null +++ b/scribo/sandbox/icdar_13_table/README_ROLAND @@ -0,0 +1,18 @@ +#------------------------------------------------------------------------------# + README - ICDAR 2013 - Table competition +#------------------------------------------------------------------------------# + +FIRST OF ALL : + * mkdir output + +Compilation and cleaning : + * make (generates the main program) + * make old (generates the old program (without scribo toolchain)) + * make clean (remove all files expect the binary) + * make mrproper (remove all files) + +Usage : + * ./table [your-pdf-file] + Generate debug images in the output/ directory and the final.xml + * ./old [your-pdf-file] + Same thing but old processing (without scribo toolchain) diff --git a/scribo/sandbox/icdar_13_table/TODO b/scribo/sandbox/icdar_13_table/TODO index b34e7a2..a4aa631 100644 --- a/scribo/sandbox/icdar_13_table/TODO +++ b/scribo/sandbox/icdar_13_table/TODO @@ -3,10 +3,8 @@ #------------------------------------------------------------------------------# Table location sub-competition : - * Load PDF files (instead of PNM) * Find links betwwen pages for mutlipages tables * Get text from reversed-video zones - * Expand the process to borderless tables Table structure recognition sub-competition : * All diff --git a/scribo/sandbox/icdar_13_table/src/new.cc b/scribo/sandbox/icdar_13_table/src/new.cc index 963aa7d..714d0c2 100644 --- a/scribo/sandbox/icdar_13_table/src/new.cc +++ b/scribo/sandbox/icdar_13_table/src/new.cc @@ -1,9 +1,10 @@ +// INCLUDES OLENA #include <mln/binarization/all.hh> #include <mln/core/image/image2d.hh> #include <mln/data/all.hh> -#include <mln/draw/line.hh> +#include <mln/draw/all.hh> #include <mln/fun/v2v/rgb_to_luma.hh> @@ -14,25 +15,45 @@ #include <mln/labeling/all.hh> #include <mln/literal/all.hh> #include <mln/logical/and.hh> +#include <mln/logical/not.hh> #include <mln/value/all.hh> +// INCLUDE TESSERACT #include <tesseract/baseapi.h> +// INCLUDES SCRIBO #include <scribo/binarization/sauvola.hh> + #include <scribo/core/component_set.hh> +#include <scribo/core/line_set.hh> +#include <scribo/core/paragraph_set.hh> + +#include <scribo/debug/links_image.hh> +#include <scribo/draw/groups_bboxes.hh> +#include <scribo/draw/line_components.hh> + +#include <scribo/filter/object_links_bbox_h_ratio.hh> + #include <scribo/preprocessing/denoise_fg.hh> -#include <scribo/primitive/extract/vertical_separators.hh> -#include <scribo/primitive/remove/separators.hh> +#include <scribo/primitive/extract/lines_h_discontinued.hh> +#include <scribo/primitive/extract/lines_v_discontinued.hh> #include <scribo/primitive/extract/separators_nonvisible.hh> - +#include <scribo/primitive/extract/vertical_separators.hh> +#include <scribo/primitive/group/from_single_link.hh> +#include <scribo/primitive/link/internal/compute_anchor.hh> #include <scribo/primitive/link/internal/dmax_width_and_height.hh> -#include <scribo/primitive/link/with_single_right_link_dmax_ratio.hh> -#include <scribo/primitive/link/with_single_left_link_dmax_ratio.hh> #include <scribo/primitive/link/merge_double_link.hh> +#include <scribo/primitive/link/with_single_left_link_dmax_ratio.hh> +#include <scribo/primitive/link/with_single_right_link_dmax_ratio.hh> +#include <scribo/primitive/remove/separators.hh> + +#include <scribo/text/extract_paragraphs.hh> +#include <scribo/text/merging.hh> using namespace mln; +// Open and initialize XML void start_xml(std::ofstream& xml, const char* name, const char* pdf) { xml.open(name); @@ -40,12 +61,14 @@ void start_xml(std::ofstream& xml, const char* name, const char* pdf) << "<document filename='" << pdf << "'>" << std::endl; } +// Finalize an close XML void end_xml(std::ofstream& xml) { xml << "</document>" << std::endl; xml.close(); } +// Write a new (simple) table in XML file void write_table(std::ofstream& xml, const point2d& start, const point2d& end) { static unsigned table = 0; @@ -62,6 +85,10 @@ void write_table(std::ofstream& xml, const point2d& start, const point2d& end) ++table; } + /********/ + /* MAIN */ + /********/ + int main(int argc, char** argv) { typedef value::label_16 V; @@ -69,10 +96,9 @@ int main(int argc, char** argv) std::ofstream xml; std::ostringstream path; - image2d<value::rgb8> original; + image2d<value::rgb8> original, ima_links, ima_groups, ima_valid; image2d<value::int_u8> filtered; - image2d<bool> bin, separators, bin_without_separators, whitespaces, - denoised, comp, links; + image2d<bool> bin, separators, bin_without_separators, whitespaces, comp, denoised; scribo::component_set< image2d<unsigned> > components; unsigned dpi = 72; @@ -89,9 +115,18 @@ int main(int argc, char** argv) bin = scribo::binarization::sauvola(filtered, 81, 0.44); // Find separators - separators = scribo::primitive::extract::vertical_separators(bin, 81); - bin_without_separators = scribo::primitive::remove::separators(bin, separators); - whitespaces = scribo::primitive::extract::separators_nonvisible(bin); + bin_without_separators = duplicate(bin); + separators = separators; + V nhlines, nvlines; + unsigned min_width = 31; + unsigned min_height = 71; + scribo::component_set<L> hlines = scribo::primitive::extract::lines_h_discontinued(bin_without_separators, c4(), nhlines, min_width, 2); + scribo::component_set<L> vlines = scribo::primitive::extract::lines_v_discontinued(bin_without_separators, c4(), nvlines, min_height, 2); + for (unsigned i = 1; i <= hlines.nelements(); ++i) + data::fill((bin_without_separators | hlines(i).bbox()).rw(), false); + + for (unsigned i = 1; i <= vlines.nelements(); ++i) + data::fill((bin_without_separators | vlines(i).bbox()).rw(), false); // Denoise denoised = scribo::preprocessing::denoise_fg(bin_without_separators, c8(), 4); @@ -103,8 +138,13 @@ int main(int argc, char** argv) initialize(comp, denoised); data::fill(comp, false); for (unsigned i = 1; i <= components.nelements(); ++i) - data::fill((comp | components(i).bbox()).rw(), true); + { + const box2d& b = components(i).bbox(); + if (b.width() > 2 && b.height() > 2) + data::fill((comp | b).rw(), true); + } + // Find links scribo::object_links< image2d<unsigned> > right_link = scribo::primitive::link::with_single_right_link_dmax_ratio(components, scribo::primitive::link::internal::dmax_width_and_height(1), scribo::anchor::MassCenter); @@ -115,10 +155,127 @@ int main(int argc, char** argv) scribo::object_links< image2d<unsigned> > merged_links = scribo::primitive::link::merge_double_link(left_link, right_link); - initialize(links, denoised); - data::fill(links, false); - for (unsigned i = 1; i <= merged_links.components().nelements(); ++i) - data::fill((links | merged_links.components()(i).bbox()).rw(), true); + // Filter links + scribo::object_links< image2d<unsigned> > hratio_filtered_links = scribo::filter::object_links_bbox_h_ratio(merged_links, 2.5f); + + ima_links = data::convert(value::rgb8(), denoised); + ima_groups = data::convert(value::rgb8(), denoised); + ima_valid = data::convert(value::rgb8(), denoised); + + // Write links + for (unsigned l = 1; l < merged_links.nelements(); ++l) + { + point2d p1 = scribo::primitive::link::internal::compute_anchor(merged_links.components(), l, scribo::anchor::MassCenter); + point2d p2 = scribo::primitive::link::internal::compute_anchor(merged_links.components(), merged_links(l), scribo::anchor::MassCenter); + + draw::line(ima_links, p1, p2, literal::red); + } + + for (unsigned l = 1; l < hratio_filtered_links.nelements(); ++l) + { + point2d p1 = scribo::primitive::link::internal::compute_anchor(hratio_filtered_links.components(), l, scribo::anchor::MassCenter); + point2d p2 = scribo::primitive::link::internal::compute_anchor(hratio_filtered_links.components(), hratio_filtered_links(l), scribo::anchor::MassCenter); + + draw::line(ima_links, p1, p2, literal::blue); + } + + // Group components + scribo::object_groups< image2d<unsigned> > groups = scribo::primitive::group::from_single_link(hratio_filtered_links); + scribo::draw::groups_bboxes(ima_groups, groups, literal::blue); + + // Compute averages + unsigned average_height = 0; + unsigned average_width = 0; + + for (unsigned i = 1; i < groups.nelements(); ++i) + { + average_height += groups(i).bbox().height(); + average_width += groups(i).bbox().width(); + } + average_height /= groups.nelements(); + average_width /= groups.nelements(); + + std::vector<short> balance(groups.nelements(), 0); + + // Draw vertical links (red) + for (unsigned i = 1; i < groups.nelements(); ++i) + { + for (unsigned j = 1; j < groups.nelements(); ++j) + { + if (i != j) + { + const box2d& b1 = groups(i).bbox(); + const box2d& b2 = groups(j).bbox(); + const point2d& p1 = b1.pcenter(); + const point2d& p2 = b2.pcenter(); + + unsigned max_height = std::max(b1.height(), b2.height()); + unsigned min_height = std::min(b1.height(), b2.height()); + + if (p1[0] < p2[0] // Avoid redundancy + && max_height * 2 < denoised.ncols() + && min_height + 3 >= max_height // Same heights + && b1.width() < 2 * average_width && b2.width() < 2 * average_width // Regular width + && (b1.pmin()[1] == b2.pmin()[1] + || (b1.pmin()[1] < b2.pmin()[1] && b1.pmax()[1] > b2.pmin()[1]) + || (b1.pmin()[1] > b2.pmin()[1] && b2.pmax()[1] > b1.pmin()[1])) // Boxes are aligned + && abs(p1[0] - p2[0]) < 3 * max_height // Reduced gap + && abs(p1[1] - p2[1]) < 20) // Vertical proximity + { + draw::line(ima_groups, p1, p2, literal::red); + balance[i] += 1; + balance[j] += 1; + break; + } + } + } + } + + // Draw horizontal links (green) + for (unsigned i = 1; i < groups.nelements(); ++i) + { + for (unsigned j = 1; j < groups.nelements(); ++j) + { + if (i != j) + { + const box2d& b1 = groups(i).bbox(); + const box2d& b2 = groups(j).bbox(); + const point2d& p1 = b1.pcenter(); + const point2d& p2 = b2.pcenter(); + + if (p1[1] < p2[1] // Avoid redundancy + && (b1.pmin()[0] == b2.pmin()[0] + || (b1.pmin()[0] < b2.pmin()[0] && b1.pmax()[0] > b2.pmin()[0]) + || (b1.pmin()[0] > b2.pmin()[0] && b2.pmax()[0] > b1.pmin()[0])) // Boxes are aligned + && abs(p1[0] - p2[0]) < 10) // Reduced gap + { + draw::line(ima_groups, p1, p2, literal::green); + balance[i] += 1; + balance[j] += 1; + break; + } + } + } + } + + // Draw weighted boxes (red < orange < cyan < green) + for (unsigned i = 0; i < balance.size(); ++i) + { + std::cout << balance[i] << " "; + if (balance[i] == 1) + draw::box(ima_valid, groups(i).bbox(), literal::red); + + if (balance[i] == 2) + draw::box(ima_valid, groups(i).bbox(), literal::orange); + + if (balance[i] == 3) + draw::box(ima_valid, groups(i).bbox(), literal::cyan); + + if (balance[i] > 3) + draw::box(ima_valid, groups(i).bbox(), literal::green); + } + std::cout << std::endl << std::endl; + // Write images and close XML path.str(""); path << "output/p" << page << "_0_bin.pbm"; @@ -127,17 +284,20 @@ int main(int argc, char** argv) path.str(""); path << "output/p" << page << "_1_bin_without_separators.pbm"; io::pbm::save(bin_without_separators, path.str()); - path.str(""); path << "output/p" << page << "_2_whitespaces.pbm"; - io::pbm::save(whitespaces, path.str()); - - path.str(""); path << "output/p" << page << "_3_denoised.pbm"; + path.str(""); path << "output/p" << page << "_2_denoised.pbm"; io::pbm::save(denoised, path.str()); - path.str(""); path << "output/p" << page << "_4_components.pbm"; + path.str(""); path << "output/p" << page << "_3_components.pbm"; io::pbm::save(comp, path.str()); - path.str(""); path << "output/p" << page << "_5_links.pbm"; - io::pbm::save(links, path.str()); + path.str(""); path << "output/p" << page << "_4_links.ppm"; + io::ppm::save(ima_links, path.str()); + + path.str(""); path << "output/p" << page << "_5_groups.ppm"; + io::ppm::save(ima_groups, path.str()); + + path.str(""); path << "output/p" << page << "_6_valid.ppm"; + io::ppm::save(ima_valid, path.str()); } end_xml(xml); -- 1.7.2.5