程序员最蛋疼的事情莫过于,明明你是后台,非得让你搞搞界面、弄弄前端,明明是算法,非得让你搞搞软件。话说回来,多学点东西总没错,但学得太杂也容易搞丢了老本行,换句话说,什么都会等于什么都不会。在这个分工愈加细化的社会上,对于搞技术的码农来说,学习的深度比学习的宽度有用得多。
闲话少说,因项目需要,作者要搞一个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<uchar>(h)[w * 3];
uchar g = src.ptr<uchar>(h)[w * 3 + 1];
uchar r = src.ptr<uchar>(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或暴风影音播放器,系统自带播放器可以挂掉),如有疑问,欢迎垂询,如有指教,欢迎留言。