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