最近邻插值、双线性插值

转自 https://blog.csdn.net/caomin1hao/article/details/81092134

转自 https://blog.csdn.net/zhangla1220/article/details/41014541

1.最近邻插值

越是简单的模型越适合用来举例子,我们就举个简单的图像:3X3 的256级灰度图,也就是高为3个象素,宽也是3个象素的图像,每个象素的取值可以是 0-255,代表该像素的亮度,255代表最亮,也就是白色,0代表最暗,即黑色。假如图像的象素矩阵如下图所示(这个原始图把它叫做源图,Source):
234   38    22
67     44    12
89     65    63

这个矩阵中,元素坐标(x,y)是这样确定的,x从左到右,从0开始,y从上到下,也是从零开始,这是图象处理中最常用的坐标系,就是这样一个坐标:

  ---------------------->X
  |
  |
  |
  |
  |
∨Y

如果想把这副图放大为 4X4大小的图像,那么该怎么做呢?那么第一步肯定想到的是先把4X4的矩阵先画出来再说,好了矩阵画出来了,如下所示,当然,矩阵的每个像素都是未知数,等待着我们去填充(这个将要被填充的图的叫做目标图,Destination):
?        ?        ?       ?
?        ?        ?       ?
?        ?        ?       ?
?        ?        ?       ? 
               
        然后要往这个空的矩阵里面填值了,要填的值从哪里来来呢?是从源图中来,好,先填写目标图最左上角的象素,坐标为(0,0),那么该坐标对应源图中的坐标可以由如下公式得出:                                      
srcX=dstX* (srcWidth/dstWidth) , srcY = dstY * (srcHeight/dstHeight)
好了,套用公式,就可以找到对应的原图的坐标了(0*(3/4),0*(3/4))=>(0*0.75,0*0.75)=>(0,0)
,找到了源图的对应坐标,就可以把源图中坐标为(0,0)处的234象素值填进去目标图的(0,0)这个位置了。

接下来,如法炮制,寻找目标图中坐标为(1,0)的象素对应源图中的坐标,套用公式:
(1*0.75,0*0.75)=>(0.75,0)
结果发现,得到的坐标里面竟然有小数,这可怎么办?计算机里的图像可是数字图像,象素就是最小单位了,象素的坐标都是整数,从来没有小数坐标。这时候采用的一种策略就是采用四舍五入的方法(也可以采用直接舍掉小数位的方法),把非整数坐标转换成整数,好,那么按照四舍五入的方法就得到坐标(1,0),完整的运算过程就是这样的:
(1*0.75,0*0.75)=>(0.75,0)=>(1,0)
那么就可以再填一个象素到目标矩阵中了,同样是把源图中坐标为(1,0)处的像素值38填入目标图中的坐标。
         
依次填完每个象素,一幅放大后的图像就诞生了,像素矩阵如下所示:
234    38     22     22  
67      44     12     12  
89      65     63     63  
89      65     63     63  

这种放大图像的方法叫做最临近插值算法,这是一种最基本、最简单的图像缩放算法,效果也是最不好的,放大后的图像有很严重的马赛克,缩小后的图像有很严重的失真;效果不好的根源就是其简单的最临近插值方法引入了严重的图像失真,比如,当由目标图的坐标反推得到的源图的的坐标是一个浮点数的时候,采用了四舍五入的方法,直接采用了和这个浮点数最接近的象素的值,这种方法是很不科学的,当推得坐标值为 0.75的时候,不应该就简单的取为1,既然是0.75,比1要小0.25 ,比0要大0.75 ,那么目标象素值其实应该根据这个源图中虚拟的点四周的四个真实的点来按照一定的规律计算出来的,这样才能达到更好的缩放效果。双线型内插值算法就是一种比较好的图像缩放算法,它充分的利用了源图中虚拟点四周的四个真实存在的像素值来共同决定目标图中的一个像素值,因此缩放效果比简单的最邻近插值要好很多。

 

2.双线性插值

根据于待求点P最近4个点的像素值,计算出P点的像素值。

1)简单理解

