tesseract 4.1.1
Loading...
Searching...
No Matches
pagesegmain.cpp
Go to the documentation of this file.
1/**********************************************************************
2 * File: pagesegmain.cpp
3 * Description: Top-level page segmenter for Tesseract.
4 * Author: Ray Smith
5 *
6 * (C) Copyright 2008, Google Inc.
7 ** Licensed under the Apache License, Version 2.0 (the "License");
8 ** you may not use this file except in compliance with the License.
9 ** You may obtain a copy of the License at
10 ** http://www.apache.org/licenses/LICENSE-2.0
11 ** Unless required by applicable law or agreed to in writing, software
12 ** distributed under the License is distributed on an "AS IS" BASIS,
13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ** See the License for the specific language governing permissions and
15 ** limitations under the License.
16 *
17 **********************************************************************/
18
19#ifdef _WIN32
20#ifndef unlink
21#include <io.h>
22#endif
23#else
24#include <unistd.h>
25#endif // _WIN32
26
27// Include automatically generated configuration file if running autoconf.
28#ifdef HAVE_CONFIG_H
29#include "config_auto.h"
30#endif
31
32#include "allheaders.h"
33#include "blobbox.h"
34#include "blread.h"
35#include "colfind.h"
36#include "debugpixa.h"
37#include "equationdetect.h"
38#include "imagefind.h"
39#include "linefind.h"
40#include "makerow.h"
41#include "osdetect.h"
42#include "tabvector.h"
43#include "tesseractclass.h"
44#include "tessvars.h"
45#include "textord.h"
46#include "tordmain.h"
47#include "wordseg.h"
48
49namespace tesseract {
50
51// Max erosions to perform in removing an enclosing circle.
52const int kMaxCircleErosions = 8;
53
54// Helper to remove an enclosing circle from an image.
55// If there isn't one, then the image will most likely get badly mangled.
56// The returned pix must be pixDestroyed after use. nullptr may be returned
57// if the image doesn't meet the trivial conditions that it uses to determine
58// success.
59static Pix* RemoveEnclosingCircle(Pix* pixs) {
60 Pix* pixsi = pixInvert(nullptr, pixs);
61 Pix* pixc = pixCreateTemplate(pixs);
62 pixSetOrClearBorder(pixc, 1, 1, 1, 1, PIX_SET);
63 pixSeedfillBinary(pixc, pixc, pixsi, 4);
64 pixInvert(pixc, pixc);
65 pixDestroy(&pixsi);
66 Pix* pixt = pixAnd(nullptr, pixs, pixc);
67 l_int32 max_count;
68 pixCountConnComp(pixt, 8, &max_count);
69 // The count has to go up before we start looking for the minimum.
70 l_int32 min_count = INT32_MAX;
71 Pix* pixout = nullptr;
72 for (int i = 1; i < kMaxCircleErosions; i++) {
73 pixDestroy(&pixt);
74 pixErodeBrick(pixc, pixc, 3, 3);
75 pixt = pixAnd(nullptr, pixs, pixc);
76 l_int32 count;
77 pixCountConnComp(pixt, 8, &count);
78 if (i == 1 || count > max_count) {
79 max_count = count;
80 min_count = count;
81 } else if (i > 1 && count < min_count) {
82 min_count = count;
83 pixDestroy(&pixout);
84 pixout = pixCopy(nullptr, pixt); // Save the best.
85 } else if (count >= min_count) {
86 break; // We have passed by the best.
87 }
88 }
89 pixDestroy(&pixt);
90 pixDestroy(&pixc);
91 return pixout;
92}
93
99int Tesseract::SegmentPage(const STRING* input_file, BLOCK_LIST* blocks,
100 Tesseract* osd_tess, OSResults* osr) {
101 ASSERT_HOST(pix_binary_ != nullptr);
102 int width = pixGetWidth(pix_binary_);
103 int height = pixGetHeight(pix_binary_);
104 // Get page segmentation mode.
105 auto pageseg_mode = static_cast<PageSegMode>(
106 static_cast<int>(tessedit_pageseg_mode));
107 // If a UNLV zone file can be found, use that instead of segmentation.
108 if (!PSM_COL_FIND_ENABLED(pageseg_mode) &&
109 input_file != nullptr && input_file->length() > 0) {
110 STRING name = *input_file;
111 const char* lastdot = strrchr(name.string(), '.');
112 if (lastdot != nullptr)
113 name[lastdot - name.string()] = '\0';
114 read_unlv_file(name, width, height, blocks);
115 }
116 if (blocks->empty()) {
117 // No UNLV file present. Work according to the PageSegMode.
118 // First make a single block covering the whole image.
119 BLOCK_IT block_it(blocks);
120 auto* block = new BLOCK("", true, 0, 0, 0, 0, width, height);
121 block->set_right_to_left(right_to_left());
122 block_it.add_to_end(block);
123 } else {
124 // UNLV file present. Use PSM_SINGLE_BLOCK.
125 pageseg_mode = PSM_SINGLE_BLOCK;
126 }
127 // The diacritic_blobs holds noise blobs that may be diacritics. They
128 // are separated out on areas of the image that seem noisy and short-circuit
129 // the layout process, going straight from the initial partition creation
130 // right through to after word segmentation, where they are added to the
131 // rej_cblobs list of the most appropriate word. From there classification
132 // will determine whether they are used.
133 BLOBNBOX_LIST diacritic_blobs;
134 int auto_page_seg_ret_val = 0;
135 TO_BLOCK_LIST to_blocks;
136 if (PSM_OSD_ENABLED(pageseg_mode) || PSM_BLOCK_FIND_ENABLED(pageseg_mode) ||
137 PSM_SPARSE(pageseg_mode)) {
138 auto_page_seg_ret_val = AutoPageSeg(
139 pageseg_mode, blocks, &to_blocks,
140 enable_noise_removal ? &diacritic_blobs : nullptr, osd_tess, osr);
141 if (pageseg_mode == PSM_OSD_ONLY)
142 return auto_page_seg_ret_val;
143 // To create blobs from the image region bounds uncomment this line:
144 // to_blocks.clear(); // Uncomment to go back to the old mode.
145 } else {
146 deskew_ = FCOORD(1.0f, 0.0f);
147 reskew_ = FCOORD(1.0f, 0.0f);
148 if (pageseg_mode == PSM_CIRCLE_WORD) {
149 Pix* pixcleaned = RemoveEnclosingCircle(pix_binary_);
150 if (pixcleaned != nullptr) {
151 pixDestroy(&pix_binary_);
152 pix_binary_ = pixcleaned;
153 }
154 }
155 }
156
157 if (auto_page_seg_ret_val < 0) {
158 return -1;
159 }
160
161 if (blocks->empty()) {
163 tprintf("Empty page\n");
164 return 0; // AutoPageSeg found an empty page.
165 }
166 bool splitting =
168 bool cjk_mode = textord_use_cjk_fp_model;
169
170 textord_.TextordPage(pageseg_mode, reskew_, width, height, pix_binary_,
171 pix_thresholds_, pix_grey_, splitting || cjk_mode,
172 &diacritic_blobs, blocks, &to_blocks);
173 return auto_page_seg_ret_val;
174}
175
200int Tesseract::AutoPageSeg(PageSegMode pageseg_mode, BLOCK_LIST* blocks,
201 TO_BLOCK_LIST* to_blocks,
202 BLOBNBOX_LIST* diacritic_blobs, Tesseract* osd_tess,
203 OSResults* osr) {
204 Pix* photomask_pix = nullptr;
205 Pix* musicmask_pix = nullptr;
206 // The blocks made by the ColumnFinder. Moved to blocks before return.
207 BLOCK_LIST found_blocks;
208 TO_BLOCK_LIST temp_blocks;
209
211 pageseg_mode, blocks, osd_tess, osr, &temp_blocks, &photomask_pix,
212 pageseg_apply_music_mask ? &musicmask_pix : nullptr);
213 int result = 0;
214 if (finder != nullptr) {
215 TO_BLOCK_IT to_block_it(&temp_blocks);
216 TO_BLOCK* to_block = to_block_it.data();
217 if (musicmask_pix != nullptr) {
218 // TODO(rays) pass the musicmask_pix into FindBlocks and mark music
219 // blocks separately. For now combine with photomask_pix.
220 pixOr(photomask_pix, photomask_pix, musicmask_pix);
221 }
222 if (equ_detect_) {
223 finder->SetEquationDetect(equ_detect_);
224 }
225 result = finder->FindBlocks(pageseg_mode, scaled_color_, scaled_factor_,
226 to_block, photomask_pix, pix_thresholds_,
227 pix_grey_, &pixa_debug_, &found_blocks,
228 diacritic_blobs, to_blocks);
229 if (result >= 0)
230 finder->GetDeskewVectors(&deskew_, &reskew_);
231 delete finder;
232 }
233 pixDestroy(&photomask_pix);
234 pixDestroy(&musicmask_pix);
235 if (result < 0) return result;
236
237 blocks->clear();
238 BLOCK_IT block_it(blocks);
239 // Move the found blocks to the input/output blocks.
240 block_it.add_list_after(&found_blocks);
241 return result;
242}
243
244// Helper adds all the scripts from sid_set converted to ids from osd_set to
245// allowed_ids.
246static void AddAllScriptsConverted(const UNICHARSET& sid_set,
247 const UNICHARSET& osd_set,
248 GenericVector<int>* allowed_ids) {
249 for (int i = 0; i < sid_set.get_script_table_size(); ++i) {
250 if (i != sid_set.null_sid()) {
251 const char* script = sid_set.get_script_from_script_id(i);
252 allowed_ids->push_back(osd_set.get_script_id_from_name(script));
253 }
254 }
255}
256
271 PageSegMode pageseg_mode, BLOCK_LIST* blocks, Tesseract* osd_tess,
272 OSResults* osr, TO_BLOCK_LIST* to_blocks, Pix** photo_mask_pix,
273 Pix** music_mask_pix) {
274 int vertical_x = 0;
275 int vertical_y = 1;
276 TabVector_LIST v_lines;
277 TabVector_LIST h_lines;
278 ICOORD bleft(0, 0);
279
280 ASSERT_HOST(pix_binary_ != nullptr);
282 pixa_debug_.AddPix(pix_binary_, "PageSegInput");
283 }
284 // Leptonica is used to find the rule/separator lines in the input.
285 LineFinder::FindAndRemoveLines(source_resolution_,
286 textord_tabfind_show_vlines, pix_binary_,
287 &vertical_x, &vertical_y, music_mask_pix,
288 &v_lines, &h_lines);
290 pixa_debug_.AddPix(pix_binary_, "NoLines");
291 }
292 // Leptonica is used to find a mask of the photo regions in the input.
293 *photo_mask_pix = ImageFind::FindImages(pix_binary_, &pixa_debug_);
295 pixa_debug_.AddPix(pix_binary_, "NoImages");
296 }
297 if (!PSM_COL_FIND_ENABLED(pageseg_mode)) v_lines.clear();
298
299 // The rest of the algorithm uses the usual connected components.
300 textord_.find_components(pix_binary_, blocks, to_blocks);
301
302 TO_BLOCK_IT to_block_it(to_blocks);
303 // There must be exactly one input block.
304 // TODO(rays) handle new textline finding with a UNLV zone file.
305 ASSERT_HOST(to_blocks->singleton());
306 TO_BLOCK* to_block = to_block_it.data();
307 TBOX blkbox = to_block->block->pdblk.bounding_box();
308 ColumnFinder* finder = nullptr;
309 int estimated_resolution = source_resolution_;
310 if (source_resolution_ == kMinCredibleResolution) {
311 // Try to estimate resolution from typical body text size.
313 if (res > estimated_resolution && res < kMaxCredibleResolution) {
314 estimated_resolution = res;
315 tprintf("Estimating resolution as %d\n", estimated_resolution);
316 }
317 }
318
319 if (to_block->line_size >= 2) {
320 finder = new ColumnFinder(static_cast<int>(to_block->line_size),
321 blkbox.botleft(), blkbox.topright(),
322 estimated_resolution, textord_use_cjk_fp_model,
324 &h_lines, vertical_x, vertical_y);
325
326 finder->SetupAndFilterNoise(pageseg_mode, *photo_mask_pix, to_block);
327
328#ifndef DISABLED_LEGACY_ENGINE
329
330 if (equ_detect_) {
331 equ_detect_->LabelSpecialText(to_block);
332 }
333
334 BLOBNBOX_CLIST osd_blobs;
335 // osd_orientation is the number of 90 degree rotations to make the
336 // characters upright. (See osdetect.h for precise definition.)
337 // We want the text lines horizontal, (vertical text indicates vertical
338 // textlines) which may conflict (eg vertically written CJK).
339 int osd_orientation = 0;
340 bool vertical_text = textord_tabfind_force_vertical_text ||
341 pageseg_mode == PSM_SINGLE_BLOCK_VERT_TEXT;
342 if (!vertical_text && textord_tabfind_vertical_text &&
343 PSM_ORIENTATION_ENABLED(pageseg_mode)) {
344 vertical_text =
346 to_block, &osd_blobs);
347 }
348 if (PSM_OSD_ENABLED(pageseg_mode) && osd_tess != nullptr && osr != nullptr) {
349 GenericVector<int> osd_scripts;
350 if (osd_tess != this) {
351 // We are running osd as part of layout analysis, so constrain the
352 // scripts to those allowed by *this.
353 AddAllScriptsConverted(unicharset, osd_tess->unicharset, &osd_scripts);
354 for (int s = 0; s < sub_langs_.size(); ++s) {
355 AddAllScriptsConverted(sub_langs_[s]->unicharset,
356 osd_tess->unicharset, &osd_scripts);
357 }
358 }
359 os_detect_blobs(&osd_scripts, &osd_blobs, osr, osd_tess);
360 if (pageseg_mode == PSM_OSD_ONLY) {
361 delete finder;
362 return nullptr;
363 }
364 osd_orientation = osr->best_result.orientation_id;
365 double osd_score = osr->orientations[osd_orientation];
366 double osd_margin = min_orientation_margin * 2;
367 for (int i = 0; i < 4; ++i) {
368 if (i != osd_orientation &&
369 osd_score - osr->orientations[i] < osd_margin) {
370 osd_margin = osd_score - osr->orientations[i];
371 }
372 }
373 int best_script_id = osr->best_result.script_id;
374 const char* best_script_str =
375 osd_tess->unicharset.get_script_from_script_id(best_script_id);
376 bool cjk = best_script_id == osd_tess->unicharset.han_sid() ||
377 best_script_id == osd_tess->unicharset.hiragana_sid() ||
378 best_script_id == osd_tess->unicharset.katakana_sid() ||
379 strcmp("Japanese", best_script_str) == 0 ||
380 strcmp("Korean", best_script_str) == 0 ||
381 strcmp("Hangul", best_script_str) == 0;
382 if (cjk) {
383 finder->set_cjk_script(true);
384 }
385 if (osd_margin < min_orientation_margin) {
386 // The margin is weak.
387 if (!cjk && !vertical_text && osd_orientation == 2) {
388 // upside down latin text is improbable with such a weak margin.
389 tprintf("OSD: Weak margin (%.2f), horiz textlines, not CJK: "
390 "Don't rotate.\n", osd_margin);
391 osd_orientation = 0;
392 } else {
393 tprintf(
394 "OSD: Weak margin (%.2f) for %d blob text block, "
395 "but using orientation anyway: %d\n",
396 osd_margin, osd_blobs.length(), osd_orientation);
397 }
398 }
399 }
400 osd_blobs.shallow_clear();
401 finder->CorrectOrientation(to_block, vertical_text, osd_orientation);
402
403#endif // ndef DISABLED_LEGACY_ENGINE
404 }
405
406 return finder;
407}
408
409} // namespace tesseract.
int os_detect_blobs(const GenericVector< int > *allowed_scripts, BLOBNBOX_CLIST *blob_list, OSResults *osr, tesseract::Tesseract *tess)
Definition: osdetect.cpp:278
bool read_unlv_file(STRING name, int32_t xsize, int32_t ysize, BLOCK_LIST *blocks)
Definition: blread.cpp:32
constexpr int kResolutionEstimationFactor
Definition: publictypes.h:45
constexpr int kMinCredibleResolution
Definition: publictypes.h:38
constexpr int kMaxCredibleResolution
Definition: publictypes.h:40
#define ASSERT_HOST(x)
Definition: errcode.h:88
int IntCastRounded(double x)
Definition: helpers.h:175
DLLSYM void tprintf(const char *format,...)
Definition: tprintf.cpp:35
int count(LIST var_list)
Definition: oldlist.cpp:95
int textord_debug_tabfind
Definition: alignedblob.cpp:27
bool PSM_OSD_ENABLED(int pageseg_mode)
Definition: publictypes.h:191
@ PSM_CIRCLE_WORD
Treat the image as a single word in a circle.
Definition: publictypes.h:175
@ PSM_OSD_ONLY
Orientation and script detection only.
Definition: publictypes.h:164
@ PSM_SINGLE_BLOCK_VERT_TEXT
aligned text.
Definition: publictypes.h:170
@ PSM_SINGLE_BLOCK
Assume a single uniform block of text. (Default.)
Definition: publictypes.h:172
bool PSM_ORIENTATION_ENABLED(int pageseg_mode)
Definition: publictypes.h:194
bool PSM_COL_FIND_ENABLED(int pageseg_mode)
Definition: publictypes.h:197
bool PSM_SPARSE(int pageseg_mode)
Definition: publictypes.h:200
const int kMaxCircleErosions
Definition: pagesegmain.cpp:52
bool PSM_BLOCK_FIND_ENABLED(int pageseg_mode)
Definition: publictypes.h:203
int push_back(T object)
int LabelSpecialText(TO_BLOCK *to_block) override
int script_id
Definition: osdetect.h:44
int orientation_id
Definition: osdetect.h:43
OSBestResult best_result
Definition: osdetect.h:81
float orientations[4]
Definition: osdetect.h:76
ColumnFinder * 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 AutoPageSeg(PageSegMode pageseg_mode, BLOCK_LIST *blocks, TO_BLOCK_LIST *to_blocks, BLOBNBOX_LIST *diacritic_blobs, Tesseract *osd_tess, OSResults *osr)
int SegmentPage(const STRING *input_file, BLOCK_LIST *blocks, Tesseract *osd_tess, OSResults *osr)
Definition: pagesegmain.cpp:99
double textord_tabfind_vertical_text_ratio
double textord_tabfind_aligned_gap_fraction
bool textord_tabfind_force_vertical_text
bool right_to_left() const
BLOCK * block
Definition: blobbox.h:777
float line_size
Definition: blobbox.h:785
void AddPix(const Pix *pix, const char *caption)
Definition: debugpixa.h:26
Definition: ocrblock.h:31
PDBLK pdblk
Page Description Block.
Definition: ocrblock.h:190
void bounding_box(ICOORD &bottom_left, ICOORD &top_right) const
get box
Definition: pdblock.h:59
integer coordinate
Definition: points.h:32
Definition: points.h:189
Definition: rect.h:34
const ICOORD & botleft() const
Definition: rect.h:92
const ICOORD & topright() const
Definition: rect.h:104
UNICHARSET unicharset
Definition: ccutil.h:73
Definition: strngs.h:45
int32_t length() const
Definition: strngs.cpp:189
const char * string() const
Definition: strngs.cpp:194
int hiragana_sid() const
Definition: unicharset.h:890
int katakana_sid() const
Definition: unicharset.h:891
int han_sid() const
Definition: unicharset.h:889
int null_sid() const
Definition: unicharset.h:884
int get_script_id_from_name(const char *script_name) const
const char * get_script_from_script_id(int id) const
Definition: unicharset.h:854
int get_script_table_size() const
Definition: unicharset.h:849
int FindBlocks(PageSegMode pageseg_mode, Pix *scaled_color, int scaled_factor, TO_BLOCK *block, Pix *photo_mask_pix, Pix *thresholds_pix, Pix *grey_pix, DebugPixa *pixa_debug, BLOCK_LIST *blocks, BLOBNBOX_LIST *diacritic_blobs, TO_BLOCK_LIST *to_blocks)
Definition: colfind.cpp:285
void GetDeskewVectors(FCOORD *deskew, FCOORD *reskew)
Definition: colfind.cpp:496
void SetupAndFilterNoise(PageSegMode pageseg_mode, Pix *photo_mask_pix, TO_BLOCK *input_block)
Definition: colfind.cpp:142
void set_cjk_script(bool is_cjk)
Definition: colfind.h:74
bool IsVerticallyAlignedText(double find_vertical_text_ratio, TO_BLOCK *block, BLOBNBOX_CLIST *osd_blobs)
Definition: colfind.cpp:179
void SetEquationDetect(EquationDetectBase *detect)
Definition: colfind.cpp:502
void CorrectOrientation(TO_BLOCK *block, bool vertical_text_lines, int recognition_rotation)
Definition: colfind.cpp:197
static Pix * FindImages(Pix *pix, DebugPixa *pixa_debug)
Definition: imagefind.cpp:62
static void FindAndRemoveLines(int resolution, bool debug, Pix *pix, int *vertical_x, int *vertical_y, Pix **pix_music_mask, TabVector_LIST *v_lines, TabVector_LIST *h_lines)
Definition: linefind.cpp:243
void find_components(Pix *pix, BLOCK_LIST *blocks, TO_BLOCK_LIST *to_blocks)
Definition: tordmain.cpp:219
void TextordPage(PageSegMode pageseg_mode, const FCOORD &reskew, int width, int height, Pix *binary_pix, Pix *thresholds_pix, Pix *grey_pix, bool use_box_bottoms, BLOBNBOX_LIST *diacritic_blobs, BLOCK_LIST *blocks, TO_BLOCK_LIST *to_blocks)
Definition: textord.cpp:226