libcvd
convolution.h
1 
2 #ifndef CVD_CONVOLUTION_H_
3 #define CVD_CONVOLUTION_H_
4 
5 #include <algorithm>
6 #include <memory>
7 #include <numeric>
8 #include <vector>
9 
10 #include <cvd/config.h>
11 #include <cvd/exceptions.h>
12 #include <cvd/image.h>
13 #include <cvd/internal/pixel_operations.h>
14 #include <cvd/utility.h>
15 
16 namespace CVD
17 {
18 
27 template <class T>
28 T gaussianKernel(std::vector<T>& k, T maxval, double stddev)
29 {
30  double sum = 0;
31  unsigned int i, argmax = 0;
32  std::vector<double> kernel(k.size());
33  for(i = 0; i < k.size(); i++)
34  {
35  double x = i + 0.5 - k.size() / 2.0;
36  sum += kernel[i] = exp(-x * x / (2 * stddev * stddev));
37  if(kernel[i] > kernel[argmax])
38  argmax = i;
39  }
40  T finalSum = 0;
41  for(i = 0; i < k.size(); i++)
42  finalSum += k[i] = (T)(kernel[i] * maxval / kernel[argmax]);
43  return finalSum;
44 }
45 
53 template <class S, class T>
54 T scaleKernel(const std::vector<S>& k, std::vector<T>& scaled, T maxval)
55 {
56  unsigned int i, argmax = 0;
57  for(i = 1; i < k.size(); i++)
58  if(k[i] > k[argmax])
59  argmax = i;
60  scaled.resize(k.size());
61  T sum = 0;
62  for(i = 0; i < k.size(); i++)
63  sum += (scaled[i] = (T)((k[i] * maxval) / k[argmax]));
64  return sum;
65 }
66 
67 template <class T>
68 void convolveGaussian5_1(BasicImage<T>& I)
69 {
70  int w = I.size().x;
71  int h = I.size().y;
72  int i, j;
73  for(j = 0; j < w; j++)
74  {
75  T* src = I.data() + j;
76  T* end = src + w * (h - 4);
77  while(src != end)
78  {
79  T sum = (T)(0.0544887 * (src[0] + src[4 * w])
80  + 0.2442010 * (src[w] + src[3 * w])
81  + 0.4026200 * src[2 * w]);
82  *(src) = sum;
83  src += w;
84  }
85  }
86  for(i = h - 5; i >= 0; i--)
87  {
88  T* src = I.data() + i * w;
89  T* end = src + w - 4;
90  while(src != end)
91  {
92  T sum = (T)(0.0544887 * (src[0] + src[4])
93  + 0.2442010 * (src[1] + src[3])
94  + 0.4026200 * src[2]);
95  *(src + 2 * w + 2) = sum;
96  ++src;
97  }
98  }
99 }
100 
101 namespace Exceptions
102 {
103 
106  namespace Convolution
107  {
110  struct All : public CVD::Exceptions::All
111  {
112  using CVD::Exceptions::All::All;
113  };
114 
117  struct IncompatibleImageSizes : public All
118  {
119  IncompatibleImageSizes(const std::string& function)
120  : All("Incompatible image sizes in " + function)
121  {
122  }
123  };
124 
127  struct OddSizedKernelRequired : public All
128  {
129  OddSizedKernelRequired(const std::string& function)
130  : All("Odd sized kernel required in " + function) {};
131  };
132  }
133 }
134 //void convolveGaussian5_1(BasicImage<byte>& I);
135 
140 template <class T>
142 {
143  typedef typename Pixel::traits<T>::wider_type sum_type;
144  if(I.size() != J.size())
145  {
146  throw Exceptions::Convolution::IncompatibleImageSizes("convolveWithBox");
147  }
148  int w = I.size().x;
149  int h = I.size().y;
150  ImageRef win = 2 * hwin + ImageRef(1, 1);
151  const double factor = 1.0 / (win.x * win.y);
152  std::vector<sum_type> buffer(w * win.y);
153  std::vector<sum_type> sums_v(w);
154  sum_type* sums = &sums_v[0];
155  sum_type* next_row = &buffer[0];
156  sum_type* oldest_row = &buffer[0];
157  zeroPixels(sums, w);
158  const T* input = I.data();
159  T* output = J[hwin.y] - hwin.x;
160  for(int i = 0; i < h; i++)
161  {
162  sum_type hsum = sum_type();
163  const T* back = input;
164  int j;
165  for(j = 0; j < win.x - 1; j++)
166  hsum += input[j];
167  for(; j < w; j++)
168  {
169  hsum += input[j];
170  next_row[j] = hsum;
171  sums[j] += hsum;
172  hsum -= *(back++);
173  }
174  if(i >= win.y - 1)
175  {
176  assign_multiple(sums + win.x - 1, factor, output + win.x - 1, w - win.x + 1);
177  differences(sums + win.x - 1, oldest_row + win.x - 1, sums + win.x - 1, w - win.x + 1);
178  output += w;
179  oldest_row += w;
180  if(oldest_row == &buffer[0] + w * win.y)
181  oldest_row = &buffer[0];
182  }
183  input += w;
184  next_row += w;
185  if(next_row == &buffer[0] + w * win.y)
186  next_row = &buffer[0];
187  }
188 }
189 
190 template <class T>
191 inline void convolveWithBox(const BasicImage<T>& I, BasicImage<T>& J, int hwin)
192 {
193  convolveWithBox(I, J, ImageRef(hwin, hwin));
194 }
195 
196 template <class T>
197 inline void convolveWithBox(BasicImage<T>& I, int hwin)
198 {
199  convolveWithBox(I, I, hwin);
200 }
201 
202 template <class T>
203 inline void convolveWithBox(BasicImage<T>& I, ImageRef hwin)
204 {
205  convolveWithBox(I, I, hwin);
206 }
207 
208 template <class T, int A, int B, int C>
209 void convolveSymmetric(Image<T>& I)
210 {
211  typedef typename Pixel::traits<T>::wider_type wider;
212  static const wider S = (A + B + C + B + A);
213  int width = I.size().x;
214  int height = I.size().y;
215  T* p = I.data();
216  int i, j;
217  for(i = 0; i < height; i++)
218  {
219  wider a = p[0];
220  wider b = p[1];
221  wider c = p[2];
222  wider d = p[3];
223  p[0] = (T)(((c + c) * A + (b + b) * B + a * C) / S);
224  p[1] = (T)(((b + d) * A + (a + c) * B + b * C) / S);
225  for(j = 0; j < width - 4; j++, p++)
226  {
227  wider e = p[4];
228  p[2] = (T)(((a + e) * A + (b + d) * B + c * C) / S);
229  a = b;
230  b = c;
231  c = d;
232  d = e;
233  }
234  p[2] = (T)(((a + c) * A + (b + d) * B + c * C) / S);
235  p[3] = (T)(((b + b) * A + (c + c) * B + d * C) / S);
236  p += 4;
237  }
238  for(j = 0; j < width; j++)
239  {
240  p = I.data() + j;
241  wider a = p[0];
242  wider b = p[width];
243  p[0] = (T)(((p[2 * width] + p[2 * width]) * A + (b + b) * B + a * C) / S);
244  p[width] = (T)(((b + p[width * 3]) * A + (a + p[2 * width]) * B + b * C) / S);
245  for(i = 0; i < height - 4; i++)
246  {
247  wider c = p[2 * width];
248  p[2 * width] = (T)(((a + p[4 * width]) * A + (b + p[3 * width]) * B + c * C) / S);
249  a = b;
250  b = c;
251  p += width;
252  }
253  wider c = p[2 * width];
254  p[2 * width] = (T)(((a + c) * A + (b + p[width * 3]) * B + c * C) / S);
255  p[3 * width] = (T)(((b + b) * A + (c + c) * B + p[width * 3] * C) / S);
256  }
257 }
258 
259 template <class T, int A, int B, int C, int D>
260 void convolveSymmetric(Image<T>& I)
261 {
262  typedef typename Pixel::traits<T>::wider_type wider;
263  static const wider S = (A + B + C + D + C + B + A);
264  int width = I.size().x;
265  int height = I.size().y;
266  T* p = I.data();
267  int i, j;
268  for(i = 0; i < height; i++)
269  {
270  wider a = p[0];
271  wider b = p[1];
272  wider c = p[2];
273  wider d = p[3];
274  p[0] = (T)(((d + d) * A + (c + c) * B + (b + b) * C + a * D) / S);
275  p[1] = (T)(((c + p[4]) * A + (b + d) * B + (a + c) * C + b * D) / S);
276  p[2] = (T)(((b + p[5]) * A + (a + p[4]) * B + (b + d) * C + c * D) / S);
277  for(j = 0; j < width - 6; j++, p++)
278  {
279  d = p[3];
280  p[3] = (T)(((a + p[6]) * A + (b + p[5]) * B + (c + p[4]) * C + d * D) / S);
281  a = b;
282  b = c;
283  c = d;
284  }
285  d = p[3];
286  wider e = p[4];
287  p[3] = (T)(((a + e) * A + (b + p[5]) * B + (c + e) * C + d * D) / S);
288  p[4] = (T)(((b + d) * A + (c + e) * B + (d + p[5]) * C + e * D) / S);
289  p[5] = (T)(((c + c) * A + (d + d) * B + (e + e) * C + p[5] * D) / S);
290  p += 6;
291  }
292  for(j = 0; j < width; j++)
293  {
294  p = I.data() + j;
295  wider a = p[0];
296  wider b = p[width];
297  wider c = p[2 * width];
298  wider d = p[3 * width];
299  p[0] = (T)(((d + d) * A + (c + c) * B + (b + b) * C + a * D) / S);
300  p[width] = (T)(((c + p[4 * width]) * A + (b + d) * B + (a + c) * C + b * D) / S);
301  p[2 * width] = (T)(((b + p[5 * width]) * A + (a + p[4 * width]) * B + (b + d) * C + c * D) / S);
302  for(i = 0; i < height - 6; i++)
303  {
304  d = p[3 * width];
305  p[3 * width] = (T)(((a + p[width * 6]) * A + (b + p[width * 5]) * B + (c + p[width * 4]) * C + d * D) / S);
306  a = b;
307  b = c;
308  c = d;
309  p += width;
310  }
311  d = p[3 * width];
312  wider e = p[4 * width];
313  p[3 * width] = (T)(((a + e) * A + (b + p[5 * width]) * B + (c + e) * C + d * D) / S);
314  p[4 * width] = (T)(((b + d) * A + (c + e) * B + (d + p[5 * width]) * C + e * D) / S);
315  p[5 * width] = (T)(((c + c) * A + (d + d) * B + (e + e) * C + p[5 * width] * D) / S);
316  }
317 }
318 
319 template <class T, class K>
320 void convolveSeparableSymmetric(Image<T>& I, const std::vector<K>& kernel, K divisor)
321 {
322  typedef typename Pixel::traits<T>::wider_type sum_type;
323  int w = I.size().x;
324  int h = I.size().y;
325  int r = (int)kernel.size() / 2;
326  int i, j;
327  int m;
328  double factor = 1.0 / divisor;
329  for(j = 0; j < w; j++)
330  {
331  T* src = I.data() + j;
332  for(i = 0; i < h - 2 * r; i++, src += w)
333  {
334  sum_type sum = src[r * w] * kernel[r], v;
335  for(m = 0; m < r; m++)
336  sum += (src[m * w] + src[(2 * r - m) * w]) * kernel[m];
337  *(src) = static_cast<T>(sum * factor);
338  }
339  }
340  int offset = r * w + r;
341  for(i = h - 2 * r - 1; i >= 0; i--)
342  {
343  T* src = I[w];
344  for(j = 0; j < w - 2 * r; j++, src++)
345  {
346  sum_type sum = src[r] * kernel[r], v;
347  for(m = 0; m < r; m++)
348  sum += (src[m] + src[2 * r - m]) * kernel[m];
349  *(src + offset) = static_cast<T>(sum * factor);
350  }
351  }
352 }
353 
354 template <class A, class B>
356 {
357  static inline const B* get(const A* row, int w, B* rowbuf)
358  {
359  std::copy(row, row + w, rowbuf);
360  return rowbuf;
361  }
362 };
363 
364 template <class T>
365 struct GetPixelRowTyped<T, T>
366 {
367  static inline const T* get(const T* row, int, T*)
368  {
369  return row;
370  }
371 };
372 
373 template <class A, class B>
374 const B* getPixelRowTyped(const A* row, int n, B* rowbuf)
375 {
376  return GetPixelRowTyped<A, B>::get(row, n, rowbuf);
377 }
378 
379 template <class T, class S>
380 struct CastCopy
381 {
382  static inline void cast_copy(const T* from, S* to, int count)
383  {
384  for(int i = 0; i < count; i++)
385  to[i] = static_cast<S>(from[i]);
386  }
387 };
388 
389 template <class T>
390 struct CastCopy<T, T>
391 {
392  static inline void cast_copy(const T* from, T* to, int count)
393  {
394  std::copy(from, from + count, to);
395  }
396 };
397 
398 template <class T, class S>
399 inline void cast_copy(const T* from, S* to, int count) { CastCopy<T, S>::cast_copy(from, to, count); }
400 
401 template <class T, int N = -1, int C = Pixel::Component<T>::count>
403 {
404  template <class S>
405  static inline T at(const T* input, const S& factor, const S* kernel) { return ConvolveMiddle<T, -1, C>::at(input, factor, kernel, N); }
406 };
407 
408 template <class T, int N>
409 struct ConvolveMiddle<T, N, 1>
410 {
411  template <class S>
412  static inline T at(const T* input, const S& factor, const S* kernel) { return ConvolveMiddle<T, N - 1>::at(input, factor, kernel) + (input[-N] + input[N]) * kernel[N - 1]; }
413 };
414 
415 template <class T>
416 struct ConvolveMiddle<T, -1, 1>
417 {
418  template <class S>
419  static inline T at(const T* input, const S& factor, const S* kernel, int ksize)
420  {
421  T hsum = *input * factor;
422  for(int k = 0; k < ksize; k++)
423  hsum += (input[-k - 1] + input[k + 1]) * kernel[k];
424  return hsum;
425  }
426 };
427 
428 template <class T, int C>
429 struct ConvolveMiddle<T, -1, C>
430 {
431  template <class S>
432  static inline T at(const T* input, const S& factor, const S* kernel, int ksize)
433  {
434  T hsum = *input * factor;
435  for(int k = 0; k < ksize; k++)
436  hsum += (input[-k - 1] + input[k + 1]) * kernel[k];
437  return hsum;
438  }
439 };
440 
441 template <class T>
442 struct ConvolveMiddle<T, 0, 1>
443 {
444  template <class S>
445  static inline T at(const T* input, const S& factor, const S*) { return *input * factor; }
446 };
447 
448 template <class T, class S>
449 inline const T* convolveMiddle(const T* input, const S& factor, const S* kernel, int ksize, int n, T* output)
450 {
451 #define CALL_CM(I) \
452  for(int j = 0; j < n; ++j, ++input, ++output) \
453  { \
454  *output = ConvolveMiddle<T, I>::at(input, factor, kernel); \
455  } \
456  break
457 
458  switch(ksize)
459  {
460  case 0:
461  CALL_CM(0);
462  case 1:
463  CALL_CM(1);
464  case 2:
465  CALL_CM(2);
466  case 3:
467  CALL_CM(3);
468  case 4:
469  CALL_CM(4);
470  case 5:
471  CALL_CM(5);
472  case 6:
473  CALL_CM(6);
474  case 7:
475  CALL_CM(7);
476  case 8:
477  CALL_CM(8);
478  default:
479  for(int j = 0; j < n; j++, input++)
480  {
481  *(output++) = ConvolveMiddle<T, -1>::at(input, factor, kernel, ksize);
482  }
483  }
484  return input;
485 #undef CALL_CM
486 }
487 
488 template <class T>
489 inline void convolveGaussian(BasicImage<T>& I, double sigma, double sigmas = 3.0)
490 {
491  convolveGaussian(I, I, sigma, sigmas);
492 }
493 
494 template <class T>
495 void convolveGaussian(const BasicImage<T>& I, BasicImage<T>& out, double sigma, double sigmas = 3.0)
496 {
497  typedef typename Pixel::traits<typename Pixel::Component<T>::type>::float_type sum_comp_type;
498  typedef typename Pixel::traits<T>::float_type sum_type;
499  assert(out.size() == I.size());
500  int ksize = (int)ceil(sigmas * sigma);
501  //std::cerr << "sigma: " << sigma << " kernel: " << ksize << std::endl;
502  std::vector<sum_comp_type> kernel(ksize);
503  sum_comp_type ksum = sum_comp_type();
504  for(int i = 1; i <= ksize; i++)
505  ksum += (kernel[i - 1] = static_cast<sum_comp_type>(exp(-i * i / (2 * sigma * sigma))));
506  for(int i = 0; i < ksize; i++)
507  kernel[i] /= (2 * ksum + 1);
508  double factor = 1.0 / (2 * ksum + 1);
509  int w = I.size().x;
510  int h = I.size().y;
511  int swin = 2 * ksize;
512 
513  if(w < ksize || h < ksize)
514  {
515  out.copy_from(I);
516  return;
517  }
518 
519  std::vector<sum_type> buffer(std::max(w, ksize) * (swin + 1));
520  std::vector<sum_type> aligned_rowbuf(w);
521  std::vector<sum_type> aligned_outbuf(w);
522 
523  sum_type* rowbuf = aligned_rowbuf.data();
524  sum_type* outbuf = aligned_outbuf.data();
525 
526  std::vector<sum_type*> rows(swin + 1);
527  for(int k = 0; k < swin + 1; k++)
528  rows[k] = buffer.data() + k * std::max(w, ksize);
529 
530  T* output = out.data();
531  for(int i = 0; i < h; i++)
532  {
533  sum_type* next_row = rows[swin];
534  const sum_type* input = getPixelRowTyped(I[i], w, rowbuf);
535  // beginning of row
536  for(int j = 0; j < ksize; j++)
537  {
538  sum_type hsum = static_cast<sum_type>(input[j] * factor);
539  for(int k = 0; k < ksize; k++)
540  hsum += (input[std::max(j - k - 1, 0)] + input[j + k + 1]) * kernel[k];
541  next_row[j] = hsum;
542  }
543  // middle of row
544  input += ksize;
545  input = convolveMiddle<sum_type, sum_comp_type>(input, static_cast<sum_comp_type>(factor), &kernel.front(), ksize, w - swin, next_row + ksize);
546  // end of row
547  for(int j = w - ksize; j < w; j++, input++)
548  {
549  sum_type hsum = static_cast<sum_type>(*input * factor);
550  const int room = w - j;
551  for(int k = 0; k < ksize; k++)
552  {
553  hsum += (input[-k - 1] + input[std::min(k + 1, room - 1)]) * kernel[k];
554  }
555  next_row[j] = hsum;
556  }
557  // vertical
558  if(i >= swin)
559  {
560  const sum_type* middle_row = rows[ksize];
561  assign_multiple(middle_row, factor, outbuf, w);
562  for(int k = 0; k < ksize; k++)
563  {
564  const sum_comp_type m = kernel[k];
565  const sum_type* row1 = rows[ksize - k - 1];
566  const sum_type* row2 = rows[ksize + k + 1];
567  add_multiple_of_sum(row1, row2, m, outbuf, w);
568  }
569  cast_copy(outbuf, output, w);
570  output += w;
571  if(i == h - 1)
572  {
573  for(int r = 0; r < ksize; r++)
574  {
575  const sum_type* middle_row = rows[ksize + r + 1];
576  assign_multiple(middle_row, factor, outbuf, w);
577  for(int k = 0; k < ksize; k++)
578  {
579  const sum_comp_type m = kernel[k];
580  const sum_type* row1 = rows[ksize + r - k];
581  const sum_type* row2 = rows[std::min(ksize + r + k + 2, swin)];
582  add_multiple_of_sum(row1, row2, m, outbuf, w);
583  }
584  cast_copy(outbuf, output, w);
585  output += w;
586  }
587  }
588  }
589  else if(i == swin - 1)
590  {
591  for(int r = 0; r < ksize; r++)
592  {
593  const sum_type* middle_row = rows[r + 1];
594  assign_multiple(middle_row, factor, outbuf, w);
595  for(int k = 0; k < ksize; k++)
596  {
597  const sum_comp_type m = kernel[k];
598  const sum_type* row1 = rows[std::max(r - k - 1, 0) + 1];
599  const sum_type* row2 = rows[r + k + 2];
600  add_multiple_of_sum(row1, row2, m, outbuf, w);
601  }
602  cast_copy(outbuf, output, w);
603  output += w;
604  }
605  }
606 
607  sum_type* tmp = rows[0];
608  for(int r = 0; r < swin; r++)
609  rows[r] = rows[r + 1];
610  rows[swin] = tmp;
611  }
612 }
613 
614 void compute_van_vliet_b(double sigma, double b[]);
615 void compute_triggs_M(const double b[], double M[][3]);
616 void van_vliet_blur(const double b[], const BasicImage<float> in, BasicImage<float> out);
617 
618 void convolveGaussian(const BasicImage<float>& I, BasicImage<float>& out, double sigma, double sigmas = 3.0);
619 void convolveGaussian_fir(const BasicImage<float>& I, BasicImage<float>& out, double sigma, double sigmas = 3.0);
620 
621 template <class T, class O, class K>
622 void convolve_gaussian_3(const BasicImage<T>& I, BasicImage<O>& out, K k1, K k2)
623 {
624  assert(I.size() == out.size());
625  const T* a = I.data();
626  const int w = I.size().x;
627  O* o = out.data() + w + 1;
628  int total = I.totalsize() - 2 * w - 2;
629  const double cross = k1 * k2;
630  k1 *= k1;
631  k2 *= k2;
632  while(total--)
633  {
634  const double sum = k1 * (a[0] + a[2] + a[w * 2] + a[w * 2 + 2]) + cross * (a[1] + a[w * 2 + 1] + a[w] + a[w + 2]) + k2 * a[w + 1];
635  *o++ = Pixel::scalar_convert<O, T, double>(sum);
636  ++a;
637  }
638 }
639 
640 } // namespace CVD
641 
642 #endif
T gaussianKernel(std::vector< T > &k, T maxval, double stddev)
creates a Gaussian kernel with given maximum value and standard deviation.
Definition: convolution.h:28
All classes and functions are within the CVD namespace.
Definition: argb.h:6
Input images have incompatible dimensions.
Definition: convolution.h:127
ImageRef size() const
What is the size of this image?
Definition: image.h:557
void add_multiple_of_sum(const A *a, const A *b, const C &c, B *out, size_t count)
Compute pointwise (a_i + b_i) * c and add to out_i This is accelerated using SIMD for some platforms ...
Definition: utility.h:157
Base class for all Image_IO exceptions.
Definition: convolution.h:110
void convolveWithBox(const BasicImage< T > &I, BasicImage< T > &J, ImageRef hwin)
convolves an image with a box of given size.
Definition: convolution.h:141
Definition: convolution.h:380
void copy(const BasicImage< S > &in, BasicImage< T > &out, ImageRef size=ImageRef(-1, -1), ImageRef begin=ImageRef(), ImageRef dst=ImageRef())
Generic image copy function for copying sub rectangles of images into other images.
Definition: utility.h:30
Definition: convolution.h:355
ConstPointerType data() const
Returns the raw image data.
Definition: image.h:535
int x
The x co-ordinate.
Definition: image_ref.h:172
Base class for all CVD exceptions.
Definition: exceptions.h:15
Input images have incompatible dimensions.
Definition: convolution.h:117
Definition: image_ref.h:29
Definition: convolution.h:402
A generic image class to manage a block of arbitrarily padded data as an image.
Definition: image.h:273
int y
The y co-ordinate.
Definition: image_ref.h:173
A full image which manages its own data.
Definition: image.h:623
void assign_multiple(const A *a, const B &c, C *out, size_t count)
Compute pointwise a_i * c and store in out_i This is accelerated using SIMD for some platforms and da...
Definition: utility.h:167
Definition: builtin_components.h:38
T scaleKernel(const std::vector< S > &k, std::vector< T > &scaled, T maxval)
scales a GaussianKernel to a different maximum value.
Definition: convolution.h:54
void differences(const A *a, const A *b, B *diff, size_t count)
Compute pointwise differences (a_i - b_i) and store in diff_i This is accelerated using SIMD for some...
Definition: utility.h:147
Definition: pixel_traits.h:16
void zeroPixels(T *pixels, int count)
Set many pixels to the default value (typically 0) For multi-component pixels, this zeros all compone...
Definition: utility.h:105