一、概括
面试的时候问到了一个图,就是如何将一个算子放缩??我第一反应是resize(),但是后来我转念一想,人家问的是插值方式,今天来总结一下
最邻近插值法原理分析及c++实现_最临近插值法-CSDN博客
我们总常用的插值方式,临近插值 、双线性插值、三次样条插值、拉格朗日插值、多项式、区域插值等
下面我们就一步一步的将其概括出来
二、临近插值
最邻近插值法 : 其核心思想是选取离目标点最近的点作为待插入的新值点
如图:其他的Q12 Q22 Q11 Q21都是已知的像素点,要求插入一个点P
从上图可以看出P到 Q12 最近,那么我就直接将P=Q12
计算两份方向上的缩放后的图像
int dst_cols = round(src.cols * sx); // 列 ==x
int 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); // 列 ==x
int dst_rows = round(src.rows * sy); // 行==y
dst = 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;
}
//B
dst.at<cv::Vec3b>(i, j)[0] = src.at<cv::Vec3b>(i_new, j_new)[0];
//G
dst.at<cv::Vec3b>(i, j)[1] = src.at<cv::Vec3b>(i_new, j_new)[1];
//R
dst.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);
}
}
}
}