[ VIGRA Homepage | Function Index | Class Index | Namespaces | File List | Main Page ]

slanted_edge_mtf.hxx VIGRA

1 /************************************************************************/
2 /* */
3 /* Copyright 1998-2006 by Ullrich Koethe */
4 /* */
5 /* This file is part of the VIGRA computer vision library. */
6 /* The VIGRA Website is */
7 /* http://hci.iwr.uni-heidelberg.de/vigra/ */
8 /* Please direct questions, bug reports, and contributions to */
9 /* ullrich.koethe@iwr.uni-heidelberg.de or */
10 /* vigra@informatik.uni-hamburg.de */
11 /* */
12 /* Permission is hereby granted, free of charge, to any person */
13 /* obtaining a copy of this software and associated documentation */
14 /* files (the "Software"), to deal in the Software without */
15 /* restriction, including without limitation the rights to use, */
16 /* copy, modify, merge, publish, distribute, sublicense, and/or */
17 /* sell copies of the Software, and to permit persons to whom the */
18 /* Software is furnished to do so, subject to the following */
19 /* conditions: */
20 /* */
21 /* The above copyright notice and this permission notice shall be */
22 /* included in all copies or substantial portions of the */
23 /* Software. */
24 /* */
25 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND */
26 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES */
27 /* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND */
28 /* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT */
29 /* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, */
30 /* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING */
31 /* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR */
32 /* OTHER DEALINGS IN THE SOFTWARE. */
33 /* */
34 /************************************************************************/
35 
36 
37 #ifndef VIGRA_SLANTED_EDGE_MTF_HXX
38 #define VIGRA_SLANTED_EDGE_MTF_HXX
39 
40 #include <algorithm>
41 #include "array_vector.hxx"
42 #include "basicgeometry.hxx"
43 #include "edgedetection.hxx"
44 #include "fftw3.hxx"
45 #include "functorexpression.hxx"
46 #include "linear_solve.hxx"
47 #include "mathutil.hxx"
48 #include "numerictraits.hxx"
49 #include "separableconvolution.hxx"
50 #include "static_assert.hxx"
51 #include "stdimage.hxx"
52 #include "transformimage.hxx"
53 #include "utilities.hxx"
54 #include "multi_shape.hxx"
55 
56 namespace vigra {
57 
58 /** \addtogroup SlantedEdgeMTF Camera MTF Estimation
59  Determine the magnitude transfer function (MTF) of a camera using the slanted edge method.
60 */
61 //@{
62 
63 /********************************************************/
64 /* */
65 /* SlantedEdgeMTFOptions */
66 /* */
67 /********************************************************/
68 
69 /** \brief Pass options to one of the \ref slantedEdgeMTF() functions.
70 
71  <tt>SlantedEdgeMTFOptions</tt> is an argument objects that holds various optional
72  parameters used by the \ref slantedEdgeMTF() functions. If a parameter is not explicitly
73  set, a suitable default will be used. Changing the defaults is only necessary if you can't
74  obtain good input data, but absolutely need an MTF estimate.
75 
76  <b> Usage:</b>
77 
78  <b>\#include</b> <vigra/slanted_edge_mtf.hxx><br>
79  Namespace: vigra
80 
81  \code
82  MultiArray<2, float> src(w,h);
83  std::vector<vigra::TinyVector<double, 2> > mtf;
84 
85  ...
86  slantedEdgeMTF(src, mtf,
87  SlantedEdgeMTFOptions().mtfSmoothingScale(1.0));
88 
89  // print the frequency / attenuation pairs found
90  for(int k=0; k<result.size(); ++k)
91  std::cout << "frequency: " << mtf[k][0] << ", estimated attenuation: " << mtf[k][1] << std::endl;
92  \endcode
93 */
94 
96 {
97  public:
98  /** Initialize all options with default values.
99  */
101  : minimum_number_of_lines(20),
102  desired_edge_width(10),
103  minimum_edge_width(5),
104  mtf_smoothing_scale(2.0)
105  {}
106 
107  /** Minimum number of pixels the edge must cross.
108 
109  The longer the edge the more accurate the resulting MTF estimate. If you don't have good
110  data, but absolutely have to compute an MTF, you may force a lower value here.<br>
111  Default: 20
112  */
114  {
115  minimum_number_of_lines = n;
116  return *this;
117  }
118 
119  /** Desired number of pixels perpendicular to the edge.
120 
121  The larger the regions to either side of the edge,
122  the more accurate the resulting MTF estimate. If you don't have good
123  data, but absolutely have to compute an MTF, you may force a lower value here.<br>
124  Default: 10
125  */
127  {
128  desired_edge_width = n;
129  return *this;
130  }
131 
132  /** Minimum acceptable number of pixels perpendicular to the edge.
133 
134  The larger the regions to either side of the edge,
135  the more accurate the resulting MTF estimate. If you don't have good
136  data, but absolutely have to compute an MTF, you may force a lower value here.<br>
137  Default: 5
138  */
140  {
141  minimum_edge_width = n;
142  return *this;
143  }
144 
145  /** Amount of smoothing of the computed MTF.
146 
147  If the data is noisy, so will be the MTF. Thus, some smoothing is useful.<br>
148  Default: 2.0
149  */
151  {
152  vigra_precondition(scale >= 0.0,
153  "SlantedEdgeMTFOptions: MTF smoothing scale must not be < 0.");
154  mtf_smoothing_scale = scale;
155  return *this;
156  }
157 
158  unsigned int minimum_number_of_lines, desired_edge_width, minimum_edge_width;
159  double mtf_smoothing_scale;
160 };
161 
162 //@}
163 
164 namespace detail {
165 
166 struct SortEdgelsByStrength
167 {
168  bool operator()(Edgel const & l, Edgel const & r) const
169  {
170  return l.strength > r.strength;
171  }
172 };
173 
174  /* Make sure that the edge runs vertically, intersects the top and bottom border
175  of the image, and white is on the left.
176  */
177 template <class SrcIterator, class SrcAccessor, class DestImage>
178 ptrdiff_t
179 prepareSlantedEdgeInput(SrcIterator sul, SrcIterator slr, SrcAccessor src, DestImage & res,
180  SlantedEdgeMTFOptions const & options)
181 {
182  ptrdiff_t w = slr.x - sul.x;
183  ptrdiff_t h = slr.y - sul.y;
184 
185  // find rough estimate of the edge
186  ArrayVector<Edgel> edgels;
187  cannyEdgelList(sul, slr, src, edgels, 2.0);
188  std::sort(edgels.begin(), edgels.end(), SortEdgelsByStrength());
189 
190  double x = 0.0, y = 0.0, x2 = 0.0, y2 = 0.0, xy = 0.0;
191  ptrdiff_t c = std::min(w, h);
192 
193  for(ptrdiff_t k = 0; k < c; ++k)
194  {
195  x += edgels[k].x;
196  y += edgels[k].y;
197  x2 += sq(edgels[k].x);
198  xy += edgels[k].x*edgels[k].y;
199  y2 += sq(edgels[k].y);
200  }
201  double xc = x / c, yc = y / c;
202  x2 = x2 / c - sq(xc);
203  xy = xy / c - xc*yc;
204  y2 = y2 / c - sq(yc);
205  double angle = 0.5*VIGRA_CSTD::atan2(2*xy, x2 - y2);
206 
207  DestImage tmp;
208  // rotate image when slope is less than +-45 degrees
209  if(VIGRA_CSTD::fabs(angle) < M_PI / 4.0)
210  {
211  xc = yc;
212  yc = w - xc - 1;
213  std::swap(w, h);
214  tmp.resize(w, h);
215  rotateImage(srcIterRange(sul, slr, src), destImage(tmp), 90);
216  angle += M_PI / 2.0;
217  }
218  else
219  {
220  tmp.resize(w, h);
221  copyImage(srcIterRange(sul, slr, src), destImage(tmp));
222  if(angle < 0.0)
223  angle += M_PI;
224  }
225  // angle is now between pi/4 and 3*pi/4
226  double slope = VIGRA_CSTD::tan(M_PI/2.0 - angle);
227  vigra_precondition(slope != 0.0,
228  "slantedEdgeMTF(): Input edge is not slanted");
229 
230  // trim image so that the edge only intersects the top and bottom border
231  ptrdiff_t minimumNumberOfLines = options.minimum_number_of_lines, //20,
232  edgeWidth = options.desired_edge_width, // 10
233  minimumEdgeWidth = options.minimum_edge_width; // 5
234 
235  ptrdiff_t y0 = 0, y1 = h;
236  for(; edgeWidth >= minimumEdgeWidth; --edgeWidth)
237  {
238  y0 = ptrdiff_t(VIGRA_CSTD::floor((edgeWidth - xc) / slope + yc + 0.5));
239  y1 = ptrdiff_t(VIGRA_CSTD::floor((w - edgeWidth - 1 - xc) / slope + yc + 0.5));
240  if(slope < 0.0)
241  std::swap(y0, y1);
242  if(y1 - y0 >= (ptrdiff_t)minimumNumberOfLines)
243  break;
244  }
245 
246  vigra_precondition(edgeWidth >= minimumEdgeWidth,
247  "slantedEdgeMTF(): Input edge is too slanted or image is too small");
248 
249  y0 = std::max(y0, ptrdiff_t(0));
250  y1 = std::min(y1+1, h);
251 
252  res.resize(w, y1-y0);
253 
254  // ensure that white is on the left
255  if(tmp(0,0) < tmp(w-1, h-1))
256  {
257  rotateImage(srcIterRange(tmp.upperLeft() + Diff2D(0, y0), tmp.upperLeft() + Diff2D(w, y1), tmp.accessor()),
258  destImage(res), 180);
259  }
260  else
261  {
262  copyImage(srcImageRange(tmp), destImage(res));
263  }
264  return edgeWidth;
265 }
266 
267 template <class Image>
268 void slantedEdgeShadingCorrection(Image & i, ptrdiff_t edgeWidth)
269 {
270  using namespace functor;
271 
272  // after prepareSlantedEdgeInput(), the white region is on the left
273  // find a plane that approximates the logarithm of the white ROI
274 
275  transformImage(srcImageRange(i), destImage(i), log(Arg1() + Param(1.0)));
276 
277  ptrdiff_t w = i.width(),
278  h = i.height();
279 
280  Matrix<double> m(3,3), r(3, 1), l(3, 1);
281  for(ptrdiff_t y = 0; y < h; ++y)
282  {
283  for(ptrdiff_t x = 0; x < edgeWidth; ++x)
284  {
285  l(0,0) = x;
286  l(1,0) = y;
287  l(2,0) = 1.0;
288  m += outer(l);
289  r += i(x,y)*l;
290  }
291  }
292 
293  linearSolve(m, r, l);
294  double a = l(0,0),
295  b = l(1,0),
296  c = l(2,0);
297 
298  // subtract the plane and go back to the non-logarithmic representation
299  for(ptrdiff_t y = 0; y < h; ++y)
300  {
301  for(ptrdiff_t x = 0; x < w; ++x)
302  {
303  i(x, y) = VIGRA_CSTD::exp(i(x,y) - a*x - b*y - c);
304  }
305  }
306 }
307 
308 template <class Image, class BackInsertable>
309 void slantedEdgeSubpixelShift(Image const & img, BackInsertable & centers, double & angle,
310  SlantedEdgeMTFOptions const & options)
311 {
312  ptrdiff_t w = img.width();
313  ptrdiff_t h = img.height();
314 
315  Image grad(w, h);
316  Kernel1D<double> kgrad;
317  kgrad.initGaussianDerivative(1.0, 1);
318 
319  separableConvolveX(srcImageRange(img), destImage(grad), kernel1d(kgrad));
320 
321  ptrdiff_t desiredEdgeWidth = (ptrdiff_t)options.desired_edge_width;
322  double sy = 0.0, sx = 0.0, syy = 0.0, sxy = 0.0;
323  for(ptrdiff_t y = 0; y < h; ++y)
324  {
325  double a = 0.0,
326  b = 0.0;
327  for(ptrdiff_t x = 0; x < w; ++x)
328  {
329  a += x*grad(x,y);
330  b += grad(x,y);
331  }
332  ptrdiff_t c = ptrdiff_t(a / b);
333  // c is biased because the numbers of black and white pixels differ
334  // repeat the analysis with a symmetric window around the edge
335  a = 0.0;
336  b = 0.0;
337  ptrdiff_t ew = desiredEdgeWidth;
338  if(c-desiredEdgeWidth < 0)
339  ew = c;
340  if(c + ew + 1 >= w)
341  ew = w - c - 1;
342  for(ptrdiff_t x = c-ew; x < c+ew+1; ++x)
343  {
344  a += x*grad(x,y);
345  b += grad(x,y);
346  }
347  double sc = a / b;
348  sy += y;
349  sx += sc;
350  syy += sq(y);
351  sxy += sc*y;
352  }
353  // fit a line to the subpixel locations
354  double a = (h * sxy - sx * sy) / (h * syy - sq(sy));
355  double b = (sx * syy - sxy * sy) / (h * syy - sq(sy));
356 
357  // compute the regularized subpixel values of the edge location
358  angle = VIGRA_CSTD::atan(a);
359  for(ptrdiff_t y = 0; y < h; ++y)
360  {
361  centers.push_back(a * y + b);
362  }
363 }
364 
365 template <class Image, class Vector>
366 void slantedEdgeCreateOversampledLine(Image const & img, Vector const & centers,
367  Image & result)
368 {
369  ptrdiff_t w = img.width();
370  ptrdiff_t h = img.height();
371  ptrdiff_t w2 = std::min(std::min(ptrdiff_t(centers[0]), ptrdiff_t(centers[h-1])),
372  std::min(ptrdiff_t(w - centers[0]) - 1,
373  ptrdiff_t(w - centers[h-1]) - 1));
374  ptrdiff_t ww = 8*w2;
375 
376  Image r(ww, 1), s(ww, 1);
377  for(ptrdiff_t y = 0; y < h; ++y)
378  {
379  ptrdiff_t x0 = ptrdiff_t(centers[y]) - w2;
380  ptrdiff_t x1 = ptrdiff_t((VIGRA_CSTD::ceil(centers[y]) - centers[y])*4);
381  for(; x1 < ww; x1 += 4)
382  {
383  r(x1, 0) += img(x0, y);
384  ++s(x1, 0);
385  ++x0;
386  }
387  }
388 
389  for(ptrdiff_t x = 0; x < ww; ++x)
390  {
391  vigra_precondition(s(x,0) > 0.0,
392  "slantedEdgeMTF(): Input edge is not slanted enough");
393  r(x,0) /= s(x,0);
394  }
395 
396  result.resize(ww-1, 1);
397  for(ptrdiff_t x = 0; x < ww-1; ++x)
398  {
399  result(x,0) = r(x+1,0) - r(x,0);
400  }
401 }
402 
403 template <class Image, class BackInsertable>
404 void slantedEdgeMTFImpl(Image const & i, BackInsertable & mtf, double angle,
405  SlantedEdgeMTFOptions const & options)
406 {
407  ptrdiff_t w = i.width();
408  ptrdiff_t h = i.height();
409 
410  double slantCorrection = VIGRA_CSTD::cos(angle);
411  ptrdiff_t desiredEdgeWidth = 4*options.desired_edge_width;
412 
413  Image magnitude;
414 
415  if(w - 2*desiredEdgeWidth < 64)
416  {
417  FFTWComplexImage otf(w, h);
418  fourierTransform(srcImageRange(i), destImage(otf));
419 
420  magnitude.resize(w, h);
421  for(ptrdiff_t y = 0; y < h; ++y)
422  {
423  for(ptrdiff_t x = 0; x < w; ++x)
424  {
425  magnitude(x, y) = norm(otf(x, y));
426  }
427  }
428  }
429  else
430  {
431  w -= 2*desiredEdgeWidth;
432  FFTWComplexImage otf(w, h);
433  fourierTransform(srcImageRange(i, Rect2D(Point2D(desiredEdgeWidth, 0), Size2D(w, h))),
434  destImage(otf));
435 
436  // create an image where the edge is skipped - presumably it only contains the
437  // noise which can then be subtracted
438  Image noise(w,h);
439  copyImage(srcImageRange(i, Rect2D(Point2D(0,0), Size2D(w/2, h))),
440  destImage(noise));
441  copyImage(srcImageRange(i, Rect2D(Point2D(i.width()-w/2, 0), Size2D(w/2, h))),
442  destImage(noise, Point2D(w/2, 0)));
443  FFTWComplexImage fnoise(w, h);
444  fourierTransform(srcImageRange(noise), destImage(fnoise));
445 
446  // subtract the noise power spectrum from the total power spectrum
447  magnitude.resize(w, h);
448  for(ptrdiff_t y = 0; y < h; ++y)
449  {
450  for(ptrdiff_t x = 0; x < w; ++x)
451  {
452  magnitude(x, y) = VIGRA_CSTD::sqrt(std::max(0.0, squaredNorm(otf(x, y))-squaredNorm(fnoise(x, y))));
453  }
454  }
455  }
456 
457  Kernel1D<double> gauss;
458  gauss.initGaussian(options.mtf_smoothing_scale);
459  Image smooth(w,h);
460  separableConvolveX(srcImageRange(magnitude), destImage(smooth), kernel1d(gauss));
461 
462  ptrdiff_t ww = w/4;
463  double maxVal = smooth(0,0),
464  minVal = maxVal;
465  for(ptrdiff_t k = 1; k < ww; ++k)
466  {
467  if(smooth(k,0) >= 0.0 && smooth(k,0) < minVal)
468  minVal = smooth(k,0);
469  }
470  double norm = maxVal-minVal;
471 
472  typedef typename BackInsertable::value_type Result;
473  mtf.push_back(Result(0.0, 1.0));
474  for(ptrdiff_t k = 1; k < ww; ++k)
475  {
476  double n = (smooth(k,0) - minVal)/norm*sq(M_PI*k/w/VIGRA_CSTD::sin(M_PI*k/w));
477  double xx = 4.0*k/w/slantCorrection;
478  if(n < 0.0 || xx > 1.0)
479  break;
480  mtf.push_back(Result(xx, n));
481  }
482 }
483 
484 } // namespace detail
485 
486 /** \addtogroup SlantedEdgeMTF Camera MTF Estimation
487  Determine the magnitude transfer function (MTF) of a camera using the slanted edge method.
488 */
489 //@{
490 
491 /********************************************************/
492 /* */
493 /* slantedEdgeMTF */
494 /* */
495 /********************************************************/
496 
497 /** \brief Determine the magnitude transfer function of the camera.
498 
499  This operator estimates the magnitude transfer function (MTF) of a camera by means of the
500  slanted edge method described in:
501 
502  ISO Standard No. 12233: <i>"Photography - Electronic still picture cameras - Resolution measurements"</i>, 2000
503 
504  The input must be an image that contains a single step edge with bright pixels on one side and dark pixels on
505  the other. However, the intensity values must be neither saturated nor zero. The algorithms computes the MTF
506  from the Fourier transform of the edge's derivative. Thus, if the actual MTF is anisotropic, the estimated
507  MTF does actually only apply in the direction perpendicular to the edge - several edges at different
508  orientations are required to estimate an anisotropic MTF.
509 
510  The algorithm returns a sequence of frequency / attenuation pairs. The frequency axis is normalized so that the
511  Nyquist frequency of the original image is 0.5. Since the edge's derivative is computed with subpixel accuracy,
512  the attenuation can usually be computed for frequencies significantly above the Nyquist frequency as well. The
513  MTF estimate ends at either the first zero crossing of the MTF or at frequency 1, whichever comes earlier.
514 
515  The present implementation improves the original slanted edge algorithm according to ISO 12233 in a number of
516  ways:
517 
518  <ul>
519  <li> The edge is not required to run nearly vertically or horizontally (i.e. with a slant of approximately 5 degrees).
520  The algorithm will automatically compute the edge's actual angle and adjust estimates accordingly.
521  However, it is still necessary for the edge to be somewhat slanted, because subpixel-accurate estimation
522  of the derivative is impossible otherwise (i.e. the edge position perpendicular to the edge direction must
523  differ by at least 1 pixel between the two ends of the edge).
524 
525  <li> Our implementation uses a more accurate subpixel derivative algorithm. In addition, we first perform a shading
526  correction in order to reduce possible derivative bias due to nonuniform illumination.
527 
528  <li> If the input image is large enough (i.e. there are at least 20 pixels on either side of the edge over
529  the edge's entire length), our algorithm attempts to subtract the estimated noise power spectrum
530  from the estimated MTF.
531  </ul>
532 
533  The source value type <TT>T1</TT> must be a scalar type which is convertible to <tt>double</tt>.
534  The result is written into the \a result sequence, which must be back-insertable (supports <tt>push_back()</tt>)
535  and whose <tt>value_type</tt> must be constructible
536  from two <tt>double</tt> values. Algorithm options can be set via the \a options object
537  (see \ref vigra::NoiseNormalizationOptions for details).
538 
539  <b> Declarations:</b>
540 
541  pass 2D array views:
542  \code
543  namespace vigra {
544  template <class T1, class S1, class BackInsertable>
545  void
546  slantedEdgeMTF(MultiArrayView<2, T1, S1> const & src, BackInsertable & mtf,
547  SlantedEdgeMTFOptions const & options = SlantedEdgeMTFOptions());
548  }
549  \endcode
550 
551  \deprecatedAPI{slantedEdgeMTF}
552  pass \ref ImageIterators and \ref DataAccessors :
553  \code
554  namespace vigra {
555  template <class SrcIterator, class SrcAccessor, class BackInsertable>
556  void
557  slantedEdgeMTF(SrcIterator sul, SrcIterator slr, SrcAccessor src, BackInsertable & mtf,
558  SlantedEdgeMTFOptions const & options = SlantedEdgeMTFOptions());
559  }
560  \endcode
561  use argument objects in conjunction with \ref ArgumentObjectFactories :
562  \code
563  namespace vigra {
564  template <class SrcIterator, class SrcAccessor, class BackInsertable>
565  void
566  slantedEdgeMTF(triple<SrcIterator, SrcIterator, SrcAccessor> src, BackInsertable & mtf,
567  SlantedEdgeMTFOptions const & options = SlantedEdgeMTFOptions())
568  }
569  \endcode
570  \deprecatedEnd
571 
572  <b> Usage:</b>
573 
574  <b>\#include</b> <vigra/slanted_edge_mtf.hxx><br>
575  Namespace: vigra
576 
577  \code
578  MultiArray<2, float> src(w,h);
579  std::vector<vigra::TinyVector<double, 2> > mtf;
580 
581  ...
582  // keep all options at their default values
583  slantedEdgeMTF(src, mtf);
584 
585  // print the frequency / attenuation pairs found
586  for(int k=0; k<result.size(); ++k)
587  std::cout << "frequency: " << mtf[k][0] << ", estimated attenuation: " << mtf[k][1] << std::endl;
588  \endcode
589 
590  \deprecatedUsage{slantedEdgeMTF}
591  \code
592  vigra::BImage src(w,h);
593  std::vector<vigra::TinyVector<double, 2> > mtf;
594 
595  ...
596  vigra::slantedEdgeMTF(srcImageRange(src), mtf);
597 
598  // print the frequency / attenuation pairs found
599  for(int k=0; k<result.size(); ++k)
600  std::cout << "frequency: " << mtf[k][0] << ", estimated attenuation: " << mtf[k][1] << std::endl;
601  \endcode
602  <b> Required Interface:</b>
603  \code
604  SrcIterator upperleft, lowerright;
605  SrcAccessor src;
606 
607  typedef SrcAccessor::value_type SrcType;
608  typedef NumericTraits<SrcType>::isScalar isScalar;
609  assert(isScalar::asBool == true);
610 
611  double value = src(uperleft);
612 
613  BackInsertable result;
614  typedef BackInsertable::value_type ResultType;
615  double intensity, variance;
616  result.push_back(ResultType(intensity, variance));
617  \endcode
618  \deprecatedEnd
619 */
620 doxygen_overloaded_function(template <...> void slantedEdgeMTF)
621 
622 template <class SrcIterator, class SrcAccessor, class BackInsertable>
623 void
624 slantedEdgeMTF(SrcIterator sul, SrcIterator slr, SrcAccessor src, BackInsertable & mtf,
625  SlantedEdgeMTFOptions const & options = SlantedEdgeMTFOptions())
626 {
627  DImage preparedInput;
628  ptrdiff_t edgeWidth = detail::prepareSlantedEdgeInput(sul, slr, src, preparedInput, options);
629  detail::slantedEdgeShadingCorrection(preparedInput, edgeWidth);
630 
631  ArrayVector<double> centers;
632  double angle = 0.0;
633  detail::slantedEdgeSubpixelShift(preparedInput, centers, angle, options);
634 
635  DImage oversampledLine;
636  detail::slantedEdgeCreateOversampledLine(preparedInput, centers, oversampledLine);
637 
638  detail::slantedEdgeMTFImpl(oversampledLine, mtf, angle, options);
639 }
640 
641 template <class SrcIterator, class SrcAccessor, class BackInsertable>
642 inline void
643 slantedEdgeMTF(triple<SrcIterator, SrcIterator, SrcAccessor> src, BackInsertable & mtf,
644  SlantedEdgeMTFOptions const & options = SlantedEdgeMTFOptions())
645 {
646  slantedEdgeMTF(src.first, src.second, src.third, mtf, options);
647 }
648 
649 template <class T1, class S1, class BackInsertable>
650 inline void
651 slantedEdgeMTF(MultiArrayView<2, T1, S1> const & src, BackInsertable & mtf,
652  SlantedEdgeMTFOptions const & options = SlantedEdgeMTFOptions())
653 {
654  slantedEdgeMTF(srcImageRange(src), mtf, options);
655 }
656 
657 /********************************************************/
658 /* */
659 /* mtfFitGaussian */
660 /* */
661 /********************************************************/
662 
663 /** \brief Fit a Gaussian function to a given MTF.
664 
665  This function expects a sequence of frequency / attenuation pairs as produced by \ref slantedEdgeMTF()
666  and finds the best fitting Gaussian point spread function (Gaussian functions are good approximations
667  of the PSF of many real cameras). It returns the standard deviation (scale) of this function. The algorithm
668  computes the standard deviation by means of a linear least square on the logarithm of the MTF, i.e.
669  an algebraic fit rather than a Euclidean fit - thus, the resulting Gaussian may not be the one that
670  intuitively fits the data optimally.
671 
672  <b> Declaration:</b>
673 
674  \code
675  namespace vigra {
676  template <class Vector>
677  double mtfFitGaussian(Vector const & mtf);
678  }
679  \endcode
680 
681  <b> Usage:</b>
682 
683  <b>\#include</b> <vigra/slanted_edge_mtf.hxx><br>
684  Namespace: vigra
685 
686  \code
687  MultiArray<2, float> src(w,h);
688  std::vector<vigra::TinyVector<double, 2> > mtf;
689 
690  ...
691  slantedEdgeMTF(src, mtf);
692  double scale = vigra::mtfFitGaussian(mtf)
693 
694  std::cout << "The camera PSF is approximately a Gaussian at scale " << scale << std::endl;
695  \endcode
696 
697  <b> Required Interface:</b>
698 
699  \code
700  Vector mtf;
701  int numberOfMeasurements = mtf.size()
702 
703  double frequency = mtf[0][0];
704  double attenuation = mtf[0][1];
705  \endcode
706 */
707 template <class Vector>
708 double mtfFitGaussian(Vector const & mtf)
709 {
710  double minVal = mtf[0][1];
711  for(ptrdiff_t k = 1; k < (ptrdiff_t)mtf.size(); ++k)
712  {
713  if(mtf[k][1] < minVal)
714  minVal = mtf[k][1];
715  }
716  double x2 = 0.0,
717  xy = 0.0;
718  for(ptrdiff_t k = 1; k < (ptrdiff_t)mtf.size(); ++k)
719  {
720  if(mtf[k][1] <= 0.0)
721  break;
722  double x = mtf[k][0],
723  y = VIGRA_CSTD::sqrt(-VIGRA_CSTD::log(mtf[k][1])/2.0)/M_PI;
724  x2 += vigra::sq(x);
725  xy += x*y;
726  if(mtf[k][1] == minVal)
727  break;
728  }
729  return xy / x2;
730 }
731 
732 //@}
733 
734 } // namespace vigra
735 
736 #endif // VIGRA_SLANTED_EDGE_MTF_HXX
FixedPoint16< 2, OverflowHandling > atan2(FixedPoint16< IntBits, OverflowHandling > y, FixedPoint16< IntBits, OverflowHandling > x)
Arctangent. Accuracy better than 1/3 degree (9 significant bits).
Definition: fixedpoint.hxx:1654
linalg::TemporaryMatrix< T > sin(MultiArrayView< 2, T, C > const &v)
double mtfFitGaussian(Vector const &mtf)
Fit a Gaussian function to a given MTF.
Definition: slanted_edge_mtf.hxx:708
SlantedEdgeMTFOptions & mtfSmoothingScale(double scale)
Definition: slanted_edge_mtf.hxx:150
linalg::TemporaryMatrix< T > exp(MultiArrayView< 2, T, C > const &v)
void transformImage(...)
Apply unary point transformation to each pixel.
FFTWComplex< R >::SquaredNormType squaredNorm(const FFTWComplex< R > &a)
squared norm (= squared magnitude)
Definition: fftw3.hxx:1044
SlantedEdgeMTFOptions & minimumEdgeWidth(unsigned int n)
Definition: slanted_edge_mtf.hxx:139
void separableConvolveX(...)
Performs a 1 dimensional convolution in x direction.
FFTWComplex< R >::NormType norm(const FFTWComplex< R > &a)
norm (= magnitude)
Definition: fftw3.hxx:1037
void cannyEdgelList(...)
Simple implementation of Canny's edge detector.
NumericTraits< T >::Promote sq(T t)
The square function.
Definition: mathutil.hxx:382
doxygen_overloaded_function(template<...> void separableConvolveBlockwise) template< unsigned int N
Separated convolution on ChunkedArrays.
void copyImage(...)
Copy source image into destination image.
SlantedEdgeMTFOptions & minimumNumberOfLines(unsigned int n)
Definition: slanted_edge_mtf.hxx:113
void outer(const MultiArrayView< 2, T, C1 > &x, const MultiArrayView< 2, T, C2 > &y, MultiArrayView< 2, T, C3 > &r)
Definition: matrix.hxx:1459
linalg::TemporaryMatrix< T > log(MultiArrayView< 2, T, C > const &v)
SlantedEdgeMTFOptions()
Definition: slanted_edge_mtf.hxx:100
void slantedEdgeMTF(...)
Determine the magnitude transfer function of the camera.
linalg::TemporaryMatrix< T > atan(MultiArrayView< 2, T, C > const &v)
linalg::TemporaryMatrix< T > tan(MultiArrayView< 2, T, C > const &v)
SlantedEdgeMTFOptions & desiredEdgeWidth(unsigned int n)
Definition: slanted_edge_mtf.hxx:126
Pass options to one of the slantedEdgeMTF() functions.
Definition: slanted_edge_mtf.hxx:95
int ceil(FixedPoint< IntBits, FracBits > v)
rounding up.
Definition: fixedpoint.hxx:675
linalg::TemporaryMatrix< T > cos(MultiArrayView< 2, T, C > const &v)
BasicImage< double > DImage
Definition: stdimage.hxx:153
int floor(FixedPoint< IntBits, FracBits > v)
rounding down.
Definition: fixedpoint.hxx:667
bool linearSolve(...)
SquareRootTraits< FixedPoint< IntBits, FracBits > >::SquareRootResult sqrt(FixedPoint< IntBits, FracBits > v)
square root.
Definition: fixedpoint.hxx:616
BasicImage< FFTWComplex<> > FFTWComplexImage
Definition: fftw3.hxx:1192
void fourierTransform(...)
Compute forward and inverse Fourier transforms.

© Ullrich Köthe (ullrich.koethe@iwr.uni-heidelberg.de)
Heidelberg Collaboratory for Image Processing, University of Heidelberg, Germany

html generated using doxygen and Python
vigra 1.11.1 (Fri May 19 2017)