插值原理与解析
在图像处理中,仿射变换(如:平移变换、旋转变换以及放缩变换)是一些基础且常用的操作。这些变换不改变图象的像素值,只是在图象平面上进行像素的重新排列。在一幅输入图象 [ u , v ] [ u , v ] [ u , v ] [u,v] [u,v][u,v] [u,v][u,v][u,v]中,灰度值仅在整数位置上有定义。然而,输出图象 [ x , y ] [x,y] [x,y]的灰度值一般由处在非整数坐标上的 ( u , v ) ( u , v ) ( u , v ) (u,v) (u,v)(u,v) (u,v)(u,v)(u,v)值来决定。这就需要插值算法来进行处理,常见的插值算法有单/双线性插值、最近邻插值和三次样条插值等。
1、单线性插值
单线性插值是一种针对一维数据的插值方法,它根据一维数据序列中需要插值的点的左右邻近两个数据点来进行数值的估计。但是它不是求这两个点数据大小的平均值(有求平均值的情况),而是根据到这两个点的距离来分配它们的比重的插值方式。如下图所示:
上图显示了线性插值的原理。根据图中的假设:已知点(x0,y0)、(x1,y1),试问在x处插值,y的值是多少?已知两个点的坐标可以得到一条线,又已知线上一点的一个坐标可以求得这个点的另一个坐标值。则差插值公式为:
2、双线性插值
双线性插值,又称为双线性内插。在数学上,双线性插值是有两个变量的插值函数的线性插值扩展,其核心思想是在两个方向分别进行一次线性插值。如下图所示:
首先,在X方向上进行两次线性插值计算,然后在Y方向上进行一次插值计算。
在图像处理的时候,我们先根据
s
r
c
X
=
d
s
t
X
∗
(
s
r
c
W
i
d
t
h
/
d
s
t
W
i
d
t
h
)
srcX=dstX* (srcWidth/dstWidth)
srcX=dstX∗(srcWidth/dstWidth)
s
r
c
Y
=
d
s
t
Y
∗
(
s
r
c
H
e
i
g
h
t
/
d
s
t
H
e
i
g
h
t
)
srcY = dstY * (srcHeight/dstHeight)
srcY=dstY∗(srcHeight/dstHeight)来计算目标像素在源图像中的位置,这里计算的srcX和srcY一般都是浮点数,比如f(1.2, 3.4)这个像素点是虚拟存在的,先找到与它临近的四个实际存在的像素点
(1,3) (2,3)
(1,4) (2,4)
写成
f
(
i
+
u
,
j
+
v
)
f(i+u,j+v)
f(i+u,j+v)的形式,则
u
=
0.2
,
v
=
0.4
,
i
=
1
,
j
=
3
u=0.2,v=0.4, i=1, j=3
u=0.2,v=0.4,i=1,j=3在沿着X方向差插值时,
f
(
R
1
)
=
u
(
f
(
Q
21
)
−
f
(
Q
11
)
)
+
f
(
Q
11
)
f(R1)=u(f(Q21)-f(Q11))+f(Q11)
f(R1)=u(f(Q21)−f(Q11))+f(Q11)沿着Y方向同理计算。 或者,直接整理一步计算,
f
(
i
+
u
,
j
+
v
)
=
(
1
−
u
)
(
1
−
v
)
f
(
i
,
j
)
+
(
1
−
u
)
v
f
(
i
,
j
+
1
)
+
u
(
1
−
v
)
f
(
i
+
1
,
j
)
+
u
v
f
(
i
+
1
,
j
+
1
)
f(i+u,j+v) = (1-u)(1-v)f(i,j) + (1-u)vf(i,j+1) + u(1-v)f(i+1,j) + uvf(i+1,j+1)
f(i+u,j+v)=(1−u)(1−v)f(i,j)+(1−u)vf(i,j+1)+u(1−v)f(i+1,j)+uvf(i+1,j+1)
双线性插值一些问题的思考:
- 由于计算的 s r c X srcX srcX和 s r c Y srcY srcY 都是浮点数,后续会进行大量的乘法,而图像数据量又大,速度不会理想,所以将浮点数运算转换成整数运算。
- 考虑到插值原点不同,用一个插值方法可能会展示出不同的插值效果,所以通过中心点对其的方式调整目标图像。
3、最邻近插值
最近邻插值,是指将目标图像中的点,对应到源图像中后,找到最相邻的整数点,作为插值后的输出。如下图所示:
目标图像中的某点如果投影到原图像中的位置为点P,则此时取P最邻近点Q11,即
f
(
P
)
=
f
(
Q
11
)
f(P)=f(Q11)
f(P)=f(Q11)。
缺点:由于最邻近区域所有投影点等于某一个值,所以在密集插值情况下会有Block效应,即块状效应。
基于OpenCV代码实现
C++实现
void cv::resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR )
/*参数解释:
src:输入图像
dst:输出图像
dsize:输出图像尺寸
fx、fy:x,y方向上的缩放因子
INTER_LINEAR:插值方法,总共五种
1. INTER_NEAREST - 最近邻插值法
2. INTER_LINEAR - 双线性插值法(默认)
3. INTER_AREA - 基于局部像素的重采样(resampling using pixel area relation)。对于图像抽取(image decimation)来说,这可能是一个更好的方法。但如果是放大图像时,它和最近邻法的效果类似。
4. INTER_CUBIC - 基于4x4像素邻域的3次插值法
5. INTER_LANCZOS4 - 基于8x8像素邻域的Lanczos插值
*/
Test code:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char* argv[])
{
Mat img = imread("C:\Users\Administrator\Desktop\wgm.jpg");
if (img.empty())
{
cout << "无法读取图像" << endl;
return 0;
}
int height = img.rows;
int width = img.cols;
// 缩小图像,比例为(0.2, 0.2)
Size dsize = Size(round(0.2 * width), round(0.2 * height));
Mat shrink;
//使用双线性插值
resize(img, shrink, dsize, 0, 0, INTER_LINEAR);
// 在缩小图像的基础上,放大图像,比例为(2, 2)
float fx = 0.5;
float fy = 0.5;
Mat enlarge1, enlarge2;
resize(shrink, enlarge1, Size(), fx, fy, INTER_NEAREST);
resize(shrink, enlarge2, Size(), fx, fy, INTER_LINEAR);
// 显示
imshow("src", img);
imshow("shrink", shrink);
imshow("INTER_NEAREST", enlarge1);
imshow("INTER_LINEAR", enlarge2);
waitKey(0);
return 0;
}
Python实现
cv2.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]])
/*参数解释:
src:原始图像
dst:目标图像
dsize:目标图像尺寸
fx:水平尺度变换因子
fy:竖直尺度变换因子
interpolation:插值方式(可选)
cv.INTER_NEAREST ---- 最近邻插值
cv.INTER_LINEAR ---- 双线性插值
cv.INTER_CUBIC ---- 三次插值法
cv.INTER_AREA ---- 基于局部像素的重采样
*/
Test code:
import cv2
if __name__ == "__main__":
img = cv2.imread('C:\Users\Administrator\Desktop\wgm.jpg', cv2.IMREAD_UNCHANGED)
print('Original Dimensions : ',img.shape)
scale_percent = 30 # percent of original size
width = int(img.shape[1] * scale_percent / 100)
height = int(img.shape[0] * scale_percent / 100)
dim = (width, height)
# resize image
resized = cv2.resize(img, dim, interpolation = cv2.INTER_LINEAR)
fx = 0.5
fy = 0.5
resized1 = cv2.resize(resized, dsize=None, fx=fx, fy=fy, interpolation = cv2.INTER_NEAREST)
resized2 = cv2.resize(resized, dsize=None, fx=fx, fy=fy, interpolation = cv2.INTER_LINEAR)
print('Resized Dimensions : ',resized.shape)
cv2.imshow("Resized image", resized)
cv2.imshow("INTER_NEAREST image", resized1)
cv2.imshow("INTER_LINEAR image", resized2)
cv2.waitKey(0)
cv2.destroyAllWindows()
效果展示:
可以看出最邻近插值在尺寸缩小图像中,会出现细节模糊(女孩头发处出现一些白色斑状现象),而双线性插值的效果能够对图像特征有较好的保存,这是由于,它充分的利用了源图中虚拟点四周的四个真实存在的像素值来共同决定目标图中的一个像素值,因此缩放效果比简单的最邻近插值要好很多。
参考博客:
【1】OpenCV框架与图像插值算法
【2】我与插值萍水相逢:线性插值(Linear Interpolation)原理及使用