对于一个目的像素,设置坐标通过反向变换得到的浮点坐标为(i+u,j+v) (其中i、j均为浮点坐标的整数部分,u、v为浮点坐标的小数部分,是取值[0,1)区间的浮点数),则这个像素得值 f(i+u,j+v) 可由原图像中坐标为 (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)vf(i,j+1) + u(1-v)f(i+1,j) + uvf(i+1,j+1)

其中f(i,j)表示源图像(i,j)处的的像素值,以此类推。

2)一般性

如上图,已知Q12,Q22,Q11,Q21,但是要插值的点为P点,这就要用双线性插值了,首先在x轴方向上,对R1和R2两个点进行插值,这个很简单,然后根据R1和R2对P点进行插值,这就是所谓的双线性插值。说明:下式中f(*)为*点处像素值。

首先在 x 方向进行线性插值,得到:

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

也即点P处像素值:

==========================================================================

==========================================================================

==========================================================================

==========================================================================

==========================================================================

==========================================================================

前一阵想改进个图像处理的算法,发现基础太差了,还是要从基础开始,毕竟一口出不成胖子

于是自己学习一下图像处理的基本知识。看到两个最简单的算法 一个是最近邻内插值和双线性插值,决定自己实现以下,顺便也能了解一下数字图像内部的存储结构。

以下为简单整理

by the way,基础知识逐渐明朗的感觉还是不错滴~程序员最大的开心应该就是程序无bug跑通的时候,无论是多简单的程序。

一个Width*Height  nChannel个通道的图像 共具有Width * Height 个像素点。每个像素点有nChannel个通道元素。

以3通道为例,3通道为B G R

如上所示,每一行有效元素共Width*nChannel个元素,实际上,有时候比这个数要多。

为了提高速度,需要按4字节对齐,因此如果每行元素个数不是4的倍数,需要补齐,这个即为平时总提到的widthstep。

基于以上的了解,实现两种算法

1 最近领内插值算法

就是令变换后像素的灰度值等于距它最近的输入像素的灰度值

如上图 按照比例一致,可知道

Y = W / w * y;

X = H / h * x;

则目标图(x,y)处的灰度值与原图(X,Y)处的灰度值一致:f(x,y)=f(X,Y)

代码:

//最近邻内插
void NearestInsert(const char *OriImagePath, char *OutImagePath, int Width, int Height)
{
	IplImage * OriImage = cvLoadImage(OriImagePath, 1);
	IplImage * OutImage = cvCreateImage(cvSize(Width, Height), 8, 3);
 
	unsigned char *OriImage_c = (unsigned char *)OriImage->imageData;
	unsigned char *OutImage_c = (unsigned char *)OutImage->imageData;
 
	double fx = (double)OriImage->height / (double)Height;//height方向上的比值
	double fy =  (double)OriImage->width / (double)Width;//width方向上的比值
	int channel = OriImage->nChannels;//通道数 一般为RGB 3通道
	
	for (int x = 0; x < Height; x++)
	{
		for(int y = 0; y < Width; y++)
		{
			for (int k = 0; k <channel; k++)
			{
				int out =  x * OutImage->widthStep + y * channel + k;
				int ori = (int)(fx * x) * OriImage->widthStep + (int)(fy * y)  * channel + k;
				OutImage_c[out] = OriImage_c[ori];
			}
 
		}
			
	}
	
	//test
	cvNamedWindow("OriImage",1);
	cvShowImage("OriImage",OriImage);
	cvWaitKey(0);
	cvNamedWindow("NearestInsert",1);
	cvShowImage("NearestInsert",OutImage);
	cvWaitKey(0);
	//cvSaveImage(OutImagePath,OutImage);
	cvReleaseImage(&OriImage);
	cvReleaseImage(&OutImage);
}

2 双线性插值

在x y两个方向上分别进行一次线性插值,方向先后不影响顺序。

如上图所示

目标图(x, y) 映射到原图是(X + u, Y + v)(计算方法同最邻近插值)。设u与v分别为X + u,Y + v的小数部分。

由于下标都是整数,因此原图其实并不存在该点。

