tesseract  3.05.02
cube_line_segmenter.cpp
Go to the documentation of this file.
1 /**********************************************************************
2  * File: cube_page_segmenter.cpp
3  * Description: Implementation of the Cube Page Segmenter Class
4  * Author: Ahmad Abdulkader
5  * Created: 2007
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 #include "cube_line_segmenter.h"
21 #include "ndminx.h"
22 
23 namespace tesseract {
24 // constants that worked for Arabic page segmenter
25 const int CubeLineSegmenter::kLineSepMorphMinHgt = 20;
26 const int CubeLineSegmenter::kHgtBins = 20;
27 const double CubeLineSegmenter::kMaxValidLineRatio = 3.2;
28 const int CubeLineSegmenter::kMaxConnCompHgt = 150;
29 const int CubeLineSegmenter::kMaxConnCompWid = 500;
30 const int CubeLineSegmenter::kMaxHorzAspectRatio = 50;
31 const int CubeLineSegmenter::kMaxVertAspectRatio = 20;
32 const int CubeLineSegmenter::kMinWid = 2;
33 const int CubeLineSegmenter::kMinHgt = 2;
34 const float CubeLineSegmenter::kMinValidLineHgtRatio = 2.5;
35 
37  cntxt_ = cntxt;
38  orig_img_ = img;
39  img_ = NULL;
40  lines_pixa_ = NULL;
41  init_ = false;
42  line_cnt_ = 0;
43  columns_ = NULL;
44  con_comps_ = NULL;
45  est_alef_hgt_ = 0.0;
46  est_dot_hgt_ = 0.0;
47 }
48 
50  if (img_ != NULL) {
51  pixDestroy(&img_);
52  img_ = NULL;
53  }
54 
55  if (lines_pixa_ != NULL) {
56  pixaDestroy(&lines_pixa_);
57  lines_pixa_ = NULL;
58  }
59 
60  if (con_comps_ != NULL) {
61  pixaDestroy(&con_comps_);
62  con_comps_ = NULL;
63  }
64 
65  if (columns_ != NULL) {
66  pixaaDestroy(&columns_);
67  columns_ = NULL;
68  }
69 }
70 
71 // compute validity ratio for a line
72 double CubeLineSegmenter::ValidityRatio(Pix *line_mask_pix, Box *line_box) {
73  return line_box->h / est_alef_hgt_;
74 }
75 
76 // validate line
77 bool CubeLineSegmenter::ValidLine(Pix *line_mask_pix, Box *line_box) {
78  double validity_ratio = ValidityRatio(line_mask_pix, line_box);
79 
80  return validity_ratio < kMaxValidLineRatio;
81 }
82 
83 // perform a vertical Closing with the specified threshold
84 // returning the resulting conn comps as a pixa
85 Pixa *CubeLineSegmenter::VerticalClosing(Pix *pix,
86  int threshold, Boxa **boxa) {
87  char sequence_str[16];
88 
89  // do the morphology
90  sprintf(sequence_str, "c100.%d", threshold);
91  Pix *morphed_pix = pixMorphCompSequence(pix, sequence_str, 0);
92  if (morphed_pix == NULL) {
93  return NULL;
94  }
95 
96  // get the resulting lines by computing concomps
97  Pixa *pixac;
98  (*boxa) = pixConnComp(morphed_pix, &pixac, 8);
99 
100  pixDestroy(&morphed_pix);
101 
102  if ((*boxa) == NULL) {
103  return NULL;
104  }
105 
106  return pixac;
107 }
108 
109 // Helper cleans up after CrackLine.
110 static void CleanupCrackLine(int line_cnt, Pixa **lines_pixa,
111  Boxa **line_con_comps,
112  Pixa **line_con_comps_pix) {
113  for (int line = 0; line < line_cnt; line++) {
114  if (lines_pixa[line] != NULL) {
115  pixaDestroy(&lines_pixa[line]);
116  }
117  }
118 
119  delete []lines_pixa;
120  boxaDestroy(line_con_comps);
121  pixaDestroy(line_con_comps_pix);
122 }
123 
124 // do a desperate attempt at cracking lines
125 Pixa *CubeLineSegmenter::CrackLine(Pix *cracked_line_pix,
126  Box *cracked_line_box, int line_cnt) {
127  // create lines pixa array
128  Pixa **lines_pixa = new Pixa*[line_cnt];
129 
130  memset(lines_pixa, 0, line_cnt * sizeof(*lines_pixa));
131 
132  // compute line conn comps
133  Pixa *line_con_comps_pix;
134  Boxa *line_con_comps = ComputeLineConComps(cracked_line_pix,
135  cracked_line_box, &line_con_comps_pix);
136 
137  if (line_con_comps == NULL) {
138  delete []lines_pixa;
139  return NULL;
140  }
141 
142  // assign each conn comp to the a line based on its centroid
143  for (int con = 0; con < line_con_comps->n; con++) {
144  Box *con_box = line_con_comps->box[con];
145  Pix *con_pix = line_con_comps_pix->pix[con];
146  int mid_y = (con_box->y - cracked_line_box->y) + (con_box->h / 2),
147  line_idx = MIN(line_cnt - 1,
148  (mid_y * line_cnt / cracked_line_box->h));
149 
150  // create the line if it has not been created?
151  if (lines_pixa[line_idx] == NULL) {
152  lines_pixa[line_idx] = pixaCreate(line_con_comps->n);
153  if (lines_pixa[line_idx] == NULL) {
154  CleanupCrackLine(line_cnt, lines_pixa, &line_con_comps,
155  &line_con_comps_pix);
156  return NULL;
157  }
158  }
159 
160  // add the concomp to the line
161  if (pixaAddPix(lines_pixa[line_idx], con_pix, L_CLONE) != 0 ||
162  pixaAddBox(lines_pixa[line_idx], con_box, L_CLONE)) {
163  CleanupCrackLine(line_cnt, lines_pixa, &line_con_comps,
164  &line_con_comps_pix);
165  return NULL;
166  }
167  }
168 
169  // create the lines pixa
170  Pixa *lines = pixaCreate(line_cnt);
171  bool success = true;
172 
173  // create and check the validity of the lines
174  for (int line = 0; line < line_cnt; line++) {
175  Pixa *line_pixa = lines_pixa[line];
176 
177  // skip invalid lines
178  if (line_pixa == NULL) {
179  continue;
180  }
181 
182  // merge the pix, check the validity of the line
183  // and add it to the lines pixa
184  Box *line_box;
185  Pix *line_pix = Pixa2Pix(line_pixa, &line_box);
186  if (line_pix == NULL ||
187  line_box == NULL ||
188  ValidLine(line_pix, line_box) == false ||
189  pixaAddPix(lines, line_pix, L_INSERT) != 0 ||
190  pixaAddBox(lines, line_box, L_INSERT) != 0) {
191  if (line_pix != NULL) {
192  pixDestroy(&line_pix);
193  }
194 
195  if (line_box != NULL) {
196  boxDestroy(&line_box);
197  }
198 
199  success = false;
200 
201  break;
202  }
203  }
204 
205  // cleanup
206  CleanupCrackLine(line_cnt, lines_pixa, &line_con_comps,
207  &line_con_comps_pix);
208 
209  if (success == false) {
210  pixaDestroy(&lines);
211  lines = NULL;
212  }
213 
214  return lines;
215 }
216 
217 // do a desperate attempt at cracking lines
218 Pixa *CubeLineSegmenter::CrackLine(Pix *cracked_line_pix,
219  Box *cracked_line_box) {
220  // estimate max line count
221  int max_line_cnt = static_cast<int>((cracked_line_box->h /
222  est_alef_hgt_) + 0.5);
223  if (max_line_cnt < 2) {
224  return NULL;
225  }
226 
227  for (int line_cnt = 2; line_cnt < max_line_cnt; line_cnt++) {
228  Pixa *lines = CrackLine(cracked_line_pix, cracked_line_box, line_cnt);
229  if (lines != NULL) {
230  return lines;
231  }
232  }
233 
234  return NULL;
235 }
236 
237 // split a line continuously until valid or fail
238 Pixa *CubeLineSegmenter::SplitLine(Pix *line_mask_pix, Box *line_box) {
239  // clone the line mask
240  Pix *line_pix = pixClone(line_mask_pix);
241 
242  if (line_pix == NULL) {
243  return NULL;
244  }
245 
246  // AND with the image to get the actual line
247  pixRasterop(line_pix, 0, 0, line_pix->w, line_pix->h,
248  PIX_SRC & PIX_DST, img_, line_box->x, line_box->y);
249 
250  // continue to do rasterop morphology on the line until
251  // it splits to valid lines or we fail
252  int morph_hgt = kLineSepMorphMinHgt - 1,
253  best_threshold = kLineSepMorphMinHgt - 1,
254  max_valid_portion = 0;
255 
256  Boxa *boxa;
257  Pixa *pixac;
258 
259  do {
260  pixac = VerticalClosing(line_pix, morph_hgt, &boxa);
261 
262  // add the box offset to all the lines
263  // and check for the validity of each
264  int line,
265  valid_line_cnt = 0,
266  valid_portion = 0;
267 
268  for (line = 0; line < pixac->n; line++) {
269  boxa->box[line]->x += line_box->x;
270  boxa->box[line]->y += line_box->y;
271 
272  if (ValidLine(pixac->pix[line], boxa->box[line]) == true) {
273  // count valid lines
274  valid_line_cnt++;
275 
276  // and the valid portions
277  valid_portion += boxa->box[line]->h;
278  }
279  }
280 
281  // all the lines are valid
282  if (valid_line_cnt == pixac->n) {
283  boxaDestroy(&boxa);
284  pixDestroy(&line_pix);
285  return pixac;
286  }
287 
288  // a larger valid portion
289  if (valid_portion > max_valid_portion) {
290  max_valid_portion = valid_portion;
291  best_threshold = morph_hgt;
292  }
293 
294  boxaDestroy(&boxa);
295  pixaDestroy(&pixac);
296 
297  morph_hgt--;
298  }
299  while (morph_hgt > 0);
300 
301  // failed to break into valid lines
302  // attempt to crack the line
303  pixac = CrackLine(line_pix, line_box);
304  if (pixac != NULL) {
305  pixDestroy(&line_pix);
306  return pixac;
307  }
308 
309  // try to leverage any of the lines
310  // did the best threshold yield a non zero valid portion
311  if (max_valid_portion > 0) {
312  // use this threshold to break lines
313  pixac = VerticalClosing(line_pix, best_threshold, &boxa);
314 
315  // add the box offset to all the lines
316  // and check for the validity of each
317  for (int line = 0; line < pixac->n; line++) {
318  boxa->box[line]->x += line_box->x;
319  boxa->box[line]->y += line_box->y;
320 
321  // remove invalid lines from the pixa
322  if (ValidLine(pixac->pix[line], boxa->box[line]) == false) {
323  pixaRemovePix(pixac, line);
324  line--;
325  }
326  }
327 
328  boxaDestroy(&boxa);
329  pixDestroy(&line_pix);
330  return pixac;
331  }
332 
333  // last resort: attempt to crack the line
334  pixDestroy(&line_pix);
335 
336  return NULL;
337 }
338 
339 // Checks of a line is too small
340 bool CubeLineSegmenter::SmallLine(Box *line_box) {
341  return line_box->h <= (kMinValidLineHgtRatio * est_dot_hgt_);
342 }
343 
344 // Compute the connected components in a line
345 Boxa * CubeLineSegmenter::ComputeLineConComps(Pix *line_mask_pix,
346  Box *line_box,
347  Pixa **con_comps_pixa) {
348  // clone the line mask
349  Pix *line_pix = pixClone(line_mask_pix);
350 
351  if (line_pix == NULL) {
352  return NULL;
353  }
354 
355  // AND with the image to get the actual line
356  pixRasterop(line_pix, 0, 0, line_pix->w, line_pix->h,
357  PIX_SRC & PIX_DST, img_, line_box->x, line_box->y);
358 
359  // compute the connected components of the line to be merged
360  Boxa *line_con_comps = pixConnComp(line_pix, con_comps_pixa, 8);
361 
362  pixDestroy(&line_pix);
363 
364  // offset boxes by the bbox of the line
365  for (int con = 0; con < line_con_comps->n; con++) {
366  line_con_comps->box[con]->x += line_box->x;
367  line_con_comps->box[con]->y += line_box->y;
368  }
369 
370  return line_con_comps;
371 }
372 
373 // create a union of two arbitrary pix
374 Pix *CubeLineSegmenter::PixUnion(Pix *dest_pix, Box *dest_box,
375  Pix *src_pix, Box *src_box) {
376  // compute dimensions of union rect
377  BOX *union_box = boxBoundingRegion(src_box, dest_box);
378 
379  // create the union pix
380  Pix *union_pix = pixCreate(union_box->w, union_box->h, src_pix->d);
381  if (union_pix == NULL) {
382  return NULL;
383  }
384 
385  // blt the src and dest pix
386  pixRasterop(union_pix,
387  src_box->x - union_box->x, src_box->y - union_box->y,
388  src_box->w, src_box->h, PIX_SRC | PIX_DST, src_pix, 0, 0);
389 
390  pixRasterop(union_pix,
391  dest_box->x - union_box->x, dest_box->y - union_box->y,
392  dest_box->w, dest_box->h, PIX_SRC | PIX_DST, dest_pix, 0, 0);
393 
394  // replace the dest_box
395  *dest_box = *union_box;
396 
397  boxDestroy(&union_box);
398 
399  return union_pix;
400 }
401 
402 // create a union of a number of arbitrary pix
403 Pix *CubeLineSegmenter::Pixa2Pix(Pixa *pixa, Box **dest_box,
404  int start_pix, int pix_cnt) {
405  // compute union_box
406  int min_x = INT_MAX,
407  max_x = INT_MIN,
408  min_y = INT_MAX,
409  max_y = INT_MIN;
410 
411  for (int pix_idx = start_pix; pix_idx < (start_pix + pix_cnt); pix_idx++) {
412  Box *pix_box = pixa->boxa->box[pix_idx];
413 
414  UpdateRange(pix_box->x, pix_box->x + pix_box->w, &min_x, &max_x);
415  UpdateRange(pix_box->y, pix_box->y + pix_box->h, &min_y, &max_y);
416  }
417 
418  (*dest_box) = boxCreate(min_x, min_y, max_x - min_x, max_y - min_y);
419  if ((*dest_box) == NULL) {
420  return NULL;
421  }
422 
423  // create the union pix
424  Pix *union_pix = pixCreate((*dest_box)->w, (*dest_box)->h, img_->d);
425  if (union_pix == NULL) {
426  boxDestroy(dest_box);
427  return NULL;
428  }
429 
430  // create a pix corresponding to the union of all pixs
431  // blt the src and dest pix
432  for (int pix_idx = start_pix; pix_idx < (start_pix + pix_cnt); pix_idx++) {
433  Box *pix_box = pixa->boxa->box[pix_idx];
434  Pix *con_pix = pixa->pix[pix_idx];
435 
436  pixRasterop(union_pix,
437  pix_box->x - (*dest_box)->x, pix_box->y - (*dest_box)->y,
438  pix_box->w, pix_box->h, PIX_SRC | PIX_DST, con_pix, 0, 0);
439  }
440 
441  return union_pix;
442 }
443 
444 // create a union of a number of arbitrary pix
445 Pix *CubeLineSegmenter::Pixa2Pix(Pixa *pixa, Box **dest_box) {
446  return Pixa2Pix(pixa, dest_box, 0, pixa->n);
447 }
448 
449 // merges a number of lines into one line given a bounding box and a mask
450 bool CubeLineSegmenter::MergeLine(Pix *line_mask_pix, Box *line_box,
451  Pixa *lines, Boxaa *lines_con_comps) {
452  // compute the connected components of the lines to be merged
453  Pixa *small_con_comps_pix;
454  Boxa *small_line_con_comps = ComputeLineConComps(line_mask_pix,
455  line_box, &small_con_comps_pix);
456 
457  if (small_line_con_comps == NULL) {
458  return false;
459  }
460 
461  // for each connected component
462  for (int con = 0; con < small_line_con_comps->n; con++) {
463  Box *small_con_comp_box = small_line_con_comps->box[con];
464  int best_line = -1,
465  best_dist = INT_MAX,
466  small_box_right = small_con_comp_box->x + small_con_comp_box->w,
467  small_box_bottom = small_con_comp_box->y + small_con_comp_box->h;
468 
469  // for each valid line
470  for (int line = 0; line < lines->n; line++) {
471  if (SmallLine(lines->boxa->box[line]) == true) {
472  continue;
473  }
474 
475  // for all the connected components in the line
476  Boxa *line_con_comps = lines_con_comps->boxa[line];
477 
478  for (int lcon = 0; lcon < line_con_comps->n; lcon++) {
479  Box *con_comp_box = line_con_comps->box[lcon];
480  int xdist,
481  ydist,
482  box_right = con_comp_box->x + con_comp_box->w,
483  box_bottom = con_comp_box->y + con_comp_box->h;
484 
485  xdist = MAX(small_con_comp_box->x, con_comp_box->x) -
486  MIN(small_box_right, box_right);
487 
488  ydist = MAX(small_con_comp_box->y, con_comp_box->y) -
489  MIN(small_box_bottom, box_bottom);
490 
491  // if there is an overlap in x-direction
492  if (xdist <= 0) {
493  if (best_line == -1 || ydist < best_dist) {
494  best_dist = ydist;
495  best_line = line;
496  }
497  }
498  }
499  }
500 
501  // if the distance is too big, do not merged
502  if (best_line != -1 && best_dist < est_alef_hgt_) {
503  // add the pix to the best line
504  Pix *new_line = PixUnion(lines->pix[best_line],
505  lines->boxa->box[best_line],
506  small_con_comps_pix->pix[con], small_con_comp_box);
507 
508  if (new_line == NULL) {
509  return false;
510  }
511 
512  pixDestroy(&lines->pix[best_line]);
513  lines->pix[best_line] = new_line;
514  }
515  }
516 
517  pixaDestroy(&small_con_comps_pix);
518  boxaDestroy(&small_line_con_comps);
519 
520  return true;
521 }
522 
523 // Creates new set of lines from the computed columns
524 bool CubeLineSegmenter::AddLines(Pixa *lines) {
525  // create an array that will hold the bounding boxes
526  // of the concomps belonging to each line
527  Boxaa *lines_con_comps = boxaaCreate(lines->n);
528  if (lines_con_comps == NULL) {
529  return false;
530  }
531 
532  for (int line = 0; line < lines->n; line++) {
533  // if the line is not valid
534  if (ValidLine(lines->pix[line], lines->boxa->box[line]) == false) {
535  // split it
536  Pixa *split_lines = SplitLine(lines->pix[line],
537  lines->boxa->box[line]);
538 
539  // remove the old line
540  if (pixaRemovePix(lines, line) != 0) {
541  return false;
542  }
543 
544  line--;
545 
546  if (split_lines == NULL) {
547  continue;
548  }
549 
550  // add the split lines instead and move the pointer
551  for (int s_line = 0; s_line < split_lines->n; s_line++) {
552  Pix *sp_line = pixaGetPix(split_lines, s_line, L_CLONE);
553  Box *sp_box = boxaGetBox(split_lines->boxa, s_line, L_CLONE);
554 
555  if (sp_line == NULL || sp_box == NULL) {
556  return false;
557  }
558 
559  // insert the new line
560  if (pixaInsertPix(lines, ++line, sp_line, sp_box) != 0) {
561  return false;
562  }
563  }
564 
565  // remove the split lines
566  pixaDestroy(&split_lines);
567  }
568  }
569 
570  // compute the concomps bboxes of each line
571  for (int line = 0; line < lines->n; line++) {
572  Boxa *line_con_comps = ComputeLineConComps(lines->pix[line],
573  lines->boxa->box[line], NULL);
574 
575  if (line_con_comps == NULL) {
576  return false;
577  }
578 
579  // insert it into the boxaa array
580  if (boxaaAddBoxa(lines_con_comps, line_con_comps, L_INSERT) != 0) {
581  return false;
582  }
583  }
584 
585  // post process the lines:
586  // merge the contents of "small" lines info legitimate lines
587  for (int line = 0; line < lines->n; line++) {
588  // a small line detected
589  if (SmallLine(lines->boxa->box[line]) == true) {
590  // merge its components to one of the valid lines
591  if (MergeLine(lines->pix[line], lines->boxa->box[line],
592  lines, lines_con_comps) == true) {
593  // remove the small line
594  if (pixaRemovePix(lines, line) != 0) {
595  return false;
596  }
597 
598  if (boxaaRemoveBoxa(lines_con_comps, line) != 0) {
599  return false;
600  }
601 
602  line--;
603  }
604  }
605  }
606 
607  boxaaDestroy(&lines_con_comps);
608 
609  // add the pix masks
610  if (pixaaAddPixa(columns_, lines, L_INSERT) != 0) {
611  return false;
612  }
613 
614  return true;
615 }
616 
617 // Index the specific pixa using RTL reading order
618 int *CubeLineSegmenter::IndexRTL(Pixa *pixa) {
619  int *pix_index = new int[pixa->n];
620 
621  for (int pix = 0; pix < pixa->n; pix++) {
622  pix_index[pix] = pix;
623  }
624 
625  for (int ipix = 0; ipix < pixa->n; ipix++) {
626  for (int jpix = ipix + 1; jpix < pixa->n; jpix++) {
627  Box *ipix_box = pixa->boxa->box[pix_index[ipix]],
628  *jpix_box = pixa->boxa->box[pix_index[jpix]];
629 
630  // swap?
631  if ((ipix_box->x + ipix_box->w) < (jpix_box->x + jpix_box->w)) {
632  int temp = pix_index[ipix];
633  pix_index[ipix] = pix_index[jpix];
634  pix_index[jpix] = temp;
635  }
636  }
637  }
638 
639  return pix_index;
640 }
641 
642 // Performs line segmentation
643 bool CubeLineSegmenter::LineSegment() {
644  // Use full image morphology to find columns
645  // This only works for simple layouts where each column
646  // of text extends the full height of the input image.
647  Pix *pix_temp1 = pixMorphCompSequence(img_, "c5.500", 0);
648  if (pix_temp1 == NULL) {
649  return false;
650  }
651 
652  // Mask with a single component over each column
653  Pixa *pixam;
654  Boxa *boxa = pixConnComp(pix_temp1, &pixam, 8);
655 
656  if (boxa == NULL) {
657  return false;
658  }
659 
660  int init_morph_min_hgt = kLineSepMorphMinHgt;
661  char sequence_str[16];
662  sprintf(sequence_str, "c100.%d", init_morph_min_hgt);
663 
664  // Use selective region-based morphology to get the textline mask.
665  Pixa *pixad = pixaMorphSequenceByRegion(img_, pixam, sequence_str, 0, 0);
666  if (pixad == NULL) {
667  return false;
668  }
669 
670  // for all columns
671  int col_cnt = boxaGetCount(boxa);
672 
673  // create columns
674  columns_ = pixaaCreate(col_cnt);
675  if (columns_ == NULL) {
676  return false;
677  }
678 
679  // index columns based on readind order (RTL)
680  int *col_order = IndexRTL(pixad);
681  if (col_order == NULL) {
682  return false;
683  }
684 
685  line_cnt_ = 0;
686 
687  for (int col_idx = 0; col_idx < col_cnt; col_idx++) {
688  int col = col_order[col_idx];
689 
690  // get the pix and box corresponding to the column
691  Pix *pixt3 = pixaGetPix(pixad, col, L_CLONE);
692  if (pixt3 == NULL) {
693  delete []col_order;
694  return false;
695  }
696 
697  Box *col_box = pixad->boxa->box[col];
698 
699  Pixa *pixac;
700  Boxa *boxa2 = pixConnComp(pixt3, &pixac, 8);
701  if (boxa2 == NULL) {
702  delete []col_order;
703  return false;
704  }
705 
706  // offset the boxes by the column box
707  for (int line = 0; line < pixac->n; line++) {
708  pixac->boxa->box[line]->x += col_box->x;
709  pixac->boxa->box[line]->y += col_box->y;
710  }
711 
712  // add the lines
713  if (AddLines(pixac) == true) {
714  if (pixaaAddBox(columns_, col_box, L_CLONE) != 0) {
715  delete []col_order;
716  return false;
717  }
718  }
719 
720  pixDestroy(&pixt3);
721  boxaDestroy(&boxa2);
722 
723  line_cnt_ += columns_->pixa[col_idx]->n;
724  }
725 
726  pixaDestroy(&pixam);
727  pixaDestroy(&pixad);
728  boxaDestroy(&boxa);
729 
730  delete []col_order;
731  pixDestroy(&pix_temp1);
732 
733  return true;
734 }
735 
736 // Estimate the parameters of the font(s) used in the page
737 bool CubeLineSegmenter::EstimateFontParams() {
738  int hgt_hist[kHgtBins];
739  int max_hgt;
740  double mean_hgt;
741 
742  // init hgt histogram of concomps
743  memset(hgt_hist, 0, sizeof(hgt_hist));
744 
745  // compute max hgt
746  max_hgt = 0;
747 
748  for (int con = 0; con < con_comps_->n; con++) {
749  // skip conn comps that are too long or too wide
750  if (con_comps_->boxa->box[con]->h > kMaxConnCompHgt ||
751  con_comps_->boxa->box[con]->w > kMaxConnCompWid) {
752  continue;
753  }
754 
755  max_hgt = MAX(max_hgt, con_comps_->boxa->box[con]->h);
756  }
757 
758  if (max_hgt <= 0) {
759  return false;
760  }
761 
762  // init hgt histogram of concomps
763  memset(hgt_hist, 0, sizeof(hgt_hist));
764 
765  // compute histogram
766  mean_hgt = 0.0;
767  for (int con = 0; con < con_comps_->n; con++) {
768  // skip conn comps that are too long or too wide
769  if (con_comps_->boxa->box[con]->h > kMaxConnCompHgt ||
770  con_comps_->boxa->box[con]->w > kMaxConnCompWid) {
771  continue;
772  }
773 
774  int bin = static_cast<int>(kHgtBins * con_comps_->boxa->box[con]->h /
775  max_hgt);
776  bin = MIN(bin, kHgtBins - 1);
777  hgt_hist[bin]++;
778  mean_hgt += con_comps_->boxa->box[con]->h;
779  }
780 
781  mean_hgt /= con_comps_->n;
782 
783  // find the top 2 bins
784  int idx[kHgtBins];
785 
786  for (int bin = 0; bin < kHgtBins; bin++) {
787  idx[bin] = bin;
788  }
789 
790  for (int ibin = 0; ibin < 2; ibin++) {
791  for (int jbin = ibin + 1; jbin < kHgtBins; jbin++) {
792  if (hgt_hist[idx[ibin]] < hgt_hist[idx[jbin]]) {
793  int swap = idx[ibin];
794  idx[ibin] = idx[jbin];
795  idx[jbin] = swap;
796  }
797  }
798  }
799 
800  // emperically, we found out that the 2 highest freq bins correspond
801  // respectively to the dot and alef
802  est_dot_hgt_ = (1.0 * (idx[0] + 1) * max_hgt / kHgtBins);
803  est_alef_hgt_ = (1.0 * (idx[1] + 1) * max_hgt / kHgtBins);
804 
805  // as a sanity check the dot hgt must be significanly lower than alef
806  if (est_alef_hgt_ < (est_dot_hgt_ * 2)) {
807  // use max_hgt to estimate instead
808  est_alef_hgt_ = mean_hgt * 1.5;
809  est_dot_hgt_ = est_alef_hgt_ / 5.0;
810  }
811 
812  est_alef_hgt_ = MAX(est_alef_hgt_, est_dot_hgt_ * 4.0);
813 
814  return true;
815 }
816 
817 // clean up the image
818 Pix *CubeLineSegmenter::CleanUp(Pix *orig_img) {
819  // get rid of long horizontal lines
820  Pix *pix_temp0 = pixMorphCompSequence(orig_img, "o300.2", 0);
821  pixXor(pix_temp0, pix_temp0, orig_img);
822 
823  // get rid of long vertical lines
824  Pix *pix_temp1 = pixMorphCompSequence(pix_temp0, "o2.300", 0);
825  pixXor(pix_temp1, pix_temp1, pix_temp0);
826 
827  pixDestroy(&pix_temp0);
828 
829  // detect connected components
830  Pixa *con_comps;
831  Boxa *boxa = pixConnComp(pix_temp1, &con_comps, 8);
832  if (boxa == NULL) {
833  return NULL;
834  }
835 
836  // detect and remove suspicious conn comps
837  for (int con = 0; con < con_comps->n; con++) {
838  Box *box = boxa->box[con];
839 
840  // remove if suspc. conn comp
841  if ((box->w > (box->h * kMaxHorzAspectRatio)) ||
842  (box->h > (box->w * kMaxVertAspectRatio)) ||
843  (box->w < kMinWid && box->h < kMinHgt)) {
844  pixRasterop(pix_temp1, box->x, box->y, box->w, box->h,
845  PIX_SRC ^ PIX_DST, con_comps->pix[con], 0, 0);
846  }
847  }
848 
849  pixaDestroy(&con_comps);
850  boxaDestroy(&boxa);
851 
852  return pix_temp1;
853 }
854 
855 // Init the page segmenter
856 bool CubeLineSegmenter::Init() {
857  if (init_ == true) {
858  return true;
859  }
860 
861  if (orig_img_ == NULL) {
862  return false;
863  }
864 
865  // call the internal line segmentation
866  return FindLines();
867 }
868 
869 // return the pix mask and box of a specific line
870 Pix *CubeLineSegmenter::Line(int line, Box **line_box) {
871  if (init_ == false && Init() == false) {
872  return NULL;
873  }
874 
875  if (line < 0 || line >= line_cnt_) {
876  return NULL;
877  }
878 
879  (*line_box) = lines_pixa_->boxa->box[line];
880  return lines_pixa_->pix[line];
881 }
882 
883 // Implements a basic rudimentary layout analysis based on Leptonica
884 // works OK for Arabic. For other languages, the function TesseractPageAnalysis
885 // should be called instead.
886 bool CubeLineSegmenter::FindLines() {
887  // convert the image to gray scale if necessary
888  Pix *gray_scale_img = NULL;
889  if (orig_img_->d != 2 && orig_img_->d != 8) {
890  gray_scale_img = pixConvertTo8(orig_img_, false);
891  if (gray_scale_img == NULL) {
892  return false;
893  }
894  } else {
895  gray_scale_img = orig_img_;
896  }
897 
898  // threshold image
899  Pix *thresholded_img;
900  thresholded_img = pixThresholdToBinary(gray_scale_img, 128);
901  // free the gray scale image if necessary
902  if (gray_scale_img != orig_img_) {
903  pixDestroy(&gray_scale_img);
904  }
905  // bail-out if thresholding failed
906  if (thresholded_img == NULL) {
907  return false;
908  }
909 
910  // deskew
911  Pix *deskew_img = pixDeskew(thresholded_img, 2);
912  if (deskew_img == NULL) {
913  return false;
914  }
915 
916  pixDestroy(&thresholded_img);
917 
918  img_ = CleanUp(deskew_img);
919  pixDestroy(&deskew_img);
920  if (img_ == NULL) {
921  return false;
922  }
923 
924  pixDestroy(&deskew_img);
925 
926  // compute connected components
927  Boxa *boxa = pixConnComp(img_, &con_comps_, 8);
928  if (boxa == NULL) {
929  return false;
930  }
931 
932  boxaDestroy(&boxa);
933 
934  // estimate dot and alef hgts
935  if (EstimateFontParams() == false) {
936  return false;
937  }
938 
939  // perform line segmentation
940  if (LineSegment() == false) {
941  return false;
942  }
943 
944  // success
945  init_ = true;
946  return true;
947 }
948 
949 }
void UpdateRange(const T1 &x, T2 *lower_bound, T2 *upper_bound)
Definition: helpers.h:125
#define MIN(x, y)
Definition: ndminx.h:28
#define new_line()
Definition: cutil.h:83
CubeLineSegmenter(CubeRecoContext *cntxt, Pix *img)
Pix * Line(int line, Box **line_box)
#define MAX(x, y)
Definition: ndminx.h:24