All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
linefind.cpp
Go to the documentation of this file.
1 // File: linefind.cpp
3 // Description: Class to find vertical lines in an image and create
4 // a corresponding list of empty blobs.
5 // Author: Ray Smith
6 // Created: Thu Mar 20 09:49:01 PDT 2008
7 //
8 // (C) Copyright 2008, Google Inc.
9 // Licensed under the Apache License, Version 2.0 (the "License");
10 // you may not use this file except in compliance with the License.
11 // You may obtain a copy of the License at
12 // http://www.apache.org/licenses/LICENSE-2.0
13 // Unless required by applicable law or agreed to in writing, software
14 // distributed under the License is distributed on an "AS IS" BASIS,
15 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 // See the License for the specific language governing permissions and
17 // limitations under the License.
18 //
20 
21 #ifdef _MSC_VER
22 #pragma warning(disable:4244) // Conversion warnings
23 #endif
24 
25 #ifdef HAVE_CONFIG_H
26 #include "config_auto.h"
27 #endif
28 
29 #include "linefind.h"
30 #include "alignedblob.h"
31 #include "tabvector.h"
32 #include "blobbox.h"
33 #include "edgblob.h"
34 #include "openclwrapper.h"
35 
36 #include "allheaders.h"
37 
38 namespace tesseract {
39 
41 const int kThinLineFraction = 20;
43 const int kMinLineLengthFraction = 4;
45 const int kCrackSpacing = 100;
47 const int kLineFindGridSize = 50;
48 // Min width of a line in pixels to be considered thick.
49 const int kMinThickLineWidth = 12;
50 // Max size of line residue. (The pixels that fail the long thin opening, and
51 // therefore don't make it to the candidate line mask, but are nevertheless
52 // part of the line.)
53 const int kMaxLineResidue = 6;
54 // Min length in inches of a line segment that exceeds kMinThickLineWidth in
55 // thickness. (Such lines shouldn't break by simple image degradation.)
56 const double kThickLengthMultiple = 0.75;
57 // Max fraction of line box area that can be occupied by non-line pixels.
58 const double kMaxNonLineDensity = 0.25;
59 // Max height of a music stave in inches.
60 const double kMaxStaveHeight = 1.0;
61 // Minimum fraction of pixels in a music rectangle connected to the staves.
62 const double kMinMusicPixelFraction = 0.75;
63 
64 // Erases the unused blobs from the line_pix image, taking into account
65 // whether this was a horizontal or vertical line set.
66 static void RemoveUnusedLineSegments(bool horizontal_lines,
67  BLOBNBOX_LIST* line_bblobs,
68  Pix* line_pix) {
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()) {
72  BLOBNBOX* blob = bbox_it.data();
73  if (blob->left_tab_type() != TT_VLINE) {
74  const TBOX& box = blob->bounding_box();
75  Box* pixbox = NULL;
76  if (horizontal_lines) {
77  // Horizontal lines are in tess format and also have x and y flipped
78  // (to use FindVerticalAlignment) so we have to flip x and y and then
79  // convert to Leptonica by height - flipped x (ie the right edge).
80  // See GetLineBoxes for more explanation.
81  pixbox = boxCreate(box.bottom(), height - box.right(),
82  box.height(), box.width());
83  } else {
84  // For vertical lines, just flip upside-down to convert to Leptonica.
85  // The y position of the box in Leptonica terms is the distance from
86  // the top of the image to the top of the box.
87  pixbox = boxCreate(box.left(), height - box.top(),
88  box.width(), box.height());
89  }
90  pixClearInRect(line_pix, pixbox);
91  boxDestroy(&pixbox);
92  }
93  }
94 }
95 
96 // Helper subtracts the line_pix image from the src_pix, and removes residue
97 // as well by removing components that touch the line, but are not in the
98 // non_line_pix mask. It is assumed that the non_line_pix mask has already
99 // been prepared to required accuracy.
100 static void SubtractLinesAndResidue(Pix* line_pix, Pix* non_line_pix,
101  int resolution, Pix* src_pix) {
102  // First remove the lines themselves.
103  pixSubtract(src_pix, src_pix, line_pix);
104  // Subtract the non-lines from the image to get the residue.
105  Pix* residue_pix = pixSubtract(NULL, src_pix, non_line_pix);
106  // Dilate the lines so they touch the residue.
107  Pix* fat_line_pix = pixDilateBrick(NULL, line_pix, 3, 3);
108  // Seed fill the fat lines to get all the residue.
109  pixSeedfillBinary(fat_line_pix, fat_line_pix, residue_pix, 8);
110  // Subtract the residue from the original image.
111  pixSubtract(src_pix, src_pix, fat_line_pix);
112  pixDestroy(&fat_line_pix);
113  pixDestroy(&residue_pix);
114 }
115 
116 // Returns the maximum strokewidth in the given binary image by doubling
117 // the maximum of the distance function.
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);
124  // Find the maximum value in the distance image.
125  int max_dist = 0;
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)
130  max_dist = pixel;
131  }
132  data += wpl;
133  }
134  pixDestroy(&dist_pix);
135  return max_dist * 2;
136 }
137 
138 // Returns the number of components in the intersection_pix touched by line_box.
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);
146  boxaDestroy(&boxa);
147  return result;
148 }
149 
150 // Returns the number of black pixels found in the box made by adding the line
151 // width to both sides of the line bounding box. (Increasing the smallest
152 // dimension of the bounding box.)
153 static int CountPixelsAdjacentToLine(int line_width, Box* line_box,
154  Pix* nonline_pix) {
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) {
158  // horizontal line.
159  int bottom = MIN(pixGetHeight(nonline_pix), y + box_height + line_width);
160  y = MAX(0, y - line_width);
161  box_height = bottom - y;
162  } else {
163  // Vertical line.
164  int right = MIN(pixGetWidth(nonline_pix), x + box_width + line_width);
165  x = MAX(0, x - line_width);
166  box_width = right - x;
167  }
168  Box* box = boxCreate(x, y, box_width, box_height);
169  Pix* rect_pix = pixClipRectangle(nonline_pix, box, NULL);
170  boxDestroy(&box);
171  l_int32 result;
172  pixCountPixels(rect_pix, &result, NULL);
173  pixDestroy(&rect_pix);
174  return result;
175 }
176 
177 // Helper erases false-positive line segments from the input/output line_pix.
178 // 1. Since thick lines shouldn't really break up, we can eliminate some false
179 // positives by marking segments that are at least kMinThickLineWidth
180 // thickness, yet have a length less than min_thick_length.
181 // 2. Lines that don't have at least 2 intersections with other lines and have
182 // a lot of neighbouring non-lines are probably not lines (perhaps arabic
183 // or Hindi words, or underlines.)
184 // Bad line components are erased from line_pix.
185 // Returns the number of remaining connected components.
186 static int FilterFalsePositives(int resolution, Pix* nonline_pix,
187  Pix* intersection_pix, Pix* line_pix) {
188  int min_thick_length = static_cast<int>(resolution * kThickLengthMultiple);
189  Pixa* pixa = NULL;
190  Boxa* boxa = pixConnComp(line_pix, &pixa, 8);
191  // Iterate over the boxes to remove false positives.
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;
202  // If the length is too short to stand-alone as a line, and the box width
203  // is thick enough, and the stroke width is thick enough it is bad.
204  if (box_width >= kMinThickLineWidth && box_height >= kMinThickLineWidth &&
205  box_width < min_thick_length && box_height < min_thick_length &&
206  max_width > kMinThickLineWidth) {
207  // Too thick for the length.
208  bad_line = true;
209  }
210  if (!bad_line &&
211  (intersection_pix == NULL ||
212  NumTouchingIntersections(box, intersection_pix) < 2)) {
213  // Test non-line density near the line.
214  int nonline_count = CountPixelsAdjacentToLine(max_width, box,
215  nonline_pix);
216  if (nonline_count > box_height * box_width * kMaxNonLineDensity)
217  bad_line = true;
218  }
219  if (bad_line) {
220  // Not a good line.
221  pixClearInRect(line_pix, box);
222  --remaining_boxes;
223  }
224  boxDestroy(&box);
225  }
226  pixaDestroy(&pixa);
227  boxaDestroy(&boxa);
228  return remaining_boxes;
229 }
230 
231 // Finds vertical and horizontal line objects in the given pix.
232 // Uses the given resolution to determine size thresholds instead of any
233 // that may be present in the pix.
234 // The output vertical_x and vertical_y contain a sum of the output vectors,
235 // thereby giving the mean vertical direction.
236 // If pix_music_mask != NULL, and music is detected, a mask of the staves
237 // and anything that is connected (bars, notes etc.) will be returned in
238 // pix_music_mask, the mask subtracted from pix, and the lines will not
239 // appear in v_lines or h_lines.
240 // The output vectors are owned by the list and Frozen (cannot refit) by
241 // having no boxes, as there is no need to refit or merge separator lines.
242 // The detected lines are removed from the pix.
243 void LineFinder::FindAndRemoveLines(int resolution, bool debug, Pix* pix,
244  int* vertical_x, int* vertical_y,
245  Pix** pix_music_mask,
246  TabVector_LIST* v_lines,
247  TabVector_LIST* h_lines) {
248  PERF_COUNT_START("FindAndRemoveLines")
249  if (pix == NULL || vertical_x == NULL || vertical_y == NULL) {
250  tprintf("Error in parameters for LineFinder::FindAndRemoveLines\n");
251  return;
252  }
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,
261  pixa_display);
262  // Find lines, convert to TabVector_LIST and remove those that are used.
263  FindAndRemoveVLines(resolution, pix_intersections, vertical_x, vertical_y,
264  &pix_vline, pix_non_vline, pix, v_lines);
265  if (pix_hline != NULL) {
266  // Recompute intersections and re-filter false positive h-lines.
267  if (pix_vline != NULL)
268  pixAnd(pix_intersections, pix_vline, pix_hline);
269  else
270  pixDestroy(&pix_intersections);
271  if (!FilterFalsePositives(resolution, pix_non_hline, pix_intersections,
272  pix_hline)) {
273  pixDestroy(&pix_hline);
274  }
275  }
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) {
283  // Remove joins (intersections) where lines cross, and the residue.
284  // Recalculate the intersections, since some lines have been deleted.
285  pixAnd(pix_intersections, pix_vline, pix_hline);
286  // Fatten up the intersections and seed-fill to get the intersection
287  // residue.
288  Pix* pix_join_residue = pixDilateBrick(NULL, pix_intersections, 5, 5);
289  pixSeedfillBinary(pix_join_residue, pix_join_residue, pix, 8);
290  // Now remove the intersection residue.
291  pixSubtract(pix, pix, pix_join_residue);
292  pixDestroy(&pix_join_residue);
293  }
294  // Remove any detected music.
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);
299  }
300  if (pixa_display != NULL)
301  pixaAddPix(pixa_display, pix, L_CLONE);
302 
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");
312 #endif
313  pixaDestroy(&pixa_display);
314  }
316 }
317 
318 // Converts the Boxa array to a list of C_BLOB, getting rid of severely
319 // overlapping outlines and those that are children of a bigger one.
320 // The output is a list of C_BLOBs that are owned by the list.
321 // The C_OUTLINEs in the C_BLOBs contain no outline data - just empty
322 // bounding boxes. The Boxa is consumed and destroyed.
323 void LineFinder::ConvertBoxaToBlobs(int image_width, int image_height,
324  Boxa** boxes, C_BLOB_LIST* blobs) {
325  C_OUTLINE_LIST outlines;
326  C_OUTLINE_IT ol_it = &outlines;
327  // Iterate the boxes to convert to 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);
332  // Make a C_OUTLINE from the leptonica box. This is a bit of a hack,
333  // as there is no outline, just a bounding box, but with some very
334  // small changes to coutln.cpp, it works nicely.
335  ICOORD top_left(x, y);
336  ICOORD bot_right(x + width, y + height);
337  CRACKEDGE startpt;
338  startpt.pos = top_left;
339  C_OUTLINE* outline = new C_OUTLINE(&startpt, top_left, bot_right, 0);
340  ol_it.add_after_then_move(outline);
341  }
342  // Use outlines_to_blobs to convert the outlines to blobs and find
343  // overlapping and contained objects. The output list of blobs in the block
344  // has all the bad ones filtered out and deleted.
345  BLOCK block;
346  ICOORD page_tl(0, 0);
347  ICOORD page_br(image_width, image_height);
348  outlines_to_blobs(&block, page_tl, page_br, &outlines);
349  // Transfer the created blobs to the output list.
350  C_BLOB_IT blob_it(blobs);
351  blob_it.add_list_after(block.blob_list());
352  // The boxes aren't needed any more.
353  boxaDestroy(boxes);
354 }
355 
356 // Finds vertical line objects in pix_vline and removes the from src_pix.
357 // Uses the given resolution to determine size thresholds instead of any
358 // that may be present in the pix.
359 // The output vertical_x and vertical_y contain a sum of the output vectors,
360 // thereby giving the mean vertical direction.
361 // The output vectors are owned by the list and Frozen (cannot refit) by
362 // having no boxes, as there is no need to refit or merge separator lines.
363 // If no good lines are found, pix_vline is destroyed.
364 // None of the input pointers may be NULL, and if *pix_vline is NULL then
365 // the function does nothing.
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);
378  ICOORD bleft(0, 0);
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);
384  ICOORD vertical;
385  vertical.set_with_shrink(*vertical_x, *vertical_y);
386  TabVector::MergeSimilarTabVectors(vertical, vectors, NULL);
387  } else {
388  pixDestroy(pix_vline);
389  }
390 }
391 
392 // Finds horizontal line objects in pix_hline and removes them from src_pix.
393 // Uses the given resolution to determine size thresholds instead of any
394 // that may be present in the pix.
395 // The output vertical_x and vertical_y contain a sum of the output vectors,
396 // thereby giving the mean vertical direction.
397 // The output vectors are owned by the list and Frozen (cannot refit) by
398 // having no boxes, as there is no need to refit or merge separator lines.
399 // If no good lines are found, pix_hline is destroyed.
400 // None of the input pointers may be NULL, and if *pix_hline is NULL then
401 // the function does nothing.
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);
413  ICOORD bleft(0, 0);
414  ICOORD tright(height, width);
415  FindLineVectors(bleft, tright, &line_bblobs, &vertical_x, &vertical_y,
416  vectors);
417  if (!vectors->empty()) {
418  RemoveUnusedLineSegments(true, &line_bblobs, *pix_hline);
419  SubtractLinesAndResidue(*pix_hline, pix_non_hline, resolution, src_pix);
420  ICOORD vertical;
421  vertical.set_with_shrink(vertical_x, vertical_y);
422  TabVector::MergeSimilarTabVectors(vertical, vectors, NULL);
423  // Iterate the vectors to flip them. x and y were flipped for horizontal
424  // lines, so FindLineVectors can work just with the vertical case.
425  // See GetLineBoxes for more on the flip.
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();
429  }
430  } else {
431  pixDestroy(pix_hline);
432  }
433 }
434 
435 // Finds vertical lines in the given list of BLOBNBOXes. bleft and tright
436 // are the bounds of the image on which the input line_bblobs were found.
437 // The input line_bblobs list is const really.
438 // The output vertical_x and vertical_y are the total of all the vectors.
439 // The output list of TabVector makes no reference to the input BLOBNBOXes.
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);
445  int b_count = 0;
446  // Put all the blobs into the grid to find the lines, and move the blobs
447  // to the output lists.
448  AlignedBlob blob_grid(kLineFindGridSize, bleft, tright);
449  for (bbox_it.mark_cycle_pt(); !bbox_it.cycled_list(); bbox_it.forward()) {
450  BLOBNBOX* bblob = bbox_it.data();
452  bblob->set_left_rule(bleft.x());
453  bblob->set_right_rule(tright.x());
454  bblob->set_left_crossing_rule(bleft.x());
455  bblob->set_right_crossing_rule(tright.x());
456  blob_grid.InsertBBox(false, true, bblob);
457  ++b_count;
458  }
459  if (b_count == 0)
460  return;
461 
462  // Search the entire grid, looking for vertical line vectors.
463  BlobGridSearch lsearch(&blob_grid);
464  BLOBNBOX* bbox;
465  TabVector_IT vector_it(vectors);
466  *vertical_x = 0;
467  *vertical_y = 1;
468  lsearch.StartFullSearch();
469  while ((bbox = lsearch.NextFullSearch()) != NULL) {
470  if (bbox->left_tab_type() == TT_MAYBE_ALIGNED) {
471  const TBOX& box = bbox->bounding_box();
472  if (AlignedBlob::WithinTestRegion(2, box.left(), box.bottom()))
473  tprintf("Finding line vector starting at bbox (%d,%d)\n",
474  box.left(), box.bottom());
475  AlignedBlobParams align_params(*vertical_x, *vertical_y, box.width());
476  TabVector* vector = blob_grid.FindVerticalAlignment(align_params, bbox,
477  vertical_x,
478  vertical_y);
479  if (vector != NULL) {
480  vector->Freeze();
481  vector_it.add_to_end(vector);
482  }
483  }
484  }
485 }
486 
487 // Returns a Pix music mask if music is detected.
488 // Any vertical line that has at least 5 intersections in sufficient density
489 // is taken to be a bar. Bars are used as a seed and the entire touching
490 // component is added to the output music mask and subtracted from the lines.
491 // Returns NULL and does minimal work if no music is found.
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) {
495  int max_stave_height = static_cast<int>(resolution * kMaxStaveHeight);
496  Pix* intersection_pix = pixAnd(NULL, pix_vline, pix_hline);
497  Boxa* boxa = pixConnComp(pix_vline, NULL, 8);
498  // Iterate over the boxes to find music bars.
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);
506  // Test for the join density being at least 5 per max_stave_height,
507  // ie (joins-1)/box_height >= (5-1)/max_stave_height.
508  if (joins >= 5 && (joins - 1) * max_stave_height >= 4 * box_height) {
509  // This is a music bar. Add to the mask.
510  if (music_mask == NULL)
511  music_mask = pixCreate(pixGetWidth(pix_vline), pixGetHeight(pix_vline),
512  1);
513  pixSetInRect(music_mask, box);
514  }
515  boxDestroy(&box);
516  }
517  boxaDestroy(&boxa);
518  pixDestroy(&intersection_pix);
519  if (music_mask != NULL) {
520  // The mask currently contains just the bars. Use the mask as a seed
521  // and the pix_closed as the mask for a seedfill to get all the
522  // intersecting staves.
523  pixSeedfillBinary(music_mask, music_mask, pix_closed, 8);
524  // Filter out false positives. CCs in the music_mask should be the vast
525  // majority of the pixels in their bounding boxes, as we expect just a
526  // tiny amount of text, a few phrase marks, and crescendo etc left.
527  Boxa* boxa = pixConnComp(music_mask, NULL, 8);
528  // Iterate over the boxes to find music components.
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);
537  l_int32 all_pixels;
538  pixCountPixels(rect_pix, &all_pixels, NULL);
539  pixDestroy(&rect_pix);
540  if (music_pixels < kMinMusicPixelFraction * all_pixels) {
541  // False positive. Delete from the music mask.
542  pixClearInRect(music_mask, box);
543  }
544  boxDestroy(&box);
545  }
546  l_int32 no_remaining_music;
547  boxaDestroy(&boxa);
548  pixZero(music_mask, &no_remaining_music);
549  if (no_remaining_music) {
550  pixDestroy(&music_mask);
551  } else {
552  pixSubtract(pix_vline, pix_vline, music_mask);
553  pixSubtract(pix_hline, pix_hline, music_mask);
554  // We may have deleted all the lines
555  pixZero(pix_vline, v_empty);
556  pixZero(pix_hline, h_empty);
557  }
558  }
559  return music_mask;
560 }
561 
562 // Most of the heavy lifting of line finding. Given src_pix and its separate
563 // resolution, returns image masks:
564 // pix_vline candidate vertical lines.
565 // pix_non_vline pixels that didn't look like vertical lines.
566 // pix_hline candidate horizontal lines.
567 // pix_non_hline pixels that didn't look like horizontal lines.
568 // pix_intersections pixels where vertical and horizontal lines meet.
569 // pix_music_mask candidate music staves.
570 // This function promises to initialize all the output (2nd level) pointers,
571 // but any of the returns that are empty will be NULL on output.
572 // None of the input (1st level) pointers may be NULL except pix_music_mask,
573 // which will disable music detection, and pixa_display.
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;
581 
582  int max_line_width = resolution / kThinLineFraction;
583  int min_line_length = resolution / kMinLineLengthFraction;
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);
587  }
588  int closing_brick = max_line_width / 3;
589 
590  PERF_COUNT_START("GetLineMasksMorph")
591 // only use opencl if compiled w/ OpenCL and selected device is opencl
592 #ifdef USE_OPENCL
593  if (OpenclDevice::selectedDeviceIsOpenCL()) {
594  // OpenCL pixGetLines Operation
595  int clStatus = OpenclDevice::initMorphCLAllocations(pixGetWpl(src_pix),
596  pixGetHeight(src_pix),
597  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);
603  } else {
604 #endif
605  // Close up small holes, making it less likely that false alarms are found
606  // in thickened text (as it will become more solid) and also smoothing over
607  // some line breaks and nicks in the edges of the lines.
608  pix_closed = pixCloseBrick(NULL, src_pix, closing_brick, closing_brick);
609  if (pixa_display != NULL)
610  pixaAddPix(pixa_display, pix_closed, L_CLONE);
611  // Open up with a big box to detect solid areas, which can then be subtracted.
612  // This is very generous and will leave in even quite wide lines.
613  Pix* pix_solid = pixOpenBrick(NULL, pix_closed, max_line_width,
614  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);
618 
619  pixDestroy(&pix_solid);
620 
621  // Now open up in both directions independently to find lines of at least
622  // 1 inch/kMinLineLengthFraction in length.
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);
627 
628  pixDestroy(&pix_hollow);
629 #ifdef USE_OPENCL
630  }
631 #endif
633 
634  // Lines are sufficiently rare, that it is worth checking for a zero image.
635  l_int32 v_empty = 0;
636  l_int32 h_empty = 0;
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,
643  &v_empty, &h_empty);
644  } else {
645  *pix_music_mask = NULL;
646  }
647  }
648  pixDestroy(&pix_closed);
649  Pix* pix_nonlines = NULL;
650  *pix_intersections = NULL;
651  Pix* extra_non_hlines = NULL;
652  if (!v_empty) {
653  // Subtract both line candidates from the source to get definite non-lines.
654  pix_nonlines = pixSubtract(NULL, src_pix, *pix_vline);
655  if (!h_empty) {
656  pixSubtract(pix_nonlines, pix_nonlines, *pix_hline);
657  // Intersections are a useful indicator for likelihood of being a line.
658  *pix_intersections = pixAnd(NULL, *pix_vline, *pix_hline);
659  // Candidate vlines are not hlines (apart from the intersections)
660  // and vice versa.
661  extra_non_hlines = pixSubtract(NULL, *pix_vline, *pix_intersections);
662  }
663  *pix_non_vline = pixErodeBrick(NULL, pix_nonlines, kMaxLineResidue, 1);
664  pixSeedfillBinary(*pix_non_vline, *pix_non_vline, pix_nonlines, 8);
665  if (!h_empty) {
666  // Candidate hlines are not vlines.
667  pixOr(*pix_non_vline, *pix_non_vline, *pix_hline);
668  pixSubtract(*pix_non_vline, *pix_non_vline, *pix_intersections);
669  }
670  if (!FilterFalsePositives(resolution, *pix_non_vline, *pix_intersections,
671  *pix_vline))
672  pixDestroy(pix_vline); // No candidates left.
673  } else {
674  // No vertical lines.
675  pixDestroy(pix_vline);
676  *pix_non_vline = NULL;
677  if (!h_empty) {
678  pix_nonlines = pixSubtract(NULL, src_pix, *pix_hline);
679  }
680  }
681  if (h_empty) {
682  pixDestroy(pix_hline);
683  *pix_non_hline = NULL;
684  if (v_empty) {
685  return;
686  }
687  } else {
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);
693  }
694  if (!FilterFalsePositives(resolution, *pix_non_hline, *pix_intersections,
695  *pix_hline))
696  pixDestroy(pix_hline); // No candidates left.
697  }
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);
710  }
711  pixDestroy(&pix_nonlines);
712 }
713 
714 // Returns a list of boxes corresponding to the candidate line segments. Sets
715 // the line_crossings member of the boxes so we can later determin the number
716 // of intersections touched by a full line.
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) {
721  // Put a single pixel crack in every line at an arbitrary spacing,
722  // so they break up and the bounding boxes can be used to get the
723  // direction accurately enough without needing outlines.
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) {
730  for (int x = kCrackSpacing; x < width; x += kCrackSpacing) {
731  CLEAR_DATA_BIT(data, x);
732  }
733  }
734  } else {
735  for (int y = kCrackSpacing; y < height; y += kCrackSpacing) {
736  memset(data + wpl * y, 0, wpl * sizeof(*data));
737  }
738  }
739  // Get the individual connected components
740  Boxa* boxa = pixConnComp(pix_lines, NULL, 8);
741  ConvertBoxaToBlobs(width, height, &boxa, line_cblobs);
742  // Make the BLOBNBOXes from the C_BLOBs.
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();
747  BLOBNBOX* bblob = new BLOBNBOX(cblob);
748  bbox_it.add_to_end(bblob);
749  // Determine whether the line segment touches two intersections.
750  const TBOX& bbox = bblob->bounding_box();
751  Box* box = boxCreate(bbox.left(), bbox.bottom(),
752  bbox.width(), bbox.height());
753  bblob->set_line_crossings(NumTouchingIntersections(box, pix_intersections));
754  boxDestroy(&box);
755  // Transform the bounding box prior to finding lines. To save writing
756  // two line finders, flip x and y for horizontal lines and re-use the
757  // tab-stop detection code. For vertical lines we still have to flip the
758  // y-coordinates to switch from leptonica coords to tesseract coords.
759  if (horizontal_lines) {
760  // Note that we have Leptonica coords stored in a Tesseract box, so that
761  // bbox.bottom(), being the MIN y coord, is actually the top, so to get
762  // back to Leptonica coords in RemoveUnusedLineSegments, we have to
763  // use height - box.right() as the top, which looks very odd.
764  TBOX new_box(height - bbox.top(), bbox.left(),
765  height - bbox.bottom(), bbox.right());
766  bblob->set_bounding_box(new_box);
767  } else {
768  TBOX new_box(bbox.left(), height - bbox.top(),
769  bbox.right(), height - bbox.bottom());
770  bblob->set_bounding_box(new_box);
771  }
772  }
773 }
774 
775 } // namespace tesseract.
776 
C_BLOB_LIST * blob_list()
get blobs
Definition: ocrblock.h:132
static void ConvertBoxaToBlobs(int image_width, int image_height, Boxa **boxes, C_BLOB_LIST *blobs)
Definition: linefind.cpp:323
GridSearch< BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT > BlobGridSearch
Definition: blobgrid.h:31
void set_right_crossing_rule(int new_right)
Definition: blobbox.h:319
static bool WithinTestRegion(int detail_level, int x, int y)
#define MAX(x, y)
Definition: ndminx.h:24
ICOORD pos
Definition: crakedge.h:30
#define tprintf(...)
Definition: tprintf.h:31
#define MIN(x, y)
Definition: ndminx.h:28
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)
Definition: linefind.cpp:243
void outlines_to_blobs(BLOCK *block, ICOORD bleft, ICOORD tright, C_OUTLINE_LIST *outlines)
Definition: edgblob.cpp:354
inT16 right() const
Definition: rect.h:75
void set_line_crossings(int value)
Definition: blobbox.h:380
const int kMinThickLineWidth
Definition: linefind.cpp:49
TabType left_tab_type() const
Definition: blobbox.h:256
const double kMaxStaveHeight
Definition: linefind.cpp:60
const double kMaxNonLineDensity
Definition: linefind.cpp:58
static void MergeSimilarTabVectors(const ICOORD &vertical, TabVector_LIST *vectors, BlobGrid *grid)
Definition: tabvector.cpp:361
inT16 left() const
Definition: rect.h:68
class DLLSYM C_OUTLINE
Definition: coutln.h:65
Definition: ocrblock.h:30
void set_bounding_box(const TBOX &new_box)
Definition: blobbox.h:220
#define PERF_COUNT_START(FUNCT_NAME)
void set_left_crossing_rule(int new_left)
Definition: blobbox.h:313
const int kCrackSpacing
Spacing of cracks across the page to break up tall vertical lines.
Definition: linefind.cpp:45
const int kLineFindGridSize
Grid size used by line finder. Not very critical.
Definition: linefind.cpp:47
integer coordinate
Definition: points.h:30
inT16 bottom() const
Definition: rect.h:61
inT16 height() const
Definition: rect.h:104
void set_right_rule(int new_right)
Definition: blobbox.h:307
inT16 width() const
Definition: rect.h:111
#define PERF_COUNT_END
inT16 x() const
access function
Definition: points.h:52
Definition: rect.h:30
const double kMinMusicPixelFraction
Definition: linefind.cpp:62
void set_left_rule(int new_left)
Definition: blobbox.h:301
#define NULL
Definition: host.h:144
const TBOX & bounding_box() const
Definition: blobbox.h:215
void set_with_shrink(int x, int y)
Set from the given x,y, shrinking the vector to fit if needed.
Definition: points.cpp:43
inT16 top() const
Definition: rect.h:54
const double kThickLengthMultiple
Definition: linefind.cpp:56
const int kMaxLineResidue
Definition: linefind.cpp:53
const int kThinLineFraction
Denominator of resolution makes max pixel width to allow thin lines.
Definition: linefind.cpp:41
const int kMinLineLengthFraction
Denominator of resolution makes min pixels to demand line lengths to be.
Definition: linefind.cpp:43
void set_left_tab_type(TabType new_type)
Definition: blobbox.h:259