一、概括
面试的时候问到了一个图,就是如何将一个算子放缩??我第一反应是resize(),但是后来我转念一想,人家问的是插值方式,今天来总结一下
最邻近插值法原理分析及c++实现_最临近插值法-CSDN博客
我们总常用的插值方式,临近插值 、双线性插值、三次样条插值、拉格朗日插值、多项式、区域插值等
下面我们就一步一步的将其概括出来
二、临近插值
最邻近插值法 : 其核心思想是选取离目标点最近的点作为待插入的新值点
如图:其他的Q12 Q22 Q11 Q21都是已知的像素点,要求插入一个点P
从上图可以看出P到 Q12 最近,那么我就直接将P=Q12
计算两份方向上的缩放后的图像
int dst_cols = round(src.cols * sx); // 列 ==xint dst_rows = round(src.rows * sy); // 行==y
有3*3 --》5*5 ,那么我们可以计算P(3,3)
sx=5/3
那么 new_i=3/sx=round(9/5)=2;
new_j=3/sy=round(9/5)=2
P(3,3)=src(2,2)=83
void NearestInterpolation(cv::Mat& src, cv::Mat& dst, float sx, float sy)
{//放大的因子 x,y 方向可能会不一样的// 放缩之后的图像的大小int dst_cols = round(src.cols * sx); // 列 ==xint dst_rows = round(src.rows * sy); // 行==ydst = cv::Mat(dst_rows, dst_cols, src.type());//灰度图像处理if (src.channels() == 1){for (int i = 0; i < dst.rows; i++){for (int j = 0; j < dst.cols; j++){//插值计算,取最近值插入到新的图像中int i_new = round(i / sy);int j_new = round(j / sx);if (i_new > src.rows - 1){i_new = src.rows - 1;}if (j_new > src.cols - 1){j_new = src.cols - 1;}dst.at<uchar>(i, j) = src.at<uchar>(i_new, j_new);}}}//彩色图像处理else {for (int i = 0; i < dst.rows; i++){for (int j = 0; j < dst.cols; j++){int i_new = round(i / sy);int j_new = round(j / sx);if (i_new > src.rows - 1){i_new = src.rows - 1;}if (j_new > src.cols - 1){j_new = src.cols - 1;}//Bdst.at<cv::Vec3b>(i, j)[0] = src.at<cv::Vec3b>(i_new, j_new)[0];//Gdst.at<cv::Vec3b>(i, j)[1] = src.at<cv::Vec3b>(i_new, j_new)[1];//Rdst.at<cv::Vec3b>(i, j)[2] = src.at<cv::Vec3b>(i_new, j_new)[2];}}}
}
优点:简单、计算量小。
缺点:效果不好,图像放大后失真现象严重。
三、线性插值
resize() 函数默认的就是双线性插值,
我们先看线性插值:
线性插值:
然后我们将上面的做个变性,就写成了如下:
双线性插值原理
顾名思义就是做两次线性插值,但是其实是3次
c++ opencv图像双线性插值的应用方法 - 知乎
案例:
算法流程:
1、先通过每个方向的缩放因子,计算出我们缩放后的图像的大小
2、计算通过新图像的row_new 和col_new 推算出原图像中的四个点 的位置并获得四个点的灰度值
3、通过上面拿到的公式来计算新插入的值
4、计算边界的特殊值
/// <summary>
/// 双线性插值处理
/// </summary>
/// <param name="src"></param>
/// <param name="scale_x"></param>
/// <param name="scale_y"></param>
/// <param name="dst"></param>
void DoubleLineInterpolate(Mat src, double scale_x,double scale_y, Mat& dst)
{int result_H = static_cast<int>(src.rows * scale_y);int result_W = static_cast<int>(src.cols * scale_x);dst = Mat::zeros(cv::Size(result_W, result_H), src.type());for (int i = 0; i < dst.rows; i++){for (int j = 0; j < dst.cols; j++){// 非常重要的一步就是用新的图像来推算出原来图像的四个像素的位置和灰度值double before_x = double(j + 0.5) / scale_x - 0.5f;double before_y = double(i + 0.5) / scale_y - 0.5;int top_y = static_cast<int>(before_y);int bottom_y = top_y + 1;int left_x = static_cast<int>(before_x);int right_x = left_x + 1;//计算变换前坐标的小数部分double u = before_x - left_x;double v = before_y - top_y;// 如果计算的原始图像的像素大于真实原始图像尺寸if ((top_y >= src.rows - 1) && (left_x >= src.cols - 1)){//右下角dst.at<uchar>(i, j) = (1. - u) * (1. - v) * src.at<uchar>(top_y, left_x);}else if (top_y >= src.rows - 1){//最后一行dst.at<uchar>(i, j)= (1. - u) * (1. - v) * src.at<uchar>(top_y, left_x)+ (1. - v) * u * src.at<uchar>(top_y, right_x);}else if (left_x >= src.cols - 1){dst.at<uchar>(i, j)= (1. - u) * (1. - v) * src.at<uchar>(top_y, left_x)+ (v) * (1. - u) * src.at<uchar>(bottom_y, left_x);}else{dst.at<uchar>(i, j)= (1. - u) * (1. - v) * src.at<uchar>(top_y, left_x)+ (1. - v) * (u)*src.at<uchar>(top_y, right_x)+ (v) * (1. - u) * src.at<uchar>(bottom_y, left_x)+ (u) * (v)*src.at<uchar>(bottom_y, right_x);}}}
}
三、双三次插值(Bicubic interpolation)
OpenCV 笔记(23):图像的缩放——立方插值、Lanczos 插值算法 - 掘金
样条插值
样条插值是使用一种名为样条的特殊分段多项式进行插值的形式。由于样条插值可以使用低阶多项式样条实现较小的插值误差,这样就避免了使用高阶多项式所出现的龙格现象,所以样条插值得到了流行。
三次样条函数的定义
https://blog.51cto.com/u_16099178/8790325
双三次样条插值是在二维空间中使用三次样条函数对图像进行插值。它将图像划分为一个网格,并在每个网格点处使用一个三次样条函数来拟合图像数据。在未知点处,通过对相邻网格点的三次样条函数进行插值来获得插值
这个理解很重要
四、区域插值(Area interpolation)
区域插值算法主要分两种情况,缩小图像和放大图像的工作原理并不相同。
·缩小图像
如果图像缩小的比例是整数倍,在调用INTER LINEAR EXACT插值算法时,如果图像的密和高的缩小比例都是2、而且图像的通道数不是2、实际上会调用INTER AREA、在调用INTER LNEAR时,如果图像的亮和高的缩小比例都是2,实际上是会调用INTER AREA。
INTER AREA实际上是个box filter,类似于均值滤波器。
放大图像
如果放大图像的比例是整数倍,与最近邻插值相似。
如果放大的比例不是整数倍,则会采用线性插值。
五、Lanczos 插值
OpenCV 笔记(23):图像的缩放——立方插值、Lanczos 插值算法 - 掘金
Lanczos 插值使用 Lanczos 核函数来计算插值后的像素值。Lanczos 核函数是一种低通滤波器,可以消除缩放过程中产生的混叠现象