『带你学算法』详解OpenCV中Reszie操作与原理

2020-12-10 10:45:10 浏览数 (1)

小宋说:在进行图片数据处理时,经常会用到图片Reszie的操作。由于是基于OpenCV的Resize接口,所以并不了解内部原理,所以这篇文章将详细讲解一下具体操作与原理。

1 OpenCV中Reszie使用

1.1 Resize接口

OpenCV支持不同的编程语言,下面是对不同语言Resize的操作:

C :

代码语言:javascript复制
void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR)  

Python:

代码语言:javascript复制
cv2.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]]) -> dst  

C:

代码语言:javascript复制
void cvResize(const CvArr* src, CvArr* dst, int interpolation=CV_INTER_LINEAR)  

1.2 参数说明:

src - 原图

dst - 目标图像。当参数dsize不为0时,dst的大小为size;否则,它的大小需要根据src的大小,参数fx和fy决定。dst的类型(type)和src图像相同

dsize - 目标图像大小。当dsize为0时,它可以通过以下公式计算得出:

所以,参数dsize和参数(fx, fy)不能够同时为0。

fx - 水平轴上的比例因子。当它为0时,计算公式如下:

fy - 垂直轴上的比例因子。当它为0时,计算公式如下:

interpolation - 插值方法

2 OpenCV中Reszie原理

2.1 插值方法介绍

插值方法。共有5种:

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插值

2.2 插值原理详解

这里以常用的双线性插值法为例详细介绍:

双线性插值,又称为双线性内插。在数学上,双线性插值是有两个变量的插值函数的线性插值扩展,其核心思想是在两个方向分别进行一次线性插值。

搞懂双线性之前需要先单线性插值法:

已知数据 (x0, y0) 与 (x1, y1),要计算 [x0, x1] 区间内某一位置 x 在直线上的y值。

其实就是用x和x0,x1的距离作为一个权重,用于y0和y1的加权。

小宋说:其实这个操作也很直观,线性就是离哪个点近结果就趋近于哪点,这就是所谓的加权操作。双线性插值本质上就是在两个方向上做线性插值。

双线性插值

在数学上,双线性插值是有两个变量的插值函数的线性插值扩展,其核心思想是在两个方向分别进行一次线性插值。见下图:

假如我们想得到未知函数 f 在点 P = (x, y) 的值,假设我们已知函数 f 在 Q11 = (x1, y1)、Q12 = (x1, y2), Q21 = (x2, y1) 以及 Q22 = (x2, y2) 四个点的值。最常见的情况,f就是一个像素点的像素值。首先在 x 方向进行线性插值,得到

然后在 y 方向进行线性插值,得到

综合起来就是双线性插值最后的结果:

2.3 双线性插值代码实现

由相邻的四像素(2*2)计算得出,公式如下:

代码实现基于C ,如下:

代码语言:javascript复制
    cv::Mat matSrc, matDst1, matDst2;
	matSrc = cv::imread("lena.jpg", 2 | 4);
	matDst1 = cv::Mat(cv::Size(800, 1000), matSrc.type(), cv::Scalar::all(0));
	matDst2 = cv::Mat(matDst1.size(), matSrc.type(), cv::Scalar::all(0));
	double scale_x = (double)matSrc.cols / matDst1.cols;
	double scale_y = (double)matSrc.rows / matDst1.rows;


    uchar* dataDst = matDst1.data;
	int stepDst = matDst1.step;
	uchar* dataSrc = matSrc.data;
	int stepSrc = matSrc.step;
	int iWidthSrc = matSrc.cols;
	int iHiehgtSrc = matSrc.rows;


	for (int j = 0; j < matDst1.rows;   j)
	{
		float fy = (float)((j   0.5) * scale_y - 0.5);
		int sy = cvFloor(fy);
		fy -= sy;
		sy = std::min(sy, iHiehgtSrc - 2);
		sy = std::max(0, sy);


		short cbufy[2];
		cbufy[0] = cv::saturate_cast<short>((1.f - fy) * 2048);
		cbufy[1] = 2048 - cbufy[0];


		for (int i = 0; i < matDst1.cols;   i)
		{
			float fx = (float)((i   0.5) * scale_x - 0.5);
			int sx = cvFloor(fx);
			fx -= sx;
 
			if (sx < 0) {
				fx = 0, sx = 0;
			}
			if (sx >= iWidthSrc - 1) {
				fx = 0, sx = iWidthSrc - 2;
			}
 
			short cbufx[2];
			cbufx[0] = cv::saturate_cast<short>((1.f - fx) * 2048);
			cbufx[1] = 2048 - cbufx[0];


			for (int k = 0; k < matSrc.channels();   k)
			{
				*(dataDst  j*stepDst   3*i   k) = (*(dataSrc   sy*stepSrc   3*sx   k) * cbufx[0] * cbufy[0]   
					*(dataSrc   (sy 1)*stepSrc   3*sx   k) * cbufx[0] * cbufy[1]   
					*(dataSrc   sy*stepSrc   3*(sx 1)   k) * cbufx[1] * cbufy[0]   
					*(dataSrc   (sy 1)*stepSrc   3*(sx 1)   k) * cbufx[1] * cbufy[1]) >> 22;
			}
		}
	}
	cv::imwrite("linear_1.jpg", matDst1);


	cv::resize(matSrc, matDst2, matDst1.size(), 0, 0, 1);


	cv::imwrite("linear_2.jpg", mat);

3 使用Reszie注意事项:

  1. dsize和fx/fy不能同时为0,要么你就指定好dsize的值,让fx和fy空置直接使用默认值,就像 resize(img, imgDst, Size(30,30)); 要么你就让dsize为0,指定好fx和fy的值,比如fx=fy=0.5,那么就相当于把原图两个方向缩小一倍!
  2. 至于最后的插值方法,正常情况下使用默认的双线性插值就够用了。 几种常用方法的效率是:最邻近插值>双线性插值>双立方插值>Lanczos插值; 但是效率和效果成反比,所以根据自己的情况酌情使用。
  3. 正常情况下,在使用之前dst图像的大小和类型都是不知道的,类型从src图像继承而来,大小也是从原图像根据参数计算出来。但是如果你事先已经指定好dst图像的大小,那么你可以通过下面这种方式来调用函数:resize(src, dst, dst.size(), 0, 0, interpolation);

-1 参考

-1.1 https://blog.csdn.net/kokozeng1995/article/details/78534842

-1.2 https://blog.csdn.net/fengbingchun/article/details/17335477

0 人点赞