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