opencv+ffmpeg将图片序列转换为高质量MP4视频

程序员最蛋疼的事情莫过于,明明你是后台,非得让你搞搞界面、弄弄前端,明明是算法,非得让你搞搞软件。话说回来,多学点东西总没错,但学得太杂也容易搞丢了老本行,换句话说,什么都会等于什么都不会。在这个分工愈加细化的社会上,对于搞技术的码农来说,学习的深度比学习的宽度有用得多。

闲话少说,因项目需要,作者要搞一个opencv图片写高质量MP4的程序,一想到视频相关,肯定离不开ffmpeg。ffmpeg是一个既让人高兴又让人忧伤的玩意,高兴是因为开源,谁都可以整一整,忧伤是因为这东西太麻烦,要从头到尾学一遍的话,对于临时借用者来说,实在浪费时间,毕竟不是谁都搞视频处理。

opencv利用ffmpeg写了一个videowriter的接口,但这接口实在让人无语,无法对视频的质量进行控制。当然你也可以用opencv,将Mat保存为图片格式,再用ffmpeg的命令进行处理,只是读写硬盘浪费时间,对于算法工程师来说,这就是最大的罪过。总而言之,在考虑程序效率和灵活性的情况下,只好搞个小程序出来跑跑。话说回来,这个小程序后续项目多次用到,还是挺好使。

ffmpeg的资料网上到处都有,但很少有系列的、深入浅出的讲解,但牛人毕竟多,最著名的是雷爷的博客(博客:http://blog.csdn.net/leixiaohua1020#,Github:http://leixiaohua1020.github.io/雷爷已经退出江湖入天堂,但江湖还有雷爷的传说,安息),你可以从网上随便找出一个由图片序列到MP4等视频文件的源程序。当然由于ffmpeg的升级,有些接口进行了更新,因此会出现一些deprecated的警告或错误,为了节省时间,我尝试直接在ffmpeg头文件里面注释掉deprecated的定义,结果发现可以跑通的,ffmpeg并没有完全放弃老版本的兼容。

这样的话,我们直接进行以下步骤:

1.使用opencv读取图片,或都opencv直接产生图片;

2.opencv对图片进行各种处理并得到Mat类型的BGR数据;

3.opencv的Mat数据转换成yuv420格式数据;

4.由yuv420格式数据转换成ffmpeg的AVFrame;

5.ffmpeg写视频并保存。

以上可以看出,核心部分在于Mat->yuv420->AVFrame,其它都好说,参看代码如下:

int fill_yuv_image(AVFrame *pict, int width, int height, Mat src) {
	int w, h, ret = av_frame_make_writable(pict);
	if (ret < 0) {
		printf("cannot make picture writable\n");
		return -1;
	}

	if (src.cols != width || src.rows != height)
	{
		printf("input image error\n");
		return -1;
	}
	//Mat dst = Mat(height *3/2,width,CV_8UC1);
	for (h = 0; h < height; h++)
	{
		for (w = 0; w < width; w++)
		{
			//注意此处Mat为BGR格式
			uchar b = src.ptr(h)[w * 3];
			uchar g = src.ptr(h)[w * 3 + 1];
			uchar r = src.ptr(h)[w * 3 + 2];
			//bgr转yuv,加减移位运算比浮点速度快
			uchar y = (uchar)((66 * r + 129 * g + 25 * b + 128) >> 8) + 16;
			uchar u = (uchar)((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128;
			uchar v = (uchar)((112 * r - 94 * g - 18 * b + 128) >> 8) + 128;
			if (y < 0) y = 0;
			else if (y > 255) y = 255;
			if (u > 255) u = 255;
			else if (u < 0) u = 0;
			if (v > 255) v = 255;
			else if (v < 0) v = 0;
			//AVFrame有个linesize,即跨度不一定与宽度一致
			//YUV420格式有点怪,yyuv排列,具体可以上网参考yuv420_plane
			pict->data[0][h*pict->linesize[0] + w] = y;
			if (w % 2 == 0 && h % 2 == 0)
			{
				pict->data[1][(h / 2)* pict->linesize[1] + w / 2] = u;
			}
			else if (w % 2 == 0)
			{
				pict->data[2][(h / 2)*pict->linesize[2] + w / 2] = v;
			}
		}
	}
	return 0;
}

以上核心代码如果耗时较多,其实可以用cuda加速,形成伪硬编码^_^

你懒的话可以下载程序源码:https://download.csdn.net/download/weixin_39212021/10510784(PS:第一次上传资源,2分不知道会不会太多),里面项目采用vs2015编译,库用的opencv3.2+ffmpeg3.4,项目名称有点奇怪,不用惊讶,因为我是在写其它项目时搞的。该项目可以直接生成库供其它项目调用,项目里可以设置比特率和帧率,其它复杂的东西没有加进去,可以生成高质量视频。(PS:高质量视频播放要使用VLC或暴风影音播放器,系统自带播放器可以挂掉),如有疑问,欢迎垂询,如有指教,欢迎留言。


你可能感兴趣的:(opencv,ffmpeg,c++)