libcvd
morphology.h
1 #ifndef CVD_INCLUDE_MORPHOLOGY_H
2 #define CVD_INCLUDE_MORPHOLOGY_H
3 
4 #include <algorithm>
5 #include <cvd/image.h>
6 #include <cvd/vision.h>
7 #include <cvd/vision_exceptions.h>
8 #include <functional>
9 #include <map>
10 #include <vector>
11 
12 namespace CVD
13 {
14 #ifndef DOXYGEN_IGNORE_INTERNAL
15 namespace Internal
16 {
17  namespace MorphologyHelpers
18  {
19  using namespace std;
20 
21  //Compute pointer offsets for a bunch of ImageRef offsets.
22  template <class T>
23  vector<ptrdiff_t> offsets(const vector<ImageRef>& v, const BasicImage<T>& s)
24  {
25  vector<ptrdiff_t> off;
26 
27  for(unsigned int i = 0; i < v.size(); i++)
28  off.push_back(v[i].x + v[i].y * s.row_stride() - 1);
29  return off;
30  }
31 
32  //Split a list of ImageRefs up in to rows.
33  vector<vector<ImageRef>> row_split(const vector<ImageRef>& v, int y_lo, int y_hi);
34  }
35 }
36 #endif
37 
77 template <class Accumulator, class T>
78 void morphology(const BasicImage<T>& in, const std::vector<ImageRef>& selem, const Accumulator& a_, BasicImage<T>& out)
79 {
80  using Internal::MorphologyHelpers::offsets;
81  using Internal::MorphologyHelpers::row_split;
82  using std::max;
83  using std::min;
84  using std::vector;
85 
86  if(in.size() != out.size())
88 
89  //Cases are:
90  //
91  // Small selem compared to image:
92  // Topleft corner, top row, top right corner
93  // left edge, centre, right edge
94  // etc
95 
97  //Find the extents of the structuring element
98  int x_lo = selem[0].x;
99  int x_hi = selem[0].x;
100  int y_lo = selem[0].y;
101  int y_hi = selem[0].y;
102 
103  for(unsigned int i = 0; i < selem.size(); i++)
104  {
105  x_lo = min(x_lo, selem[i].x);
106  x_hi = max(x_hi, selem[i].x);
107  y_lo = min(y_lo, selem[i].y);
108  y_hi = max(y_hi, selem[i].y);
109  }
110 
112  //Shift the structure element by one and find the differeneces
113  vector<ImageRef> structure_element = selem;
114  vector<ImageRef> shifted;
115 
116  sort(structure_element.begin(), structure_element.end());
117  for(unsigned int i = 0; i < structure_element.size(); i++)
118  shifted.push_back(structure_element[i] + ImageRef(1, 0));
119 
120  vector<ImageRef> add, remove;
121  set_difference(shifted.begin(), shifted.end(), structure_element.begin(), structure_element.end(), back_inserter(add));
122  set_difference(structure_element.begin(), structure_element.end(), shifted.begin(), shifted.end(), back_inserter(remove));
123 
125  //
126  //Compute the integer offsets to pixels for speed;
127  vector<ptrdiff_t> add_off = offsets(add, in);
128  vector<ptrdiff_t> remove_off = offsets(remove, in);
129 
131  //
132  // Split by rows, to make the top and bottom edges easier.
133  //
134  //Because of set operations, the ImageRefs are ordered within each row.
135  vector<vector<ImageRef>> split_selem = row_split(structure_element, y_lo, y_hi);
136  vector<vector<ImageRef>> split_add = row_split(add, y_lo, y_hi);
137  vector<vector<ImageRef>> split_remove = row_split(remove, y_lo, y_hi);
138 
139  Accumulator acc(a_);
140  //If the image is at least as wide as the structuring element
141  if(x_hi - x_lo + 1 <= in.size().x)
142  for(int y = 0; y < in.size().y; y++)
143  {
144  //Find the rows which overlap with the image. Only work with these rows.
145  int startrow = max(0, -y_lo - y);
146  int endrow = static_cast<int>(split_selem.size()) - max(0, y + y_hi - in.size().y + 1);
147 
148  //Figure out the range of the "easy" bit.
149  int x_first_full = max(0, -x_lo); //This is the first position at which we have a full kernel in the image
150  int x_after_last_full = min(in.size().x, in.size().x - x_hi); //This is one beyone the end of the position where the last kernel fits in the image.
151 
152  //Clear the accumulator
153  acc.clear();
154 
155  //Fill in the accumulator sitting up against the left hand side of the image.
156  for(int i = startrow; i < endrow; i++)
157  for(int j = (int)split_selem[i].size() - 1; j >= 0 && split_selem[i][j].x >= 0; j--)
158  acc.insert(in[y + split_selem[i][0].y][split_selem[i][j].x]);
159 
160  out[y][0] = acc.get();
161 
162  //Shift the kernel until we get to the point where
163  //we can start shifting the kernel without testing to
164  //see it fits withing the image width.
165  for(int x = 1; x <= x_first_full; x++)
166  {
167  for(int i = startrow; i < endrow; i++)
168  for(int j = (int)split_remove[i].size() - 1; j >= 0 && split_remove[i][j].x + x - 1 >= 0; j--)
169  acc.remove(in[y + split_remove[i][0].y][x + split_remove[i][j].x - 1]);
170 
171  for(int i = startrow; i < endrow; i++)
172  for(int j = (int)split_add[i].size() - 1; j >= 0 && split_add[i][j].x + x - 1 >= 0; j--)
173  acc.insert(in[y + split_add[i][0].y][x + split_add[i][j].x - 1]);
174 
175  out[y][x] = acc.get();
176  }
177 
178  //Go through the two incremental kernels to figure out which
179  //indices are fit within the image. This removes a test from
180  //the following shift section.
181  int add_start = 0, add_end = 0, remove_start = 0, remove_end = 0;
182  for(int i = 0; i < startrow; i++)
183  {
184  add_start += static_cast<int>(split_add[i].size());
185  remove_start += static_cast<int>(split_remove[i].size());
186  }
187  for(int i = 0; i < endrow; i++)
188  {
189  add_end += static_cast<int>(split_add[i].size());
190  remove_end += static_cast<int>(split_remove[i].size());
191  }
192 
193  //Shift the kernel in the area which requires no tests.
194  for(int x = max(0, -x_lo + 1); x < x_after_last_full; x++)
195  {
196  for(int i = remove_start; i < remove_end; i++)
197  acc.remove(*(in[y] + x + remove_off[i]));
198 
199  for(int i = add_start; i < add_end; i++)
200  acc.insert(*(in[y] + x + add_off[i]));
201 
202  out[y][x] = acc.get();
203  }
204 
205  //Now perform the right hand edge
206  for(int x = x_after_last_full; x < in.size().x; x++)
207  {
208  for(int i = startrow; i < endrow; i++)
209  for(int j = 0; j < (int)split_remove[i].size() && split_remove[i][j].x + x - 1 < in.size().x; j++)
210  acc.remove(in[y + split_remove[i][0].y][x + split_remove[i][j].x - 1]);
211 
212  for(int i = startrow; i < endrow; i++)
213  for(int j = 0; j < (int)split_add[i].size() && split_add[i][j].x + x - 1 < in.size().x; j++)
214  acc.insert(in[y + split_add[i][0].y][x + split_add[i][j].x - 1]);
215 
216  out[y][x] = acc.get();
217  }
218  }
219  else
220  {
221  //The image is too narrow to have a clear area in the middle.
222 
223  for(int y = 0; y < in.size().y; y++)
224  {
225  //Find the rows which overlap with the image. Only work with these rows.
226  int startrow = max(0, -y_lo - y);
227  int endrow = static_cast<int>(split_selem.size()) - max(0, y + y_hi - in.size().y + 1);
228 
229  //Clear the accumulator
230  acc.clear();
231 
232  //Fill in the accumulator sitting up against the left hand side of the image.
233  for(int i = startrow; i < endrow; i++)
234  for(int j = 0; j < (int)split_selem[i].size(); j++)
235  {
236  int xp = split_selem[i][j].x;
237  if(xp >= 0 && xp < in.size().x)
238  acc.insert(in[y + split_selem[i][0].y][xp]);
239  }
240 
241  out[y][0] = acc.get();
242 
243  //Shift the kernel using the incrementals
244  for(int x = 1; x < in.size().x; x++)
245  {
246  for(int i = startrow; i < endrow; i++)
247  for(int j = 0; j < (int)split_remove[i].size(); j++)
248  {
249  int xp = x + split_remove[i][j].x - 1;
250  if(xp >= 0 && xp < in.size().x)
251  acc.remove(in[y + split_add[i][0].y][xp]);
252  }
253 
254  for(int i = startrow; i < endrow; i++)
255  for(int j = 0; j < (int)split_add[i].size(); j++)
256  {
257  int xp = x + split_add[i][j].x - 1;
258  if(xp >= 0 && xp < in.size().x)
259  acc.insert(in[y + split_add[i][0].y][xp]);
260  }
261 
262  out[y][x] = acc.get();
263  }
264  }
265  }
266 }
267 
268 #ifndef DOXYGEN_IGNORE_INTERNAL
269 namespace Internal
270 {
271  template <class C, class D>
273  {
274  };
275 
276  template <class C, class D>
278  {
279  ImagePromise(const BasicImage<C>& im, const D& acc, const std::vector<ImageRef>& s_)
280  : i(im)
281  , a(acc)
282  , s(s_)
283  {
284  }
285 
286  const BasicImage<C>& i;
287  const D& a;
288  const std::vector<ImageRef>& s;
289 
290  template <class E>
291  void execute(Image<E>& j)
292  {
293  j.resize(i.size());
294  if(i.data() == j.data())
295  {
296  Image<E> b(j.size());
297  morphology(i, s, a, b);
298  j = b;
299  }
300  else
301  morphology(i, s, a, j);
302  }
303  };
304 };
305 
306 template <class C, class D>
307 Internal::ImagePromise<Internal::PerformMorphology<C, D>> morphology(const BasicImage<C>& c, const std::vector<ImageRef>& selem, const D& a)
308 {
310 }
311 #else
312 
320 Image<T> morphology(const BasicImage<T>& in, const std::vector<ImageRef>& selem, const Accumulator& a_);
321 
322 #endif
323 
326 namespace Morphology
327 {
332  template <class T, template <class> class Cmp>
333  struct BasicGray
334  {
335  private:
336  std::map<T, int, Cmp<T>> pix;
337 
338  public:
339  void clear()
340  {
341  pix.clear();
342  }
343  void insert(const T& t)
344  {
345  pix[t]++;
346  }
347 
348  void remove(const T& t)
349  {
350  --pix[t];
351  }
352 
353  T get()
354  {
355  typedef typename std::map<T, int, Cmp<T>>::iterator it;
356 
357  for(it i = pix.begin(); i != pix.end();)
358  {
359  it old = i;
360  i++;
361 
362  if(old->second == 0)
363  pix.erase(old);
364  else
365  return old->first;
366  }
367 
368  assert(0);
369  return 0;
370  }
371  };
372 
375  template <class T>
376  class Erode : public BasicGray<T, std::less>
377  {
378  };
379 
382  template <class T>
383  class Dilate : public BasicGray<T, std::greater>
384  {
385  };
386 
389  template <class T>
390  class Percentile;
391 
394  template <class T>
395  class Median;
396 
402  {
403  protected:
404  int histogram[256];
405  int total;
406 
407  public:
408  BasicGrayByte()
409  {
410  clear();
411  }
412 
413  void clear()
414  {
415  total = 0;
416  for(int i = 0; i < 256; i++)
417  histogram[i] = 0;
418  }
419 
420  void insert(byte t)
421  {
422  total++;
423  histogram[t]++;
424  }
425 
426  void remove(byte t)
427  {
428  total--;
429  histogram[t]--;
430  }
431  };
432 
435  template <>
436  class Erode<byte> : public BasicGrayByte
437  {
438  public:
439  byte get()
440  {
441  for(int j = 0; j < 256; j++)
442  if(histogram[j])
443  return static_cast<byte>(j);
444 
445  assert(0);
446  return 0;
447  }
448  };
449 
452  template <>
453  class Dilate<byte> : public BasicGrayByte
454  {
455  public:
456  byte get()
457  {
458  for(int j = 255; j >= 0; j--)
459  if(histogram[j])
460  return static_cast<byte>(j);
461 
462  assert(0);
463  return 0;
464  }
465  };
466 
469  template <>
470  class Percentile<byte> : public BasicGrayByte
471  {
472  private:
473  double ptile;
474 
475  public:
476  Percentile(double p)
477  : ptile(p)
478  {
479  }
480 
481  byte get()
482  {
483  using std::max;
484 
485  if(ptile < 0.5)
486  {
487  int sum = 0;
488  //because we use a > test below (to work for the 0th ptile)
489  //we have to use the scaled threshold -1 otherwise it will
490  //not work for the 100th percentile.
491  int threshold = max(0, (int)floor(total * ptile + .5) - 1);
492 
493  for(int j = 0; j < 255; j++)
494  {
495  sum += histogram[j];
496 
497  if(sum > threshold)
498  return static_cast<byte>(j);
499  }
500 
501  return 255;
502  }
503  else
504  {
505  //Approach from the top for high percentiles
506  int sum = 0;
507  int threshold = max(0, (int)floor(total * (1 - ptile) + .5) - 1);
508 
509  for(int j = 255; j > 0; j--)
510  {
511  sum += histogram[j];
512 
513  if(sum > threshold)
514  return static_cast<byte>(j);
515  }
516 
517  return 0;
518  }
519  }
520  };
521 
524  template <>
525  class Median<byte> : public Percentile<byte>
526  {
527  public:
528  Median()
529  : Percentile<byte>(0.5)
530  {
531  }
532  };
533 
537  template <class T>
538  struct BasicBinary
539  {
540  int t, f;
541  BasicBinary()
542  : t(0)
543  , f(0)
544  {
545  }
546 
547  void insert(bool b)
548  {
549  if(b)
550  t++;
551  else
552  f++;
553  }
554 
555  void remove(bool b)
556  {
557  if(b)
558  t--;
559  else
560  f--;
561  }
562 
563  void clear()
564  {
565  t = f = 0;
566  }
567  };
568 
571  template <class T = bool>
572  struct BinaryErode : public BasicBinary<T>
573  {
574  using BasicBinary<T>::f;
575  T get()
576  {
577  return !f;
578  }
579  };
580 
583  template <class T = bool>
584  struct BinaryDilate : public BasicBinary<T>
585  {
586  using BasicBinary<T>::t;
587  T get()
588  {
589  return t != 0;
590  }
591  };
592 
595  template <class T = bool>
596  struct BinaryMedian : public BasicBinary<T>
597  {
598  using BasicBinary<T>::t;
599  using BasicBinary<T>::f;
600  T get()
601  {
602  return (t > f);
603  }
604  };
605 }
606 
607 namespace median
608 {
609  //Some helper classes for median
610  template <class T>
611  T median4(T a, T b, T c, T d)
612  {
613  T v[4] = { a, b, c, d };
614  std::nth_element(v, v + 2, v + 4);
615  return v[2];
616  }
617 
618  template <class T>
619  T median4(const BasicImage<T>& im, int r, int c)
620  {
621  return median4(im[r][c], im[r][c + 1], im[r + 1][c], im[r + 1][c + 1]);
622  }
623 
624  template <class T>
625  T median6(T a, T b, T c, T d, T e, T f)
626  {
627  T v[6] = { a, b, c, d, e, f };
628  std::nth_element(v, v + 3, v + 6);
629  return v[3];
630  }
631 
632  template <class T>
633  T median6_row(const BasicImage<T>& im, int r, int c)
634  {
635  return median6(im[r][c], im[r][c + 1], im[r][c + 2], im[r + 1][c], im[r + 1][c + 1], im[r + 1][c + 2]);
636  }
637  template <class T>
638  T median6_col(const BasicImage<T>& im, int r, int c)
639  {
640  return median6(im[r][c], im[r][c + 1], im[r + 1][c], im[r + 1][c + 1], im[r + 2][c], im[r + 2][c + 1]);
641  }
642 
643 };
644 
645 #ifndef DOXYGEN_IGNORE_INTERNAL
646 //Overload for median filtering of byte images, to special-case 3x3
647 void morphology(const BasicImage<byte>& in, const std::vector<ImageRef>& selem, const Morphology::Median<byte>& m, BasicImage<byte>& out);
648 #endif
649 
650 }
651 
652 #endif
Input images have incompatible dimensions.
Definition: vision_exceptions.h:26
Class for performing percentile filtering of bytes.
Definition: morphology.h:525
Class for performing binary median filtering.
Definition: morphology.h:596
All classes and functions are within the CVD namespace.
Definition: argb.h:6
ImageRef size() const
What is the size of this image?
Definition: image.h:557
void threshold(BasicImage< T > &im, const T &minimum, const T &hi)
thresholds an image by setting all pixel values below a minimum to 0 and all values above to a given ...
Definition: vision.h:256
Class for performing greyscale dilation.
Definition: morphology.h:383
Class for performing binary dilation.
Definition: morphology.h:584
void resize(const ImageRef &size)
Resize the image (destroying the data).
Definition: image.h:731
A helper class for performing basic grayscale morphology on an image of bytes.
Definition: morphology.h:401
Class for performing percentile filtering of bytes.
Definition: morphology.h:470
Class for performing greyscale erosion.
Definition: morphology.h:376
ConstPointerType data() const
Returns the raw image data.
Definition: image.h:535
Definition: morphology.h:272
Class for performing binary morphology.
Definition: morphology.h:538
Definition: image_ref.h:29
unsigned char byte
An 8-bit datatype.
Definition: byte.h:8
Class for performing binary erosion.
Definition: morphology.h:572
Definition: image.h:62
A generic image class to manage a block of arbitrarily padded data as an image.
Definition: image.h:273
A full image which manages its own data.
Definition: image.h:623
void morphology(const BasicImage< T > &in, const std::vector< ImageRef > &selem, const Accumulator &a_, BasicImage< T > &out)
Perform a morphological operation on the image.
Definition: morphology.h:78
A helper class for performing basic grayscale morphology on an image.
Definition: morphology.h:333
Class for performing median filtering.
Definition: morphology.h:395
Class for performing percentile filtering.
Definition: morphology.h:390