8

I'm trying to understand the function:

function [weights, indices] = contributions(in_length, out_length, ...
                                            scale, kernel, ...
                                            kernel_width, antialiasing)


if (scale < 1) && (antialiasing)
    % Use a modified kernel to simultaneously interpolate and
    % antialias.
    h = @(x) scale * kernel(scale * x);
    kernel_width = kernel_width / scale;
else
    % No antialiasing; use unmodified kernel.
    h = kernel;
end

I don't really understand what does this line means

 h = @(x) scale * kernel(scale * x);

my scale is 0.5
kernel is cubic.

But other than that what does it mean? I think it is like creating a function which will be called later ?

chappjc
  • 30,359
  • 6
  • 75
  • 132
Gilad
  • 6,437
  • 14
  • 61
  • 119

2 Answers2

16

This is sort of a follow-up to your previous questions about the difference between imresize in MATLAB and cv::resize in OpenCV given a bicubic interpolation.

I was interested myself in finding out why there's a difference. These are my findings (as I understood the algorithms, please correct me if I make any mistakes).


Think of resizing an image as a planar transformation from an input image of size M-by-N to an output image of size scaledM-by-scaledN.

The problem is that the points do not necessarily fit on the discrete grid, therefore to obtain intensities of pixels in the output image, we need to interpolate the values of some of the neighboring samples (usually performed in the reverse order, that is for each output pixel, we find the corresponding non-integer point in the input space, and interpolate around it).

This is where interpolation algorithms differ, by choosing the size of the neighborhood and the weight coefficients giving to each point in that neighborhood. The relationship can be first or higher order (where the variable involved is the distance from the inverse-mapped non-integer sample to the discrete points on the original image grid). Typically you assign higher weights to closer points.

Looking at imresize in MATLAB, here are the weights functions for the linear and cubic kernels:

function f = triangle(x)
    % or simply: 1-abs(x) for x in [-1,1]
    f = (1+x) .* ((-1 <= x) & (x < 0)) + ...
        (1-x) .* ((0 <= x) & (x <= 1));
end

function f = cubic(x)
    absx = abs(x);
    absx2 = absx.^2;
    absx3 = absx.^3;
    f = (1.5*absx3 - 2.5*absx2 + 1) .* (absx <= 1) + ...
        (-0.5*absx3 + 2.5*absx2 - 4*absx + 2) .* ((1 < absx) & (absx <= 2));
end

(These basically return the interpolation weight of a sample based on how far it is from an interpolated point.)

This is how these functions look like:

>> subplot(121), ezplot(@triangle,[-2 2])  % triangle
>> subplot(122), ezplot(@cubic,[-3 3])     % Mexican hat

interpolation_kernels

Note that the linear kernel (piece-wise linear functions on [-1,0] and [0,1] intervals, and zeros elsewhere) works on the 2-neighboring points, while the cubic kernel (piece-wise cubic functions on the intervals [-2,-1], [-1,1], and [1,2], and zeros elsewhere) works on 4-neighboring points.

Here is an illustration for the 1-dimensional case, showing how to interpolate the value x from the discrete points f(x_k) using a cubic kernel:

1d_interpolation

The kernel function h(x) is centered at x, the location of the point to be interpolated. The interpolated value f(x) is the weighted sum of the discrete neighboring points (2 to the left and 2 to the right) scaled by the value of interpolation function at those discrete points.

Say if the distance between x and the nearest point is d (0 <= d < 1), the interpolated value at location x will be:

f(x) = f(x1)*h(-d-1) + f(x2)*h(-d) + f(x3)*h(-d+1) + f(x4)*h(-d+2)

where the order of points is depicted below (note that x(k+1)-x(k) = 1):

x1      x2   x    x3       x4
o--------o---+----o--------o
         \___/
       distance d

Now since the points are discrete and sampled at uniform intervals, and the kernel width is usually small, the interpolation can be formulated concisely as a convolution operation:

interp_conv_equation

The concept extends to 2 dimensions simply by first interpolating along one dimension, and then interpolating across the other dimension using the results of the previous step.

Here is an example of bilinear interpolation, which in 2D considers 4 neighboring points:

bilinear_interpolation

The bicubic interpolation in 2D uses 16 neighboring points:

bicubic

First we interpolate along the rows (the red points) using the 16 grid samples (pink). Then we interpolate along the other dimension (red line) using the interpolated points from the previous step. In each step, a regular 1D interpolation is performed. In this the equations are too long and complicated for me to work out by hand!


Now if we go back to the cubic function in MATLAB, it actually matches the definition of the convolution kernel shown in the reference paper as equation (4). Here is the same thing taken from Wikipedia:

conv_kernel

You can see that in the above definition, MATLAB chose a value of a=-0.5.

Now the difference between the implementation in MATLAB and OpenCV is that OpenCV chose a value of a=-0.75.

static inline void interpolateCubic( float x, float* coeffs )
{
    const float A = -0.75f;

    coeffs[0] = ((A*(x + 1) - 5*A)*(x + 1) + 8*A)*(x + 1) - 4*A;
    coeffs[1] = ((A + 2)*x - (A + 3))*x*x + 1;
    coeffs[2] = ((A + 2)*(1 - x) - (A + 3))*(1 - x)*(1 - x) + 1;
    coeffs[3] = 1.f - coeffs[0] - coeffs[1] - coeffs[2];
}

This might not be obvious right away, but the code does compute the terms of the cubic convolution function (listed right after equation (25) in the paper):

bicubic_kernel

We can verify that with the help of the Symbolic Math Toolbox:

