22 #include "config_auto.h" 32 #include "allheaders.h" 64 static void RemoveUnusedLineSegments(
bool horizontal_lines,
65 BLOBNBOX_LIST* line_bblobs,
67 int height = pixGetHeight(line_pix);
68 BLOBNBOX_IT bbox_it(line_bblobs);
69 for (bbox_it.mark_cycle_pt(); !bbox_it.cycled_list(); bbox_it.forward()) {
73 Box* pixbox =
nullptr;
74 if (horizontal_lines) {
79 pixbox = boxCreate(box.
bottom(), height - box.
right(),
85 pixbox = boxCreate(box.
left(), height - box.
top(),
88 pixClearInRect(line_pix, pixbox);
98 static void SubtractLinesAndResidue(Pix* line_pix, Pix* non_line_pix,
99 int resolution, Pix* src_pix) {
101 pixSubtract(src_pix, src_pix, line_pix);
103 Pix* residue_pix = pixSubtract(
nullptr, src_pix, non_line_pix);
105 Pix* fat_line_pix = pixDilateBrick(
nullptr, line_pix, 3, 3);
107 pixSeedfillBinary(fat_line_pix, fat_line_pix, residue_pix, 8);
109 pixSubtract(src_pix, src_pix, fat_line_pix);
110 pixDestroy(&fat_line_pix);
111 pixDestroy(&residue_pix);
116 static int MaxStrokeWidth(Pix* pix) {
117 Pix* dist_pix = pixDistanceFunction(pix, 4, 8, L_BOUNDARY_BG);
118 int width = pixGetWidth(dist_pix);
119 int height = pixGetHeight(dist_pix);
120 int wpl = pixGetWpl(dist_pix);
121 l_uint32* data = pixGetData(dist_pix);
124 for (
int y = 0; y < height; ++y) {
125 for (
int x = 0; x < width; ++x) {
126 int pixel = GET_DATA_BYTE(data, x);
127 if (pixel > max_dist)
132 pixDestroy(&dist_pix);
137 static int NumTouchingIntersections(Box* line_box, Pix* intersection_pix) {
138 if (intersection_pix ==
nullptr)
return 0;
139 Pix* rect_pix = pixClipRectangle(intersection_pix, line_box,
nullptr);
140 Boxa* boxa = pixConnComp(rect_pix,
nullptr, 8);
141 pixDestroy(&rect_pix);
142 if (boxa ==
nullptr)
return false;
143 int result = boxaGetCount(boxa);
151 static int CountPixelsAdjacentToLine(
int line_width, Box* line_box,
153 l_int32 x, y, box_width, box_height;
154 boxGetGeometry(line_box, &x, &y, &box_width, &box_height);
155 if (box_width > box_height) {
157 int bottom = std::min(pixGetHeight(nonline_pix), y + box_height + line_width);
158 y = std::max(0, y - line_width);
159 box_height = bottom - y;
162 int right = std::min(pixGetWidth(nonline_pix), x + box_width + line_width);
163 x = std::max(0, x - line_width);
164 box_width = right - x;
166 Box* box = boxCreate(x, y, box_width, box_height);
167 Pix* rect_pix = pixClipRectangle(nonline_pix, box,
nullptr);
170 pixCountPixels(rect_pix, &result,
nullptr);
171 pixDestroy(&rect_pix);
184 static int FilterFalsePositives(
int resolution, Pix* nonline_pix,
185 Pix* intersection_pix, Pix* line_pix) {
187 Pixa* pixa =
nullptr;
188 Boxa* boxa = pixConnComp(line_pix, &pixa, 8);
190 int nboxes = boxaGetCount(boxa);
191 int remaining_boxes = nboxes;
192 for (
int i = 0; i < nboxes; ++i) {
193 Box* box = boxaGetBox(boxa, i, L_CLONE);
194 l_int32 x, y, box_width, box_height;
195 boxGetGeometry(box, &x, &y, &box_width, &box_height);
196 Pix* comp_pix = pixaGetPix(pixa, i, L_CLONE);
197 int max_width = MaxStrokeWidth(comp_pix);
198 pixDestroy(&comp_pix);
199 bool bad_line =
false;
203 box_width < min_thick_length && box_height < min_thick_length &&
209 (intersection_pix ==
nullptr ||
210 NumTouchingIntersections(box, intersection_pix) < 2)) {
212 int nonline_count = CountPixelsAdjacentToLine(max_width, box,
219 pixClearInRect(line_pix, box);
226 return remaining_boxes;
242 int* vertical_x,
int* vertical_y,
243 Pix** pix_music_mask,
244 TabVector_LIST* v_lines,
245 TabVector_LIST* h_lines) {
247 if (pix ==
nullptr || vertical_x ==
nullptr || vertical_y ==
nullptr) {
248 tprintf(
"Error in parameters for LineFinder::FindAndRemoveLines\n");
251 Pix* pix_vline =
nullptr;
252 Pix* pix_non_vline =
nullptr;
253 Pix* pix_hline =
nullptr;
254 Pix* pix_non_hline =
nullptr;
255 Pix* pix_intersections =
nullptr;
256 Pixa* pixa_display = debug ? pixaCreate(0) :
nullptr;
257 GetLineMasks(resolution, pix, &pix_vline, &pix_non_vline, &pix_hline,
258 &pix_non_hline, &pix_intersections, pix_music_mask,
261 FindAndRemoveVLines(resolution, pix_intersections, vertical_x, vertical_y,
262 &pix_vline, pix_non_vline, pix, v_lines);
263 if (pix_hline !=
nullptr) {
265 if (pix_vline !=
nullptr)
266 pixAnd(pix_intersections, pix_vline, pix_hline);
268 pixDestroy(&pix_intersections);
269 if (!FilterFalsePositives(resolution, pix_non_hline, pix_intersections,
271 pixDestroy(&pix_hline);
274 FindAndRemoveHLines(resolution, pix_intersections, *vertical_x, *vertical_y,
275 &pix_hline, pix_non_hline, pix, h_lines);
276 if (pixa_display !=
nullptr && pix_vline !=
nullptr)
277 pixaAddPix(pixa_display, pix_vline, L_CLONE);
278 if (pixa_display !=
nullptr && pix_hline !=
nullptr)
279 pixaAddPix(pixa_display, pix_hline, L_CLONE);
280 if (pix_vline !=
nullptr && pix_hline !=
nullptr) {
283 pixAnd(pix_intersections, pix_vline, pix_hline);
286 Pix* pix_join_residue = pixDilateBrick(
nullptr, pix_intersections, 5, 5);
287 pixSeedfillBinary(pix_join_residue, pix_join_residue, pix, 8);
289 pixSubtract(pix, pix, pix_join_residue);
290 pixDestroy(&pix_join_residue);
293 if (pix_music_mask !=
nullptr && *pix_music_mask !=
nullptr) {
294 if (pixa_display !=
nullptr)
295 pixaAddPix(pixa_display, *pix_music_mask, L_CLONE);
296 pixSubtract(pix, pix, *pix_music_mask);
298 if (pixa_display !=
nullptr)
299 pixaAddPix(pixa_display, pix, L_CLONE);
301 pixDestroy(&pix_vline);
302 pixDestroy(&pix_non_vline);
303 pixDestroy(&pix_hline);
304 pixDestroy(&pix_non_hline);
305 pixDestroy(&pix_intersections);
306 if (pixa_display !=
nullptr) {
307 pixaConvertToPdf(pixa_display, resolution, 1.0f, 0, 0,
"LineFinding",
308 "vhlinefinding.pdf");
309 pixaDestroy(&pixa_display);
320 Boxa** boxes, C_BLOB_LIST* blobs) {
321 C_OUTLINE_LIST outlines;
322 C_OUTLINE_IT ol_it = &outlines;
324 int nboxes = boxaGetCount(*boxes);
325 for (
int i = 0; i < nboxes; ++i) {
326 l_int32 x, y, width, height;
327 boxaGetBoxGeometry(*boxes, i, &x, &y, &width, &height);
332 ICOORD bot_right(x + width, y + height);
334 startpt.
pos = top_left;
336 ol_it.add_after_then_move(outline);
343 ICOORD page_br(image_width, image_height);
346 C_BLOB_IT blob_it(blobs);
347 blob_it.add_list_after(block.
blob_list());
362 void LineFinder::FindAndRemoveVLines(
int resolution,
363 Pix* pix_intersections,
364 int* vertical_x,
int* vertical_y,
365 Pix** pix_vline, Pix* pix_non_vline,
366 Pix* src_pix, TabVector_LIST* vectors) {
367 if (pix_vline ==
nullptr || *pix_vline ==
nullptr)
return;
368 C_BLOB_LIST line_cblobs;
369 BLOBNBOX_LIST line_bblobs;
370 GetLineBoxes(
false, *pix_vline, pix_intersections,
371 &line_cblobs, &line_bblobs);
372 int width = pixGetWidth(src_pix);
373 int height = pixGetHeight(src_pix);
375 ICOORD tright(width, height);
376 FindLineVectors(bleft, tright, &line_bblobs, vertical_x, vertical_y, vectors);
377 if (!vectors->empty()) {
378 RemoveUnusedLineSegments(
false, &line_bblobs, *pix_vline);
379 SubtractLinesAndResidue(*pix_vline, pix_non_vline, resolution, src_pix);
384 pixDestroy(pix_vline);
398 void LineFinder::FindAndRemoveHLines(
int resolution,
399 Pix* pix_intersections,
400 int vertical_x,
int vertical_y,
401 Pix** pix_hline, Pix* pix_non_hline,
402 Pix* src_pix, TabVector_LIST* vectors) {
403 if (pix_hline ==
nullptr || *pix_hline ==
nullptr)
return;
404 C_BLOB_LIST line_cblobs;
405 BLOBNBOX_LIST line_bblobs;
406 GetLineBoxes(
true, *pix_hline, pix_intersections, &line_cblobs, &line_bblobs);
407 int width = pixGetWidth(src_pix);
408 int height = pixGetHeight(src_pix);
410 ICOORD tright(height, width);
411 FindLineVectors(bleft, tright, &line_bblobs, &vertical_x, &vertical_y,
413 if (!vectors->empty()) {
414 RemoveUnusedLineSegments(
true, &line_bblobs, *pix_hline);
415 SubtractLinesAndResidue(*pix_hline, pix_non_hline, resolution, src_pix);
422 TabVector_IT h_it(vectors);
423 for (h_it.mark_cycle_pt(); !h_it.cycled_list(); h_it.forward()) {
424 h_it.data()->XYFlip();
427 pixDestroy(pix_hline);
436 void LineFinder::FindLineVectors(
const ICOORD& bleft,
const ICOORD& tright,
437 BLOBNBOX_LIST* line_bblobs,
438 int* vertical_x,
int* vertical_y,
439 TabVector_LIST* vectors) {
440 BLOBNBOX_IT bbox_it(line_bblobs);
445 for (bbox_it.mark_cycle_pt(); !bbox_it.cycled_list(); bbox_it.forward()) {
452 blob_grid.InsertBBox(
false,
true, bblob);
461 TabVector_IT vector_it(vectors);
464 lsearch.StartFullSearch();
465 while ((bbox = lsearch.NextFullSearch()) !=
nullptr) {
469 tprintf(
"Finding line vector starting at bbox (%d,%d)\n",
471 AlignedBlobParams align_params(*vertical_x, *vertical_y, box.
width());
472 TabVector* vector = blob_grid.FindVerticalAlignment(align_params, bbox,
475 if (vector !=
nullptr) {
477 vector_it.add_to_end(vector);
488 static Pix* FilterMusic(
int resolution, Pix* pix_closed,
489 Pix* pix_vline, Pix* pix_hline,
490 l_int32* v_empty, l_int32* h_empty) {
492 Pix* intersection_pix = pixAnd(
nullptr, pix_vline, pix_hline);
493 Boxa* boxa = pixConnComp(pix_vline,
nullptr, 8);
495 int nboxes = boxaGetCount(boxa);
496 Pix* music_mask =
nullptr;
497 for (
int i = 0; i < nboxes; ++i) {
498 Box* box = boxaGetBox(boxa, i, L_CLONE);
499 l_int32 x, y, box_width, box_height;
500 boxGetGeometry(box, &x, &y, &box_width, &box_height);
501 int joins = NumTouchingIntersections(box, intersection_pix);
504 if (joins >= 5 && (joins - 1) * max_stave_height >= 4 * box_height) {
506 if (music_mask ==
nullptr)
507 music_mask = pixCreate(pixGetWidth(pix_vline), pixGetHeight(pix_vline),
509 pixSetInRect(music_mask, box);
514 pixDestroy(&intersection_pix);
515 if (music_mask !=
nullptr) {
519 pixSeedfillBinary(music_mask, music_mask, pix_closed, 8);
523 Boxa* boxa = pixConnComp(music_mask,
nullptr, 8);
525 int nboxes = boxaGetCount(boxa);
526 for (
int i = 0; i < nboxes; ++i) {
527 Box* box = boxaGetBox(boxa, i, L_CLONE);
528 Pix* rect_pix = pixClipRectangle(music_mask, box,
nullptr);
529 l_int32 music_pixels;
530 pixCountPixels(rect_pix, &music_pixels,
nullptr);
531 pixDestroy(&rect_pix);
532 rect_pix = pixClipRectangle(pix_closed, box,
nullptr);
534 pixCountPixels(rect_pix, &all_pixels,
nullptr);
535 pixDestroy(&rect_pix);
538 pixClearInRect(music_mask, box);
542 l_int32 no_remaining_music;
544 pixZero(music_mask, &no_remaining_music);
545 if (no_remaining_music) {
546 pixDestroy(&music_mask);
548 pixSubtract(pix_vline, pix_vline, music_mask);
549 pixSubtract(pix_hline, pix_hline, music_mask);
551 pixZero(pix_vline, v_empty);
552 pixZero(pix_hline, h_empty);
570 void LineFinder::GetLineMasks(
int resolution, Pix* src_pix,
571 Pix** pix_vline, Pix** pix_non_vline,
572 Pix** pix_hline, Pix** pix_non_hline,
573 Pix** pix_intersections, Pix** pix_music_mask,
574 Pixa* pixa_display) {
575 Pix* pix_closed =
nullptr;
576 Pix* pix_hollow =
nullptr;
580 if (pixa_display !=
nullptr) {
581 tprintf(
"Image resolution = %d, max line width = %d, min length=%d\n",
582 resolution, max_line_width, min_line_length);
584 int closing_brick = max_line_width / 3;
589 if (OpenclDevice::selectedDeviceIsOpenCL()) {
591 int clStatus = OpenclDevice::initMorphCLAllocations(pixGetWpl(src_pix),
592 pixGetHeight(src_pix),
594 bool getpixclosed = pix_music_mask !=
nullptr;
595 OpenclDevice::pixGetLinesCL(
nullptr, src_pix, pix_vline, pix_hline,
596 &pix_closed, getpixclosed, closing_brick,
597 closing_brick, max_line_width, max_line_width,
598 min_line_length, min_line_length);
604 pix_closed = pixCloseBrick(
nullptr, src_pix, closing_brick, closing_brick);
605 if (pixa_display !=
nullptr)
606 pixaAddPix(pixa_display, pix_closed, L_CLONE);
609 Pix* pix_solid = pixOpenBrick(
nullptr, pix_closed, max_line_width,
611 if (pixa_display !=
nullptr)
612 pixaAddPix(pixa_display, pix_solid, L_CLONE);
613 pix_hollow = pixSubtract(
nullptr, pix_closed, pix_solid);
615 pixDestroy(&pix_solid);
619 if (pixa_display !=
nullptr)
620 pixaAddPix(pixa_display, pix_hollow, L_CLONE);
621 *pix_vline = pixOpenBrick(
nullptr, pix_hollow, 1, min_line_length);
622 *pix_hline = pixOpenBrick(
nullptr, pix_hollow, min_line_length, 1);
624 pixDestroy(&pix_hollow);
633 pixZero(*pix_vline, &v_empty);
634 pixZero(*pix_hline, &h_empty);
635 if (pix_music_mask !=
nullptr) {
636 if (!v_empty && !h_empty) {
637 *pix_music_mask = FilterMusic(resolution, pix_closed,
638 *pix_vline, *pix_hline,
641 *pix_music_mask =
nullptr;
644 pixDestroy(&pix_closed);
645 Pix* pix_nonlines =
nullptr;
646 *pix_intersections =
nullptr;
647 Pix* extra_non_hlines =
nullptr;
650 pix_nonlines = pixSubtract(
nullptr, src_pix, *pix_vline);
652 pixSubtract(pix_nonlines, pix_nonlines, *pix_hline);
654 *pix_intersections = pixAnd(
nullptr, *pix_vline, *pix_hline);
657 extra_non_hlines = pixSubtract(
nullptr, *pix_vline, *pix_intersections);
659 *pix_non_vline = pixErodeBrick(
nullptr, pix_nonlines,
kMaxLineResidue, 1);
660 pixSeedfillBinary(*pix_non_vline, *pix_non_vline, pix_nonlines, 8);
663 pixOr(*pix_non_vline, *pix_non_vline, *pix_hline);
664 pixSubtract(*pix_non_vline, *pix_non_vline, *pix_intersections);
666 if (!FilterFalsePositives(resolution, *pix_non_vline, *pix_intersections,
668 pixDestroy(pix_vline);
671 pixDestroy(pix_vline);
672 *pix_non_vline =
nullptr;
674 pix_nonlines = pixSubtract(
nullptr, src_pix, *pix_hline);
678 pixDestroy(pix_hline);
679 *pix_non_hline =
nullptr;
684 *pix_non_hline = pixErodeBrick(
nullptr, pix_nonlines, 1,
kMaxLineResidue);
685 pixSeedfillBinary(*pix_non_hline, *pix_non_hline, pix_nonlines, 8);
686 if (extra_non_hlines !=
nullptr) {
687 pixOr(*pix_non_hline, *pix_non_hline, extra_non_hlines);
688 pixDestroy(&extra_non_hlines);
690 if (!FilterFalsePositives(resolution, *pix_non_hline, *pix_intersections,
692 pixDestroy(pix_hline);
694 if (pixa_display !=
nullptr) {
695 if (*pix_vline !=
nullptr) pixaAddPix(pixa_display, *pix_vline, L_CLONE);
696 if (*pix_hline !=
nullptr) pixaAddPix(pixa_display, *pix_hline, L_CLONE);
697 if (pix_nonlines !=
nullptr) pixaAddPix(pixa_display, pix_nonlines, L_CLONE);
698 if (*pix_non_vline !=
nullptr)
699 pixaAddPix(pixa_display, *pix_non_vline, L_CLONE);
700 if (*pix_non_hline !=
nullptr)
701 pixaAddPix(pixa_display, *pix_non_hline, L_CLONE);
702 if (*pix_intersections !=
nullptr)
703 pixaAddPix(pixa_display, *pix_intersections, L_CLONE);
704 if (pix_music_mask !=
nullptr && *pix_music_mask !=
nullptr)
705 pixaAddPix(pixa_display, *pix_music_mask, L_CLONE);
707 pixDestroy(&pix_nonlines);
713 void LineFinder::GetLineBoxes(
bool horizontal_lines,
714 Pix* pix_lines, Pix* pix_intersections,
715 C_BLOB_LIST* line_cblobs,
716 BLOBNBOX_LIST* line_bblobs) {
720 int wpl = pixGetWpl(pix_lines);
721 int width = pixGetWidth(pix_lines);
722 int height = pixGetHeight(pix_lines);
723 l_uint32* data = pixGetData(pix_lines);
724 if (horizontal_lines) {
725 for (
int y = 0; y < height; ++y, data += wpl) {
727 CLEAR_DATA_BIT(data, x);
732 memset(data + wpl * y, 0, wpl *
sizeof(*data));
736 Boxa* boxa = pixConnComp(pix_lines,
nullptr, 8);
739 C_BLOB_IT blob_it(line_cblobs);
740 BLOBNBOX_IT bbox_it(line_bblobs);
741 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
742 C_BLOB* cblob = blob_it.data();
744 bbox_it.add_to_end(bblob);
747 Box* box = boxCreate(bbox.
left(), bbox.
bottom(),
755 if (horizontal_lines) {
void set_with_shrink(int x, int y)
Set from the given x,y, shrinking the vector to fit if needed.
const int kMinLineLengthFraction
Denominator of resolution makes min pixels to demand line lengths to be.
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)
const double kThickLengthMultiple
const int kThinLineFraction
Denominator of resolution makes max pixel width to allow thin lines.
static bool WithinTestRegion(int detail_level, int x, int y)
C_BLOB_LIST * blob_list()
get blobs
#define PERF_COUNT_START(FUNCT_NAME)
void set_left_rule(int new_left)
const int kLineFindGridSize
Grid size used by line finder. Not very critical.
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 set_left_crossing_rule(int new_left)
const double kMinMusicPixelFraction
void set_right_rule(int new_right)
const int kMaxLineResidue
const int kMinThickLineWidth
void set_bounding_box(const TBOX &new_box)
int16_t x() const
access function
TabType left_tab_type() const
GridSearch< BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT > BlobGridSearch
DLLSYM void tprintf(const char *format,...)
static void MergeSimilarTabVectors(const ICOORD &vertical, TabVector_LIST *vectors, BlobGrid *grid)
const TBOX & bounding_box() const
static void ConvertBoxaToBlobs(int image_width, int image_height, Boxa **boxes, C_BLOB_LIST *blobs)
const double kMaxNonLineDensity
void set_left_tab_type(TabType new_type)
void set_right_crossing_rule(int new_right)
const double kMaxStaveHeight
void set_line_crossings(int value)