最简单的视频P帧编码的C++实现

   日期:2020-08-27     浏览:98    评论:0    
核心提示:一、前言本篇博客尝试动手实现一个简单的P帧编码。二、主要实现:参考图像P1,欲编码图像P2,通过在P1中去进行宏块匹配,来拼凑出图像P3,最后在对编码出的图像P3进行残差补偿来完成简单的图像编码,得到编码后图像P4。整体上只简单的应用了帧间编码的思想。三、运行结果1. 参考图像P1这帧图像就是我们在编码中的参考帧,大多数情况下为I帧。通常情况下参考帧的数量不会是只有1个。2 欲编码图像P2这帧图像就是我们想要进行编码的图像的源图像。3 宏块匹配拼凑出的图像P3把参考帧P1和欲编码帧P

一、前言

本篇博客尝试动手实现一个简单的P帧编码。

二、主要实现:

参考图像P1,欲编码图像P2,通过在P1中去进行宏块匹配,来拼凑出图像P3,最后在对编码出的图像P3进行残差补偿来完成简单的图像编码,得到编码后图像P4。整体上只简单的应用了帧间编码的思想。

三、运行结果

1. 参考图像P1

这帧图像就是我们在编码中的参考帧,大多数情况下为I帧。通常情况下参考帧的数量不会是只有1个。

2 欲编码图像P2

这帧图像就是我们想要进行编码的图像的源图像。

3 宏块匹配拼凑出的图像P3

把参考帧P1和欲编码帧P2都划分成8*8的宏块,寻找P1中与P2中相对应的最相近的宏块,记录下宏块的坐标,即运动矢量。然后根据图像P1中的宏块去拼凑出图像P3,理论上P3应该与P2有较大的相似性,并且在图像未变化的区域应该表现出平滑且清晰的画面。图像中人的区域有明显的模糊。

4 编码后图像P4

帧间编码主要是运动检测和运动补偿,宏块匹配得到运动矢量,已经大大缩减了码流,然后再计算P2与P3之间的残差(欲编码图像P2与拼凑出来的类P2的图像P3),得到残差和运动矢量一起进行传输,就可以达到节省码流的目的。下面是运动补偿后的图像。几乎与原图像P2一致。

有趣的现象

事实上即便参考帧和欲编码帧的是完全不同的两幅图像,即没有时域相关性,通过拼凑也是可以得到欲编码帧的大概轮廓,如下

1. 参考图像P1

2 欲编码图像P2

3 宏块匹配拼凑出的图像P3

完全不相关的图像也是根据参考帧拼凑出来了图2的大概模样。

四、实验过程

这只是一个简单的模拟,并没有去做DCT变换和熵编码和传输模块,图像的显示借助的是opencv开源库的Mat,可以更直观的观测到结果。

1、图片读取

注意: imread();这个函数有两个参数,我这个实验使用灰度图去模拟的所有只需要单通道,将第二个参数置0,默认是1(三通道),如果你想读取灰度图,即便你读取的源图片就是灰度图,如果你不将参数置为0的话,那么读取的数据仍然是三通道的数据,这在后面处理data数据会造成错误。

	
	Mat P1 = imread("test11.jpg",0);
	resize(P1, P1, Size(640, 360));
	Mat P2 = imread("test10.jpg",0);
	resize(P2,P2,Size(640,360));
	//未经残差补偿的图像。
	Mat P3(P1.rows, P1.cols, P1.type());
	//残差补偿后的图像
	Mat P4(P1.rows, P1.cols, P1.type());
	unsigned char* data1 = P1.data;
	unsigned char* data2 = P2.data;
	//data3是根据参考帧P1拼凑出来的图片,理论上应该与P2很相近。
	unsigned char* data3 =P3.data;

2 宏块划分、匹配

将源图像P1和待编码图像P2切分为8*8的小宏块,然后按顺序取出待编码图像P2的宏块在P1中进行匹配。匹配的算法实际上在应用中一般是以待匹配宏块为中心向四周搜索,这里为了实现简单,直接简单粗暴的进行全局搜索,这么做带来的问题就是计算量的暴增。
寻找到匹配快后要记录匹配块的坐标x,y,即运动矢量。

for (int y = 0; y < height; y += 8)
	{
		for (int x = 0; x < width; x += 8)
		{   
			// 宏块的y数据
			int  block[64] = { 0 };
			//单个宏块 8*8
			for (int off_y = 0; off_y < 8; ++off_y)
			{
				for (int off_x =0; off_x < 8; ++off_x)
				{
					//像素点对应宏块里的索引坐标
					int block_index = (off_y * 8 + off_x);
					//像素点对应整幅图像的坐标。
					int src_index = (((y + off_y) * width) + (x + off_x)); 
					//取出像素,这里读取的是灰度图,所以读取出来就直接是Y分量,不需要再进行像素格式转换
					block[block_index] = data2[src_index];
				}
			}
			//取出像素后,要去P1中去匹配宏块
			int ref_x=0, ref_y=0;
			block_search_nearest(data1, &ref_x, &ref_y, block, width, height);	
		}
	}

宏块匹配其实就是计算距离矢量,代码实现如下:

int block_diff(int *data1, int * data2)
{
	int sum = 0;
	for (int j = 0; j < 8; j++)
	{
		for (int i = 0; i < 8; i++)
		{
			int c1 = data1[j * 8 + i];
			int c2 = data2[j * 8 + i];
			sum += (c1 - c2) * (c1 - c2);
		}
	}
	return sum;
}

3 拼凑图像P3

宏块匹配过后记录下运动矢量,即对应宏块坐标,根据坐标在参考帧P1中去拼凑出一帧新的图像P3,P3理论上应该与P2很相似,时域相关性强的情况下应该只有少部分模糊或不同。

for (int ref_y = 0; ref_y < height; ref_y += 8)
	{
		for (int ref_x = 0; ref_x < width; ref_x += 8)
		{
			//取出P1中对应的像素,存放到新的图像数据data3中。
			if (match_block_list->size() == 0)
			{
				break;
			}
			struct Match_Block_Index index;
			index = match_block_list->front();
			match_block_list->pop_front();
			//从data1中拿取数据去拼凑data3
			CopyRect(data1,data3,index.x,index.y,ref_x,ref_y,width);
		}
	}

运动补偿

最后计算图像P2与P3之间的差值,这部分在实际应用中在编码中进行,然后将残差进行网络传输,最后在解码端接收残差,然后与根据参考帧建立的图像进行整合,从而构建出完整的图像。这个模拟中就直接一起进行了。

	//残差计算
	unsigned char* diff_data = (unsigned char*)malloc(P1.rows * P1.cols);
	for (int i = 0; i < P1.rows * P1.cols; i++)
	{
		diff_data[i] = (data2[i] - data4[i]) / 2;
	}
	//运动补偿
	for (int i = 0; i < P1.rows * P1.cols; i++)
	{
		data4[i] = data4[i] + diff_data[i]*2;
	}

总结:

如果有需要源程序的可以下载我上传的工程,包括一个本篇博客的编码程序完整版,然后还有一个tiny_jpeg.c开源程序,两个工程都是可以直接打开就运行的。、

源代码下载地址: 最简单的视频P帧编码的C++实现和tiny_jpeg工程,vs打开即可运行

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服