22 #pragma warning(disable:4244) // Conversion warnings
33 #include "config_auto.h"
35 #include "allheaders.h"
65 static void RemoveUnusedLineSegments(
bool horizontal_lines,
66 BLOBNBOX_LIST* line_bblobs,
68 int height = pixGetHeight(line_pix);
69 BLOBNBOX_IT bbox_it(line_bblobs);
70 for (bbox_it.mark_cycle_pt(); !bbox_it.cycled_list(); bbox_it.forward()) {
75 if (horizontal_lines) {
80 pixbox = boxCreate(box.
bottom(), height - box.
right(),
87 pixbox = boxCreate(box.
left(), height - box.
top(),
90 pixClearInRect(line_pix, pixbox);
100 static void SubtractLinesAndResidue(Pix* line_pix, Pix* non_line_pix,
101 int resolution, Pix* src_pix) {
103 pixSubtract(src_pix, src_pix, line_pix);
105 Pix* residue_pix = pixSubtract(
NULL, src_pix, non_line_pix);
107 Pix* fat_line_pix = pixDilateBrick(
NULL, line_pix, 3, 3);
109 pixSeedfillBinary(fat_line_pix, fat_line_pix, residue_pix, 8);
111 pixSubtract(src_pix, src_pix, fat_line_pix);
112 pixDestroy(&fat_line_pix);
113 pixDestroy(&residue_pix);
118 static int MaxStrokeWidth(Pix* pix) {
119 Pix* dist_pix = pixDistanceFunction(pix, 4, 8, L_BOUNDARY_BG);
120 int width = pixGetWidth(dist_pix);
121 int height = pixGetHeight(dist_pix);
122 int wpl = pixGetWpl(dist_pix);
123 l_uint32* data = pixGetData(dist_pix);
126 for (
int y = 0; y < height; ++y) {
127 for (
int x = 0; x < width; ++x) {
128 int pixel = GET_DATA_BYTE(data, x);
129 if (pixel > max_dist)
134 pixDestroy(&dist_pix);
139 static int NumTouchingIntersections(Box* line_box, Pix* intersection_pix) {
140 if (intersection_pix ==
NULL)
return 0;
141 Pix* rect_pix = pixClipRectangle(intersection_pix, line_box,
NULL);
142 Boxa* boxa = pixConnComp(rect_pix,
NULL, 8);
143 pixDestroy(&rect_pix);
144 if (boxa ==
NULL)
return false;
145 int result = boxaGetCount(boxa);
153 static int CountPixelsAdjacentToLine(
int line_width, Box* line_box,
155 l_int32 x, y, box_width, box_height;
156 boxGetGeometry(line_box, &x, &y, &box_width, &box_height);
157 if (box_width > box_height) {
159 int bottom =
MIN(pixGetHeight(nonline_pix), y + box_height + line_width);
160 y =
MAX(0, y - line_width);
161 box_height = bottom - y;
164 int right =
MIN(pixGetWidth(nonline_pix), x + box_width + line_width);
165 x =
MAX(0, x - line_width);
166 box_width = right - x;
168 Box* box = boxCreate(x, y, box_width, box_height);
169 Pix* rect_pix = pixClipRectangle(nonline_pix, box,
NULL);
172 pixCountPixels(rect_pix, &result,
NULL);
173 pixDestroy(&rect_pix);
186 static int FilterFalsePositives(
int resolution, Pix* nonline_pix,
187 Pix* intersection_pix, Pix* line_pix) {
190 Boxa* boxa = pixConnComp(line_pix, &pixa, 8);
192 int nboxes = boxaGetCount(boxa);
193 int remaining_boxes = nboxes;
194 for (
int i = 0; i < nboxes; ++i) {
195 Box* box = boxaGetBox(boxa, i, L_CLONE);
196 l_int32 x, y, box_width, box_height;
197 boxGetGeometry(box, &x, &y, &box_width, &box_height);
198 Pix* comp_pix = pixaGetPix(pixa, i, L_CLONE);
199 int max_width = MaxStrokeWidth(comp_pix);
200 pixDestroy(&comp_pix);
201 bool bad_line =
false;
205 box_width < min_thick_length && box_height < min_thick_length &&
211 (intersection_pix ==
NULL ||
212 NumTouchingIntersections(box, intersection_pix) < 2)) {
214 int nonline_count = CountPixelsAdjacentToLine(max_width, box,
221 pixClearInRect(line_pix, box);
228 return remaining_boxes;
244 int* vertical_x,
int* vertical_y,
245 Pix** pix_music_mask,
246 TabVector_LIST* v_lines,
247 TabVector_LIST* h_lines) {
248 if (pix ==
NULL || vertical_x ==
NULL || vertical_y ==
NULL) {
249 tprintf(
"Error in parameters for LineFinder::FindAndRemoveLines\n");
252 Pix* pix_vline =
NULL;
253 Pix* pix_non_vline =
NULL;
254 Pix* pix_hline =
NULL;
255 Pix* pix_non_hline =
NULL;
256 Pix* pix_intersections =
NULL;
257 Pixa* pixa_display = debug ? pixaCreate(0) :
NULL;
258 GetLineMasks(resolution, pix, &pix_vline, &pix_non_vline, &pix_hline,
259 &pix_non_hline, &pix_intersections, pix_music_mask,
262 FindAndRemoveVLines(resolution, pix_intersections, vertical_x, vertical_y,
263 &pix_vline, pix_non_vline, pix, v_lines);
264 if (pix_hline !=
NULL) {
266 if (pix_vline !=
NULL)
267 pixAnd(pix_intersections, pix_vline, pix_hline);
269 pixDestroy(&pix_intersections);
270 if (!FilterFalsePositives(resolution, pix_non_hline, pix_intersections,
272 pixDestroy(&pix_hline);
275 FindAndRemoveHLines(resolution, pix_intersections, *vertical_x, *vertical_y,
276 &pix_hline, pix_non_hline, pix, h_lines);
277 if (pixa_display !=
NULL && pix_vline !=
NULL)
278 pixaAddPix(pixa_display, pix_vline, L_CLONE);
279 if (pixa_display !=
NULL && pix_hline !=
NULL)
280 pixaAddPix(pixa_display, pix_hline, L_CLONE);
281 if (pix_vline !=
NULL && pix_hline !=
NULL) {
284 pixAnd(pix_intersections, pix_vline, pix_hline);
287 Pix* pix_join_residue = pixDilateBrick(
NULL, pix_intersections, 5, 5);
288 pixSeedfillBinary(pix_join_residue, pix_join_residue, pix, 8);
290 pixSubtract(pix, pix, pix_join_residue);
291 pixDestroy(&pix_join_residue);
294 if (pix_music_mask !=
NULL && *pix_music_mask !=
NULL) {
295 if (pixa_display !=
NULL)
296 pixaAddPix(pixa_display, *pix_music_mask, L_CLONE);
297 pixSubtract(pix, pix, *pix_music_mask);
299 if (pixa_display !=
NULL)
300 pixaAddPix(pixa_display, pix, L_CLONE);
302 pixDestroy(&pix_vline);
303 pixDestroy(&pix_non_vline);
304 pixDestroy(&pix_hline);
305 pixDestroy(&pix_non_hline);
306 pixDestroy(&pix_intersections);
307 if (pixa_display !=
NULL) {
308 #if LIBLEPT_MINOR_VERSION >= 69 || LIBLEPT_MAJOR_VERSION > 1
309 pixaConvertToPdf(pixa_display, resolution, 1.0
f, 0, 0,
"LineFinding",
310 "vhlinefinding.pdf");
312 pixaDestroy(&pixa_display);
322 Boxa** boxes, C_BLOB_LIST* blobs) {
323 C_OUTLINE_LIST outlines;
324 C_OUTLINE_IT ol_it = &outlines;
326 int nboxes = boxaGetCount(*boxes);
327 for (
int i = 0; i < nboxes; ++i) {
328 l_int32 x, y, width, height;
329 boxaGetBoxGeometry(*boxes, i, &x, &y, &width, &height);
334 ICOORD bot_right(x + width, y + height);
336 startpt.
pos = top_left;
338 ol_it.add_after_then_move(outline);
345 ICOORD page_br(image_width, image_height);
348 C_BLOB_IT blob_it(blobs);
349 blob_it.add_list_after(block.
blob_list());
364 void LineFinder::FindAndRemoveVLines(
int resolution,
365 Pix* pix_intersections,
366 int* vertical_x,
int* vertical_y,
367 Pix** pix_vline, Pix* pix_non_vline,
368 Pix* src_pix, TabVector_LIST* vectors) {
369 if (pix_vline ==
NULL || *pix_vline ==
NULL)
return;
370 C_BLOB_LIST line_cblobs;
371 BLOBNBOX_LIST line_bblobs;
372 GetLineBoxes(
false, *pix_vline, pix_intersections,
373 &line_cblobs, &line_bblobs);
374 int width = pixGetWidth(src_pix);
375 int height = pixGetHeight(src_pix);
377 ICOORD tright(width, height);
378 FindLineVectors(bleft, tright, &line_bblobs, vertical_x, vertical_y, vectors);
379 if (!vectors->empty()) {
380 RemoveUnusedLineSegments(
false, &line_bblobs, *pix_vline);
381 SubtractLinesAndResidue(*pix_vline, pix_non_vline, resolution, src_pix);
386 pixDestroy(pix_vline);
400 void LineFinder::FindAndRemoveHLines(
int resolution,
401 Pix* pix_intersections,
402 int vertical_x,
int vertical_y,
403 Pix** pix_hline, Pix* pix_non_hline,
404 Pix* src_pix, TabVector_LIST* vectors) {
405 if (pix_hline ==
NULL || *pix_hline ==
NULL)
return;
406 C_BLOB_LIST line_cblobs;
407 BLOBNBOX_LIST line_bblobs;
408 GetLineBoxes(
true, *pix_hline, pix_intersections, &line_cblobs, &line_bblobs);
409 int width = pixGetWidth(src_pix);
410 int height = pixGetHeight(src_pix);
412 ICOORD tright(height, width);
413 FindLineVectors(bleft, tright, &line_bblobs, &vertical_x, &vertical_y,
415 if (!vectors->empty()) {
416 RemoveUnusedLineSegments(
true, &line_bblobs, *pix_hline);
417 SubtractLinesAndResidue(*pix_hline, pix_non_hline, resolution, src_pix);
424 TabVector_IT h_it(vectors);
425 for (h_it.mark_cycle_pt(); !h_it.cycled_list(); h_it.forward()) {
426 h_it.data()->XYFlip();
429 pixDestroy(pix_hline);
438 void LineFinder::FindLineVectors(
const ICOORD& bleft,
const ICOORD& tright,
439 BLOBNBOX_LIST* line_bblobs,
440 int* vertical_x,
int* vertical_y,
441 TabVector_LIST* vectors) {
442 BLOBNBOX_IT bbox_it(line_bblobs);
447 for (bbox_it.mark_cycle_pt(); !bbox_it.cycled_list(); bbox_it.forward()) {
454 blob_grid.InsertBBox(
false,
true, bblob);
463 TabVector_IT vector_it(vectors);
466 lsearch.StartFullSearch();
467 while ((bbox = lsearch.NextFullSearch()) !=
NULL) {
471 tprintf(
"Finding line vector starting at bbox (%d,%d)\n",
473 AlignedBlobParams align_params(*vertical_x, *vertical_y, box.
width());
474 TabVector* vector = blob_grid.FindVerticalAlignment(align_params, bbox,
477 if (vector !=
NULL) {
479 vector_it.add_to_end(vector);
490 static Pix* FilterMusic(
int resolution, Pix* pix_closed,
491 Pix* pix_vline, Pix* pix_hline,
492 l_int32* v_empty, l_int32* h_empty) {
494 Pix* intersection_pix = pixAnd(
NULL, pix_vline, pix_hline);
495 Boxa* boxa = pixConnComp(pix_vline,
NULL, 8);
497 int nboxes = boxaGetCount(boxa);
498 Pix* music_mask =
NULL;
499 for (
int i = 0; i < nboxes; ++i) {
500 Box* box = boxaGetBox(boxa, i, L_CLONE);
501 l_int32 x, y, box_width, box_height;
502 boxGetGeometry(box, &x, &y, &box_width, &box_height);
503 int joins = NumTouchingIntersections(box, intersection_pix);
506 if (joins >= 5 && (joins - 1) * max_stave_height >= 4 * box_height) {
508 if (music_mask ==
NULL)
509 music_mask = pixCreate(pixGetWidth(pix_vline), pixGetHeight(pix_vline),
511 pixSetInRect(music_mask, box);
516 pixDestroy(&intersection_pix);
517 if (music_mask !=
NULL) {
521 pixSeedfillBinary(music_mask, music_mask, pix_closed, 8);
525 Boxa* boxa = pixConnComp(music_mask,
NULL, 8);
527 int nboxes = boxaGetCount(boxa);
528 for (
int i = 0; i < nboxes; ++i) {
529 Box* box = boxaGetBox(boxa, i, L_CLONE);
530 Pix* rect_pix = pixClipRectangle(music_mask, box,
NULL);
531 l_int32 music_pixels;
532 pixCountPixels(rect_pix, &music_pixels,
NULL);
533 pixDestroy(&rect_pix);
534 rect_pix = pixClipRectangle(pix_closed, box,
NULL);
536 pixCountPixels(rect_pix, &all_pixels,
NULL);
537 pixDestroy(&rect_pix);
540 pixClearInRect(music_mask, box);
544 l_int32 no_remaining_music;
546 pixZero(music_mask, &no_remaining_music);
547 if (no_remaining_music) {
548 pixDestroy(&music_mask);
550 pixSubtract(pix_vline, pix_vline, music_mask);
551 pixSubtract(pix_hline, pix_hline, music_mask);
553 pixZero(pix_vline, v_empty);
554 pixZero(pix_hline, h_empty);
572 void LineFinder::GetLineMasks(
int resolution, Pix* src_pix,
573 Pix** pix_vline, Pix** pix_non_vline,
574 Pix** pix_hline, Pix** pix_non_hline,
575 Pix** pix_intersections, Pix** pix_music_mask,
576 Pixa* pixa_display) {
579 if (pixa_display !=
NULL) {
580 tprintf(
"Image resolution = %d, max line width = %d, min length=%d\n",
581 resolution, max_line_width, min_line_length);
583 int closing_brick = max_line_width / 3;
588 Pix* pix_closed = pixCloseBrick(
NULL, src_pix, closing_brick, closing_brick);
589 if (pixa_display !=
NULL)
590 pixaAddPix(pixa_display, pix_closed, L_CLONE);
593 Pix* pix_solid = pixOpenBrick(
NULL, pix_closed, max_line_width,
595 if (pixa_display !=
NULL)
596 pixaAddPix(pixa_display, pix_solid, L_CLONE);
597 Pix* pix_hollow = pixSubtract(
NULL, pix_closed, pix_solid);
598 pixDestroy(&pix_solid);
601 if (pixa_display !=
NULL)
602 pixaAddPix(pixa_display, pix_hollow, L_CLONE);
603 *pix_vline = pixOpenBrick(
NULL, pix_hollow, 1, min_line_length);
604 *pix_hline = pixOpenBrick(
NULL, pix_hollow, min_line_length, 1);
605 pixDestroy(&pix_hollow);
609 pixZero(*pix_vline, &v_empty);
610 pixZero(*pix_hline, &h_empty);
611 if (pix_music_mask !=
NULL) {
612 if (!v_empty && !h_empty) {
613 *pix_music_mask = FilterMusic(resolution, pix_closed,
614 *pix_vline, *pix_hline,
617 *pix_music_mask =
NULL;
620 pixDestroy(&pix_closed);
621 Pix* pix_nonlines =
NULL;
622 *pix_intersections =
NULL;
623 Pix* extra_non_hlines =
NULL;
626 pix_nonlines = pixSubtract(
NULL, src_pix, *pix_vline);
628 pixSubtract(pix_nonlines, pix_nonlines, *pix_hline);
630 *pix_intersections = pixAnd(
NULL, *pix_vline, *pix_hline);
633 extra_non_hlines = pixSubtract(
NULL, *pix_vline, *pix_intersections);
636 pixSeedfillBinary(*pix_non_vline, *pix_non_vline, pix_nonlines, 8);
639 pixOr(*pix_non_vline, *pix_non_vline, *pix_hline);
640 pixSubtract(*pix_non_vline, *pix_non_vline, *pix_intersections);
642 if (!FilterFalsePositives(resolution, *pix_non_vline, *pix_intersections,
644 pixDestroy(pix_vline);
647 pixDestroy(pix_vline);
648 *pix_non_vline =
NULL;
650 pix_nonlines = pixSubtract(
NULL, src_pix, *pix_hline);
654 pixDestroy(pix_hline);
655 *pix_non_hline =
NULL;
661 pixSeedfillBinary(*pix_non_hline, *pix_non_hline, pix_nonlines, 8);
662 if (extra_non_hlines !=
NULL) {
663 pixOr(*pix_non_hline, *pix_non_hline, extra_non_hlines);
664 pixDestroy(&extra_non_hlines);
666 if (!FilterFalsePositives(resolution, *pix_non_hline, *pix_intersections,
668 pixDestroy(pix_hline);
670 if (pixa_display !=
NULL) {
671 if (*pix_vline !=
NULL) pixaAddPix(pixa_display, *pix_vline, L_CLONE);
672 if (*pix_hline !=
NULL) pixaAddPix(pixa_display, *pix_hline, L_CLONE);
673 if (pix_nonlines !=
NULL) pixaAddPix(pixa_display, pix_nonlines, L_CLONE);
674 if (*pix_non_vline !=
NULL)
675 pixaAddPix(pixa_display, *pix_non_vline, L_CLONE);
676 if (*pix_non_hline !=
NULL)
677 pixaAddPix(pixa_display, *pix_non_hline, L_CLONE);
678 if (*pix_intersections !=
NULL)
679 pixaAddPix(pixa_display, *pix_intersections, L_CLONE);
680 if (pix_music_mask !=
NULL && *pix_music_mask !=
NULL)
681 pixaAddPix(pixa_display, *pix_music_mask, L_CLONE);
683 pixDestroy(&pix_nonlines);
689 void LineFinder::GetLineBoxes(
bool horizontal_lines,
690 Pix* pix_lines, Pix* pix_intersections,
691 C_BLOB_LIST* line_cblobs,
692 BLOBNBOX_LIST* line_bblobs) {
696 int wpl = pixGetWpl(pix_lines);
697 int width = pixGetWidth(pix_lines);
698 int height = pixGetHeight(pix_lines);
699 l_uint32* data = pixGetData(pix_lines);
700 if (horizontal_lines) {
701 for (
int y = 0; y < height; ++y, data += wpl) {
703 CLEAR_DATA_BIT(data, x);
708 memset(data + wpl * y, 0, wpl *
sizeof(*data));
712 Boxa* boxa = pixConnComp(pix_lines,
NULL, 8);
715 C_BLOB_IT blob_it(line_cblobs);
716 BLOBNBOX_IT bbox_it(line_bblobs);
717 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
718 C_BLOB* cblob = blob_it.data();
720 bbox_it.add_to_end(bblob);
723 Box* box = boxCreate(bbox.
left(), bbox.
bottom(),
731 if (horizontal_lines) {