30 #if defined(USE_OPENCL)
64 static 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);
95 static void SubtractLinesAndResidue(Image line_pix, Image non_line_pix,
int resolution,
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();
113 static 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) {
135 static 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);
153 static 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);
185 static 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;
241 int *vertical_y,
Image *pix_music_mask, TabVector_LIST *v_lines,
242 TabVector_LIST *h_lines) {
243 if (pix ==
nullptr || vertical_x ==
nullptr || vertical_y ==
nullptr) {
244 tprintf(
"Error in parameters for LineFinder::FindAndRemoveLines\n");
247 Image pix_vline =
nullptr;
248 Image pix_non_vline =
nullptr;
249 Image pix_hline =
nullptr;
250 Image pix_non_hline =
nullptr;
251 Image pix_intersections =
nullptr;
252 Pixa *pixa_display = debug ? pixaCreate(0) :
nullptr;
253 GetLineMasks(resolution, pix, &pix_vline, &pix_non_vline, &pix_hline, &pix_non_hline,
254 &pix_intersections, pix_music_mask, pixa_display);
256 FindAndRemoveVLines(resolution, pix_intersections, vertical_x, vertical_y, &pix_vline,
257 pix_non_vline, pix, v_lines);
259 if (pix_hline !=
nullptr) {
261 if (pix_vline !=
nullptr) {
262 pix_intersections = pix_vline & pix_hline;
264 if (!FilterFalsePositives(resolution, pix_non_hline, pix_intersections, pix_hline)) {
268 FindAndRemoveHLines(resolution, pix_intersections, *vertical_x, *vertical_y, &pix_hline,
269 pix_non_hline, pix, h_lines);
270 if (pixa_display !=
nullptr && pix_vline !=
nullptr) {
271 pixaAddPix(pixa_display, pix_vline, L_CLONE);
273 if (pixa_display !=
nullptr && pix_hline !=
nullptr) {
274 pixaAddPix(pixa_display, pix_hline, L_CLONE);
277 if (pix_vline !=
nullptr && pix_hline !=
nullptr) {
280 pix_intersections = pix_vline & pix_hline;
283 Image pix_join_residue = pixDilateBrick(
nullptr, pix_intersections, 5, 5);
284 pixSeedfillBinary(pix_join_residue, pix_join_residue, pix, 8);
286 pixSubtract(pix, pix, pix_join_residue);
290 if (pix_music_mask !=
nullptr && *pix_music_mask !=
nullptr) {
291 if (pixa_display !=
nullptr) {
292 pixaAddPix(pixa_display, *pix_music_mask, L_CLONE);
294 pixSubtract(pix, pix, *pix_music_mask);
296 if (pixa_display !=
nullptr) {
297 pixaAddPix(pixa_display, pix, L_CLONE);
305 if (pixa_display !=
nullptr) {
306 pixaConvertToPdf(pixa_display, resolution, 1.0f, 0, 0,
"LineFinding",
"vhlinefinding.pdf");
307 pixaDestroy(&pixa_display);
317 C_BLOB_LIST *blobs) {
318 C_OUTLINE_LIST outlines;
319 C_OUTLINE_IT ol_it = &outlines;
321 int nboxes = boxaGetCount(*boxes);
322 for (
int i = 0; i < nboxes; ++i) {
323 l_int32 x, y, width, height;
324 boxaGetBoxGeometry(*boxes, i, &x, &y, &width, &height);
329 ICOORD bot_right(x + width, y + height);
331 startpt.
pos = top_left;
332 auto *outline =
new C_OUTLINE(&startpt, top_left, bot_right, 0);
333 ol_it.add_after_then_move(outline);
340 ICOORD page_br(image_width, image_height);
343 C_BLOB_IT blob_it(blobs);
344 blob_it.add_list_after(block.
blob_list());
359 void LineFinder::FindAndRemoveVLines(
int resolution,
Image pix_intersections,
int *vertical_x,
360 int *vertical_y,
Image *pix_vline,
Image pix_non_vline,
361 Image src_pix, TabVector_LIST *vectors) {
362 if (pix_vline ==
nullptr || *pix_vline ==
nullptr) {
365 C_BLOB_LIST line_cblobs;
366 BLOBNBOX_LIST line_bblobs;
367 GetLineBoxes(
false, *pix_vline, pix_intersections, &line_cblobs, &line_bblobs);
368 int width = pixGetWidth(src_pix);
369 int height = pixGetHeight(src_pix);
371 ICOORD tright(width, height);
372 FindLineVectors(bleft, tright, &line_bblobs, vertical_x, vertical_y, vectors);
373 if (!vectors->empty()) {
374 RemoveUnusedLineSegments(
false, &line_bblobs, *pix_vline);
375 SubtractLinesAndResidue(*pix_vline, pix_non_vline, resolution, src_pix);
377 vertical.set_with_shrink(*vertical_x, *vertical_y);
394 void LineFinder::FindAndRemoveHLines(
int resolution, Image pix_intersections,
int vertical_x,
395 int vertical_y, Image *pix_hline, Image pix_non_hline,
396 Image src_pix, TabVector_LIST *vectors) {
397 if (pix_hline ==
nullptr || *pix_hline ==
nullptr) {
400 C_BLOB_LIST line_cblobs;
401 BLOBNBOX_LIST line_bblobs;
402 GetLineBoxes(
true, *pix_hline, pix_intersections, &line_cblobs, &line_bblobs);
403 int width = pixGetWidth(src_pix);
404 int height = pixGetHeight(src_pix);
406 ICOORD tright(height, width);
407 FindLineVectors(bleft, tright, &line_bblobs, &vertical_x, &vertical_y, vectors);
408 if (!vectors->empty()) {
409 RemoveUnusedLineSegments(
true, &line_bblobs, *pix_hline);
410 SubtractLinesAndResidue(*pix_hline, pix_non_hline, resolution, src_pix);
412 vertical.set_with_shrink(vertical_x, vertical_y);
417 TabVector_IT h_it(vectors);
418 for (h_it.mark_cycle_pt(); !h_it.cycled_list(); h_it.forward()) {
419 h_it.data()->XYFlip();
422 pix_hline->destroy();
431 void LineFinder::FindLineVectors(
const ICOORD &bleft,
const ICOORD &tright,
432 BLOBNBOX_LIST *line_bblobs,
int *vertical_x,
int *vertical_y,
433 TabVector_LIST *vectors) {
434 BLOBNBOX_IT bbox_it(line_bblobs);
439 for (bbox_it.mark_cycle_pt(); !bbox_it.cycled_list(); bbox_it.forward()) {
440 BLOBNBOX *bblob = bbox_it.data();
442 bblob->set_left_rule(bleft.x());
443 bblob->set_right_rule(tright.x());
444 bblob->set_left_crossing_rule(bleft.x());
445 bblob->set_right_crossing_rule(tright.x());
446 blob_grid.InsertBBox(
false,
true, bblob);
456 TabVector_IT vector_it(vectors);
459 lsearch.StartFullSearch();
460 while ((bbox = lsearch.NextFullSearch()) !=
nullptr) {
462 const TBOX &box = bbox->bounding_box();
464 tprintf(
"Finding line vector starting at bbox (%d,%d)\n", box.left(), box.bottom());
466 AlignedBlobParams align_params(*vertical_x, *vertical_y, box.width());
468 blob_grid.FindVerticalAlignment(align_params, bbox, vertical_x, vertical_y);
469 if (vector !=
nullptr) {
471 vector_it.add_to_end(vector);
482 static Image FilterMusic(
int resolution, Image pix_closed, Image pix_vline, Image pix_hline,
483 bool &v_empty,
bool &h_empty) {
485 Image intersection_pix = pix_vline & pix_hline;
486 Boxa *boxa = pixConnComp(pix_vline,
nullptr, 8);
488 int nboxes = boxaGetCount(boxa);
489 Image music_mask =
nullptr;
490 for (
int i = 0; i < nboxes; ++i) {
491 Box *box = boxaGetBox(boxa, i, L_CLONE);
492 l_int32 x, y, box_width, box_height;
493 boxGetGeometry(box, &x, &y, &box_width, &box_height);
494 int joins = NumTouchingIntersections(box, intersection_pix);
497 if (joins >= 5 && (joins - 1) * max_stave_height >= 4 * box_height) {
499 if (music_mask ==
nullptr) {
500 music_mask = pixCreate(pixGetWidth(pix_vline), pixGetHeight(pix_vline), 1);
502 pixSetInRect(music_mask, box);
507 intersection_pix.destroy();
508 if (music_mask !=
nullptr) {
512 pixSeedfillBinary(music_mask, music_mask, pix_closed, 8);
516 Boxa *boxa = pixConnComp(music_mask,
nullptr, 8);
518 int nboxes = boxaGetCount(boxa);
519 for (
int i = 0; i < nboxes; ++i) {
520 Box *box = boxaGetBox(boxa, i, L_CLONE);
521 Image rect_pix = pixClipRectangle(music_mask, box,
nullptr);
522 l_int32 music_pixels;
523 pixCountPixels(rect_pix, &music_pixels,
nullptr);
525 rect_pix = pixClipRectangle(pix_closed, box,
nullptr);
527 pixCountPixels(rect_pix, &all_pixels,
nullptr);
531 pixClearInRect(music_mask, box);
536 if (music_mask.isZero()) {
537 music_mask.destroy();
539 pixSubtract(pix_vline, pix_vline, music_mask);
540 pixSubtract(pix_hline, pix_hline, music_mask);
542 v_empty = pix_vline.isZero();
543 h_empty = pix_hline.isZero();
561 void LineFinder::GetLineMasks(
int resolution, Image src_pix, Image *pix_vline, Image *pix_non_vline,
562 Image *pix_hline, Image *pix_non_hline, Image *pix_intersections,
563 Image *pix_music_mask, Pixa *pixa_display) {
564 Image pix_closed =
nullptr;
565 Image pix_hollow =
nullptr;
569 if (pixa_display !=
nullptr) {
570 tprintf(
"Image resolution = %d, max line width = %d, min length=%d\n", resolution,
571 max_line_width, min_line_length);
573 int closing_brick = max_line_width / 3;
577 if (OpenclDevice::selectedDeviceIsOpenCL()) {
580 OpenclDevice::initMorphCLAllocations(pixGetWpl(src_pix), pixGetHeight(src_pix), src_pix);
581 bool getpixclosed = pix_music_mask !=
nullptr;
582 OpenclDevice::pixGetLinesCL(
nullptr, src_pix, pix_vline, pix_hline, &pix_closed, getpixclosed,
583 closing_brick, closing_brick, max_line_width, max_line_width,
584 min_line_length, min_line_length);
590 pix_closed = pixCloseBrick(
nullptr, src_pix, closing_brick, closing_brick);
591 if (pixa_display !=
nullptr) {
592 pixaAddPix(pixa_display, pix_closed, L_CLONE);
597 Image pix_solid = pixOpenBrick(
nullptr, pix_closed, max_line_width, max_line_width);
598 if (pixa_display !=
nullptr) {
599 pixaAddPix(pixa_display, pix_solid, L_CLONE);
601 pix_hollow = pixSubtract(
nullptr, pix_closed, pix_solid);
607 if (pixa_display !=
nullptr) {
608 pixaAddPix(pixa_display, pix_hollow, L_CLONE);
610 *pix_vline = pixOpenBrick(
nullptr, pix_hollow, 1, min_line_length);
611 *pix_hline = pixOpenBrick(
nullptr, pix_hollow, min_line_length, 1);
613 pix_hollow.destroy();
619 bool v_empty = pix_vline->isZero();
620 bool h_empty = pix_hline->isZero();
621 if (pix_music_mask !=
nullptr) {
622 if (!v_empty && !h_empty) {
624 FilterMusic(resolution, pix_closed, *pix_vline, *pix_hline, v_empty, h_empty);
626 *pix_music_mask =
nullptr;
629 pix_closed.destroy();
630 Image pix_nonlines =
nullptr;
631 *pix_intersections =
nullptr;
632 Image extra_non_hlines =
nullptr;
635 pix_nonlines = pixSubtract(
nullptr, src_pix, *pix_vline);
637 pixSubtract(pix_nonlines, pix_nonlines, *pix_hline);
639 *pix_intersections = *pix_vline & *pix_hline;
642 extra_non_hlines = pixSubtract(
nullptr, *pix_vline, *pix_intersections);
644 *pix_non_vline = pixErodeBrick(
nullptr, pix_nonlines,
kMaxLineResidue, 1);
645 pixSeedfillBinary(*pix_non_vline, *pix_non_vline, pix_nonlines, 8);
648 *pix_non_vline |= *pix_hline;
649 pixSubtract(*pix_non_vline, *pix_non_vline, *pix_intersections);
651 if (!FilterFalsePositives(resolution, *pix_non_vline, *pix_intersections, *pix_vline)) {
652 pix_vline->destroy();
656 pix_vline->destroy();
657 *pix_non_vline =
nullptr;
659 pix_nonlines = pixSubtract(
nullptr, src_pix, *pix_hline);
663 pix_hline->destroy();
664 *pix_non_hline =
nullptr;
669 *pix_non_hline = pixErodeBrick(
nullptr, pix_nonlines, 1,
kMaxLineResidue);
670 pixSeedfillBinary(*pix_non_hline, *pix_non_hline, pix_nonlines, 8);
671 if (extra_non_hlines !=
nullptr) {
672 *pix_non_hline |= extra_non_hlines;
673 extra_non_hlines.destroy();
675 if (!FilterFalsePositives(resolution, *pix_non_hline, *pix_intersections, *pix_hline)) {
676 pix_hline->destroy();
679 if (pixa_display !=
nullptr) {
680 if (*pix_vline !=
nullptr) {
681 pixaAddPix(pixa_display, *pix_vline, L_CLONE);
683 if (*pix_hline !=
nullptr) {
684 pixaAddPix(pixa_display, *pix_hline, L_CLONE);
686 if (pix_nonlines !=
nullptr) {
687 pixaAddPix(pixa_display, pix_nonlines, L_CLONE);
689 if (*pix_non_vline !=
nullptr) {
690 pixaAddPix(pixa_display, *pix_non_vline, L_CLONE);
692 if (*pix_non_hline !=
nullptr) {
693 pixaAddPix(pixa_display, *pix_non_hline, L_CLONE);
695 if (*pix_intersections !=
nullptr) {
696 pixaAddPix(pixa_display, *pix_intersections, L_CLONE);
698 if (pix_music_mask !=
nullptr && *pix_music_mask !=
nullptr) {
699 pixaAddPix(pixa_display, *pix_music_mask, L_CLONE);
702 pix_nonlines.destroy();
708 void LineFinder::GetLineBoxes(
bool horizontal_lines, Image pix_lines, Image pix_intersections,
709 C_BLOB_LIST *line_cblobs, BLOBNBOX_LIST *line_bblobs) {
713 int wpl = pixGetWpl(pix_lines);
714 int width = pixGetWidth(pix_lines);
715 int height = pixGetHeight(pix_lines);
716 l_uint32 *data = pixGetData(pix_lines);
717 if (horizontal_lines) {
718 for (
int y = 0; y < height; ++y, data += wpl) {
720 CLEAR_DATA_BIT(data, x);
725 memset(data + wpl * y, 0, wpl *
sizeof(*data));
729 Boxa *boxa = pixConnComp(pix_lines,
nullptr, 8);
732 C_BLOB_IT blob_it(line_cblobs);
733 BLOBNBOX_IT bbox_it(line_bblobs);
734 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
735 C_BLOB *cblob = blob_it.data();
736 auto *bblob =
new BLOBNBOX(cblob);
737 bbox_it.add_to_end(bblob);
739 const TBOX &bbox = bblob->bounding_box();
740 Box *box = boxCreate(bbox.left(), bbox.bottom(), bbox.width(), bbox.height());
741 bblob->set_line_crossings(NumTouchingIntersections(box, pix_intersections));
747 if (horizontal_lines) {
752 TBOX new_box(height - bbox.top(), bbox.left(), height - bbox.bottom(), bbox.right());
753 bblob->set_bounding_box(new_box);
755 TBOX new_box(bbox.left(), height - bbox.top(), bbox.right(), height - bbox.bottom());
756 bblob->set_bounding_box(new_box);
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
TabType left_tab_type() const
const TBOX & bounding_box() const
C_BLOB_LIST * blob_list()
get blobs
TDimension height() const
TDimension bottom() const
static bool WithinTestRegion(int detail_level, int x, int y)
static void ConvertBoxaToBlobs(int image_width, int image_height, Boxa **boxes, C_BLOB_LIST *blobs)
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)