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;
55 if (lines_pixa_ !=
NULL) {
56 pixaDestroy(&lines_pixa_);
60 if (con_comps_ !=
NULL) {
61 pixaDestroy(&con_comps_);
65 if (columns_ !=
NULL) {
66 pixaaDestroy(&columns_);
72 double CubeLineSegmenter::ValidityRatio(Pix *line_mask_pix, Box *line_box) {
73 return line_box->h / est_alef_hgt_;
77 bool CubeLineSegmenter::ValidLine(Pix *line_mask_pix, Box *line_box) {
78 double validity_ratio = ValidityRatio(line_mask_pix, line_box);
80 return validity_ratio < kMaxValidLineRatio;
85 Pixa *CubeLineSegmenter::VerticalClosing(Pix *pix,
86 int threshold, Boxa **boxa) {
87 char sequence_str[16];
90 sprintf(sequence_str,
"c100.%d", threshold);
91 Pix *morphed_pix = pixMorphCompSequence(pix, sequence_str, 0);
92 if (morphed_pix ==
NULL) {
98 (*boxa) = pixConnComp(morphed_pix, &pixac, 8);
100 pixDestroy(&morphed_pix);
102 if ((*boxa) ==
NULL) {
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]);
120 boxaDestroy(line_con_comps);
121 pixaDestroy(line_con_comps_pix);
125 Pixa *CubeLineSegmenter::CrackLine(Pix *cracked_line_pix,
126 Box *cracked_line_box,
int line_cnt) {
128 Pixa **lines_pixa =
new Pixa*[line_cnt];
129 if (lines_pixa ==
NULL) {
133 memset(lines_pixa, 0, line_cnt *
sizeof(*lines_pixa));
136 Pixa *line_con_comps_pix;
137 Boxa *line_con_comps = ComputeLineConComps(cracked_line_pix,
138 cracked_line_box, &line_con_comps_pix);
140 if (line_con_comps ==
NULL) {
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));
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);
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);
173 Pixa *lines = pixaCreate(line_cnt);
177 for (
int line = 0; line < line_cnt; line++) {
178 Pixa *line_pixa = lines_pixa[line];
181 if (line_pixa ==
NULL) {
188 Pix *line_pix = Pixa2Pix(line_pixa, &line_box);
189 if (line_pix ==
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);
198 if (line_box !=
NULL) {
199 boxDestroy(&line_box);
209 CleanupCrackLine(line_cnt, lines_pixa, &line_con_comps,
210 &line_con_comps_pix);
212 if (success ==
false) {
221 Pixa *CubeLineSegmenter::CrackLine(Pix *cracked_line_pix,
222 Box *cracked_line_box) {
224 int max_line_cnt =
static_cast<int>((cracked_line_box->h /
225 est_alef_hgt_) + 0.5);
226 if (max_line_cnt < 2) {
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);
241 Pixa *CubeLineSegmenter::SplitLine(Pix *line_mask_pix, Box *line_box) {
243 Pix *line_pix = pixClone(line_mask_pix);
245 if (line_pix ==
NULL) {
250 pixRasterop(line_pix, 0, 0, line_pix->w, line_pix->h,
251 PIX_SRC & PIX_DST, img_, line_box->x, line_box->y);
255 int morph_hgt = kLineSepMorphMinHgt - 1,
256 best_threshold = kLineSepMorphMinHgt - 1,
257 max_valid_portion = 0;
263 pixac = VerticalClosing(line_pix, morph_hgt, &boxa);
271 for (line = 0; line < pixac->n; line++) {
272 boxa->box[line]->x += line_box->x;
273 boxa->box[line]->y += line_box->y;
275 if (ValidLine(pixac->pix[line], boxa->box[line]) ==
true) {
280 valid_portion += boxa->box[line]->h;
285 if (valid_line_cnt == pixac->n) {
287 pixDestroy(&line_pix);
292 if (valid_portion > max_valid_portion) {
293 max_valid_portion = valid_portion;
294 best_threshold = morph_hgt;
302 while (morph_hgt > 0);
306 pixac = CrackLine(line_pix, line_box);
308 pixDestroy(&line_pix);
314 if (max_valid_portion > 0) {
316 pixac = VerticalClosing(line_pix, best_threshold, &boxa);
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;
325 if (ValidLine(pixac->pix[line], boxa->box[line]) ==
false) {
326 pixaRemovePix(pixac, line);
332 pixDestroy(&line_pix);
337 pixDestroy(&line_pix);
343 bool CubeLineSegmenter::SmallLine(Box *line_box) {
344 return line_box->h <= (kMinValidLineHgtRatio * est_dot_hgt_);
348 Boxa * CubeLineSegmenter::ComputeLineConComps(Pix *line_mask_pix,
350 Pixa **con_comps_pixa) {
352 Pix *line_pix = pixClone(line_mask_pix);
354 if (line_pix ==
NULL) {
359 pixRasterop(line_pix, 0, 0, line_pix->w, line_pix->h,
360 PIX_SRC & PIX_DST, img_, line_box->x, line_box->y);
363 Boxa *line_con_comps = pixConnComp(line_pix, con_comps_pixa, 8);
365 pixDestroy(&line_pix);
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;
373 return line_con_comps;
377 Pix *CubeLineSegmenter::PixUnion(Pix *dest_pix, Box *dest_box,
378 Pix *src_pix, Box *src_box) {
380 BOX *union_box = boxBoundingRegion(src_box, dest_box);
383 Pix *union_pix = pixCreate(union_box->w, union_box->h, src_pix->d);
384 if (union_pix ==
NULL) {
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);
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);
398 *dest_box = *union_box;
400 boxDestroy(&union_box);
406 Pix *CubeLineSegmenter::Pixa2Pix(Pixa *pixa, Box **dest_box,
407 int start_pix,
int pix_cnt) {
414 for (
int pix_idx = start_pix; pix_idx < (start_pix + pix_cnt); pix_idx++) {
415 Box *pix_box = pixa->boxa->box[pix_idx];
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);
421 (*dest_box) = boxCreate(min_x, min_y, max_x - min_x, max_y - min_y);
422 if ((*dest_box) ==
NULL) {
427 Pix *union_pix = pixCreate((*dest_box)->w, (*dest_box)->h, img_->d);
428 if (union_pix ==
NULL) {
429 boxDestroy(dest_box);
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];
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);
448 Pix *CubeLineSegmenter::Pixa2Pix(Pixa *pixa, Box **dest_box) {
449 return Pixa2Pix(pixa, dest_box, 0, pixa->n);
453 bool CubeLineSegmenter::MergeLine(Pix *line_mask_pix, Box *line_box,
454 Pixa *lines, Boxaa *lines_con_comps) {
456 Pixa *small_con_comps_pix;
457 Boxa *small_line_con_comps = ComputeLineConComps(line_mask_pix,
458 line_box, &small_con_comps_pix);
460 if (small_line_con_comps ==
NULL) {
465 for (
int con = 0; con < small_line_con_comps->n; con++) {
466 Box *small_con_comp_box = small_line_con_comps->box[con];
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;
473 for (
int line = 0; line < lines->n; line++) {
474 if (SmallLine(lines->boxa->box[line]) ==
true) {
479 Boxa *line_con_comps = lines_con_comps->boxa[line];
481 for (
int lcon = 0; lcon < line_con_comps->n; lcon++) {
482 Box *con_comp_box = line_con_comps->box[lcon];
485 box_right = con_comp_box->x + con_comp_box->w,
486 box_bottom = con_comp_box->y + con_comp_box->h;
488 xdist =
MAX(small_con_comp_box->x, con_comp_box->x) -
489 MIN(small_box_right, box_right);
491 ydist =
MAX(small_con_comp_box->y, con_comp_box->y) -
492 MIN(small_box_bottom, box_bottom);
496 if (best_line == -1 || ydist < best_dist) {
505 if (best_line != -1 && best_dist < est_alef_hgt_) {
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);
511 if (new_line ==
NULL) {
515 pixDestroy(&lines->pix[best_line]);
520 pixaDestroy(&small_con_comps_pix);
521 boxaDestroy(&small_line_con_comps);
527 bool CubeLineSegmenter::AddLines(Pixa *lines) {
530 Boxaa *lines_con_comps = boxaaCreate(lines->n);
531 if (lines_con_comps ==
NULL) {
535 for (
int line = 0; line < lines->n; line++) {
537 if (ValidLine(lines->pix[line], lines->boxa->box[line]) ==
false) {
539 Pixa *split_lines = SplitLine(lines->pix[line],
540 lines->boxa->box[line]);
543 if (pixaRemovePix(lines, line) != 0) {
549 if (split_lines ==
NULL) {
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);
558 if (sp_line ==
NULL || sp_box ==
NULL) {
563 if (pixaInsertPix(lines, ++line, sp_line, sp_box) != 0) {
569 pixaDestroy(&split_lines);
574 for (
int line = 0; line < lines->n; line++) {
575 Boxa *line_con_comps = ComputeLineConComps(lines->pix[line],
576 lines->boxa->box[line],
NULL);
578 if (line_con_comps ==
NULL) {
583 if (boxaaAddBoxa(lines_con_comps, line_con_comps, L_INSERT) != 0) {
590 for (
int line = 0; line < lines->n; line++) {
592 if (SmallLine(lines->boxa->box[line]) ==
true) {
594 if (MergeLine(lines->pix[line], lines->boxa->box[line],
595 lines, lines_con_comps) ==
true) {
597 if (pixaRemovePix(lines, line) != 0) {
601 if (boxaaRemoveBoxa(lines_con_comps, line) != 0) {
610 boxaaDestroy(&lines_con_comps);
613 if (pixaaAddPixa(columns_, lines, L_INSERT) != 0) {
621 int *CubeLineSegmenter::IndexRTL(Pixa *pixa) {
622 int *pix_index =
new int[pixa->n];
623 if (pix_index ==
NULL) {
627 for (
int pix = 0; pix < pixa->n; pix++) {
628 pix_index[pix] = pix;
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]];
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;
649 bool CubeLineSegmenter::LineSegment() {
653 Pix *pix_temp1 = pixMorphCompSequence(img_,
"c5.500", 0);
654 if (pix_temp1 ==
NULL) {
660 Boxa *boxa = pixConnComp(pix_temp1, &pixam, 8);
666 int init_morph_min_hgt = kLineSepMorphMinHgt;
667 char sequence_str[16];
668 sprintf(sequence_str,
"c100.%d", init_morph_min_hgt);
671 Pixa *pixad = pixaMorphSequenceByRegion(img_, pixam, sequence_str, 0, 0);
677 int col_cnt = boxaGetCount(boxa);
680 columns_ = pixaaCreate(col_cnt);
681 if (columns_ ==
NULL) {
686 int *col_order = IndexRTL(pixad);
687 if (col_order ==
NULL) {
693 for (
int col_idx = 0; col_idx < col_cnt; col_idx++) {
694 int col = col_order[col_idx];
697 Pix *pixt3 = pixaGetPix(pixad, col, L_CLONE);
703 Box *col_box = pixad->boxa->box[col];
706 Boxa *boxa2 = pixConnComp(pixt3, &pixac, 8);
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;
719 if (AddLines(pixac) ==
true) {
720 if (pixaaAddBox(columns_, col_box, L_CLONE) != 0) {
729 line_cnt_ += columns_->pixa[col_idx]->n;
737 pixDestroy(&pix_temp1);
743 bool CubeLineSegmenter::EstimateFontParams() {
744 int hgt_hist[kHgtBins];
749 memset(hgt_hist, 0,
sizeof(hgt_hist));
754 for (
int con = 0; con < con_comps_->n; con++) {
756 if (con_comps_->boxa->box[con]->h > kMaxConnCompHgt ||
757 con_comps_->boxa->box[con]->w > kMaxConnCompWid) {
761 max_hgt =
MAX(max_hgt, con_comps_->boxa->box[con]->h);
769 memset(hgt_hist, 0,
sizeof(hgt_hist));
773 for (
int con = 0; con < con_comps_->n; con++) {
775 if (con_comps_->boxa->box[con]->h > kMaxConnCompHgt ||
776 con_comps_->boxa->box[con]->w > kMaxConnCompWid) {
780 int bin =
static_cast<int>(kHgtBins * con_comps_->boxa->box[con]->h /
782 bin =
MIN(bin, kHgtBins - 1);
784 mean_hgt += con_comps_->boxa->box[con]->h;
787 mean_hgt /= con_comps_->n;
792 for (
int bin = 0; bin < kHgtBins; bin++) {
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];
808 est_dot_hgt_ = (1.0 * (idx[0] + 1) * max_hgt / kHgtBins);
809 est_alef_hgt_ = (1.0 * (idx[1] + 1) * max_hgt / kHgtBins);
812 if (est_alef_hgt_ < (est_dot_hgt_ * 2)) {
814 est_alef_hgt_ = mean_hgt * 1.5;
815 est_dot_hgt_ = est_alef_hgt_ / 5.0;
818 est_alef_hgt_ =
MAX(est_alef_hgt_, est_dot_hgt_ * 4.0);
824 Pix *CubeLineSegmenter::CleanUp(Pix *orig_img) {
826 Pix *pix_temp0 = pixMorphCompSequence(orig_img,
"o300.2", 0);
827 pixXor(pix_temp0, pix_temp0, orig_img);
830 Pix *pix_temp1 = pixMorphCompSequence(pix_temp0,
"o2.300", 0);
831 pixXor(pix_temp1, pix_temp1, pix_temp0);
833 pixDestroy(&pix_temp0);
837 Boxa *boxa = pixConnComp(pix_temp1, &con_comps, 8);
843 for (
int con = 0; con < con_comps->n; con++) {
844 Box *box = boxa->box[con];
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);
855 pixaDestroy(&con_comps);
862 bool CubeLineSegmenter::Init() {
867 if (orig_img_ ==
NULL) {
877 if (init_ ==
false && Init() ==
false) {
881 if (line < 0 || line >= line_cnt_) {
885 (*line_box) = lines_pixa_->boxa->box[line];
886 return lines_pixa_->pix[line];
892 bool CubeLineSegmenter::FindLines() {
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) {
901 gray_scale_img = orig_img_;
905 Pix *thresholded_img;
906 thresholded_img = pixThresholdToBinary(gray_scale_img, 128);
908 if (gray_scale_img != orig_img_) {
909 pixDestroy(&gray_scale_img);
912 if (thresholded_img ==
NULL) {
917 Pix *deskew_img = pixDeskew(thresholded_img, 2);
918 if (deskew_img ==
NULL) {
922 pixDestroy(&thresholded_img);
924 img_ = CleanUp(deskew_img);
925 pixDestroy(&deskew_img);
930 pixDestroy(&deskew_img);
933 Boxa *boxa = pixConnComp(img_, &con_comps_, 8);
941 if (EstimateFontParams() ==
false) {
946 if (LineSegment() ==
false) {
void UpdateRange(const T1 &x, T2 *lower_bound, T2 *upper_bound)
Pix * Line(int line, Box **line_box)
CubeLineSegmenter(CubeRecoContext *cntxt, Pix *img)