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

slanted_edge_mtf.hxx
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
56namespace 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
164namespace detail {
165
166struct 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 */
177template <class SrcIterator, class SrcAccessor, class DestImage>
178ptrdiff_t
179prepareSlantedEdgeInput(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
267template <class Image>
268void 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
308template <class Image, class BackInsertable>
309void 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
365template <class Image, class Vector>
366void 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
403template <class Image, class BackInsertable>
404void 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*/
620doxygen_overloaded_function(template <...> void slantedEdgeMTF)
621
622template <class SrcIterator, class SrcAccessor, class BackInsertable>
623void
624slantedEdgeMTF(SrcIterator sul, SrcIterator slr, SrcAccessor src, BackInsertable & mtf,
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
641template <class SrcIterator, class SrcAccessor, class BackInsertable>
642inline void
643slantedEdgeMTF(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
649template <class T1, class S1, class BackInsertable>
650inline void
651slantedEdgeMTF(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*/
707template <class Vector>
708double 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
Definition array_vector.hxx:514
Pass options to one of the slantedEdgeMTF() functions.
Definition slanted_edge_mtf.hxx:96
SlantedEdgeMTFOptions()
Definition slanted_edge_mtf.hxx:100
SlantedEdgeMTFOptions & desiredEdgeWidth(unsigned int n)
Definition slanted_edge_mtf.hxx:126
SlantedEdgeMTFOptions & minimumNumberOfLines(unsigned int n)
Definition slanted_edge_mtf.hxx:113
SlantedEdgeMTFOptions & minimumEdgeWidth(unsigned int n)
Definition slanted_edge_mtf.hxx:139
SlantedEdgeMTFOptions & mtfSmoothingScale(double scale)
Definition slanted_edge_mtf.hxx:150
void transformImage(...)
Apply unary point transformation to each pixel.
void fourierTransform(...)
Compute forward and inverse Fourier transforms.
void separableConvolveX(...)
Performs a 1 dimensional convolution in x direction.
double mtfFitGaussian(Vector const &mtf)
Fit a Gaussian function to a given MTF.
Definition slanted_edge_mtf.hxx:708
FFTWComplex< R >::NormType norm(const FFTWComplex< R > &a)
norm (= magnitude)
Definition fftw3.hxx:1037
BasicImage< FFTWComplex<> > FFTWComplexImage
Definition fftw3.hxx:1192
NumericTraits< T >::Promote sq(T t)
The square function.
Definition mathutil.hxx:382
void cannyEdgelList(...)
Simple implementation of Canny's edge detector.
void slantedEdgeMTF(...)
Determine the magnitude transfer function of the camera.
FFTWComplex< R >::SquaredNormType squaredNorm(const FFTWComplex< R > &a)
squared norm (= squared magnitude)
Definition fftw3.hxx:1044
void copyImage(...)
Copy source image into destination image.

© 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.12.2