// Insert the given blocks at the front of the completed_blocks_ list so // they can be kept in the correct reading order. void WorkingPartSet::InsertCompletedBlocks(BLOCK_LIST* blocks, TO_BLOCK_LIST* to_blocks) { BLOCK_IT block_it(&completed_blocks_); block_it.add_list_before(blocks); TO_BLOCK_IT to_block_it(&to_blocks_); to_block_it.add_list_before(to_blocks); }
// Make a block using lines parallel to the given vector that fit between // the min and max coordinates specified by the ColPartitions. // Construct a block from the given list of partitions. void WorkingPartSet::MakeBlocks(const ICOORD& bleft, const ICOORD& tright, int resolution, ColPartition_LIST* used_parts) { part_it_.move_to_first(); while (!part_it_.empty()) { // Gather a list of ColPartitions in block_parts that will be split // by linespacing into smaller blocks. ColPartition_LIST block_parts; ColPartition_IT block_it(&block_parts); ColPartition* next_part = NULL; bool text_block = false; do { ColPartition* part = part_it_.extract(); if (part->blob_type() == BRT_UNKNOWN || part->blob_type() == BRT_TEXT) text_block = true; part->set_working_set(NULL); part_it_.forward(); block_it.add_after_then_move(part); next_part = part->SingletonPartner(false); if (part_it_.empty() || next_part != part_it_.data()) { // Sequences of partitions can get split by titles. next_part = NULL; } // Merge adjacent blocks that are of the same type and let the // linespacing determine the real boundaries. if (next_part == NULL && !part_it_.empty()) { ColPartition* next_block_part = part_it_.data(); const TBOX& part_box = part->bounding_box(); const TBOX& next_box = next_block_part->bounding_box(); // In addition to the same type, the next box must not be above the // current box, nor (if image) too far below. PolyBlockType type = part->type(), next_type = next_block_part->type(); if (ColPartition::TypesSimilar(type, next_type) && next_box.bottom() <= part_box.top() && (text_block || part_box.bottom() - next_box.top() < part_box.height())) next_part = next_block_part; } } while (!part_it_.empty() && next_part != NULL); if (!text_block) { TO_BLOCK* to_block = ColPartition::MakeBlock(bleft, tright, &block_parts, used_parts); if (to_block != NULL) { TO_BLOCK_IT to_block_it(&to_blocks_); to_block_it.add_to_end(to_block); BLOCK_IT block_it(&completed_blocks_); block_it.add_to_end(to_block->block); } } else { // Further sub-divide text blocks where linespacing changes. ColPartition::LineSpacingBlocks(bleft, tright, resolution, &block_parts, used_parts, &completed_blocks_, &to_blocks_); } } part_it_.set_to_list(&part_set_); latest_part_ = NULL; ASSERT_HOST(completed_blocks_.length() == to_blocks_.length()); }
/** * Auto page segmentation. Divide the page image into blocks of uniform * text linespacing and images. * * Resolution (in ppi) is derived from the input image. * * The output goes in the blocks list with corresponding TO_BLOCKs in the * to_blocks list. * * If single_column is true, then no attempt is made to divide the image * into columns, but multiple blocks are still made if the text is of * non-uniform linespacing. * * If osd (orientation and script detection) is true then that is performed * as well. If only_osd is true, then only orientation and script detection is * performed. If osd is desired, (osd or only_osd) then osr_tess must be * another Tesseract that was initialized especially for osd, and the results * will be output into osr (orientation and script result). */ int Tesseract::AutoPageSeg(PageSegMode pageseg_mode, BLOCK_LIST* blocks, TO_BLOCK_LIST* to_blocks, Tesseract* osd_tess, OSResults* osr) { if (textord_debug_images) { WriteDebugBackgroundImage(textord_debug_printable, pix_binary_); } Pix* photomask_pix = NULL; Pix* musicmask_pix = NULL; // The blocks made by the ColumnFinder. Moved to blocks before return. BLOCK_LIST found_blocks; TO_BLOCK_LIST temp_blocks; bool single_column = !PSM_COL_FIND_ENABLED(pageseg_mode); bool osd_enabled = PSM_OSD_ENABLED(pageseg_mode); bool osd_only = pageseg_mode == PSM_OSD_ONLY; ColumnFinder* finder = SetupPageSegAndDetectOrientation( single_column, osd_enabled, osd_only, blocks, osd_tess, osr, &temp_blocks, &photomask_pix, &musicmask_pix); int result = 0; if (finder != NULL) { TO_BLOCK_IT to_block_it(&temp_blocks); TO_BLOCK* to_block = to_block_it.data(); if (musicmask_pix != NULL) { // TODO(rays) pass the musicmask_pix into FindBlocks and mark music // blocks separately. For now combine with photomask_pix. pixOr(photomask_pix, photomask_pix, musicmask_pix); } if (equ_detect_) { finder->SetEquationDetect(equ_detect_); } result = finder->FindBlocks(pageseg_mode, scaled_color_, scaled_factor_, to_block, photomask_pix, pix_thresholds_, pix_grey_, &found_blocks, to_blocks); if (result >= 0) finder->GetDeskewVectors(&deskew_, &reskew_); delete finder; } pixDestroy(&photomask_pix); pixDestroy(&musicmask_pix); if (result < 0) return result; blocks->clear(); BLOCK_IT block_it(blocks); // Move the found blocks to the input/output blocks. block_it.add_list_after(&found_blocks); if (textord_debug_images) { // The debug image is no longer needed so delete it. unlink(AlignedBlob::textord_debug_pix().string()); } return result; }
// Make blocks out of any partitions in this WorkingPartSet, and append // them to the end of the blocks list. bleft, tright and resolution give // the bounds and resolution of the source image, so that blocks can be // made to fit in the bounds. // All ColPartitions go in the used_parts list, as they need to be kept // around, but are no longer needed. void WorkingPartSet::ExtractCompletedBlocks(const ICOORD& bleft, const ICOORD& tright, int resolution, ColPartition_LIST* used_parts, BLOCK_LIST* blocks, TO_BLOCK_LIST* to_blocks) { MakeBlocks(bleft, tright, resolution, used_parts); BLOCK_IT block_it(blocks); block_it.move_to_last(); block_it.add_list_after(&completed_blocks_); TO_BLOCK_IT to_block_it(to_blocks); to_block_it.move_to_last(); to_block_it.add_list_after(&to_blocks_); }
/** * Auto page segmentation. Divide the page image into blocks of uniform * text linespacing and images. * * Resolution (in ppi) is derived from the input image. * * The output goes in the blocks list with corresponding TO_BLOCKs in the * to_blocks list. * * If !PSM_COL_FIND_ENABLED(pageseg_mode), then no attempt is made to divide * the image into columns, but multiple blocks are still made if the text is * of non-uniform linespacing. * * If diacritic_blobs is non-null, then diacritics/noise blobs, that would * confuse layout anaylsis by causing textline overlap, are placed there, * with the expectation that they will be reassigned to words later and * noise/diacriticness determined via classification. * * If osd (orientation and script detection) is true then that is performed * as well. If only_osd is true, then only orientation and script detection is * performed. If osd is desired, (osd or only_osd) then osr_tess must be * another Tesseract that was initialized especially for osd, and the results * will be output into osr (orientation and script result). */ int Tesseract::AutoPageSeg(PageSegMode pageseg_mode, BLOCK_LIST* blocks, TO_BLOCK_LIST* to_blocks, BLOBNBOX_LIST* diacritic_blobs, Tesseract* osd_tess, OSResults* osr) { Pix* photomask_pix = NULL; Pix* musicmask_pix = NULL; // The blocks made by the ColumnFinder. Moved to blocks before return. BLOCK_LIST found_blocks; TO_BLOCK_LIST temp_blocks; ColumnFinder* finder = SetupPageSegAndDetectOrientation( pageseg_mode, blocks, osd_tess, osr, &temp_blocks, &photomask_pix, &musicmask_pix); int result = 0; if (finder != NULL) { TO_BLOCK_IT to_block_it(&temp_blocks); TO_BLOCK* to_block = to_block_it.data(); if (musicmask_pix != NULL) { // TODO(rays) pass the musicmask_pix into FindBlocks and mark music // blocks separately. For now combine with photomask_pix. pixOr(photomask_pix, photomask_pix, musicmask_pix); } if (equ_detect_) { finder->SetEquationDetect(equ_detect_); } result = finder->FindBlocks(pageseg_mode, scaled_color_, scaled_factor_, to_block, photomask_pix, pix_thresholds_, pix_grey_, &pixa_debug_, &found_blocks, diacritic_blobs, to_blocks); if (result >= 0) finder->GetDeskewVectors(&deskew_, &reskew_); delete finder; } pixDestroy(&photomask_pix); pixDestroy(&musicmask_pix); if (result < 0) return result; blocks->clear(); BLOCK_IT block_it(blocks); // Move the found blocks to the input/output blocks. block_it.add_list_after(&found_blocks); return result; }
/** * Sets up auto page segmentation, determines the orientation, and corrects it. * Somewhat arbitrary chunk of functionality, factored out of AutoPageSeg to * facilitate testing. * photo_mask_pix is a pointer to a NULL pointer that will be filled on return * with the leptonica photo mask, which must be pixDestroyed by the caller. * to_blocks is an empty list that will be filled with (usually a single) * block that is used during layout analysis. This ugly API is required * because of the possibility of a unlv zone file. * TODO(rays) clean this up. * See AutoPageSeg for other arguments. * The returned ColumnFinder must be deleted after use. */ ColumnFinder* Tesseract::SetupPageSegAndDetectOrientation( PageSegMode pageseg_mode, BLOCK_LIST* blocks, Tesseract* osd_tess, OSResults* osr, TO_BLOCK_LIST* to_blocks, Pix** photo_mask_pix, Pix** music_mask_pix) { int vertical_x = 0; int vertical_y = 1; TabVector_LIST v_lines; TabVector_LIST h_lines; ICOORD bleft(0, 0); ASSERT_HOST(pix_binary_ != NULL); if (tessedit_dump_pageseg_images) { pixa_debug_.AddPix(pix_binary_, "PageSegInput"); } // Leptonica is used to find the rule/separator lines in the input. LineFinder::FindAndRemoveLines(source_resolution_, textord_tabfind_show_vlines, pix_binary_, &vertical_x, &vertical_y, music_mask_pix, &v_lines, &h_lines); if (tessedit_dump_pageseg_images) { pixa_debug_.AddPix(pix_binary_, "NoLines"); } // Leptonica is used to find a mask of the photo regions in the input. *photo_mask_pix = ImageFind::FindImages(pix_binary_, &pixa_debug_); if (tessedit_dump_pageseg_images) { pixa_debug_.AddPix(pix_binary_, "NoImages"); } if (!PSM_COL_FIND_ENABLED(pageseg_mode)) v_lines.clear(); // The rest of the algorithm uses the usual connected components. textord_.find_components(pix_binary_, blocks, to_blocks); TO_BLOCK_IT to_block_it(to_blocks); // There must be exactly one input block. // TODO(rays) handle new textline finding with a UNLV zone file. ASSERT_HOST(to_blocks->singleton()); TO_BLOCK* to_block = to_block_it.data(); TBOX blkbox = to_block->block->bounding_box(); ColumnFinder* finder = NULL; int estimated_resolution = source_resolution_; if (source_resolution_ == kMinCredibleResolution) { // Try to estimate resolution from typical body text size. int res = IntCastRounded(to_block->line_size * kResolutionEstimationFactor); if (res > estimated_resolution && res < kMaxCredibleResolution) { estimated_resolution = res; tprintf("Estimating resolution as %d\n", estimated_resolution); } } if (to_block->line_size >= 2) { finder = new ColumnFinder(static_cast<int>(to_block->line_size), blkbox.botleft(), blkbox.topright(), estimated_resolution, textord_use_cjk_fp_model, textord_tabfind_aligned_gap_fraction, &v_lines, &h_lines, vertical_x, vertical_y); finder->SetupAndFilterNoise(pageseg_mode, *photo_mask_pix, to_block); if (equ_detect_) { equ_detect_->LabelSpecialText(to_block); } BLOBNBOX_CLIST osd_blobs; // osd_orientation is the number of 90 degree rotations to make the // characters upright. (See osdetect.h for precise definition.) // We want the text lines horizontal, (vertical text indicates vertical // textlines) which may conflict (eg vertically written CJK). int osd_orientation = 0; bool vertical_text = textord_tabfind_force_vertical_text || pageseg_mode == PSM_SINGLE_BLOCK_VERT_TEXT; if (!vertical_text && textord_tabfind_vertical_text && PSM_ORIENTATION_ENABLED(pageseg_mode)) { vertical_text = finder->IsVerticallyAlignedText(textord_tabfind_vertical_text_ratio, to_block, &osd_blobs); } if (PSM_OSD_ENABLED(pageseg_mode) && osd_tess != NULL && osr != NULL) { GenericVector<int> osd_scripts; if (osd_tess != this) { // We are running osd as part of layout analysis, so constrain the // scripts to those allowed by *this. AddAllScriptsConverted(unicharset, osd_tess->unicharset, &osd_scripts); for (int s = 0; s < sub_langs_.size(); ++s) { AddAllScriptsConverted(sub_langs_[s]->unicharset, osd_tess->unicharset, &osd_scripts); } } os_detect_blobs(&osd_scripts, &osd_blobs, osr, osd_tess); if (pageseg_mode == PSM_OSD_ONLY) { delete finder; return NULL; } osd_orientation = osr->best_result.orientation_id; double osd_score = osr->orientations[osd_orientation]; double osd_margin = min_orientation_margin * 2; for (int i = 0; i < 4; ++i) { if (i != osd_orientation && osd_score - osr->orientations[i] < osd_margin) { osd_margin = osd_score - osr->orientations[i]; } } int best_script_id = osr->best_result.script_id; const char* best_script_str = osd_tess->unicharset.get_script_from_script_id(best_script_id); bool cjk = best_script_id == osd_tess->unicharset.han_sid() || best_script_id == osd_tess->unicharset.hiragana_sid() || best_script_id == osd_tess->unicharset.katakana_sid() || strcmp("Japanese", best_script_str) == 0 || strcmp("Korean", best_script_str) == 0 || strcmp("Hangul", best_script_str) == 0; if (cjk) { finder->set_cjk_script(true); } if (osd_margin < min_orientation_margin) { // The margin is weak. if (!cjk && !vertical_text && osd_orientation == 2) { // upside down latin text is improbable with such a weak margin. tprintf("OSD: Weak margin (%.2f), horiz textlines, not CJK: " "Don't rotate.\n", osd_margin); osd_orientation = 0; } else { tprintf( "OSD: Weak margin (%.2f) for %d blob text block, " "but using orientation anyway: %d\n", osd_margin, osd_blobs.length(), osd_orientation); } } } osd_blobs.shallow_clear(); finder->CorrectOrientation(to_block, vertical_text, osd_orientation); } return finder; }
// Make the textlines and words inside each block. void Textord::TextordPage(PageSegMode pageseg_mode, const FCOORD &reskew, int width, int height, Pix *binary_pix, Pix *thresholds_pix, Pix *grey_pix, bool use_box_bottoms, BLOCK_LIST *blocks, TO_BLOCK_LIST *to_blocks) { page_tr_.set_x(width); page_tr_.set_y(height); if (to_blocks->empty()) { // AutoPageSeg was not used, so we need to find_components first. find_components(binary_pix, blocks, to_blocks); TO_BLOCK_IT it(to_blocks); for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) { TO_BLOCK *to_block = it.data(); // Compute the edge offsets whether or not there is a grey_pix. // We have by-passed auto page seg, so we have to run it here. // By page segmentation mode there is no non-text to avoid running on. to_block->ComputeEdgeOffsets(thresholds_pix, grey_pix); } } else if (!PSM_SPARSE(pageseg_mode)) { // AutoPageSeg does not need to find_components as it did that already. // Filter_blobs sets up the TO_BLOCKs the same as find_components does. filter_blobs(page_tr_, to_blocks, true); } ASSERT_HOST(!to_blocks->empty()); if (pageseg_mode == PSM_SINGLE_BLOCK_VERT_TEXT) { const FCOORD anticlockwise90(0.0f, 1.0f); const FCOORD clockwise90(0.0f, -1.0f); TO_BLOCK_IT it(to_blocks); for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) { TO_BLOCK *to_block = it.data(); BLOCK *block = to_block->block; // Create a fake poly_block in block from its bounding box. block->set_poly_block(new POLY_BLOCK(block->bounding_box(), PT_VERTICAL_TEXT)); // Rotate the to_block along with its contained block and blobnbox lists. to_block->rotate(anticlockwise90); // Set the block's rotation values to obey the convention followed in // layout analysis for vertical text. block->set_re_rotation(clockwise90); block->set_classify_rotation(clockwise90); } } TO_BLOCK_IT to_block_it(to_blocks); TO_BLOCK *to_block = to_block_it.data(); // Make the rows in the block. float gradient = 0; // Do it the old fashioned way. if (PSM_LINE_FIND_ENABLED(pageseg_mode)) { gradient = make_rows(page_tr_, to_blocks); } else if (!PSM_SPARSE(pageseg_mode)) { // RAW_LINE, SINGLE_LINE, SINGLE_WORD and SINGLE_CHAR all need a single row. gradient = make_single_row(page_tr_, pageseg_mode != PSM_RAW_LINE, to_block, to_blocks); } BaselineDetect baseline_detector(textord_baseline_debug, reskew, to_blocks); baseline_detector.ComputeStraightBaselines(use_box_bottoms); baseline_detector.ComputeBaselineSplinesAndXheights(page_tr_, true, textord_heavy_nr, textord_show_final_rows, this); // Now make the words in the lines. if (PSM_WORD_FIND_ENABLED(pageseg_mode)) { // SINGLE_LINE uses the old word maker on the single line. make_words(this, page_tr_, gradient, blocks, to_blocks); } else { // SINGLE_WORD and SINGLE_CHAR cram all the blobs into a // single word, and in SINGLE_CHAR mode, all the outlines // go in a single blob. TO_BLOCK *to_block = to_block_it.data(); make_single_word(pageseg_mode == PSM_SINGLE_CHAR, to_block->get_rows(), to_block->block->row_list()); } cleanup_blocks(PSM_WORD_FIND_ENABLED(pageseg_mode), blocks); // Remove empties. // Compute the margins for each row in the block, to be used later for // paragraph detection. BLOCK_IT b_it(blocks); for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) { b_it.data()->compute_row_margins(); } #ifndef GRAPHICS_DISABLED close_to_win(); #endif }
/** * Sets up auto page segmentation, determines the orientation, and corrects it. * Somewhat arbitrary chunk of functionality, factored out of AutoPageSeg to * facilitate testing. * photo_mask_pix is a pointer to a NULL pointer that will be filled on return * with the leptonica photo mask, which must be pixDestroyed by the caller. * to_blocks is an empty list that will be filled with (usually a single) * block that is used during layout analysis. This ugly API is required * because of the possibility of a unlv zone file. * TODO(rays) clean this up. * See AutoPageSeg for other arguments. * The returned ColumnFinder must be deleted after use. */ ColumnFinder* Tesseract::SetupPageSegAndDetectOrientation( bool single_column, bool osd, bool only_osd, BLOCK_LIST* blocks, Tesseract* osd_tess, OSResults* osr, TO_BLOCK_LIST* to_blocks, Pix** photo_mask_pix, Pix** music_mask_pix) { int vertical_x = 0; int vertical_y = 1; TabVector_LIST v_lines; TabVector_LIST h_lines; ICOORD bleft(0, 0); ASSERT_HOST(pix_binary_ != NULL); if (tessedit_dump_pageseg_images) { pixWrite("tessinput.png", pix_binary_, IFF_PNG); } // Leptonica is used to find the rule/separator lines in the input. LineFinder::FindAndRemoveLines(source_resolution_, textord_tabfind_show_vlines, pix_binary_, &vertical_x, &vertical_y, music_mask_pix, &v_lines, &h_lines); if (tessedit_dump_pageseg_images) pixWrite("tessnolines.png", pix_binary_, IFF_PNG); // Leptonica is used to find a mask of the photo regions in the input. *photo_mask_pix = ImageFind::FindImages(pix_binary_); if (tessedit_dump_pageseg_images) pixWrite("tessnoimages.png", pix_binary_, IFF_PNG); if (single_column) v_lines.clear(); // The rest of the algorithm uses the usual connected components. textord_.find_components(pix_binary_, blocks, to_blocks); TO_BLOCK_IT to_block_it(to_blocks); // There must be exactly one input block. // TODO(rays) handle new textline finding with a UNLV zone file. ASSERT_HOST(to_blocks->singleton()); TO_BLOCK* to_block = to_block_it.data(); TBOX blkbox = to_block->block->bounding_box(); ColumnFinder* finder = NULL; if (to_block->line_size >= 2) { finder = new ColumnFinder(static_cast<int>(to_block->line_size), blkbox.botleft(), blkbox.topright(), source_resolution_, &v_lines, &h_lines, vertical_x, vertical_y); finder->SetupAndFilterNoise(*photo_mask_pix, to_block); if (equ_detect_) { equ_detect_->LabelSpecialText(to_block); } BLOBNBOX_CLIST osd_blobs; // osd_orientation is the number of 90 degree rotations to make the // characters upright. (See osdetect.h for precise definition.) // We want the text lines horizontal, (vertical text indicates vertical // textlines) which may conflict (eg vertically written CJK). int osd_orientation = 0; bool vertical_text = finder->IsVerticallyAlignedText(to_block, &osd_blobs); if (osd && osd_tess != NULL && osr != NULL) { os_detect_blobs(&osd_blobs, osr, osd_tess); if (only_osd) { delete finder; return NULL; } osd_orientation = osr->best_result.orientation_id; double osd_score = osr->orientations[osd_orientation]; double osd_margin = min_orientation_margin * 2; for (int i = 0; i < 4; ++i) { if (i != osd_orientation && osd_score - osr->orientations[i] < osd_margin) { osd_margin = osd_score - osr->orientations[i]; } } if (osd_margin < min_orientation_margin) { // The margin is weak. int best_script_id = osr->best_result.script_id; bool cjk = (best_script_id == osd_tess->unicharset.han_sid()) || (best_script_id == osd_tess->unicharset.hiragana_sid()) || (best_script_id == osd_tess->unicharset.katakana_sid()); if (!cjk && !vertical_text && osd_orientation == 2) { // upside down latin text is improbable with such a weak margin. tprintf("OSD: Weak margin (%.2f), horiz textlines, not CJK: " "Don't rotate.\n", osd_margin); osd_orientation = 0; } else { tprintf("OSD: Weak margin (%.2f) for %d blob text block, " "but using orientation anyway: %d\n", osd_blobs.length(), osd_margin, osd_orientation); } } } osd_blobs.shallow_clear(); finder->CorrectOrientation(to_block, vertical_text, osd_orientation); } return finder; }
// Segment the page according to the current value of tessedit_pageseg_mode. // If the pix_binary_ member is not NULL, it is used as the source image, // and copied to image, otherwise it just uses image as the input. // On return the blocks list owns all the constructed page layout. int Tesseract::SegmentPage(const STRING* input_file, IMAGE* image, BLOCK_LIST* blocks) { int width = image->get_xsize(); int height = image->get_ysize(); int resolution = image->get_res(); #ifdef HAVE_LIBLEPT if (pix_binary_ != NULL) { width = pixGetWidth(pix_binary_); height = pixGetHeight(pix_binary_); resolution = pixGetXRes(pix_binary_); } #endif // Zero resolution messes up the algorithms, so make sure it is credible. if (resolution < kMinCredibleResolution) resolution = kDefaultResolution; // Get page segmentation mode. PageSegMode pageseg_mode = static_cast<PageSegMode>( static_cast<int>(tessedit_pageseg_mode)); // If a UNLV zone file can be found, use that instead of segmentation. if (pageseg_mode != tesseract::PSM_AUTO && input_file != NULL && input_file->length() > 0) { STRING name = *input_file; const char* lastdot = strrchr(name.string(), '.'); if (lastdot != NULL) name[lastdot - name.string()] = '\0'; read_unlv_file(name, width, height, blocks); } bool single_column = pageseg_mode > PSM_AUTO; if (blocks->empty()) { // No UNLV file present. Work according to the PageSegMode. // First make a single block covering the whole image. BLOCK_IT block_it(blocks); BLOCK* block = new BLOCK("", TRUE, 0, 0, 0, 0, width, height); block_it.add_to_end(block); } else { // UNLV file present. Use PSM_SINGLE_COLUMN. pageseg_mode = PSM_SINGLE_COLUMN; } TO_BLOCK_LIST land_blocks, port_blocks; TBOX page_box; if (pageseg_mode <= PSM_SINGLE_COLUMN) { if (AutoPageSeg(width, height, resolution, single_column, image, blocks, &port_blocks) < 0) { return -1; } // To create blobs from the image region bounds uncomment this line: // port_blocks.clear(); // Uncomment to go back to the old mode. } else { #if HAVE_LIBLEPT image->FromPix(pix_binary_); #endif deskew_ = FCOORD(1.0f, 0.0f); reskew_ = FCOORD(1.0f, 0.0f); } if (blocks->empty()) { tprintf("Empty page\n"); return 0; // AutoPageSeg found an empty page. } if (port_blocks.empty()) { // AutoPageSeg was not used, so we need to find_components first. find_components(blocks, &land_blocks, &port_blocks, &page_box); } else { // AutoPageSeg does not need to find_components as it did that already. page_box.set_left(0); page_box.set_bottom(0); page_box.set_right(width); page_box.set_top(height); // Filter_blobs sets up the TO_BLOCKs the same as find_components does. filter_blobs(page_box.topright(), &port_blocks, true); } TO_BLOCK_IT to_block_it(&port_blocks); ASSERT_HOST(!port_blocks.empty()); TO_BLOCK* to_block = to_block_it.data(); if (pageseg_mode <= PSM_SINGLE_BLOCK || to_block->line_size < 2) { // For now, AUTO, SINGLE_COLUMN and SINGLE_BLOCK all map to the old // textord. The difference is the number of blocks and how the are made. textord_page(page_box.topright(), blocks, &land_blocks, &port_blocks, this); } else { // SINGLE_LINE, SINGLE_WORD and SINGLE_CHAR all need a single row. float gradient = make_single_row(page_box.topright(), to_block, &port_blocks, this); if (pageseg_mode == PSM_SINGLE_LINE) { // SINGLE_LINE uses the old word maker on the single line. make_words(page_box.topright(), gradient, blocks, &land_blocks, &port_blocks, this); } else { // SINGLE_WORD and SINGLE_CHAR cram all the blobs into a // single word, and in SINGLE_CHAR mode, all the outlines // go in a single blob. make_single_word(pageseg_mode == PSM_SINGLE_CHAR, to_block->get_rows(), to_block->block->row_list()); } } return 0; }
// Auto page segmentation. Divide the page image into blocks of uniform // text linespacing and images. // Width, height and resolution are derived from the input image. // If the pix is non-NULL, then it is assumed to be the input, and it is // copied to the image, otherwise the image is used directly. // The output goes in the blocks list with corresponding TO_BLOCKs in the // to_blocks list. // If single_column is true, then no attempt is made to divide the image // into columns, but multiple blocks are still made if the text is of // non-uniform linespacing. int Tesseract::AutoPageSeg(int width, int height, int resolution, bool single_column, IMAGE* image, BLOCK_LIST* blocks, TO_BLOCK_LIST* to_blocks) { int vertical_x = 0; int vertical_y = 1; TabVector_LIST v_lines; TabVector_LIST h_lines; ICOORD bleft(0, 0); Boxa* boxa = NULL; Pixa* pixa = NULL; // The blocks made by the ColumnFinder. Moved to blocks before return. BLOCK_LIST found_blocks; #ifdef HAVE_LIBLEPT if (pix_binary_ != NULL) { if (textord_debug_images) { Pix* grey_pix = pixCreate(width, height, 8); // Printable images are light grey on white, but for screen display // they are black on dark grey so the other colors show up well. if (textord_debug_printable) { pixSetAll(grey_pix); pixSetMasked(grey_pix, pix_binary_, 192); } else { pixSetAllArbitrary(grey_pix, 64); pixSetMasked(grey_pix, pix_binary_, 0); } AlignedBlob::IncrementDebugPix(); pixWrite(AlignedBlob::textord_debug_pix().string(), grey_pix, IFF_PNG); pixDestroy(&grey_pix); } if (tessedit_dump_pageseg_images) pixWrite("tessinput.png", pix_binary_, IFF_PNG); // Leptonica is used to find the lines and image regions in the input. LineFinder::FindVerticalLines(resolution, pix_binary_, &vertical_x, &vertical_y, &v_lines); LineFinder::FindHorizontalLines(resolution, pix_binary_, &h_lines); if (tessedit_dump_pageseg_images) pixWrite("tessnolines.png", pix_binary_, IFF_PNG); ImageFinder::FindImages(pix_binary_, &boxa, &pixa); if (tessedit_dump_pageseg_images) pixWrite("tessnoimages.png", pix_binary_, IFF_PNG); // Copy the Pix to the IMAGE. image->FromPix(pix_binary_); if (single_column) v_lines.clear(); } #endif TO_BLOCK_LIST land_blocks, port_blocks; TBOX page_box; // The rest of the algorithm uses the usual connected components. find_components(blocks, &land_blocks, &port_blocks, &page_box); TO_BLOCK_IT to_block_it(&port_blocks); ASSERT_HOST(!to_block_it.empty()); for (to_block_it.mark_cycle_pt(); !to_block_it.cycled_list(); to_block_it.forward()) { TO_BLOCK* to_block = to_block_it.data(); TBOX blkbox = to_block->block->bounding_box(); if (to_block->line_size >= 2) { // Note: if there are multiple blocks, then v_lines, boxa, and pixa // are empty on the next iteration, but in this case, we assume // that there aren't any interesting line separators or images, since // it means that we have a pre-defined unlv zone file. ColumnFinder finder(static_cast<int>(to_block->line_size), blkbox.botleft(), blkbox.topright(), &v_lines, &h_lines, vertical_x, vertical_y); if (finder.FindBlocks(height, resolution, single_column, to_block, boxa, pixa, &found_blocks, to_blocks) < 0) return -1; finder.ComputeDeskewVectors(&deskew_, &reskew_); boxa = NULL; pixa = NULL; } } #ifdef HAVE_LIBLEPT boxaDestroy(&boxa); pixaDestroy(&pixa); #endif blocks->clear(); BLOCK_IT block_it(blocks); // Move the found blocks to the input/output blocks. block_it.add_list_after(&found_blocks); if (textord_debug_images) { // The debug image is no longer needed so delete it. unlink(AlignedBlob::textord_debug_pix().string()); } return 0; }