我正在尝试理解这个功能:
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
我真的不明白这条线意味着什么
h = @(x) scale * kernel(scale * x);
我的比例为0.5
内核是立方体。
但除此之外是什么意思? 我认为这就像创建一个稍后会调用的函数一样?
答案 0 :(得分:11)
imresize
在缩小图像时通过简单地扩展立方内核而不是离散的预处理步骤来完成抗锯齿。
对于4个像素的kernel_width
(重新缩放后为8个像素),其中contributions
函数为每个像素使用10个邻居,kernel
vs h
(缩放) kernel)看起来像(非标准化,忽略x轴):
这比在单独的预处理步骤中首次执行低通滤波器或高斯卷积更容易。
立方内核在imresize.m
的底部定义为:
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));
相关部分是等式(15):
这是以下等式中a = -0.5
的一般插值方程的特定版本:
a
通常设置为-0.5或-0.75。请注意,a = -0.5
对应于Cubic Hermite spline,它将是连续的,并且具有连续的第一个派生。 OpenCV seems to use -0.75
但是,如果您编辑[OPENCV_SRC] \ modules \ imgproc \ src \ imgwarp.cpp并更改代码:
static inline void interpolateCubic( float x, float* coeffs )
{
const float A = -0.75f;
...
为:
static inline void interpolateCubic( float x, float* coeffs )
{
const float A = -0.50f;
...
并重建OpenCV(提示:在短编译时禁用CUDA和gpu模块),然后得到相同的结果。通过OP查看my other answer中的匹配输出到相关问题。
答案 1 :(得分:10)
这是对previous questions关于MATLAB中imresize
与OpenCV中cv::resize
给出双三次插值之间差异的一种后续行为。
我对自己有兴趣了解为什么会有区别。这些是我的发现(因为我理解算法,如果我犯了任何错误,请纠正我。)
考虑将图像大小调整为从大小为M-by-N
的输入图像到大小为scaledM-by-scaledN
的输出图像的平面变换。
问题是这些点不一定适合离散网格,因此为了获得输出图像中像素的强度,我们需要插入一些相邻样本的值(通常以相反的顺序执行,即对于每个输出像素,我们在输入空间中找到相应的非整数点,并在其周围进行插值)。
这是插值算法的不同之处,通过选择邻域的大小和给予该邻域中每个点的权重系数。该关系可以是一阶或更高阶(其中涉及的变量是从逆映射的非整数样本到原始图像网格上的离散点的距离)。通常,您可以为更近的点分配更高的权重。
在MATLAB中查看imresize
,这里是线性和立方内核的权重函数:
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
(这些基本上返回样本的插值权重,基于它与插值点的距离。)
这就是这些函数的样子:
>> subplot(121), ezplot(@triangle,[-2 2]) % triangle
>> subplot(122), ezplot(@cubic,[-3 3]) % Mexican hat
请注意,线性内核([-1,0]和[0,1]间隔上的分段线性函数,以及其他地方的零)适用于2个相邻点,而立方内核(分段立方体)区间[-2,-1],[-1,1]和[1,2]上的函数,以及其他地方的零)对4个相邻点起作用。
以下是1维案例的说明,展示了如何使用立方内核从离散点x
插值f(x_k)
:
核函数h(x)
以x
为中心,即要插值的点的位置。插值f(x)
是离散相邻点(左侧为2,右侧为2)的加权和,由这些离散点处的插值函数值进行缩放。
假设x
与最近点之间的距离为d
(0 <= d < 1
),位置x
处的插值将为:
f(x) = f(x1)*h(-d-1) + f(x2)*h(-d) + f(x3)*h(-d+1) + f(x4)*h(-d+2)
其中点的顺序如下所示(注意x(k+1)-x(k) = 1
):
x1 x2 x x3 x4
o--------o---+----o--------o
\___/
distance d
现在由于点是离散的并且以均匀的间隔采样,并且内核宽度通常很小,因此插值可以简洁地表示为卷积操作:
这个概念只需先沿一个维度进行插值,然后使用上一步的结果在另一个维度上进行插值,就可以扩展到2维。
以下是双线性插值的示例,其在2D中考虑4个相邻点:
2D中的双三次插值使用16个相邻点:
首先,我们使用16个网格样本(粉红色)沿着行(红点)进行插值。然后我们使用上一步中的插值点沿其他维度(红线)进行插值。在每个步骤中,执行常规的1D插值。在这方面,方程太长而且复杂,我无法手工锻炼!
现在,如果我们回到MATLAB中的cubic
函数,它实际上将reference paper中显示的卷积内核的定义与等式(4)相匹配。以下是Wikipedia:
您可以看到,在上面的定义中,MATLAB选择了值a=-0.5
。
现在,MATLAB和OpenCV中的实现之间的区别在于OpenCV选择了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];
}
这可能不会立即显现,但代码确实计算了三次卷积函数的项(在文章中的等式(25)之后列出):
我们可以在符号数学工具箱的帮助下验证:
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;
这些表达式可以改写为:
>> 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
匹配上述等式中的术语。
显然,MATLAB和OpenCV之间的区别归结为对自由项a
使用不同的值。根据该论文的作者,值0.5
是首选,因为它意味着比a
的任何其他选择更好的近似误差属性。