则取其附近四个领域点为(X, Y) (X, Y + 1) (X + 1, Y) (X + 1, Y + 1)

则目标图(x, y)处的值为 f(x , y) = f(X + u, Y + v) =f (X, Y)  * (1 - u) * (1 - v) + f(X, Y + 1) * (1 - u) * v + f(X + 1, Y) * u * (1 - v) + f (X + 1, Y + 1) * u * v;
代码如下:

//双线性内插
void BilinearInsert(const char *OriImagePath, char *OutImagePath, int Width, int Height)
{
	IplImage * OriImage = cvLoadImage(OriImagePath, 1);
	IplImage * OutImage = cvCreateImage(cvSize(Width, Height), 8, 3);
 
	unsigned char *OriImage_c = (unsigned char *)OriImage->imageData;
	unsigned char *OutImage_c = (unsigned char *)OutImage->imageData;
 
	double fx = (double)OriImage->height / (double)Height;//height方向上的比值
	double fy =  (double)OriImage->width / (double)Width;//width方向上的比值
	int channel = OriImage->nChannels;//通道数 一般为RGB 3通道
 
	for (int x = 0; x < Height; x++)
	{
		for(int y = 0; y < Width; y++)
		{
			//对于目标图像(x, y),实际映射到原图的坐标为(fx * x, fy * y),但是若为小数,原图并不存在该店,因此近似为(i, j)
			int i = fx * x;
			int j = fy * y;
			int out =  x * OutImage->widthStep + y * channel;
			//找到四个领域的下标
			int ori1 = i * OriImage->widthStep + j * channel;
			int ori2 = i * OriImage->widthStep + (j + 1)  * channel;
			int ori3 = (i + 1) * OriImage->widthStep + j  * channel;
			int ori4 = (i + 1) * OriImage->widthStep + (j + 1)  * channel;
			//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)  
			for (int k = 0; k <channel; k++)
			{
				float u = (float)x * fx - i;//
				float v = (float)y * fy - j;
				OutImage_c[out + k] = OriImage_c[ori1 + k] * (1 - u) * (1 - v) 
					+ OriImage_c[ori2 + k] * (1 - u) * v
					+ OriImage_c[ori3 + k] * u * (1 - v)
					+ OriImage_c[ori4 + k] * u * v;
					
			}
 
		}
 
	}
 
	//test
	cvNamedWindow("BilinearInsert",1);
	cvShowImage("BilinearInsert",OutImage);
	cvWaitKey(0);
	//cvSaveImage(OutImagePath,OutImage);
	cvReleaseImage(&OriImage);
	cvReleaseImage(&OutImage);
 
}

===============================================================================

===============================================================================

===============================================================================

自己根据该两篇文章,写了如下代码:

// 双线性插值
static void bilinear_interpolation(float x_ratio, float y_ratio, int origin_point_x, int origin_point_y, int step_width, float *x_decimal, float *y_decimal, int *ori1, int * ori2, int *ori3, int *ori4)
{
    int x_int = x_ratio * origin_point_x;
    int y_int = y_ratio * origin_point_y;

    // 找到四个领域的下标
    *ori1 = y_int * step_width + x_int;
    *ori2 = y_int * step_width + (x_int + 1);
    *ori3 = (y_int + 1) * step_width + x_int;
    *ori4 = (y_int + 1) * step_width + (x_int + 1);
    
    *x_decimal = origin_point_x * 1.0 * x_ratio - x_int;
    *y_decimal = origin_point_y * 1.0 * y_ratio - y_int;
}

调用代码:

bilinear_interpolation(x_ratio, y_ratio, hor, ver, dst_frame->linesize[0], &x_decimal, &y_decimal, &ori1, &ori2, &ori3, &ori4);

dst_frame->data[0][ver*dst_frame->linesize[0]+hor] = cp_target_frame_y[ori1] * (1 - y_decimal) * (1 - x_decimal) + cp_target_frame_y[ori2] * (1 - y_decimal) * x_decimal + cp_target_frame_y[ori3] * y_decimal * (1 - x_decimal) + cp_target_frame_y[ori4] * y_decimal * x_decimal;

 

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值