22 #pragma warning(disable:4244) // Conversion warnings 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()) {
465 TabVector_IT vector_it(vectors);
473 tprintf(
"Finding line vector starting at bbox (%d,%d)\n",
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) {
static bool WithinTestRegion(int detail_level, int x, int y)
const double kThickLengthMultiple
void set_right_rule(int new_right)
void set_bounding_box(const TBOX &new_box)
const int kThinLineFraction
Denominator of resolution makes max pixel width to allow thin lines.
TabType left_tab_type() const
void InsertBBox(bool h_spread, bool v_spread, BBC *bbox)
static void MergeSimilarTabVectors(const ICOORD &vertical, TabVector_LIST *vectors, BlobGrid *grid)
static void ConvertBoxaToBlobs(int image_width, int image_height, Boxa **boxes, C_BLOB_LIST *blobs)
const int kLineFindGridSize
Grid size used by line finder. Not very critical.
const double kMaxStaveHeight
void set_with_shrink(int x, int y)
Set from the given x,y, shrinking the vector to fit if needed.
const int kMaxLineResidue
void set_left_crossing_rule(int new_left)
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)
const int kMinThickLineWidth
const int kCrackSpacing
Spacing of cracks across the page to break up tall vertical lines.
void outlines_to_blobs(BLOCK *block, ICOORD bleft, ICOORD tright, C_OUTLINE_LIST *outlines)
void set_right_crossing_rule(int new_right)
const TBOX & bounding_box() const
inT16 x() const
access function
void set_left_tab_type(TabType new_type)
const double kMaxNonLineDensity
#define PERF_COUNT_START(FUNCT_NAME)
void set_line_crossings(int value)
void set_left_rule(int new_left)
C_BLOB_LIST * blob_list()
get blobs
TabVector * FindVerticalAlignment(AlignedBlobParams align_params, BLOBNBOX *bbox, int *vertical_x, int *vertical_y)
const int kMinLineLengthFraction
Denominator of resolution makes min pixels to demand line lengths to be.
const double kMinMusicPixelFraction