21# include "config_auto.h"
30#if defined(USE_OPENCL)
64static void RemoveUnusedLineSegments(
bool horizontal_lines, BLOBNBOX_LIST *line_bblobs,
66 int height = pixGetHeight(line_pix);
67 BLOBNBOX_IT bbox_it(line_bblobs);
68 for (bbox_it.mark_cycle_pt(); !bbox_it.cycled_list(); bbox_it.forward()) {
72 Box *pixbox =
nullptr;
73 if (horizontal_lines) {
85 pixClearInRect(line_pix, pixbox);
95static void SubtractLinesAndResidue(Image line_pix, Image non_line_pix,
98 pixSubtract(src_pix, src_pix, line_pix);
100 Image residue_pix = pixSubtract(
nullptr, src_pix, non_line_pix);
102 Image fat_line_pix = pixDilateBrick(
nullptr, line_pix, 3, 3);
104 pixSeedfillBinary(fat_line_pix, fat_line_pix, residue_pix, 8);
106 pixSubtract(src_pix, src_pix, fat_line_pix);
107 fat_line_pix.destroy();
108 residue_pix.destroy();
113static int MaxStrokeWidth(Image pix) {
114 Image dist_pix = pixDistanceFunction(pix, 4, 8, L_BOUNDARY_BG);
115 int width = pixGetWidth(dist_pix);
116 int height = pixGetHeight(dist_pix);
117 int wpl = pixGetWpl(dist_pix);
118 l_uint32 *data = pixGetData(dist_pix);
121 for (
int y = 0;
y < height; ++
y) {
122 for (
int x = 0;
x < width; ++
x) {
123 int pixel = GET_DATA_BYTE(data,
x);
124 if (pixel > max_dist) {
135static int NumTouchingIntersections(Box *line_box, Image intersection_pix) {
136 if (intersection_pix ==
nullptr) {
139 Image rect_pix = pixClipRectangle(intersection_pix, line_box,
nullptr);
140 Boxa *boxa = pixConnComp(rect_pix,
nullptr, 8);
142 if (boxa ==
nullptr) {
145 int result = boxaGetCount(boxa);
153static int CountPixelsAdjacentToLine(
int line_width, Box *line_box, Image nonline_pix) {
154 l_int32
x,
y, box_width, box_height;
155 boxGetGeometry(line_box, &
x, &
y, &box_width, &box_height);
156 if (box_width > box_height) {
158 int bottom = std::min(pixGetHeight(nonline_pix),
y + box_height + line_width);
159 y = std::max(0,
y - line_width);
160 box_height = bottom -
y;
163 int right = std::min(pixGetWidth(nonline_pix),
x + box_width + line_width);
164 x = std::max(0,
x - line_width);
165 box_width = right -
x;
167 Box *box = boxCreate(
x,
y, box_width, box_height);
168 Image rect_pix = pixClipRectangle(nonline_pix, box,
nullptr);
171 pixCountPixels(rect_pix, &result,
nullptr);
185static int FilterFalsePositives(
int resolution, Image nonline_pix, Image intersection_pix,
188 Pixa *pixa =
nullptr;
189 Boxa *boxa = pixConnComp(line_pix, &pixa, 8);
191 int nboxes = boxaGetCount(boxa);
192 int remaining_boxes = nboxes;
193 for (
int i = 0;
i < nboxes; ++
i) {
194 Box *box = boxaGetBox(boxa,
i, L_CLONE);
195 l_int32
x,
y, box_width, box_height;
196 boxGetGeometry(box, &
x, &
y, &box_width, &box_height);
197 Image comp_pix = pixaGetPix(pixa,
i, L_CLONE);
198 int max_width = MaxStrokeWidth(comp_pix);
200 bool bad_line =
false;
204 box_width < min_thick_length && box_height < min_thick_length &&
209 if (!bad_line && (NumTouchingIntersections(box, intersection_pix) < 2)) {
211 int nonline_count = CountPixelsAdjacentToLine(max_width, box, nonline_pix);
218 pixClearInRect(line_pix, box);
225 return remaining_boxes;
233static void ConvertBoxaToBlobs(
int image_width,
int image_height, Boxa **boxes,
234 C_BLOB_LIST *blobs) {
235 C_OUTLINE_LIST outlines;
236 C_OUTLINE_IT ol_it = &outlines;
238 int nboxes = boxaGetCount(*boxes);
239 for (
int i = 0;
i < nboxes; ++
i) {
240 l_int32
x,
y, width, height;
241 boxaGetBoxGeometry(*boxes,
i, &
x, &
y, &width, &height);
245 ICOORD top_left(
x,
y);
246 ICOORD bot_right(
x + width,
y + height);
248 startpt.pos = top_left;
249 auto *outline =
new C_OUTLINE(&startpt, top_left, bot_right, 0);
250 ol_it.add_after_then_move(outline);
256 ICOORD page_tl(0, 0);
257 ICOORD page_br(image_width, image_height);
260 C_BLOB_IT blob_it(blobs);
261 blob_it.add_list_after(block.blob_list());
269static void GetLineBoxes(
bool horizontal_lines, Image pix_lines, Image pix_intersections,
270 C_BLOB_LIST *line_cblobs, BLOBNBOX_LIST *line_bblobs) {
274 int wpl = pixGetWpl(pix_lines);
275 int width = pixGetWidth(pix_lines);
276 int height = pixGetHeight(pix_lines);
277 l_uint32 *data = pixGetData(pix_lines);
278 if (horizontal_lines) {
279 for (
int y = 0;
y < height; ++
y, data += wpl) {
281 CLEAR_DATA_BIT(data,
x);
286 memset(data + wpl *
y, 0, wpl *
sizeof(*data));
290 Boxa *boxa = pixConnComp(pix_lines,
nullptr, 8);
291 ConvertBoxaToBlobs(width, height, &boxa, line_cblobs);
293 C_BLOB_IT blob_it(line_cblobs);
294 BLOBNBOX_IT bbox_it(line_bblobs);
295 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
296 C_BLOB *cblob = blob_it.data();
297 auto *bblob =
new BLOBNBOX(cblob);
298 bbox_it.add_to_end(bblob);
300 const TBOX &bbox = bblob->bounding_box();
301 Box *box = boxCreate(bbox.left(), bbox.bottom(), bbox.width(), bbox.height());
302 bblob->set_line_crossings(NumTouchingIntersections(box, pix_intersections));
308 if (horizontal_lines) {
313 TBOX new_box(height - bbox.top(), bbox.left(), height - bbox.bottom(), bbox.right());
314 bblob->set_bounding_box(new_box);
316 TBOX new_box(bbox.left(), height - bbox.top(), bbox.right(), height - bbox.bottom());
317 bblob->set_bounding_box(new_box);
327static void FindLineVectors(
const ICOORD &bleft,
const ICOORD &tright,
328 BLOBNBOX_LIST *line_bblobs,
int *vertical_x,
int *vertical_y,
329 TabVector_LIST *vectors) {
330 BLOBNBOX_IT bbox_it(line_bblobs);
335 for (bbox_it.mark_cycle_pt(); !bbox_it.cycled_list(); bbox_it.forward()) {
336 BLOBNBOX *bblob = bbox_it.data();
338 bblob->set_left_rule(bleft.x());
339 bblob->set_right_rule(tright.x());
340 bblob->set_left_crossing_rule(bleft.x());
341 bblob->set_right_crossing_rule(tright.x());
342 blob_grid.InsertBBox(
false,
true, bblob);
352 TabVector_IT vector_it(vectors);
355 lsearch.StartFullSearch();
356 while ((bbox = lsearch.NextFullSearch()) !=
nullptr) {
358 const TBOX &box = bbox->bounding_box();
360 tprintf(
"Finding line vector starting at bbox (%d,%d)\n", box.left(), box.bottom());
362 AlignedBlobParams align_params(*vertical_x, *vertical_y, box.width());
364 blob_grid.FindVerticalAlignment(align_params, bbox, vertical_x, vertical_y);
365 if (vector !=
nullptr) {
367 vector_it.add_to_end(vector);
378static Image FilterMusic(
int resolution, Image pix_closed, Image pix_vline, Image pix_hline,
379 bool &v_empty,
bool &h_empty) {
381 Image intersection_pix = pix_vline & pix_hline;
382 Boxa *boxa = pixConnComp(pix_vline,
nullptr, 8);
384 int nboxes = boxaGetCount(boxa);
385 Image music_mask =
nullptr;
386 for (
int i = 0;
i < nboxes; ++
i) {
387 Box *box = boxaGetBox(boxa,
i, L_CLONE);
388 l_int32
x,
y, box_width, box_height;
389 boxGetGeometry(box, &
x, &
y, &box_width, &box_height);
390 int joins = NumTouchingIntersections(box, intersection_pix);
393 if (joins >= 5 && (joins - 1) * max_stave_height >= 4 * box_height) {
395 if (music_mask ==
nullptr) {
396 music_mask = pixCreate(pixGetWidth(pix_vline), pixGetHeight(pix_vline), 1);
398 pixSetInRect(music_mask, box);
403 intersection_pix.destroy();
404 if (music_mask !=
nullptr) {
408 pixSeedfillBinary(music_mask, music_mask, pix_closed, 8);
412 Boxa *boxa = pixConnComp(music_mask,
nullptr, 8);
414 int nboxes = boxaGetCount(boxa);
415 for (
int i = 0;
i < nboxes; ++
i) {
416 Box *box = boxaGetBox(boxa,
i, L_CLONE);
417 Image rect_pix = pixClipRectangle(music_mask, box,
nullptr);
418 l_int32 music_pixels;
419 pixCountPixels(rect_pix, &music_pixels,
nullptr);
421 rect_pix = pixClipRectangle(pix_closed, box,
nullptr);
423 pixCountPixels(rect_pix, &all_pixels,
nullptr);
427 pixClearInRect(music_mask, box);
432 if (music_mask.isZero()) {
433 music_mask.destroy();
435 pixSubtract(pix_vline, pix_vline, music_mask);
436 pixSubtract(pix_hline, pix_hline, music_mask);
438 v_empty = pix_vline.isZero();
439 h_empty = pix_hline.isZero();
458static void GetLineMasks(
int resolution, Image src_pix, Image *pix_vline, Image *pix_non_vline,
459 Image *pix_hline, Image *pix_non_hline, Image *pix_intersections,
460 Image *pix_music_mask, Pixa *pixa_display) {
461 Image pix_closed =
nullptr;
462 Image pix_hollow =
nullptr;
466 if (pixa_display !=
nullptr) {
467 tprintf(
"Image resolution = %d, max line width = %d, min length=%d\n", resolution,
468 max_line_width, min_line_length);
470 int closing_brick = max_line_width / 3;
474 if (OpenclDevice::selectedDeviceIsOpenCL()) {
477 OpenclDevice::initMorphCLAllocations(pixGetWpl(src_pix), pixGetHeight(src_pix), src_pix);
478 bool getpixclosed = pix_music_mask !=
nullptr;
479 OpenclDevice::pixGetLinesCL(
nullptr, src_pix, pix_vline, pix_hline, &pix_closed, getpixclosed,
480 closing_brick, closing_brick, max_line_width, max_line_width,
481 min_line_length, min_line_length);
487 pix_closed = pixCloseBrick(
nullptr, src_pix, closing_brick, closing_brick);
488 if (pixa_display !=
nullptr) {
489 pixaAddPix(pixa_display, pix_closed, L_CLONE);
494 Image pix_solid = pixOpenBrick(
nullptr, pix_closed, max_line_width, max_line_width);
495 if (pixa_display !=
nullptr) {
496 pixaAddPix(pixa_display, pix_solid, L_CLONE);
498 pix_hollow = pixSubtract(
nullptr, pix_closed, pix_solid);
504 if (pixa_display !=
nullptr) {
505 pixaAddPix(pixa_display, pix_hollow, L_CLONE);
507 *pix_vline = pixOpenBrick(
nullptr, pix_hollow, 1, min_line_length);
508 *pix_hline = pixOpenBrick(
nullptr, pix_hollow, min_line_length, 1);
510 pix_hollow.destroy();
516 bool v_empty = pix_vline->isZero();
517 bool h_empty = pix_hline->isZero();
518 if (pix_music_mask !=
nullptr) {
519 if (!v_empty && !h_empty) {
521 FilterMusic(resolution, pix_closed, *pix_vline, *pix_hline, v_empty, h_empty);
523 *pix_music_mask =
nullptr;
526 pix_closed.destroy();
527 Image pix_nonlines =
nullptr;
528 *pix_intersections =
nullptr;
529 Image extra_non_hlines =
nullptr;
532 pix_nonlines = pixSubtract(
nullptr, src_pix, *pix_vline);
534 pixSubtract(pix_nonlines, pix_nonlines, *pix_hline);
536 *pix_intersections = *pix_vline & *pix_hline;
539 extra_non_hlines = pixSubtract(
nullptr, *pix_vline, *pix_intersections);
541 *pix_non_vline = pixErodeBrick(
nullptr, pix_nonlines,
kMaxLineResidue, 1);
542 pixSeedfillBinary(*pix_non_vline, *pix_non_vline, pix_nonlines, 8);
545 *pix_non_vline |= *pix_hline;
546 pixSubtract(*pix_non_vline, *pix_non_vline, *pix_intersections);
548 if (!FilterFalsePositives(resolution, *pix_non_vline, *pix_intersections, *pix_vline)) {
549 pix_vline->destroy();
553 pix_vline->destroy();
554 *pix_non_vline =
nullptr;
556 pix_nonlines = pixSubtract(
nullptr, src_pix, *pix_hline);
560 pix_hline->destroy();
561 *pix_non_hline =
nullptr;
566 *pix_non_hline = pixErodeBrick(
nullptr, pix_nonlines, 1,
kMaxLineResidue);
567 pixSeedfillBinary(*pix_non_hline, *pix_non_hline, pix_nonlines, 8);
568 if (extra_non_hlines !=
nullptr) {
569 *pix_non_hline |= extra_non_hlines;
570 extra_non_hlines.destroy();
572 if (!FilterFalsePositives(resolution, *pix_non_hline, *pix_intersections, *pix_hline)) {
573 pix_hline->destroy();
576 if (pixa_display !=
nullptr) {
577 if (*pix_vline !=
nullptr) {
578 pixaAddPix(pixa_display, *pix_vline, L_CLONE);
580 if (*pix_hline !=
nullptr) {
581 pixaAddPix(pixa_display, *pix_hline, L_CLONE);
583 if (pix_nonlines !=
nullptr) {
584 pixaAddPix(pixa_display, pix_nonlines, L_CLONE);
586 if (*pix_non_vline !=
nullptr) {
587 pixaAddPix(pixa_display, *pix_non_vline, L_CLONE);
589 if (*pix_non_hline !=
nullptr) {
590 pixaAddPix(pixa_display, *pix_non_hline, L_CLONE);
592 if (*pix_intersections !=
nullptr) {
593 pixaAddPix(pixa_display, *pix_intersections, L_CLONE);
595 if (pix_music_mask !=
nullptr && *pix_music_mask !=
nullptr) {
596 pixaAddPix(pixa_display, *pix_music_mask, L_CLONE);
599 pix_nonlines.destroy();
612static void FindAndRemoveVLines(Image pix_intersections,
int *vertical_x,
613 int *vertical_y, Image *pix_vline, Image pix_non_vline,
614 Image src_pix, TabVector_LIST *vectors) {
615 if (pix_vline ==
nullptr || *pix_vline ==
nullptr) {
618 C_BLOB_LIST line_cblobs;
619 BLOBNBOX_LIST line_bblobs;
620 GetLineBoxes(
false, *pix_vline, pix_intersections, &line_cblobs, &line_bblobs);
621 int width = pixGetWidth(src_pix);
622 int height = pixGetHeight(src_pix);
624 ICOORD tright(width, height);
625 FindLineVectors(bleft, tright, &line_bblobs, vertical_x, vertical_y, vectors);
626 if (!vectors->empty()) {
627 RemoveUnusedLineSegments(
false, &line_bblobs, *pix_vline);
628 SubtractLinesAndResidue(*pix_vline, pix_non_vline, src_pix);
630 vertical.set_with_shrink(*vertical_x, *vertical_y);
633 pix_vline->destroy();
647static void FindAndRemoveHLines(Image pix_intersections,
int vertical_x,
648 int vertical_y, Image *pix_hline, Image pix_non_hline,
649 Image src_pix, TabVector_LIST *vectors) {
650 if (pix_hline ==
nullptr || *pix_hline ==
nullptr) {
653 C_BLOB_LIST line_cblobs;
654 BLOBNBOX_LIST line_bblobs;
655 GetLineBoxes(
true, *pix_hline, pix_intersections, &line_cblobs, &line_bblobs);
656 int width = pixGetWidth(src_pix);
657 int height = pixGetHeight(src_pix);
659 ICOORD tright(height, width);
660 FindLineVectors(bleft, tright, &line_bblobs, &vertical_x, &vertical_y, vectors);
661 if (!vectors->empty()) {
662 RemoveUnusedLineSegments(
true, &line_bblobs, *pix_hline);
663 SubtractLinesAndResidue(*pix_hline, pix_non_hline, src_pix);
665 vertical.set_with_shrink(vertical_x, vertical_y);
670 TabVector_IT h_it(vectors);
671 for (h_it.mark_cycle_pt(); !h_it.cycled_list(); h_it.forward()) {
672 h_it.data()->XYFlip();
675 pix_hline->destroy();
692 int *vertical_y,
Image *pix_music_mask, TabVector_LIST *v_lines,
693 TabVector_LIST *h_lines) {
694 if (pix ==
nullptr || vertical_x ==
nullptr || vertical_y ==
nullptr) {
695 tprintf(
"Error in parameters for LineFinder::FindAndRemoveLines\n");
698 Image pix_vline =
nullptr;
699 Image pix_non_vline =
nullptr;
700 Image pix_hline =
nullptr;
701 Image pix_non_hline =
nullptr;
702 Image pix_intersections =
nullptr;
703 Pixa *pixa_display = debug ? pixaCreate(0) :
nullptr;
704 GetLineMasks(resolution, pix, &pix_vline, &pix_non_vline, &pix_hline, &pix_non_hline,
705 &pix_intersections, pix_music_mask, pixa_display);
707 FindAndRemoveVLines(pix_intersections, vertical_x, vertical_y, &pix_vline,
708 pix_non_vline, pix, v_lines);
710 if (pix_hline !=
nullptr) {
712 if (pix_vline !=
nullptr) {
713 pix_intersections = pix_vline & pix_hline;
715 if (!FilterFalsePositives(resolution, pix_non_hline, pix_intersections, pix_hline)) {
719 FindAndRemoveHLines(pix_intersections, *vertical_x, *vertical_y, &pix_hline,
720 pix_non_hline, pix, h_lines);
721 if (pixa_display !=
nullptr && pix_vline !=
nullptr) {
722 pixaAddPix(pixa_display, pix_vline, L_CLONE);
724 if (pixa_display !=
nullptr && pix_hline !=
nullptr) {
725 pixaAddPix(pixa_display, pix_hline, L_CLONE);
728 if (pix_vline !=
nullptr && pix_hline !=
nullptr) {
731 pix_intersections = pix_vline & pix_hline;
734 Image pix_join_residue = pixDilateBrick(
nullptr, pix_intersections, 5, 5);
735 pixSeedfillBinary(pix_join_residue, pix_join_residue, pix, 8);
737 pixSubtract(pix, pix, pix_join_residue);
741 if (pix_music_mask !=
nullptr && *pix_music_mask !=
nullptr) {
742 if (pixa_display !=
nullptr) {
743 pixaAddPix(pixa_display, *pix_music_mask, L_CLONE);
745 pixSubtract(pix, pix, *pix_music_mask);
747 if (pixa_display !=
nullptr) {
748 pixaAddPix(pixa_display, pix, L_CLONE);
756 if (pixa_display !=
nullptr) {
757 pixaConvertToPdf(pixa_display, resolution, 1.0f, 0, 0,
"LineFinding",
"vhlinefinding.pdf");
758 pixaDestroy(&pixa_display);
const double kMinMusicPixelFraction
const double kMaxStaveHeight
void tprintf(const char *format,...)
const int kCrackSpacing
Spacing of cracks across the page to break up tall vertical lines.
const double kThickLengthMultiple
const int kMinThickLineWidth
const double kMaxNonLineDensity
void outlines_to_blobs(BLOCK *block, ICOORD bleft, ICOORD tright, C_OUTLINE_LIST *outlines)
const int kMinLineLengthFraction
Denominator of resolution makes min pixels to demand line lengths to be.
const int kMaxLineResidue
const int kLineFindGridSize
Grid size used by line finder. Not very critical.
const int kThinLineFraction
Denominator of resolution makes max pixel width to allow thin lines.
GridSearch< BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT > BlobGridSearch
const TBOX & bounding_box() const
TabType left_tab_type() const
TDimension height() const
TDimension bottom() const
static bool WithinTestRegion(int detail_level, int x, int y)
static void FindAndRemoveLines(int resolution, bool debug, Image pix, int *vertical_x, int *vertical_y, Image *pix_music_mask, TabVector_LIST *v_lines, TabVector_LIST *h_lines)
static void MergeSimilarTabVectors(const ICOORD &vertical, TabVector_LIST *vectors, BlobGrid *grid)