$extrastylesheet
Olena  User documentation 2.1
An Image Processing Platform
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
lines_h_thick_and_thin.hh
1 // Copyright (C) 2011, 2012, 2013 EPITA Research and Development Laboratory
2 // (LRDE)
3 //
4 // This file is part of Olena.
5 //
6 // Olena is free software: you can redistribute it and/or modify it under
7 // the terms of the GNU General Public License as published by the Free
8 // Software Foundation, version 2 of the License.
9 //
10 // Olena is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Olena. If not, see <http://www.gnu.org/licenses/>.
17 //
18 // As a special exception, you may use this file as part of a free
19 // software project without restriction. Specifically, if other files
20 // instantiate templates or use macros or inline functions from this
21 // file, or you compile this file and link it with other files to produce
22 // an executable, this file does not by itself cause the resulting
23 // executable to be covered by the GNU General Public License. This
24 // exception does not however invalidate any other reasons why the
25 // executable file might be covered by the GNU General Public License.
26 
27 #ifndef SCRIBO_PRIMITIVE_EXTRACT_LINES_H_THICK_AND_THIN_HH
28 # define SCRIBO_PRIMITIVE_EXTRACT_LINES_H_THICK_AND_THIN_HH
29 
33 
34 # include <mln/io/pbm/load.hh>
35 # include <mln/io/pbm/save.hh>
36 # include <mln/io/ppm/save.hh>
37 
38 # include <mln/logical/and.hh>
39 # include <mln/logical/or.hh>
40 
41 # include <mln/make/w_window2d.hh>
42 
43 # include <mln/extension/adjust_fill.hh>
44 # include <mln/accu/transform_line.hh>
45 # include <mln/accu/count_value.hh>
46 # include <mln/data/fill.hh>
47 # include <mln/literal/grays.hh>
48 # include <mln/literal/colors.hh>
49 
50 # include <mln/pw/all.hh>
51 # include <mln/core/routine/duplicate.hh>
52 # include <mln/win/rectangle2d.hh>
53 # include <mln/win/hline2d.hh>
54 # include <mln/morpho/dilation.hh>
55 
56 # include <mln/data/convert.hh>
57 # include <mln/data/fill.hh>
58 # include <mln/core/image/dmorph/image_if.hh>
59 
60 # include <mln/core/alias/neighb2d.hh>
61 # include <mln/labeling/compute.hh>
62 # include <mln/labeling/foreground.hh>
63 
64 # include <scribo/core/macros.hh>
65 # include <scribo/core/def/lbl_type.hh>
66 # include <scribo/primitive/internal/rd.hh>
67 
68 # include <scribo/debug/logger.hh>
69 
70 
71 namespace scribo
72 {
73 
74  namespace primitive
75  {
76 
77  namespace extract
78  {
79  using namespace mln;
80 
84  template <typename I>
85  mln_concrete(I)
86  lines_h_thick_and_thin(const Image<I>& binary_image,
87  unsigned length, unsigned delta,
88  float p_few = 0.2, // very tolerant (v. severe is 0.05)
89  float p_enough = 0.6, // very tolerant (v. severe is 0.80)
90  float ratio = 8);
91 
92 
93 # ifndef MLN_INCLUDE_ONLY
94 
95  namespace internal
96  {
97 
98  const unsigned
99  tag_bg = 0, // literal::black background
100 
101  tag_thin = 1, // literal::white thin
102 
103  tag_top = 2, // literal::red
104  tag_mdl = 3, // literal::green thick
105  tag_bot = 4, // literal::blue
106 
107  tag_spc = 5; // literal::dark_gray "white space"
108 
109 
110 
112  colorize(const image2d<value::int_u8>& input)
113  {
114  image2d<value::rgb8> output(input.domain());
115  mln_piter_(box2d) p(input.domain());
116  for_all(p)
117  switch (input(p))
118  {
119  // bg:
120  case tag_bg:
121  output(p) = literal::black;
122  break;
123 
124  // thin:
125  case tag_thin:
126  output(p) = literal::white;
127  break;
128 
129  // thick:
130  case tag_top:
131  output(p) = literal::red;
132  break;
133  case tag_mdl:
134  output(p) = literal::green;
135  break;
136  case tag_bot:
137  output(p) = literal::blue;
138  break;
139 
140  // space:
141  case tag_spc:
142  output(p) = literal::dark_gray;
143  break;
144  }
145  return output;
146  }
147 
148 
150  tag_it(const image2d<bool>& input, unsigned length, unsigned delta,
151  float p_few = 0.2, // very tolerant (v. severe is 0.05)
152  float p_enough = 0.6) // very tolerant (v. severe is 0.80)
153  {
154  unsigned
155  few = unsigned(p_few * length + 0.49999), // usage: <= few
156  enough = unsigned(p_enough * length + 0.49999), // usage: >= enough
157  a_lot = length - few; // usage: >= a_lot
158 
159 
160  extension::adjust_fill(input, length / 2, 0);
161  accu::count_value<bool> accu(true);
162  image2d<unsigned> count = accu::transform_line(accu, input, length, 1);
163  border::fill(count, 0); // FIXME: correct?
164 
165  image2d<value::int_u8> output;
166  initialize(output, count);
167  data::fill(output, 0);
168 
169  const unsigned nrows = count.nrows(), ncols = count.ncols();
170 
171  dpoint2d up(-delta, 0), down(+delta, 0);
172  const int
173  offset_up = count.delta_offset(up),
174  offset_down = count.delta_offset(down);
175 
176  typedef const unsigned* ptr_t;
177  value::int_u8* p_out;
178 
179  for (unsigned row = 0; row < nrows; ++row)
180  {
181  ptr_t
182  p_ = & count.at_(row, -1),
183  p_top = p_ + offset_up,
184  p_bot = p_ + offset_down;
185  p_out = & output.at_(row, -1);
186  for (unsigned col = 0; col < ncols; ++col)
187  {
188  ++p_;
189  ++p_top;
190  ++p_bot;
191  ++p_out;
192 
193  if (*p_top <= few && *p_bot <= few)
194  {
195  if (*p_ <= few)
196  *p_out = tag_spc;
197  else if (*p_ >= enough)
198  *p_out = tag_thin;
199  // no other case with both n_top and n_bot <= few
200  continue;
201  }
202 
203  if (*p_ < a_lot)
204  continue;
205 
206  if (*p_top <= few && *p_bot >= a_lot)
207  {
208  *p_out = tag_top;
209  continue;
210  }
211 
212  if (*p_top >= a_lot && *p_bot <= few)
213  {
214  *p_out = tag_bot;
215  continue;
216  }
217 
218  *p_out = tag_mdl;
219 
220  }
221  }
222 
223  return output;
224  }
225 
226 
228  image_with_tag(const image2d<value::int_u8>& input, value::int_u8 tag)
229  {
230  image2d<bool> output(input.domain());
231  mln_pixter_(image2d<bool>) p_o(output);
232  mln_pixter_(const image2d<value::int_u8>) p_i(input);
233  for_all_2(p_i, p_o)
234  p_o.val() = p_i.val() == tag;
235  return output;
236  }
237 
238 
239 
240 
241  void flush_tag(const image2d<value::int_u8>& input, unsigned col, value::int_u8 tag,
242  // out:
243  int& row, value::int_u8& next_tag)
244  {
245  int row_offset = input.delta_offset(dpoint2d(+1, 0));
246  const value::int_u8* p = & input.at_(row, col);
247  while (*p == tag)
248  {
249  p += row_offset;
250  ++row;
251  }
252  next_tag = *p;
253  }
254 
255 
256  void draw_vertical(image2d<bool>& output, unsigned col, int row_start, int row_end)
257  {
258  const unsigned offset = output.delta_offset(dpoint2d(+1, 0)); // next row
259  bool* p_out = & output.at_(row_start, col);
260  for (int row = row_start; row < row_end; ++row, p_out += offset)
261  *p_out = true;
262  }
263 
264 
266  detect_thick(const image2d<value::int_u8>& input)
267  {
268  image2d<bool> output;
269  initialize(output, input);
270  data::fill(output, false);
271 
272  const mln::def::coord
273  maxrow = geom::max_row(input),
274  maxcol = geom::max_col(input);
275  mln::def::coord row, col;
276 
277  col = geom::min_col(input);
278  do
279  {
280  row = geom::min_row(input) - 1;
281  do
282  {
283  ++row;
284  const value::int_u8& t = input.at_(row, col);
285  if (t != tag_top)
286  continue;
287  assert(t == tag_top);
288  int r = row;
289  value::int_u8 next_tag;
290  flush_tag(input, col, tag_top, r, next_tag); // top
291  if (next_tag == tag_mdl || next_tag == tag_thin)
292  {
293  flush_tag(input, col, next_tag, r, next_tag); // mdl or thin
294  if (next_tag != tag_bot)
295  continue;
296  flush_tag(input, col, tag_bot, r, next_tag); // bot
297  draw_vertical(output, col, row, r);
298  row = r - 1;
299  }
300  else if (next_tag == tag_bot)
301  {
302  flush_tag(input, col, tag_bot, r, next_tag); // bot
303  draw_vertical(output, col, row, r);
304  row = r - 1;
305  }
306  }
307  while (row <= maxrow);
308  }
309  while (++col <= maxcol);
310 
311  return output;
312  }
313 
314 
315  void
316  add_thin(const image2d<value::int_u8>& input, image2d<bool>& output)
317  {
318  unsigned N = input.nelements();
319  if (output.nelements() != N)
320  std::abort();
321  const value::int_u8* p_in = input.buffer();
322  bool* p_out = output.buffer();
323  for (unsigned i = 0; i < N; ++i)
324  {
325  if (*p_in == tag_thin)
326  *p_out = true;
327  ++p_in;
328  ++p_out;
329  }
330  }
331 
332 
333  template <typename I, typename J,
334  typename N, typename W, typename D>
335  mln_concrete(I)
336  rd3_fast(const Image<I>& f_,
337  const Image<J>& g_,
338  const Neighborhood<N>& nbh_,
339  const Weighted_Window<W>& w_win_,
340  D max)
341  {
342  const I& f = exact(f_);
343  const J& g = exact(g_);
344  const N& nbh = exact(nbh_);
345  const W& w_win = exact(w_win_);
346 
347  mln_precondition(f.is_valid());
348  mln_precondition(w_win.is_valid());
349 
350  // Handling w_win.
351  extension::adjust(f, w_win);
352  border::resize(g, f.border());
353  const unsigned n_ws = w_win.size();
354  util::array<int> dp = offsets_wrt(f, w_win.win());
355  mln_invariant(dp.nelements() == n_ws);
356 
357  // Output.
358  mln_concrete(I) o = duplicate(f);
359  if (o.border() != f.border())
360  std::abort();
361 
362  // Distance map.
363  mln_ch_value(I, D) dmap;
364  initialize(dmap, f);
365  data::fill(dmap, max);
366 
367  // Mod determination.
368  unsigned mod;
369  {
371  for (unsigned i = 0; i < w_win.size(); ++i)
372  m.take(w_win.w(i));
373  mod = unsigned(m) + 1;
374  }
375 
376  // Aux data.
377  typedef std::vector<unsigned> bucket_t;
378  std::vector<bucket_t> bucket(mod);
379  unsigned bucket_size = 0;
380 
381  // Initialization.
382  {
383  // For the extension to be ignored:
384  extension::fill(f, true);
385  extension::fill(dmap, D(0));
386 
387  mln_pixter(const I) p(f);
388  mln_nixter(const I, N) n(p, nbh);
389  for_all(p)
390  if (p.val() == true)
391  {
392  dmap.element(p.offset()) = 0;
393  for_all(n)
394  if (n.val() == false)
395  {
396  bucket[0].push_back(p.offset());
397  ++bucket_size;
398  break;
399  }
400  }
401  } // end of Initialization.
402 
403  // Propagation.
404  {
405  unsigned p;
406 
407  for (unsigned d = 0; bucket_size != 0; ++d)
408  {
409  bucket_t& bucket_d = bucket[d % mod];
410  for (unsigned i = 0; i < bucket_d.size(); ++i)
411  {
412  p = bucket_d[i];
413 
414  if (dmap.element(p) == max)
415  {
416  // Saturation so stop.
417  bucket_size = bucket_d.size(); // So at end bucket_size == 0.
418  break;
419  }
420 
421  if (dmap.element(p) < d)
422  // p has already been processed, having a distance less than d.
423  continue;
424 
425  for (unsigned i = 0; i < n_ws; ++i)
426  {
427  unsigned q = p + dp[i];
428  if (dmap.element(q) > d)
429  {
430  unsigned d_ = d + w_win.w(i);
431  if (d_ < dmap.element(q) && g.element(q) == true)
432  {
433  o.element(q) = true;
434  dmap.element(q) = d_;
435  bucket[d_ % mod].push_back(q);
436  ++bucket_size;
437  }
438  }
439  }
440  }
441  bucket_size -= bucket_d.size();
442  bucket_d.clear();
443  }
444  } // end of Propagation.
445 
446  return o;
447  }
448 
449 
450  template <typename I, typename J>
451  inline
452  mln_concrete(I)
453  rd3_fast(const I& f, const J& g, unsigned length, unsigned delta)
454  {
455  unsigned mean = (length + delta) / 2;
456  unsigned ws[] = { mean, length, mean,
457  delta, 0, delta,
458  mean, length, mean };
460  unsigned max = length * delta + 1;
461 
462  mln_concrete(I) output = rd3_fast(f, g, c4(), w_win, max);
463 
464  return output;
465  }
466 
467 
468  } // scribo::primitive::extract::internal
469 
470 
471  template <typename I>
472  mln_concrete(I)
473  lines_h_thick_and_thin(const Image<I>& binary_image_,
474  unsigned length, unsigned delta,
475  float p_few, float p_enough,
476  float ratio)
477  {
478  mln_trace("scribo::primitive::extract::lines_h_thick_and_thin");
479 
480  mlc_is(mln_value(I), bool)::check();
481 
482  const I& binary_image = exact(binary_image_);
483  mln_precondition(binary_image.is_valid());
484 
485  if (length % 2 == 0)
486  ++length;
487 
488  // Find lines
489  mln_ch_value(I,value::int_u8)
490  tags = internal::tag_it(binary_image, length, delta, p_few, p_enough);
491  mln_concrete(I) mask = internal::detect_thick(tags);
492  internal::add_thin(tags, mask);
493 
494 
495  debug::logger().log_image(debug::AuxiliaryResults,
496  mask, "lines_h_thick_and_thin_mask");
497 
498  image2d<bool> output = internal::rd3_fast(mask, binary_image,
499  2 * length, 2 * delta);
500 
501  debug::logger().log_image(debug::AuxiliaryResults,
502  output, "lines_h_thick_and_thin_output_before_filter");
503 
504  // Remove invalid lines
505  typedef scribo::def::lbl_type V;
506  V nlabels;
507  mln_ch_value(I,V) lbl = labeling::foreground(output, c8(), nlabels);
508  mln::util::array<box2d>
509  bbox = labeling::compute(accu::shape::bbox<point2d>(), lbl, nlabels);
510 
511  image2d<value::int_u8> debug;
512  initialize(debug, binary_image);
513  data::fill(debug, 0);
514  for_all_ncomponents(e, nlabels)
515  {
516  if (bbox(e).width() < length ||
517  (std::max(bbox(e).width(), bbox(e).height()) /
518  std::min(bbox(e).width(), bbox(e).height()) + 0.49999) < ratio)
519  data::fill(((output | bbox(e)).rw()
520  | (pw::value(lbl) == pw::cst(e))).rw(), false);
521  }
522 
523  debug::logger().log_image(debug::Results,
524  output, "lines_h_thick_and_thin_output");
525 
526 
527  return output;
528  }
529 
530 # endif // ! MLN_INCLUDE_ONLY
531 
532  } // end of namespace scribo::primitive::extract
533 
534  } // end of namespace scribo::primitive
535 
536 } // end of namespace scribo
537 
538 #endif // ! SCRIBO_PRIMITIVE_EXTRACT_LINES_H_THICK_AND_THIN_HH