小宋说:在进行图片数据处理时,经常会用到图片 Reszie 的操作。由于是基于 OpenCV 的 Resize 接口,所以并不了解内部原理,所以这篇文章将详细讲解一下具体操作与原理。
1 OpenCV 中 Reszie 使用
1.1 Resize 接口
OpenCV 支持不同的编程语言,下面是对不同语言 Resize 的操作:
C++:
void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR)
Python:
cv2.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]]) -> dst
C:
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++,如下:
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);
sy = std::min(sy, iHiehgtSrc - 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);
if (sx >= iWidthSrc - 1) {
fx = 0, sx = iWidthSrc - 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 注意事项:
- dsize 和 fx/fy 不能同时为 0,要么你就指定好 dsize 的值,让 fx 和 fy 空置直接使用默认值,就像
resize(img, imgDst, Size(30,30));
要么你就让 dsize 为 0,指定好 fx 和 fy 的值,比如 fx=fy=0.5,那么就相当于把原图两个方向缩小一倍! - 至于最后的插值方法,正常情况下使用默认的双线性插值就够用了。
几种常用方法的效率是:最邻近插值 > 双线性插值 > 双立方插值 > Lanczos 插值;
但是效率和效果成反比,所以根据自己的情况酌情使用。 - 正常情况下,在使用之前 dst 图像的大小和类型都是不知道的,类型从 src 图像继承而来,大小也是从原图像根据参数计算出来。但是如果你事先已经指定好 dst 图像的大小,那么你可以通过下面这种方式来调用函数:resize(src, dst, dst.size(), 0, 0, interpolation);
-1 参考
-1.1 blog.csdn.net/kokozeng199…
-1.2 blog.csdn.net/fengbingchu…
欢迎大家关注小宋公众号**《极简 AI》**带你学深度学习:
基于深度学习的理论学习与应用开发技术分享,笔者会经常分享深度学习干货内容,大家在学习或者应用深度学习时,遇到什么问题也可以与我在上面交流知无不答。