All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
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  * Created: Thu Sep 25 17:12:01 PDT 2008
6  *
7  * (C) Copyright 2008, Google Inc.
8  ** Licensed under the Apache License, Version 2.0 (the "License");
9  ** you may not use this file except in compliance with the License.
10  ** You may obtain a copy of the License at
11  ** http://www.apache.org/licenses/LICENSE-2.0
12  ** Unless required by applicable law or agreed to in writing, software
13  ** distributed under the License is distributed on an "AS IS" BASIS,
14  ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  ** See the License for the specific language governing permissions and
16  ** limitations under the License.
17  *
18  **********************************************************************/
19 
20 #ifdef _WIN32
21 #ifndef __GNUC__
22 #include <windows.h>
23 #endif // __GNUC__
24 #ifndef unlink
25 #include <io.h>
26 #endif
27 #else
28 #include <unistd.h>
29 #endif // _WIN32
30 #ifdef _MSC_VER
31 #pragma warning(disable:4244) // Conversion warnings
32 #endif
33 
34 // Include automatically generated configuration file if running autoconf.
35 #ifdef HAVE_CONFIG_H
36 #include "config_auto.h"
37 #endif
38 
39 #include "allheaders.h"
40 #include "blobbox.h"
41 #include "blread.h"
42 #include "colfind.h"
43 #include "equationdetect.h"
44 #include "imagefind.h"
45 #include "linefind.h"
46 #include "makerow.h"
47 #include "osdetect.h"
48 #include "tabvector.h"
49 #include "tesseractclass.h"
50 #include "tessvars.h"
51 #include "textord.h"
52 #include "tordmain.h"
53 #include "wordseg.h"
54 
55 namespace tesseract {
56 
58 const int kMinCredibleResolution = 70;
60 const int kDefaultResolution = 300;
61 // Max erosions to perform in removing an enclosing circle.
62 const int kMaxCircleErosions = 8;
63 
64 // Helper to remove an enclosing circle from an image.
65 // If there isn't one, then the image will most likely get badly mangled.
66 // The returned pix must be pixDestroyed after use. NULL may be returned
67 // if the image doesn't meet the trivial conditions that it uses to determine
68 // success.
69 static Pix* RemoveEnclosingCircle(Pix* pixs) {
70  Pix* pixsi = pixInvert(NULL, pixs);
71  Pix* pixc = pixCreateTemplate(pixs);
72  pixSetOrClearBorder(pixc, 1, 1, 1, 1, PIX_SET);
73  pixSeedfillBinary(pixc, pixc, pixsi, 4);
74  pixInvert(pixc, pixc);
75  pixDestroy(&pixsi);
76  Pix* pixt = pixAnd(NULL, pixs, pixc);
77  l_int32 max_count;
78  pixCountConnComp(pixt, 8, &max_count);
79  // The count has to go up before we start looking for the minimum.
80  l_int32 min_count = MAX_INT32;
81  Pix* pixout = NULL;
82  for (int i = 1; i < kMaxCircleErosions; i++) {
83  pixDestroy(&pixt);
84  pixErodeBrick(pixc, pixc, 3, 3);
85  pixt = pixAnd(NULL, pixs, pixc);
86  l_int32 count;
87  pixCountConnComp(pixt, 8, &count);
88  if (i == 1 || count > max_count) {
89  max_count = count;
90  min_count = count;
91  } else if (i > 1 && count < min_count) {
92  min_count = count;
93  pixDestroy(&pixout);
94  pixout = pixCopy(NULL, pixt); // Save the best.
95  } else if (count >= min_count) {
96  break; // We have passed by the best.
97  }
98  }
99  pixDestroy(&pixt);
100  pixDestroy(&pixc);
101  return pixout;
102 }
103 
109 int Tesseract::SegmentPage(const STRING* input_file, BLOCK_LIST* blocks,
110  Tesseract* osd_tess, OSResults* osr) {
111  ASSERT_HOST(pix_binary_ != NULL);
112  int width = pixGetWidth(pix_binary_);
113  int height = pixGetHeight(pix_binary_);
114  // Get page segmentation mode.
115  PageSegMode pageseg_mode = static_cast<PageSegMode>(
116  static_cast<int>(tessedit_pageseg_mode));
117  // If a UNLV zone file can be found, use that instead of segmentation.
118  if (!PSM_COL_FIND_ENABLED(pageseg_mode) &&
119  input_file != NULL && input_file->length() > 0) {
120  STRING name = *input_file;
121  const char* lastdot = strrchr(name.string(), '.');
122  if (lastdot != NULL)
123  name[lastdot - name.string()] = '\0';
124  read_unlv_file(name, width, height, blocks);
125  }
126  if (blocks->empty()) {
127  // No UNLV file present. Work according to the PageSegMode.
128  // First make a single block covering the whole image.
129  BLOCK_IT block_it(blocks);
130  BLOCK* block = new BLOCK("", TRUE, 0, 0, 0, 0, width, height);
132  block_it.add_to_end(block);
133  } else {
134  // UNLV file present. Use PSM_SINGLE_BLOCK.
135  pageseg_mode = PSM_SINGLE_BLOCK;
136  }
137  // The diacritic_blobs holds noise blobs that may be diacritics. They
138  // are separated out on areas of the image that seem noisy and short-circuit
139  // the layout process, going straight from the initial partition creation
140  // right through to after word segmentation, where they are added to the
141  // rej_cblobs list of the most appropriate word. From there classification
142  // will determine whether they are used.
143  BLOBNBOX_LIST diacritic_blobs;
144  int auto_page_seg_ret_val = 0;
145  TO_BLOCK_LIST to_blocks;
146  if (PSM_OSD_ENABLED(pageseg_mode) || PSM_BLOCK_FIND_ENABLED(pageseg_mode) ||
147  PSM_SPARSE(pageseg_mode)) {
148  auto_page_seg_ret_val = AutoPageSeg(
149  pageseg_mode, blocks, &to_blocks,
150  enable_noise_removal ? &diacritic_blobs : NULL, osd_tess, osr);
151  if (pageseg_mode == PSM_OSD_ONLY)
152  return auto_page_seg_ret_val;
153  // To create blobs from the image region bounds uncomment this line:
154  // to_blocks.clear(); // Uncomment to go back to the old mode.
155  } else {
156  deskew_ = FCOORD(1.0f, 0.0f);
157  reskew_ = FCOORD(1.0f, 0.0f);
158  if (pageseg_mode == PSM_CIRCLE_WORD) {
159  Pix* pixcleaned = RemoveEnclosingCircle(pix_binary_);
160  if (pixcleaned != NULL) {
161  pixDestroy(&pix_binary_);
162  pix_binary_ = pixcleaned;
163  }
164  }
165  }
166 
167  if (auto_page_seg_ret_val < 0) {
168  return -1;
169  }
170 
171  if (blocks->empty()) {
173  tprintf("Empty page\n");
174  return 0; // AutoPageSeg found an empty page.
175  }
176  bool splitting =
178  bool cjk_mode = textord_use_cjk_fp_model;
179 
180  textord_.TextordPage(pageseg_mode, reskew_, width, height, pix_binary_,
181  pix_thresholds_, pix_grey_, splitting || cjk_mode,
182  &diacritic_blobs, blocks, &to_blocks);
183  return auto_page_seg_ret_val;
184 }
185 
186 // Helper writes a grey image to a file for use by scrollviewer.
187 // Normally for speed we don't display the image in the layout debug windows.
188 // If textord_debug_images is true, we draw the image as a background to some
189 // of the debug windows. printable determines whether these
190 // images are optimized for printing instead of screen display.
191 static void WriteDebugBackgroundImage(bool printable, Pix* pix_binary) {
192  Pix* grey_pix = pixCreate(pixGetWidth(pix_binary),
193  pixGetHeight(pix_binary), 8);
194  // Printable images are light grey on white, but for screen display
195  // they are black on dark grey so the other colors show up well.
196  if (printable) {
197  pixSetAll(grey_pix);
198  pixSetMasked(grey_pix, pix_binary, 192);
199  } else {
200  pixSetAllArbitrary(grey_pix, 64);
201  pixSetMasked(grey_pix, pix_binary, 0);
202  }
204  pixWrite(AlignedBlob::textord_debug_pix().string(), grey_pix, IFF_PNG);
205  pixDestroy(&grey_pix);
206 }
207 
232 int Tesseract::AutoPageSeg(PageSegMode pageseg_mode, BLOCK_LIST* blocks,
233  TO_BLOCK_LIST* to_blocks,
234  BLOBNBOX_LIST* diacritic_blobs, Tesseract* osd_tess,
235  OSResults* osr) {
236  if (textord_debug_images) {
237  WriteDebugBackgroundImage(textord_debug_printable, pix_binary_);
238  }
239  Pix* photomask_pix = NULL;
240  Pix* musicmask_pix = NULL;
241  // The blocks made by the ColumnFinder. Moved to blocks before return.
242  BLOCK_LIST found_blocks;
243  TO_BLOCK_LIST temp_blocks;
244 
246  pageseg_mode, blocks, osd_tess, osr, &temp_blocks, &photomask_pix,
247  &musicmask_pix);
248  int result = 0;
249  if (finder != NULL) {
250  TO_BLOCK_IT to_block_it(&temp_blocks);
251  TO_BLOCK* to_block = to_block_it.data();
252  if (musicmask_pix != NULL) {
253  // TODO(rays) pass the musicmask_pix into FindBlocks and mark music
254  // blocks separately. For now combine with photomask_pix.
255  pixOr(photomask_pix, photomask_pix, musicmask_pix);
256  }
257  if (equ_detect_) {
258  finder->SetEquationDetect(equ_detect_);
259  }
260  result = finder->FindBlocks(
261  pageseg_mode, scaled_color_, scaled_factor_, to_block, photomask_pix,
262  pix_thresholds_, pix_grey_, &found_blocks, diacritic_blobs, to_blocks);
263  if (result >= 0)
264  finder->GetDeskewVectors(&deskew_, &reskew_);
265  delete finder;
266  }
267  pixDestroy(&photomask_pix);
268  pixDestroy(&musicmask_pix);
269  if (result < 0) return result;
270 
271  blocks->clear();
272  BLOCK_IT block_it(blocks);
273  // Move the found blocks to the input/output blocks.
274  block_it.add_list_after(&found_blocks);
275 
276  if (textord_debug_images) {
277  // The debug image is no longer needed so delete it.
278  unlink(AlignedBlob::textord_debug_pix().string());
279  }
280  return result;
281 }
282 
283 // Helper adds all the scripts from sid_set converted to ids from osd_set to
284 // allowed_ids.
285 static void AddAllScriptsConverted(const UNICHARSET& sid_set,
286  const UNICHARSET& osd_set,
287  GenericVector<int>* allowed_ids) {
288  for (int i = 0; i < sid_set.get_script_table_size(); ++i) {
289  if (i != sid_set.null_sid()) {
290  const char* script = sid_set.get_script_from_script_id(i);
291  allowed_ids->push_back(osd_set.get_script_id_from_name(script));
292  }
293  }
294 }
295 
310  PageSegMode pageseg_mode, BLOCK_LIST* blocks, Tesseract* osd_tess,
311  OSResults* osr, TO_BLOCK_LIST* to_blocks, Pix** photo_mask_pix,
312  Pix** music_mask_pix) {
313  int vertical_x = 0;
314  int vertical_y = 1;
315  TabVector_LIST v_lines;
316  TabVector_LIST h_lines;
317  ICOORD bleft(0, 0);
318 
319  ASSERT_HOST(pix_binary_ != NULL);
321  pixWrite("tessinput.png", pix_binary_, IFF_PNG);
322  }
323  // Leptonica is used to find the rule/separator lines in the input.
324  LineFinder::FindAndRemoveLines(source_resolution_,
325  textord_tabfind_show_vlines, pix_binary_,
326  &vertical_x, &vertical_y, music_mask_pix,
327  &v_lines, &h_lines);
329  pixWrite("tessnolines.png", pix_binary_, IFF_PNG);
330  // Leptonica is used to find a mask of the photo regions in the input.
331  *photo_mask_pix = ImageFind::FindImages(pix_binary_);
333  pixWrite("tessnoimages.png", pix_binary_, IFF_PNG);
334  if (!PSM_COL_FIND_ENABLED(pageseg_mode)) v_lines.clear();
335 
336  // The rest of the algorithm uses the usual connected components.
337  textord_.find_components(pix_binary_, blocks, to_blocks);
338 
339  TO_BLOCK_IT to_block_it(to_blocks);
340  // There must be exactly one input block.
341  // TODO(rays) handle new textline finding with a UNLV zone file.
342  ASSERT_HOST(to_blocks->singleton());
343  TO_BLOCK* to_block = to_block_it.data();
344  TBOX blkbox = to_block->block->bounding_box();
345  ColumnFinder* finder = NULL;
346 
347  if (to_block->line_size >= 2) {
348  finder = new ColumnFinder(static_cast<int>(to_block->line_size),
349  blkbox.botleft(), blkbox.topright(),
350  source_resolution_, textord_use_cjk_fp_model,
352  &v_lines, &h_lines, vertical_x, vertical_y);
353 
354  finder->SetupAndFilterNoise(pageseg_mode, *photo_mask_pix, to_block);
355 
356  if (equ_detect_) {
357  equ_detect_->LabelSpecialText(to_block);
358  }
359 
360  BLOBNBOX_CLIST osd_blobs;
361  // osd_orientation is the number of 90 degree rotations to make the
362  // characters upright. (See osdetect.h for precise definition.)
363  // We want the text lines horizontal, (vertical text indicates vertical
364  // textlines) which may conflict (eg vertically written CJK).
365  int osd_orientation = 0;
366  bool vertical_text = textord_tabfind_force_vertical_text ||
367  pageseg_mode == PSM_SINGLE_BLOCK_VERT_TEXT;
368  if (!vertical_text && textord_tabfind_vertical_text &&
369  PSM_ORIENTATION_ENABLED(pageseg_mode)) {
370  vertical_text =
372  to_block, &osd_blobs);
373  }
374  if (PSM_OSD_ENABLED(pageseg_mode) && osd_tess != NULL && osr != NULL) {
375  GenericVector<int> osd_scripts;
376  if (osd_tess != this) {
377  // We are running osd as part of layout analysis, so constrain the
378  // scripts to those allowed by *this.
379  AddAllScriptsConverted(unicharset, osd_tess->unicharset, &osd_scripts);
380  for (int s = 0; s < sub_langs_.size(); ++s) {
381  AddAllScriptsConverted(sub_langs_[s]->unicharset,
382  osd_tess->unicharset, &osd_scripts);
383  }
384  }
385  os_detect_blobs(&osd_scripts, &osd_blobs, osr, osd_tess);
386  if (pageseg_mode == PSM_OSD_ONLY) {
387  delete finder;
388  return NULL;
389  }
390  osd_orientation = osr->best_result.orientation_id;
391  double osd_score = osr->orientations[osd_orientation];
392  double osd_margin = min_orientation_margin * 2;
393  for (int i = 0; i < 4; ++i) {
394  if (i != osd_orientation &&
395  osd_score - osr->orientations[i] < osd_margin) {
396  osd_margin = osd_score - osr->orientations[i];
397  }
398  }
399  int best_script_id = osr->best_result.script_id;
400  const char* best_script_str =
401  osd_tess->unicharset.get_script_from_script_id(best_script_id);
402  bool cjk = best_script_id == osd_tess->unicharset.han_sid() ||
403  best_script_id == osd_tess->unicharset.hiragana_sid() ||
404  best_script_id == osd_tess->unicharset.katakana_sid() ||
405  strcmp("Japanese", best_script_str) == 0 ||
406  strcmp("Korean", best_script_str) == 0 ||
407  strcmp("Hangul", best_script_str) == 0;
408  if (cjk) {
409  finder->set_cjk_script(true);
410  }
411  if (osd_margin < min_orientation_margin) {
412  // The margin is weak.
413  if (!cjk && !vertical_text && osd_orientation == 2) {
414  // upside down latin text is improbable with such a weak margin.
415  tprintf("OSD: Weak margin (%.2f), horiz textlines, not CJK: "
416  "Don't rotate.\n", osd_margin);
417  osd_orientation = 0;
418  } else {
419  tprintf("OSD: Weak margin (%.2f) for %d blob text block, "
420  "but using orientation anyway: %d\n",
421  osd_blobs.length(), osd_margin, osd_orientation);
422  }
423  }
424  }
425  osd_blobs.shallow_clear();
426  finder->CorrectOrientation(to_block, vertical_text, osd_orientation);
427  }
428 
429  return finder;
430 }
431 
432 } // namespace tesseract.
int katakana_sid() const
Definition: unicharset.h:838
Treat the image as a single word in a circle.
Definition: publictypes.h:163
bool right_to_left() const
const ICOORD & botleft() const
Definition: rect.h:88
static Pix * FindImages(Pix *pix)
Definition: imagefind.cpp:65
int hiragana_sid() const
Definition: unicharset.h:837
bool PSM_SPARSE(int pageseg_mode)
Definition: publictypes.h:188
bool textord_debug_images
Definition: alignedblob.cpp:33
int push_back(T object)
int script_id
Definition: osdetect.h:42
static void IncrementDebugPix()
Definition: alignedblob.cpp:77
int null_sid() const
Definition: unicharset.h:831
#define tprintf(...)
Definition: tprintf.h:31
int han_sid() const
Definition: unicharset.h:836
UNICHARSET unicharset
Definition: ccutil.h:72
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
inT32 length() const
Definition: strngs.cpp:188
static const STRING & textord_debug_pix()
Definition: alignedblob.h:112
int get_script_table_size() const
Definition: unicharset.h:797
float orientations[4]
Definition: osdetect.h:74
int SegmentPage(const STRING *input_file, BLOCK_LIST *blocks, Tesseract *osd_tess, OSResults *osr)
void SetupAndFilterNoise(PageSegMode pageseg_mode, Pix *photo_mask_pix, TO_BLOCK *input_block)
Definition: colfind.cpp:153
#define ASSERT_HOST(x)
Definition: errcode.h:84
int FindBlocks(PageSegMode pageseg_mode, Pix *scaled_color, int scaled_factor, TO_BLOCK *block, Pix *photo_mask_pix, Pix *thresholds_pix, Pix *grey_pix, BLOCK_LIST *blocks, BLOBNBOX_LIST *diacritic_blobs, TO_BLOCK_LIST *to_blocks)
Definition: colfind.cpp:297
const int kDefaultResolution
Default resolution used if input in not believable.
Definition: pagesegmain.cpp:60
int get_script_id_from_name(const char *script_name) const
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:268
int orientation_id
Definition: osdetect.h:41
double textord_tabfind_vertical_text_ratio
bool IsVerticallyAlignedText(double find_vertical_text_ratio, TO_BLOCK *block, BLOBNBOX_CLIST *osd_blobs)
Definition: colfind.cpp:191
int textord_debug_tabfind
Definition: alignedblob.cpp:27
const int kMinCredibleResolution
Minimum believable resolution.
Definition: baseapi.cpp:108
void SetEquationDetect(EquationDetectBase *detect)
Definition: colfind.cpp:515
bool PSM_ORIENTATION_ENABLED(int pageseg_mode)
Definition: publictypes.h:182
name_table name
Definition: ocrblock.h:30
Orientation and script detection only.
Definition: publictypes.h:152
bool textord_debug_printable
Definition: alignedblob.cpp:34
void set_cjk_script(bool is_cjk)
Definition: colfind.h:76
void CorrectOrientation(TO_BLOCK *block, bool vertical_text_lines, int recognition_rotation)
Definition: colfind.cpp:209
int LabelSpecialText(TO_BLOCK *to_block)
#define MAX_INT32
Definition: host.h:120
integer coordinate
Definition: points.h:30
Assume a single uniform block of text. (Default.)
Definition: publictypes.h:160
const char * get_script_from_script_id(int id) const
Definition: unicharset.h:802
int AutoPageSeg(PageSegMode pageseg_mode, BLOCK_LIST *blocks, TO_BLOCK_LIST *to_blocks, BLOBNBOX_LIST *diacritic_blobs, Tesseract *osd_tess, OSResults *osr)
const int kMaxCircleErosions
Definition: pagesegmain.cpp:62
int count(LIST var_list)
Definition: oldlist.cpp:108
Definition: rect.h:30
#define TRUE
Definition: capi.h:28
int os_detect_blobs(const GenericVector< int > *allowed_scripts, BLOBNBOX_CLIST *blob_list, OSResults *osr, tesseract::Tesseract *tess)
Definition: osdetect.cpp:274
void find_components(Pix *pix, BLOCK_LIST *blocks, TO_BLOCK_LIST *to_blocks)
Definition: tordmain.cpp:208
Definition: strngs.h:44
bool PSM_COL_FIND_ENABLED(int pageseg_mode)
Definition: publictypes.h:185
bool PSM_BLOCK_FIND_ENABLED(int pageseg_mode)
Definition: publictypes.h:191
#define NULL
Definition: host.h:144
const ICOORD & topright() const
Definition: rect.h:100
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)
bool PSM_OSD_ENABLED(int pageseg_mode)
Definition: publictypes.h:179
const char * string() const
Definition: strngs.cpp:193
bool read_unlv_file(STRING name, inT32 xsize, inT32 ysize, BLOCK_LIST *blocks)
Definition: blread.cpp:36
bool textord_tabfind_force_vertical_text
Definition: points.h:189
double textord_tabfind_aligned_gap_fraction
void set_right_to_left(bool value)
Definition: ocrblock.h:86
OSBestResult best_result
Definition: osdetect.h:79
void GetDeskewVectors(FCOORD *deskew, FCOORD *reskew)
Definition: colfind.cpp:509