A = -0.5;
syms x
c0 = ((A*(x + 1) - 5*A)*(x + 1) + 8*A)*(x + 1) - 4*A;
c1 = ((A + 2)*x - (A + 3))*x*x + 1;
c2 = ((A + 2)*(1 - x) - (A + 3))*(1 - x)*(1 - x) + 1;
c3 = 1 - c0 - c1 - c2;

Those expressions can be rewritten as:

>> expand([c0;c1;c2;c3])
ans =
       - x^3/2 + x^2 - x/2
 (3*x^3)/2 - (5*x^2)/2 + 1
 - (3*x^3)/2 + 2*x^2 + x/2
             x^3/2 - x^2/2

which match the terms from the equation above.

Obviously the difference between MATLAB and OpenCV boils down to using a different value for the free term a. According to the authors of the paper, a value of 0.5 is the preferred choice because it implies better properties for the approximation error than any other choice for a.

Amro
  • 123,847
  • 25
  • 243
  • 454
  • Excellent answer. Thanks for verifying the equations match with the Symbolic Math toolbox. Well worth the effort! – chappjc Nov 09 '14 at 16:58
  • 1
    @chappjc: Thanks. Too bad the value of the param `a` is hard code in both implementations. If we want them to match, In MATLAB you'd have to modify the built-in `imresize` function (which I never like to do), and in OpenCV you'd have to compile the whole thing from sources just for flipping a tine value! It'd be interesting to see if OP does change `a` in OpenCV to `-0.5` and verifies that we get identical results between the two implementations.. Last time I tried it, I remember it taking 10 minutes to compile OpenCV from scratch. – Amro Nov 10 '14 at 06:13
  • @Amro OMG thank you so much! I need like 2 days to read your entire findings! – Gilad Nov 10 '14 at 07:27
  • 2
    @Gilad I just verified that changing OpenCV to `a = -0.5f` gives identical results to MATLAB. I updated [my answer](http://stackoverflow.com/a/26812346/2778484) to your other question, but I'll add a bit to my answer here too. The compile took about 6 minutes on my i7 laptop with a regular HDD, btw. :) Just disable CUDA! – chappjc Nov 10 '14 at 18:47
  • I usually just download the binary. :D – Gilad Nov 10 '14 at 20:24
  • BTW when you guys are refering to anti-aliasing, to which value of wiki are you refering to? http://en.wikipedia.org/wiki/Anti-aliasing my guess is spatial... – Gilad Nov 10 '14 at 20:39
  • @Gilad The same concepts apply to both 1D and 2D, namely the removal or attenuation of frequencies not possible to be accurately represented by the new (lower) sampling rate. If not suppressed, the signal will manifest new frequency content - anti-aliasing artifacts. – chappjc Nov 10 '14 at 23:51
  • @chappjc: great, that is good to know! Thanks for taking the time to make sure we got it right. And I agree, compiling the CUDA modules takes ages to finish! – Amro Nov 11 '14 at 04:02
13

imresize accomplishes anti-aliasing when downsizing an image by simply broadening the cubic kernel, rather than a discrete pre-processing step.

For a kernel_width of 4 pixels (8 after re-scaled), where the contributions function utilizes 10 neighbors for each pixel, the kernel vs h (scaled kernel) look like (unnormalized, ignore x-axis):

enter image description here

This is easier than first performing a low-pass filter or Gaussian convolution in a separate pre-processing step.

The cubic kernel is defined at the bottom of imresize.m as:

function f = cubic(x)
% See Keys, "Cubic Convolution Interpolation for Digital Image
% Processing," IEEE Transactions on Acoustics, Speech, and Signal
% Processing, Vol. ASSP-29, No. 6, December 1981, p. 1155.

absx = abs(x);
absx2 = absx.^2;
absx3 = absx.^3;

f = (1.5*absx3 - 2.5*absx2 + 1) .* (absx <= 1) + ...
                (-0.5*absx3 + 2.5*absx2 - 4*absx + 2) .* ...
                ((1 < absx) & (absx <= 2));

PDF of the referenced paper.

The relevant part is equation (15):

enter image description here

This is a specific version of the general interpolation equations for a = -0.5 in the following equations:

enter image description here

a is usually set to -0.5, or -0.75. Note that a = -0.5 corresponds to the Cubic Hermite spline, which will be continuous and have a continuous first derivitive. OpenCV seems to use -0.75.

However, if you edit [OPENCV_SRC]\modules\imgproc\src\imgwarp.cpp and change the code :

static inline void interpolateCubic( float x, float* coeffs )
{
    const float A = -0.75f;
    ...

to:

static inline void interpolateCubic( float x, float* coeffs )
{
    const float A = -0.50f;
    ...

and rebuild OpenCV (tip: disable CUDA and the gpu module for short compile time), then you get the same results. See the matching output in my other answer to a related question by the OP.

Community
  • 1
  • 1
chappjc
  • 30,359
  • 6
  • 75
  • 132
  • 2
    @Gilad I recall you were looking into MATLAB vs. OpenCV cubic interpolation and it seems that the difference is a=-0.5 for MATLAB and [a=-0.75 for OpenCV](https://github.com/Itseez/opencv/blob/master/modules/imgproc/src/imgwarp.cpp#L155). – chappjc Nov 09 '14 at 01:49
  • 2
    @chappjc: +1 good find. I was actually writing an answer about that :) – Amro Nov 09 '14 at 01:57
  • @chappjc i'm getting close to understanding how to implement the matlab version of this in c++. i think i lack some basic knowledge in signal processing, so thank you. – Gilad Nov 09 '14 at 07:38
  • 1
    @chappjc: I finally finished my answer. It took longer than I expected! – Amro Nov 09 '14 at 13:38
  • 1
    @Gilad you'd flip the OpenCV code to using -0.5. Amro's answer verified that the equations match aside from that constant. – chappjc Nov 09 '14 at 16:50