图像缩放操作
微信公众号:幼儿园的学霸
在图像处理过程中,有时需要把图像调整到同样大小,便于处理,这时需要用到图像resize(),该函数比较简单,此处对函数中涉及的各种插值方法进行分析。
目录
文章目录
- 图像缩放操作
- 目录
- 函数介绍
- 插值方法分析
- INTER_NEAREST最近邻插值
- INTER_LINEAR | 双线性插值法(默认)
- CV_INTER_AREA:区域插值
- INTER_CUBIC 三次样条插值
- INTER_LANCZOS4 Lanczos插值
- OpenCV图像缩放代码示例
函数介绍
函数原型如下:
void resize(InputArray src, //输入图像
OutputArray dst, //输出图像
Size dsize, //输出图尺寸
double fx=0, //沿x轴缩系数(宽度方向)
double fy=0, //沿y轴缩放系(高度方)
int interpolation=INTER_LINEAR //插值方式
)
- dsize表示输出图像的大小,如果为0,则输出图像大小按下式计:
d s i z = S i z e ( r o u n d ( f x ∗ s r c . c o l s ) , r o u n d ( f y ∗ s r c . r o w s ) ) dsiz = Size(round(fx*src.cols),round(fy*src.rows)) dsiz=Size(round(fx∗src.cols),round(fy∗src.rows))
- dsize和fx、fy不能同时为0。fx、fy是沿x轴和y轴的缩放系数;默认取0时,计算如下:
f x = ( d o u b l e ) d s t . c o l s s r c . c o l s − − f y = ( d o u b l e ) d s t . r o w s s r c . r o w s fx = (double)\frac{dst.cols}{src.cols} -- fy = (double)\frac{dst.rows}{src.rows} fx=(double)src.colsdst.cols−−fy=(double)src.rowsdst.rows
-
nterpolation:这个是指定插值的方式,图像缩放之后,肯定像素要进行重新计算的,就靠这个参数来指定重新计算像素的方式,有以下几种:
-
opencv中的5种插值方式支持的数据类型有uchar,ushort,short,float,double。
INTER_NEAREST - 最邻近插值
INTER_LINEAR - 双线性插值,如果最后一个参数你不指定,默认使用这种方法
INTER_AREA - 区域插值.
INTER_CUBIC - 4x4像素邻域内的双立方插值
INTER_LANCZOS4 - 8x8像素邻域内的Lanczos插值
下面对基本的插补策略做进一步研究,here,只讨论前两种插值方法
插值方法分析
INTER_NEAREST最近邻插值
目标图像的像素点为对应的最近的原图像的像素点已知原图像为src,目标图像为dst,那么高度和宽度的缩放比例为:
h
_
r
a
t
i
o
=
s
r
c
.
r
o
w
s
d
s
t
.
r
o
w
s
=
1
f
y
,
w
_
r
a
t
i
o
=
s
r
c
.
r
o
w
s
d
s
t
.
r
o
w
s
=
1
f
x
h\_ratio = \frac{src.rows}{dst.rows} = \frac{1}{fy}, w\_ratio = \frac{src.rows}{dst.rows} = \frac{1}{fx}
h_ratio=dst.rowssrc.rows=fy1,w_ratio=dst.rowssrc.rows=fx1
对面目标图像dst上像素:(dstX, dstX)其值等于原图像(x * w_ration, y * h_ration)处的值,由此可以得到图像和目标图像的坐标点对应关系,即:
d
s
t
X
=
x
∗
w
_
r
a
t
i
o
=
x
f
x
,
d
s
t
Y
=
y
∗
h
_
r
a
t
i
o
=
x
f
y
dstX = x*w\_ratio = \frac{x}{fx} , dstY = y*h\_ratio = \frac{x}{fy}
dstX=x∗w_ratio=fxx,dstY=y∗h_ratio=fyx
原图像和目标图像的长度和宽度是对应成比例变化的。
举个例子,假设原图像src如下:
s
r
c
=
[
234
38
22
67
44
12
89
65
63
]
src = \left[ \begin{matrix} 234 & 38 & 22 \\ 67 & 44 & 12 \\ 89 & 65 & 63 \end{matrix} \right]
src=
2346789384465221263
目标图像dst大为4x4,则fx=fy=4.0/3=1.333
,目标图像(2,3)处对应原图像的坐标为(2/fx,3/fy)=(1.5,2.25)
.结果发现,得到的坐标里面竟然有小数,这可怎么办?计算机里的图像可是数字图像,像素就是最小单位了,像素的坐标都是整数,从来没有小数坐标。这时候采用的一种策略就是四舍五入的方法(也可以采用直接舍掉小数位的方法),把非整数坐标转换成整数,那么得到其对应原图坐标为(2,2),那么相应的其像素值为65,依次计算,可以得到放大后的图像像素矩阵如下所示:
d s t = [ 234 38 22 22 67 44 12 12 89 65 63 63 89 65 63 63 ] dst = \left[ \begin{matrix} 234 & 38 & 22 &22 \\ 67 & 44 & 12 & 12 \\ 89 & 65 & 63 & 63 \\ 89 & 65 & 63 & 63 \end{matrix} \right] dst= 234678989384465652212636322126363
这种放大图像的方法叫做最临近插值算法,这是一种最基本、最简单的图像缩放算法,效果也是最不好的,放大后的图像有很严重的马赛克,缩小后的图像有很严重的失真;效果不好的根源就是其简单的最临近插值方法引入了严重的图像失真,比如,当由目标图的坐标反推得到的源图的的坐标是一个浮点数的时候,采用了四舍五入的方法,直接采用了和这个浮点数最接近的象素的值,这种方法是很不科学的。
最临近插值算法计算量较小,但可能会造成插值生成的图像灰度上的不连续,在灰度变化的地方可能出现明显的锯齿状。
INTER_LINEAR | 双线性插值法(默认)
双线型内插值算法就是一种比较好的图像缩放算法,它充分的利用了源图中虚拟点四周的四个真实存在的像素值来共同决定目标图中的一个像素值,因此缩放效果比简单的最邻近插值要好很多。
在推得坐标值为 0.75的时候,不应该就简单的取为1,既然是0.75,比1要小0.25 ,比0要大0.75 ,那么目标象素值其实应该根据这个源图中虚拟的点四周的四个真实的点来按照一定的规律计算出来的,这样才能达到更好的缩放效果。
双线性内插值算法描述如下:
对于一个目的像素,假设坐标通过反向变换得到的浮点坐标为
(
i
+
u
,
j
+
v
)
(i+u,j+v)
(i+u,j+v)`(其中i、j均为浮点坐标的整数部分,u、v为浮点坐标的小数部分,是取值[0,1)区间的浮点数),则这个像素的值
f
(
i
+
u
,
j
+
v
)
f(i+u,j+v)
f(i+u,j+v) 可由原图像中坐标为
(
i
,
j
)
,
(
i
+
1
,
j
)
,
(
i
,
j
+
1
)
,
(
i
+
1
,
j
+
1
)
(i,j),(i+1,j),(i,j+1),(i+1,j+1)
(i,j),(i+1,j),(i,j+1),(i+1,j+1)所对应的周围四个像素的值决定,即:
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)
其中f(i,j)表示源图像(i,j)处的的像素值,以此类推。
比如,像刚才的例子,现在假如目标图的象素坐标为(1,1),那么反推得到的对应于源图的坐标是(0.75 , 0.75), 这其实只是一个概念上的虚拟象素,实际在源图中并不存在这样一个象素,那么目标图的象素(1,1)的取值不能够由这个虚拟象素来决定,而只能由源图的这四个象素共同决定:(0,0)(0,1)(1,0)(1,1),而由于(0.75,0.75)离(1,1)要更近一些,那么(1,1)所起的决定作用更大一些,这从公式1中的系数uv=0.75×0.75就可以体现出来,而(0.75,0.75)离(0,0)最远,所以(0,0)所起的决定作用就要小一些,公式中系数为(1-u)(1-v)=0.25×0.25也体现出了这一特点。
双线性内插法的计算比最邻近点法复杂,计算量较大,但没有灰度不连续的缺点,结果基本令人满意。它具有低通滤波性质,使高频分量受损,图像轮廓可能会有一点模糊。
CV_INTER_AREA:区域插值
区域插值分为3种情况。图像放大时类似于线性插值,图像缩小时可以避免波纹出现。
INTER_CUBIC 三次样条插值
使用4x4邻域内的像素双3次插值,公式类似于双线性.三次曲线插值方法计算量较大,但插值后的图像效果最好
INTER_LANCZOS4 Lanczos插值
使用8×8像素邻域的Lanczos插值,公式类似于双线性
OpenCV中resize默认采用C++ Concurrency进行优化加速,你也可以采用TBB、OpenMP等进行优化加速
OpenCV图像缩放代码示例
如下代码分别展示最近邻插值和三样条插值进行图像缩放的效:
//====================================================================//
// Created by liheng on 19-3-2.
//Program:图像缩放操作
//Data:2019.3.2
//Author:liheng
//Version:V1.0
//====================================================================//
#include <opencv2/opencv.hpp>
int main()
{
cv::Mat srcMat = (cv::Mat_<char>(3,3) <<
234, 38, 22,
67, 44, 12,
89, 65, 63);
//std::cout<<srcMat<<std::endl;
cv::Mat dstMat;
srcMat = cv::imread("../pictures/000177.png",cv::IMREAD_GRAYSCALE);
cv::imshow("src",srcMat);
cv::resize(srcMat,dstMat,cv::Size(640,480),0,0,cv::INTER_NEAREST);
cv::cvtColor(dstMat,dstMat,cv::COLOR_GRAY2BGR);
cv::putText(dstMat,"INTER_NEAREST",cv::Point(0,30),cv::FONT_HERSHEY_SIMPLEX,1.2,cv::Scalar(0,255,0),2);
cv::Mat dst1 = dstMat.clone();
cv::resize(srcMat,dstMat,cv::Size(640,480),0,0,cv::INTER_CUBIC);
cv::cvtColor(dstMat,dstMat,cv::COLOR_GRAY2BGR);
cv::putText(dstMat,"INTER_CUBIC",cv::Point(0,30),cv::FONT_HERSHEY_SIMPLEX,1.2,cv::Scalar(0,255,0),2);
cv::hconcat(dst1,dstMat,dstMat);
cv::imshow("INTER_CUBIC",dstMat);
cv::imwrite("src.jpg",srcMat);
cv::waitKey(0);
return 0;
}
原图像:
结果图像:
注意观察结果图像中圈出来的部分,可以明显看到三次样条插值的效果比最近邻插值效果要好。
最后在opencv中,也可以采用ocl和cuda接口的resize函数来实现图像缩放的加速。