libcvd
vision.h
1 
2 #ifndef CVD_VISION_H_
3 #define CVD_VISION_H_
4 
5 #include <algorithm>
6 #include <memory>
7 #include <vector>
8 
9 #include <cvd/image.h>
10 #include <cvd/internal/pixel_operations.h>
11 #include <cvd/utility.h>
12 #include <cvd/vision_exceptions.h>
13 
14 #if defined(CVD_HAVE_TOON)
15 #include <TooN/TooN.h>
16 #include <TooN/helpers.h>
17 #endif
18 
19 namespace CVD
20 {
21 
40 template <class C>
42 {
43  Image<C> out(ImageRef(std::floor(in.size().x * scale), std::floor(in.size().y * scale)));
44 
45  scale = 1. / scale;
46 
47  if(scale == 1)
48  out.copy_from(in);
49  else
50  for(int r = 0; r < out.size().y; r++)
51  {
52  float r_in = r * scale;
53  int r_i = floor(r_in);
54  float r_delta = r_in - floor(r_in);
55 
56  //4x unrolling makes it about 15% faster for some reason.
57  //Maybe because of aliasing?
58  int c = 0;
59  for(; c < out.size().x - 4; c += 4)
60  {
61  float c_in1 = c * scale;
62  int c_i1 = floor(c_in1);
63  float c_delta1 = c_in1 - floor(c_in1);
64  auto r11 = in[r_i][c_i1] * (1 - c_delta1) + in[r_i][c_i1 + 1] * c_delta1;
65  auto r21 = in[r_i + 1][c_i1] * (1 - c_delta1) + in[r_i + 1][c_i1 + 1] * c_delta1;
66 
67  float c_in2 = (c + 1) * scale;
68  int c_i2 = floor(c_in2);
69  float c_delta2 = c_in2 - floor(c_in2);
70  auto r12 = in[r_i][c_i2] * (1 - c_delta2) + in[r_i][c_i2 + 1] * c_delta2;
71  auto r22 = in[r_i + 1][c_i2] * (1 - c_delta2) + in[r_i + 1][c_i2 + 1] * c_delta2;
72 
73  float c_in3 = (c + 2) * scale;
74  int c_i3 = floor(c_in3);
75  float c_delta3 = c_in3 - floor(c_in3);
76  auto r13 = in[r_i][c_i3] * (1 - c_delta3) + in[r_i][c_i3 + 1] * c_delta3;
77  auto r23 = in[r_i + 1][c_i3] * (1 - c_delta3) + in[r_i + 1][c_i3 + 1] * c_delta3;
78 
79  float c_in4 = (c + 3) * scale;
80  int c_i4 = floor(c_in4);
81  float c_delta4 = c_in4 - floor(c_in4);
82  auto r14 = in[r_i][c_i4] * (1 - c_delta4) + in[r_i][c_i4 + 1] * c_delta4;
83  auto r24 = in[r_i + 1][c_i4] * (1 - c_delta4) + in[r_i + 1][c_i4 + 1] * c_delta4;
84 
85  out[r][c] = r11 * (1 - r_delta) + r21 * r_delta;
86  out[r][c + 1] = r12 * (1 - r_delta) + r22 * r_delta;
87  out[r][c + 2] = r13 * (1 - r_delta) + r23 * r_delta;
88  out[r][c + 3] = r14 * (1 - r_delta) + r24 * r_delta;
89  }
90 
91  for(; c < out.size().x; c++)
92  {
93  float c_in1 = c * scale;
94  int c_i1 = floor(c_in1);
95  float c_delta1 = c_in1 - floor(c_in1);
96  auto r11 = in[r_i][c_i1] * (1 - c_delta1) + in[r_i][c_i1 + 1] * c_delta1;
97  auto r21 = in[r_i + 1][c_i1] * (1 - c_delta1) + in[r_i + 1][c_i1 + 1] * c_delta1;
98 
99  out[r][c] = r11 * (1 - r_delta) + r21 * r_delta;
100  }
101  }
102 
103  return out;
104 }
105 
120 template <class C>
122 {
123  const BasicImage<C>* current_ptr = &in;
124  Image<C> reduced;
125 
126  while(scale < 0.5)
127  {
128  reduced = halfSample(*current_ptr);
129  current_ptr = &reduced;
130  scale *= 2;
131  }
132 
133  if(scale < 2. / 3)
134  {
135  reduced = twoThirdsSample(*current_ptr);
136  current_ptr = &reduced;
137  scale *= 3. / 2.;
138  }
139 
140  return linearInterpolationDownsample(*current_ptr, scale);
141 }
142 
149 template <class C>
151 {
152  typedef typename Pixel::traits<C>::wider_type sum_type;
153  if((in.size() / 3 * 2) != out.size())
154  throw Exceptions::Vision::IncompatibleImageSizes(__FUNCTION__);
155 
156  for(int yy = 0, y = 0; y < in.size().y - 2; y += 3, yy += 2)
157  for(int xx = 0, x = 0; x < in.size().x - 2; x += 3, xx += 2)
158  {
159  // a b c
160  // d e f
161  // g h i
162 
163  sum_type a(in[y][x]);
164  sum_type b(in[y][x + 1]);
165  sum_type c(in[y][x + 2]);
166  sum_type d(in[y + 1][x]);
167  sum_type e(in[y + 1][x + 1]);
168  sum_type f(in[y + 1][x + 2]);
169  sum_type g(in[y + 2][x]);
170  sum_type h(in[y + 2][x + 1]);
171  sum_type i(in[y + 2][x + 2]);
172 
173  out[yy][xx] = static_cast<C>((a * 4 + b * 2 + d * 2 + e) / 9);
174  out[yy][xx + 1] = static_cast<C>((c * 4 + b * 2 + f * 2 + e) / 9);
175  out[yy + 1][xx] = static_cast<C>((g * 4 + h * 2 + d * 2 + e) / 9);
176  out[yy + 1][xx + 1] = static_cast<C>((i * 4 + h * 2 + f * 2 + e) / 9);
177  }
178 }
179 
184 
192 template <class C>
194 {
195  Image<C> to(from.size() / 3 * 2);
196  twoThirdsSample(from, to);
197  return to;
198 }
199 
205 template <class T>
207 {
208  typedef typename Pixel::traits<T>::wider_type sum_type;
209  if((in.size() / 2) != out.size())
210  throw Exceptions::Vision::IncompatibleImageSizes("halfSample");
211 
212  for(int yo = 0; yo < out.size().y; yo++)
213  for(int xo = 0; xo < out.size().x; xo++)
214  out[yo][xo] = static_cast<T>((sum_type(in[yo * 2][xo * 2]) + in[yo * 2 + 1][xo * 2] + in[yo * 2][xo * 2 + 1] + in[yo * 2 + 1][xo * 2 + 1]) / 4);
215 }
216 
217 void halfSample(const BasicImage<byte>& in, BasicImage<byte>& out);
218 
224 template <class T>
226 {
227  Image<T> out(in.size() / 2);
228  halfSample(in, out);
229  return out;
230 }
231 
240 template <class T>
241 inline Image<T> halfSample(Image<T> in, unsigned int octaves)
242 {
243  for(; octaves > 0; --octaves)
244  {
245  in = halfSample(in);
246  }
247  return in;
248 }
249 
255 template <class T>
256 void threshold(BasicImage<T>& im, const T& minimum, const T& hi)
257 {
258  typename BasicImage<T>::iterator it = im.begin();
259  typename BasicImage<T>::iterator end = im.end();
260  while(it != end)
261  {
262  if(*it < minimum)
263  *it = T();
264  else
265  *it = hi;
266  ++it;
267  }
268 }
269 
276 template <class T>
277 void stats(const BasicImage<T>& im, T& mean, T& stddev)
278 {
279  const int c = Pixel::Component<T>::count;
280  double v;
281  double sum[c] = { 0 };
282  double sumSq[c] = { 0 };
283  const T* p = im.data();
284  const T* end = im.data() + im.totalsize();
285  while(p != end)
286  {
287  for(int k = 0; k < c; k++)
288  {
289  v = Pixel::Component<T>::get(*p, k);
290  sum[k] += v;
291  sumSq[k] += v * v;
292  }
293  ++p;
294  }
295  for(int k = 0; k < c; k++)
296  {
297  double m = sum[k] / im.totalsize();
298  Pixel::Component<T>::get(mean, k) = (typename Pixel::Component<T>::type)m;
299  sumSq[k] /= im.totalsize();
300  Pixel::Component<T>::get(stddev, k) = (typename Pixel::Component<T>::type)sqrt(sumSq[k] - m * m);
301  }
302 }
303 
306 template <class T>
308 {
309  const T& factor;
310  multiplyBy(const T& f)
311  : factor(f) {};
312  template <class S>
313  inline S operator()(const S& s) const
314  {
315  return s * factor;
316  }
317 };
318 
319 template <class S, class T, int Sn = Pixel::Component<S>::count, int Tn = Pixel::Component<T>::count>
320 struct Gradient;
321 
322 template <class S, class T>
323 struct Gradient<S, T, 1, 2>
324 {
325  typedef typename Pixel::Component<S>::type SComp;
326  typedef typename Pixel::Component<T>::type TComp;
327  typedef typename Pixel::traits<SComp>::wider_type diff_type;
328  static void gradient(const BasicImage<S>& I, BasicImage<T>& grad)
329  {
330  for(int y = 0; y < I.size().y; y++)
331  for(int x = 0; x < I.size().x; x++)
332  {
333  Pixel::Component<T>::get(grad[y][x], 0) = Pixel::scalar_convert<TComp, SComp, diff_type>(diff_type(I[y][x + 1]) - I[y][x - 1]);
334  Pixel::Component<T>::get(grad[y][x], 1) = Pixel::scalar_convert<TComp, SComp, diff_type>(diff_type(I[y + 1][x]) - I[y - 1][x]);
335  }
336 
337  zeroBorders(grad);
338  }
339 };
340 
347 template <class S, class T>
348 void gradient(const BasicImage<S>& im, BasicImage<T>& out)
349 {
350  if(im.size() != out.size())
352  Gradient<S, T>::gradient(im, out);
353 }
354 
355 #ifndef DOXYGEN_IGNORE_INTERNAL
356 void gradient(const BasicImage<byte>& im, BasicImage<short[2]>& out);
357 #endif
358 
359 template <class T, class S, typename Precision>
360 inline void sample(const BasicImage<S>& im, Precision x, Precision y, T& result)
361 {
362  typedef typename Pixel::Component<S>::type SComp;
363  typedef typename Pixel::Component<T>::type TComp;
364  const int lx = (int)x;
365  const int ly = (int)y;
366  x -= lx;
367  y -= ly;
368  for(unsigned int i = 0; i < Pixel::Component<T>::count; i++)
369  {
370  Pixel::Component<T>::get(result, i) = Pixel::scalar_convert<TComp, SComp>(
371  (1 - y) * ((1 - x) * Pixel::Component<S>::get(im[ly][lx], i) + x * Pixel::Component<S>::get(im[ly][lx + 1], i)) + y * ((1 - x) * Pixel::Component<S>::get(im[ly + 1][lx], i) + x * Pixel::Component<S>::get(im[ly + 1][lx + 1], i)));
372  }
373 }
374 
375 template <class T, class S, typename Precision>
376 inline T sample(const BasicImage<S>& im, Precision x, Precision y)
377 {
378  T result;
379  sample(im, x, y, result);
380  return result;
381 }
382 
383 inline void sample(const BasicImage<float>& im, double x, double y, float& result)
384 {
385  const int lx = (int)x;
386  const int ly = (int)y;
387  const int w = im.row_stride();
388  const float* base = im[ly] + lx;
389  const float a = base[0];
390  const float b = base[1];
391  const float c = base[w];
392  const float d = base[w + 1];
393  const float e = a - b;
394  x -= lx;
395  y -= ly;
396  result = (float)(x * (y * (e - c + d) - e) + y * (c - a) + a);
397 }
398 
399 #if defined(CVD_HAVE_TOON)
400 
411 template <typename T, typename S, typename P>
412 int transform(const BasicImage<S>& in, BasicImage<T>& out, const TooN::Matrix<2, 2, P>& M, const TooN::Vector<2, P>& inOrig, const TooN::Vector<2, P>& outOrig, const T defaultValue = T())
413 {
414  const int w = out.size().x, h = out.size().y, iw = in.size().x, ih = in.size().y;
415  const TooN::Vector<2, P> across = M.T()[0];
416  const TooN::Vector<2, P> down = M.T()[1];
417 
418  const TooN::Vector<2, P> p0 = inOrig - M * outOrig;
419  const TooN::Vector<2, P> p1 = p0 + w * across;
420  const TooN::Vector<2, P> p2 = p0 + h * down;
421  const TooN::Vector<2, P> p3 = p0 + w * across + h * down;
422 
423  // ul --> p0
424  // ur --> w*across + p0
425  // ll --> h*down + p0
426  // lr --> w*across + h*down + p0
427  P min_x = p0[0], min_y = p0[1];
428  P max_x = min_x, max_y = min_y;
429 
430  // Minimal comparisons needed to determine bounds
431  if(across[0] < 0)
432  min_x += w * across[0];
433  else
434  max_x += w * across[0];
435  if(down[0] < 0)
436  min_x += h * down[0];
437  else
438  max_x += h * down[0];
439  if(across[1] < 0)
440  min_y += w * across[1];
441  else
442  max_y += w * across[1];
443  if(down[1] < 0)
444  min_y += h * down[1];
445  else
446  max_y += h * down[1];
447 
448  // This gets from the end of one row to the beginning of the next
449  const TooN::Vector<2, P> carriage_return = down - w * across;
450 
451  //If the patch being extracted is completely in the image then no
452  //check is needed with each point.
453  if(min_x >= 0 && min_y >= 0 && max_x < iw - 1 && max_y < ih - 1)
454  {
455  TooN::Vector<2, P> p = p0;
456  for(int i = 0; i < h; ++i, p += carriage_return)
457  for(int j = 0; j < w; ++j, p += across)
458  sample(in, p[0], p[1], out[i][j]);
459  return 0;
460  }
461  else // Check each source location
462  {
463  // Store as doubles to avoid conversion cost for comparison
464  const P x_bound = iw - 1;
465  const P y_bound = ih - 1;
466  int count = 0;
467  TooN::Vector<2, P> p = p0;
468  for(int i = 0; i < h; ++i, p += carriage_return)
469  {
470  for(int j = 0; j < w; ++j, p += across)
471  {
472  //Make sure that we are extracting pixels in the image
473  if(0 <= p[0] && 0 <= p[1] && p[0] < x_bound && p[1] < y_bound)
474  sample(in, p[0], p[1], out[i][j]);
475  else
476  {
477  out[i][j] = defaultValue;
478  ++count;
479  }
480  }
481  }
482  return count;
483  }
484 }
485 
486 template <class T>
487 void transform(const BasicImage<T>& in, BasicImage<T>& out, const TooN::Matrix<3>& Minv /* <-- takes points in "out" to points in "in" */)
488 {
489  TooN::Vector<3> base = Minv.T()[2];
490  TooN::Vector<2> offset;
491  offset[0] = in.size().x / 2;
492  offset[1] = in.size().y / 2;
493  offset -= TooN::project(base);
494  TooN::Vector<3> across = Minv.T()[0];
495  TooN::Vector<3> down = Minv.T()[1];
496  double w = in.size().x - 1;
497  double h = in.size().y - 1;
498  int ow = out.size().x;
499  int oh = out.size().y;
500  base -= down * (oh / 2) + across * (ow / 2);
501  for(int row = 0; row < oh; row++, base += down)
502  {
503  TooN::Vector<3> x = base;
504  for(int col = 0; col < ow; col++, x += across)
505  {
506  TooN::Vector<2> p = project(x) + offset;
507  if(p[0] >= 0 && p[0] <= w - 1 && p[1] >= 0 && p[1] <= h - 1)
508  sample(in, p[0], p[1], out[row][col]);
509  else
510  zeroPixel(out[row][col]);
511  }
512  }
513 }
514 
516 template <typename T, typename CAM1, typename CAM2>
517 void warp(const BasicImage<T>& in, const CAM1& cam_in, BasicImage<T>& out, const CAM2& cam_out)
518 {
519  const ImageRef size = out.size();
520  for(int y = 0; y < size.y; ++y)
521  {
522  for(int x = 0; x < size.x; ++x)
523  {
524  TooN::Vector<2> l = cam_in.project(cam_out.unproject(TooN::makeVector(x, y)));
525  if(l[0] >= 0 && l[0] <= in.size().x - 1 && l[1] >= 0 && l[1] <= in.size().y - 1)
526  {
527  sample(in, l[0], l[1], out[y][x]);
528  }
529  else
530  out[y][x] = T();
531  }
532  }
533 }
534 
538 template <typename T, typename CAM1, typename CAM2>
539 Image<T> warp(const BasicImage<T>& in, const CAM1& cam_in, const ImageRef& size, const CAM2& cam_out)
540 {
541  Image<T> result(size);
542  warp(in, cam_in, result, cam_out);
543  return result;
544 }
545 
549 template <typename T, typename CAM1, typename CAM2>
550 Image<T> warp(const BasicImage<T>& in, const CAM1& cam_in, const CAM2& cam_out)
551 {
552  Image<T> result(in.size());
553  warp(in, cam_in, result, cam_out);
554  return result;
555 }
556 
557 #endif
558 
559 namespace Internal
560 {
561  template <class T>
562  void simpleTranspose(const SubImage<T>& in, SubImage<T> out)
563  {
564  CVD_ASSERT(in.size().transpose() == out.size());
565  for(int r = 0; r < in.size().y; r++)
566  for(int c = 0; c < in.size().x; c++)
567  out[c][r] = in[r][c];
568  }
569 
570  template <class T>
571  void recursiveTranspose(const SubImage<T>& in, SubImage<T> out, const int bytes = 2048)
572  {
573 
574  CVD_ASSERT(in.size().transpose() == out.size());
575 
576  if(in.size().area() * static_cast<int>(sizeof(T)) < bytes || in.size().x == 1 || in.size().y == 1)
577  simpleTranspose(in, out);
578  else if(in.size().x >= in.size().y)
579  {
580  //The image is very wide, so the strategy of picking largest-sqare-and-remainder
581  //can lead to linear recursion depth, so instead split it in half
582 
583  const int width_left = in.size().x / 2;
584  const int width_right = in.size().x - width_left;
585  const ImageRef left_chunk { width_left, in.size().y };
586  const ImageRef right_chunk { width_right, in.size().y };
587  const ImageRef right_start { width_left, 0 };
588 
589  recursiveTranspose(in.sub_image(ImageRef(0, 0), left_chunk), out.sub_image(ImageRef(0, 0), left_chunk.transpose()));
590  recursiveTranspose(in.sub_image(right_start, right_chunk), out.sub_image(right_start.transpose(), right_chunk.transpose()));
591  }
592  else
593  {
594  const int height_top = in.size().y / 2;
595  const int height_bottom = in.size().y - height_top;
596  ImageRef top_chunk { in.size().x, height_top };
597  ImageRef bottom_chunk { in.size().x, height_bottom };
598  ImageRef bottom_start { 0, height_top };
599 
600  recursiveTranspose(in.sub_image(ImageRef(0, 0), top_chunk), out.sub_image(ImageRef(0, 0), top_chunk.transpose()));
601  recursiveTranspose(in.sub_image(bottom_start, bottom_chunk), out.sub_image(bottom_start.transpose(), bottom_chunk.transpose()));
602  }
603  }
604 
605 }
606 
607 template <class T>
608 void transpose(const SubImage<T>& in, SubImage<T>&& out)
609 {
610  CVD_ASSERT(in.size().transpose() == out.size());
611  Internal::recursiveTranspose(in, out);
612 }
613 
614 template <class T>
615 Image<T> transpose(const SubImage<T>& in)
616 {
617  Image<T> out(in.size().transpose());
618  Internal::recursiveTranspose(in, out);
619  return out;
620 }
621 
623 template <class T>
625 {
626  for(int r = 0; r < in.size().y / 2; r++)
627  for(int c = 0; c < in.size().x; c++)
628  {
629  std::swap(in[r][c], in[in.size().y - 1 - r][c]);
630  }
631 }
632 
633 template <class T>
634 void flipVertical(SubImage<T>& in)
635 {
636  flipVertical(std::move(in));
637 }
638 
640 template <class T>
642 {
643  for(int r = 0; r < in.size().y; r++)
644  std::reverse(in[r], in[r] + in.size().x);
645 }
646 
647 template <class T>
648 void flipHorizontal(SubImage<T>& in)
649 {
650  flipHorizontal(std::move(in));
651 }
652 
653 namespace median
654 {
655  template <class T>
656  inline T median3(T a, T b, T c)
657  {
658  if(b < a)
659  return std::max(b, std::min(a, c));
660  else
661  return std::max(a, std::min(b, c));
662  }
663 
664  template <class T>
665  inline void sort3(T& a, T& b, T& c)
666  {
667  using std::swap;
668  if(b < a)
669  swap(a, b);
670  if(c < b)
671  swap(b, c);
672  if(b < a)
673  swap(a, b);
674  }
675 
676  template <class T>
677  T median_3x3(const T* p, const int w)
678  {
679  T a = p[-w - 1], b = p[-w], c = p[-w + 1], d = p[-1], e = p[0], f = p[1], g = p[w - 1], h = p[w], i = p[w + 1];
680  sort3(a, b, c);
681  sort3(d, e, f);
682  sort3(g, h, i);
683  e = median3(b, e, h);
684  g = std::max(std::max(a, d), g);
685  c = std::min(c, std::min(f, i));
686  return median3(c, e, g);
687  }
688 
689  template <class T>
690  void median_filter_3x3(const T* p, const int w, const int n, T* out)
691  {
692  T a = p[-w - 1], b = p[-w], d = p[-1], e = p[0], g = p[w - 1], h = p[w];
693  sort3(a, d, g);
694  sort3(b, e, h);
695  for(int j = 0; j < n; ++j, ++p, ++out)
696  {
697  T c = p[-w + 1], f = p[1], i = p[w + 1];
698  sort3(c, f, i);
699  *out = median3(std::min(std::min(g, h), i),
700  median3(d, e, f),
701  std::max(std::max(a, b), c));
702  a = b;
703  b = c;
704  d = e;
705  e = f;
706  g = h;
707  h = i;
708  }
709  }
710 }
711 
712 template <class T>
713 void median_filter_3x3(const BasicImage<T>& I, BasicImage<T> out)
714 {
715  assert(out.size() == I.size());
716  const int s = I.row_stride();
717  const int n = I.size().x - 2;
718  for(int i = 1; i + 1 < I.size().y; ++i)
719  median::median_filter_3x3(I[i] + 1, s, n, out[i] + 1);
720 }
721 
722 void median_filter_3x3(const BasicImage<byte>& I, BasicImage<byte> out);
723 
724 //template<class T>
725 
726 }; // namespace CVD
727 
728 #endif // CVD_VISION_H_
void stats(const BasicImage< T > &im, T &mean, T &stddev)
computes mean and stddev of intensities in an image.
Definition: vision.h:277
Input images have incompatible dimensions.
Definition: vision_exceptions.h:26
void gradient(const BasicImage< S > &im, BasicImage< T > &out)
computes the gradient image from an image.
Definition: vision.h:348
Image< C > fastApproximateDownSample(const BasicImage< C > &in, double scale)
Downsample an image using some fast hacks.
Definition: vision.h:121
constexpr int area() const
Area (product of x and y; signed)
Definition: image_ref.h:213
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
void flipHorizontal(SubImage< T > &&in)
flips an image horizontally in place.
Definition: vision.h:641
void halfSample(const BasicImage< T > &in, BasicImage< T > &out)
subsamples an image to half its size by averaging 2x2 pixel blocks
Definition: vision.h:206
ConstPointerType data() const
Returns the raw image data.
Definition: image.h:535
int x
The x co-ordinate.
Definition: image_ref.h:172
a functor multiplying pixels with constant value.
Definition: vision.h:307
Image< C > linearInterpolationDownsample(const BasicImage< C > &in, float scale)
Downsample an image using linear interpolation.
Definition: vision.h:41
Definition: image_ref.h:29
void zeroBorders(BasicImage< T > &I)
Set the one-pixel border (top, bottom, sides) of an image to zero values.
Definition: utility.h:109
void flipVertical(SubImage< T > &&in)
flips an image vertically in place.
Definition: vision.h:624
void zeroPixel(T &pixel)
Set a pixel to the default value (typically 0) For multi-component pixels, this zeros all components ...
Definition: utility.h:100
int y
The y co-ordinate.
Definition: image_ref.h:173
A full image which manages its own data.
Definition: image.h:623
Definition: vision.h:320
Definition: builtin_components.h:38
void twoThirdsSample(const BasicImage< C > &in, BasicImage< C > &out)
Subsamples an image to 2/3 of its size by averaging 3x3 blocks into 2x2 blocks.
Definition: vision.h:150