22 #pragma warning(disable:4244) // Conversion warnings
26 #include "config_auto.h"
36 #include "allheaders.h"
66 static void RemoveUnusedLineSegments(
bool horizontal_lines,
67 BLOBNBOX_LIST* line_bblobs,
69 int height = pixGetHeight(line_pix);
70 BLOBNBOX_IT bbox_it(line_bblobs);
71 for (bbox_it.mark_cycle_pt(); !bbox_it.cycled_list(); bbox_it.forward()) {
76 if (horizontal_lines) {
81 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;
204 if (box_width >= kMinThickLineWidth && box_height >= kMinThickLineWidth &&
205 box_width < min_thick_length && box_height < min_thick_length &&
206 max_width > kMinThickLineWidth) {
211 (intersection_pix ==
NULL ||
212 NumTouchingIntersections(box, intersection_pix) < 2)) {
214 int nonline_count = CountPixelsAdjacentToLine(max_width, box,
216 if (nonline_count > box_height * box_width * kMaxNonLineDensity)
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) {
249 if (pix ==
NULL || vertical_x ==
NULL || vertical_y ==
NULL) {
250 tprintf(
"Error in parameters for LineFinder::FindAndRemoveLines\n");
253 Pix* pix_vline =
NULL;
254 Pix* pix_non_vline =
NULL;
255 Pix* pix_hline =
NULL;
256 Pix* pix_non_hline =
NULL;
257 Pix* pix_intersections =
NULL;
258 Pixa* pixa_display = debug ? pixaCreate(0) :
NULL;
259 GetLineMasks(resolution, pix, &pix_vline, &pix_non_vline, &pix_hline,
260 &pix_non_hline, &pix_intersections, pix_music_mask,
263 FindAndRemoveVLines(resolution, pix_intersections, vertical_x, vertical_y,
264 &pix_vline, pix_non_vline, pix, v_lines);
265 if (pix_hline !=
NULL) {
267 if (pix_vline !=
NULL)
268 pixAnd(pix_intersections, pix_vline, pix_hline);
270 pixDestroy(&pix_intersections);
271 if (!FilterFalsePositives(resolution, pix_non_hline, pix_intersections,
273 pixDestroy(&pix_hline);
276 FindAndRemoveHLines(resolution, pix_intersections, *vertical_x, *vertical_y,
277 &pix_hline, pix_non_hline, pix, h_lines);
278 if (pixa_display !=
NULL && pix_vline !=
NULL)
279 pixaAddPix(pixa_display, pix_vline, L_CLONE);
280 if (pixa_display !=
NULL && pix_hline !=
NULL)
281 pixaAddPix(pixa_display, pix_hline, L_CLONE);
282 if (pix_vline !=
NULL && pix_hline !=
NULL) {
285 pixAnd(pix_intersections, pix_vline, pix_hline);
288 Pix* pix_join_residue = pixDilateBrick(
NULL, pix_intersections, 5, 5);
289 pixSeedfillBinary(pix_join_residue, pix_join_residue, pix, 8);
291 pixSubtract(pix, pix, pix_join_residue);
292 pixDestroy(&pix_join_residue);
295 if (pix_music_mask !=
NULL && *pix_music_mask !=
NULL) {
296 if (pixa_display !=
NULL)
297 pixaAddPix(pixa_display, *pix_music_mask, L_CLONE);
298 pixSubtract(pix, pix, *pix_music_mask);
300 if (pixa_display !=
NULL)
301 pixaAddPix(pixa_display, pix, L_CLONE);
303 pixDestroy(&pix_vline);
304 pixDestroy(&pix_non_vline);
305 pixDestroy(&pix_hline);
306 pixDestroy(&pix_non_hline);
307 pixDestroy(&pix_intersections);
308 if (pixa_display !=
NULL) {
309 #if LIBLEPT_MINOR_VERSION >= 69 || LIBLEPT_MAJOR_VERSION > 1
310 pixaConvertToPdf(pixa_display, resolution, 1.0f, 0, 0,
"LineFinding",
311 "vhlinefinding.pdf");
313 pixaDestroy(&pixa_display);
324 Boxa** boxes, C_BLOB_LIST* blobs) {
325 C_OUTLINE_LIST outlines;
326 C_OUTLINE_IT ol_it = &outlines;
328 int nboxes = boxaGetCount(*boxes);
329 for (
int i = 0; i < nboxes; ++i) {
330 l_int32 x, y, width, height;
331 boxaGetBoxGeometry(*boxes, i, &x, &y, &width, &height);
336 ICOORD bot_right(x + width, y + height);
338 startpt.
pos = top_left;
340 ol_it.add_after_then_move(outline);
347 ICOORD page_br(image_width, image_height);
350 C_BLOB_IT blob_it(blobs);
351 blob_it.add_list_after(block.
blob_list());
366 void LineFinder::FindAndRemoveVLines(
int resolution,
367 Pix* pix_intersections,
368 int* vertical_x,
int* vertical_y,
369 Pix** pix_vline, Pix* pix_non_vline,
370 Pix* src_pix, TabVector_LIST* vectors) {
371 if (pix_vline ==
NULL || *pix_vline ==
NULL)
return;
372 C_BLOB_LIST line_cblobs;
373 BLOBNBOX_LIST line_bblobs;
374 GetLineBoxes(
false, *pix_vline, pix_intersections,
375 &line_cblobs, &line_bblobs);
376 int width = pixGetWidth(src_pix);
377 int height = pixGetHeight(src_pix);
379 ICOORD tright(width, height);
380 FindLineVectors(bleft, tright, &line_bblobs, vertical_x, vertical_y, vectors);
381 if (!vectors->empty()) {
382 RemoveUnusedLineSegments(
false, &line_bblobs, *pix_vline);
383 SubtractLinesAndResidue(*pix_vline, pix_non_vline, resolution, src_pix);
388 pixDestroy(pix_vline);
402 void LineFinder::FindAndRemoveHLines(
int resolution,
403 Pix* pix_intersections,
404 int vertical_x,
int vertical_y,
405 Pix** pix_hline, Pix* pix_non_hline,
406 Pix* src_pix, TabVector_LIST* vectors) {
407 if (pix_hline ==
NULL || *pix_hline ==
NULL)
return;
408 C_BLOB_LIST line_cblobs;
409 BLOBNBOX_LIST line_bblobs;
410 GetLineBoxes(
true, *pix_hline, pix_intersections, &line_cblobs, &line_bblobs);
411 int width = pixGetWidth(src_pix);
412 int height = pixGetHeight(src_pix);
414 ICOORD tright(height, width);
415 FindLineVectors(bleft, tright, &line_bblobs, &vertical_x, &vertical_y,
417 if (!vectors->empty()) {
418 RemoveUnusedLineSegments(
true, &line_bblobs, *pix_hline);
419 SubtractLinesAndResidue(*pix_hline, pix_non_hline, resolution, src_pix);
426 TabVector_IT h_it(vectors);
427 for (h_it.mark_cycle_pt(); !h_it.cycled_list(); h_it.forward()) {
428 h_it.data()->XYFlip();
431 pixDestroy(pix_hline);
440 void LineFinder::FindLineVectors(
const ICOORD& bleft,
const ICOORD& tright,
441 BLOBNBOX_LIST* line_bblobs,
442 int* vertical_x,
int* vertical_y,
443 TabVector_LIST* vectors) {
444 BLOBNBOX_IT bbox_it(line_bblobs);
448 AlignedBlob blob_grid(kLineFindGridSize, bleft, tright);
449 for (bbox_it.mark_cycle_pt(); !bbox_it.cycled_list(); bbox_it.forward()) {
456 blob_grid.InsertBBox(
false,
true, bblob);
465 TabVector_IT vector_it(vectors);
468 lsearch.StartFullSearch();
469 while ((bbox = lsearch.NextFullSearch()) !=
NULL) {
473 tprintf(
"Finding line vector starting at bbox (%d,%d)\n",
475 AlignedBlobParams align_params(*vertical_x, *vertical_y, box.
width());
476 TabVector* vector = blob_grid.FindVerticalAlignment(align_params, bbox,
479 if (vector !=
NULL) {
481 vector_it.add_to_end(vector);
492 static Pix* FilterMusic(
int resolution, Pix* pix_closed,
493 Pix* pix_vline, Pix* pix_hline,
494 l_int32* v_empty, l_int32* h_empty) {
496 Pix* intersection_pix = pixAnd(
NULL, pix_vline, pix_hline);
497 Boxa* boxa = pixConnComp(pix_vline,
NULL, 8);
499 int nboxes = boxaGetCount(boxa);
500 Pix* music_mask =
NULL;
501 for (
int i = 0; i < nboxes; ++i) {
502 Box* box = boxaGetBox(boxa, i, L_CLONE);
503 l_int32 x, y, box_width, box_height;
504 boxGetGeometry(box, &x, &y, &box_width, &box_height);
505 int joins = NumTouchingIntersections(box, intersection_pix);
508 if (joins >= 5 && (joins - 1) * max_stave_height >= 4 * box_height) {
510 if (music_mask ==
NULL)
511 music_mask = pixCreate(pixGetWidth(pix_vline), pixGetHeight(pix_vline),
513 pixSetInRect(music_mask, box);
518 pixDestroy(&intersection_pix);
519 if (music_mask !=
NULL) {
523 pixSeedfillBinary(music_mask, music_mask, pix_closed, 8);
527 Boxa* boxa = pixConnComp(music_mask,
NULL, 8);
529 int nboxes = boxaGetCount(boxa);
530 for (
int i = 0; i < nboxes; ++i) {
531 Box* box = boxaGetBox(boxa, i, L_CLONE);
532 Pix* rect_pix = pixClipRectangle(music_mask, box,
NULL);
533 l_int32 music_pixels;
534 pixCountPixels(rect_pix, &music_pixels,
NULL);
535 pixDestroy(&rect_pix);
536 rect_pix = pixClipRectangle(pix_closed, box,
NULL);
538 pixCountPixels(rect_pix, &all_pixels,
NULL);
539 pixDestroy(&rect_pix);
540 if (music_pixels < kMinMusicPixelFraction * all_pixels) {
542 pixClearInRect(music_mask, box);
546 l_int32 no_remaining_music;
548 pixZero(music_mask, &no_remaining_music);
549 if (no_remaining_music) {
550 pixDestroy(&music_mask);
552 pixSubtract(pix_vline, pix_vline, music_mask);
553 pixSubtract(pix_hline, pix_hline, music_mask);
555 pixZero(pix_vline, v_empty);
556 pixZero(pix_hline, h_empty);
574 void LineFinder::GetLineMasks(
int resolution, Pix* src_pix,
575 Pix** pix_vline, Pix** pix_non_vline,
576 Pix** pix_hline, Pix** pix_non_hline,
577 Pix** pix_intersections, Pix** pix_music_mask,
578 Pixa* pixa_display) {
579 Pix* pix_closed =
NULL;
580 Pix* pix_hollow =
NULL;
584 if (pixa_display !=
NULL) {
585 tprintf(
"Image resolution = %d, max line width = %d, min length=%d\n",
586 resolution, max_line_width, min_line_length);
588 int closing_brick = max_line_width / 3;
593 if (OpenclDevice::selectedDeviceIsOpenCL()) {
595 int clStatus = OpenclDevice::initMorphCLAllocations(pixGetWpl(src_pix),
596 pixGetHeight(src_pix),
598 bool getpixclosed = pix_music_mask !=
NULL ?
true :
false;
599 OpenclDevice::pixGetLinesCL(
NULL, src_pix, pix_vline, pix_hline,
600 &pix_closed, getpixclosed, closing_brick,
601 closing_brick, max_line_width, max_line_width,
602 min_line_length, min_line_length);
608 pix_closed = pixCloseBrick(
NULL, src_pix, closing_brick, closing_brick);
609 if (pixa_display !=
NULL)
610 pixaAddPix(pixa_display, pix_closed, L_CLONE);
613 Pix* pix_solid = pixOpenBrick(
NULL, pix_closed, max_line_width,
615 if (pixa_display !=
NULL)
616 pixaAddPix(pixa_display, pix_solid, L_CLONE);
617 pix_hollow = pixSubtract(
NULL, pix_closed, pix_solid);
619 pixDestroy(&pix_solid);
623 if (pixa_display !=
NULL)
624 pixaAddPix(pixa_display, pix_hollow, L_CLONE);
625 *pix_vline = pixOpenBrick(
NULL, pix_hollow, 1, min_line_length);
626 *pix_hline = pixOpenBrick(
NULL, pix_hollow, min_line_length, 1);
628 pixDestroy(&pix_hollow);
637 pixZero(*pix_vline, &v_empty);
638 pixZero(*pix_hline, &h_empty);
639 if (pix_music_mask !=
NULL) {
640 if (!v_empty && !h_empty) {
641 *pix_music_mask = FilterMusic(resolution, pix_closed,
642 *pix_vline, *pix_hline,
645 *pix_music_mask =
NULL;
648 pixDestroy(&pix_closed);
649 Pix* pix_nonlines =
NULL;
650 *pix_intersections =
NULL;
651 Pix* extra_non_hlines =
NULL;
654 pix_nonlines = pixSubtract(
NULL, src_pix, *pix_vline);
656 pixSubtract(pix_nonlines, pix_nonlines, *pix_hline);
658 *pix_intersections = pixAnd(
NULL, *pix_vline, *pix_hline);
661 extra_non_hlines = pixSubtract(
NULL, *pix_vline, *pix_intersections);
663 *pix_non_vline = pixErodeBrick(
NULL, pix_nonlines, kMaxLineResidue, 1);
664 pixSeedfillBinary(*pix_non_vline, *pix_non_vline, pix_nonlines, 8);
667 pixOr(*pix_non_vline, *pix_non_vline, *pix_hline);
668 pixSubtract(*pix_non_vline, *pix_non_vline, *pix_intersections);
670 if (!FilterFalsePositives(resolution, *pix_non_vline, *pix_intersections,
672 pixDestroy(pix_vline);
675 pixDestroy(pix_vline);
676 *pix_non_vline =
NULL;
678 pix_nonlines = pixSubtract(
NULL, src_pix, *pix_hline);
682 pixDestroy(pix_hline);
683 *pix_non_hline =
NULL;
688 *pix_non_hline = pixErodeBrick(
NULL, pix_nonlines, 1, kMaxLineResidue);
689 pixSeedfillBinary(*pix_non_hline, *pix_non_hline, pix_nonlines, 8);
690 if (extra_non_hlines !=
NULL) {
691 pixOr(*pix_non_hline, *pix_non_hline, extra_non_hlines);
692 pixDestroy(&extra_non_hlines);
694 if (!FilterFalsePositives(resolution, *pix_non_hline, *pix_intersections,
696 pixDestroy(pix_hline);
698 if (pixa_display !=
NULL) {
699 if (*pix_vline !=
NULL) pixaAddPix(pixa_display, *pix_vline, L_CLONE);
700 if (*pix_hline !=
NULL) pixaAddPix(pixa_display, *pix_hline, L_CLONE);
701 if (pix_nonlines !=
NULL) pixaAddPix(pixa_display, pix_nonlines, L_CLONE);
702 if (*pix_non_vline !=
NULL)
703 pixaAddPix(pixa_display, *pix_non_vline, L_CLONE);
704 if (*pix_non_hline !=
NULL)
705 pixaAddPix(pixa_display, *pix_non_hline, L_CLONE);
706 if (*pix_intersections !=
NULL)
707 pixaAddPix(pixa_display, *pix_intersections, L_CLONE);
708 if (pix_music_mask !=
NULL && *pix_music_mask !=
NULL)
709 pixaAddPix(pixa_display, *pix_music_mask, L_CLONE);
711 pixDestroy(&pix_nonlines);
717 void LineFinder::GetLineBoxes(
bool horizontal_lines,
718 Pix* pix_lines, Pix* pix_intersections,
719 C_BLOB_LIST* line_cblobs,
720 BLOBNBOX_LIST* line_bblobs) {
724 int wpl = pixGetWpl(pix_lines);
725 int width = pixGetWidth(pix_lines);
726 int height = pixGetHeight(pix_lines);
727 l_uint32* data = pixGetData(pix_lines);
728 if (horizontal_lines) {
729 for (
int y = 0; y < height; ++y, data += wpl) {
731 CLEAR_DATA_BIT(data, x);
735 for (
int y = kCrackSpacing; y < height; y +=
kCrackSpacing) {
736 memset(data + wpl * y, 0, wpl *
sizeof(*data));
740 Boxa* boxa = pixConnComp(pix_lines,
NULL, 8);
743 C_BLOB_IT blob_it(line_cblobs);
744 BLOBNBOX_IT bbox_it(line_bblobs);
745 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
746 C_BLOB* cblob = blob_it.data();
748 bbox_it.add_to_end(bblob);
751 Box* box = boxCreate(bbox.
left(), bbox.
bottom(),
759 if (horizontal_lines) {
C_BLOB_LIST * blob_list()
get blobs
static void ConvertBoxaToBlobs(int image_width, int image_height, Boxa **boxes, C_BLOB_LIST *blobs)
GridSearch< BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT > BlobGridSearch
void set_right_crossing_rule(int new_right)
static bool WithinTestRegion(int detail_level, int x, int y)
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)
void outlines_to_blobs(BLOCK *block, ICOORD bleft, ICOORD tright, C_OUTLINE_LIST *outlines)
void set_line_crossings(int value)
const int kMinThickLineWidth
TabType left_tab_type() const
const double kMaxStaveHeight
const double kMaxNonLineDensity
static void MergeSimilarTabVectors(const ICOORD &vertical, TabVector_LIST *vectors, BlobGrid *grid)
void set_bounding_box(const TBOX &new_box)
#define PERF_COUNT_START(FUNCT_NAME)
void set_left_crossing_rule(int new_left)
const int kCrackSpacing
Spacing of cracks across the page to break up tall vertical lines.
const int kLineFindGridSize
Grid size used by line finder. Not very critical.
void set_right_rule(int new_right)
inT16 x() const
access function
const double kMinMusicPixelFraction
void set_left_rule(int new_left)
const TBOX & bounding_box() const
void set_with_shrink(int x, int y)
Set from the given x,y, shrinking the vector to fit if needed.
const double kThickLengthMultiple
const int kMaxLineResidue
const int kThinLineFraction
Denominator of resolution makes max pixel width to allow thin lines.
const int kMinLineLengthFraction
Denominator of resolution makes min pixels to demand line lengths to be.
void set_left_tab_type(TabType new_type)