OpenCV 下写视频,只需要使用 cv::VideoWriter
类,代码非常简单。很可惜的是,无论是使用安装包预编译好的静态库和动态库,还是使用 CMake 在本地重新配置生成新的静态库和动态库,都无法写出用 h.264 格式编码的视频。我们运行这个简单的程序:
#include
#include
#include
int main()
{
const char* videoIn = "old.avi";
const char* videoOut = "new.avi";
cv::VideoCapture vin(videoIn);
cv::VideoWriter vout(videoOut, CV_FOURCC('X','2','6','4'), vin.get(CV_CAP_PROP_FPS),
cv::Size(vin.get(CV_CAP_PROP_FRAME_WIDTH), vin.get(CV_CAP_PROP_FRAME_HEIGHT)));
for (int i = 0; i < 1000; i++)
{
cv::Mat frame;
vin.read(frame);
vout.write(frame);
}
vout.release();
return 0;
}
得到的结果是:
Could not find encoder for codec id 28: Encoder not found请按任意键继续. . .
为了使 OpenCV 支持写 h.264 格式的视频,需要编译支持 x264 的 ffmpeg,其中 x264 是能够进行 h.264 格式视频编码的开源库,ffmpeg 是视频编解码的通用框架。本文将 windows 操作系统下 32 位程序的操作方法进行说明,64 位下的操作还未尝试。使用的 OpenCV 版本是 2.4.5,更新版本的 OpenCV 的操作方法类似。
在进行操作之前,先来简述一下 OpenCV 是如何支持视频的读写的。OpenCV2.0 版本之后采用了新式的接口 cv::VideoCapture
和 cv::VideoWriter
分别进行视频读写操作,这两个类实际上是对 OpenCV1.0 版本 C 接口的 CvCapture
和 CvVideoWriter
进行的封装,而 CvCapture
和 CvVideoWriter
底层是使用 ffmpeg 提供的接口进行视频读写的操作的。所以,从本质上说,OpenCV 提供的是 ffmpeg 接口高层的抽象的易用的的封装,隐藏了 ffmpeg 繁杂的调用细节,让 OpenCV 的使用者专注于视觉方面算法的工作。
一般情况下,我们使用的是 OpenCV 的动态库,解压 opencv2.4.5 的官方安装文件到 D:/OpenCV2.4.5,在 D:/OpenCV2.4.5/build/x86/vc10/bin 路径下,给出了适用于 visual studio 2010 的 32 位的 OpenCV 的动态库。这些库有 opencv_core245.dll,opencv_imgproc245.dll 等。这些库都放在一起,我们还通常把这些动态库的路径写入系统环境变量 Path 中。这些动态库中,有一个 opencv_ffmpeg245.dll,这个动态库封装了视频读写中需要的 ffmpeg 的功能。注意到这个动态库是没有对应的 lib 文件的,我们在写 OpenCV 程序的时候从来不需要在 Project->Properties->Configuration Properties->Linker->Input->Additional Dependencies 中添加 opencv_ffmpeg245.lib。opencv_ffmpeg245.dll 的加载是动态加载。如果我们的程序需要进行视频读写,那么我们的程序在链接的时候需要使用 opencv_highgui245.lib。程序执行的时候,会加载相应的 opencv_highgui245.dll,一旦发生视频读写操作,opencv_highgui245.dll 中的内部会打开这个 dll 所在路径下的 opencv_ffmpeg245.dll,找到所需函数的入口地址,调用相关函数,完成视频读写操作。如果opencv_highgui245.dll 所在路径下不存在 opencv_ffmpeg245.dll,将无法读写视频。
通过上述简介我们知道,为了让 OpenCV 支持写 h.264 格式的视频,最关键的步骤就是重新生成 opencv_ffmpeg245.dll。进入 D:/OpenCV2.4.5/3rdparty/ffmpeg(新版本如 2.4.10 的路径是 D:/OpenCV2.4.10/sources/3rdparty/ffmpeg)文件夹,里面有个 readme.txt 说明了重新生成 opencv_ffmpeg.dll 的操作方法。过程简述如下:
第一,安装 MinGW;
第二,使用 msys 终端编译 ffmpeg,得到 *.a 静态库;
第三,修改 D:/OpenCV2.4.5/3rdparty/ffmpeg/make.bat 文件,使用 msys 终端编译出 opencv_ffmpeg.dll。
默认情况下,ffmpeg 是不支持编码 h.264 格式视频的,如果我们要让 ffmpeg 支持,简要步骤改成:
第一,安装 MinGW;
第二,使用 msys 终端编译 x264,得到 *.a 静态库;
第三,使用 msys 终端编译支持 x264 的 ffmpeg,得到 *.a 静态库;
第四,修改 D:/OpenCV2.4.5/3rdparty/ffmpeg/make.bat 文件,使用 msys 终端编译出opencv_ffmpeg.dll。
下面将详细说明这四个操作步骤。
在 http://sourceforge.net/projects/mingw/files/ 下载 mingw-get-setup.exe。
运行这个 exe 安装 MinGW,安装基本的库、可执行文件,最主要是 C 和 C++ 的编译器。安装过程很简单,可参考 http://tieba.baidu.com/p/2826016538。记住要把 MinGW 路径下的 bin 文件夹的全路径加入系统环境变量 Path 中。
我的安装路径是 D:/MinGW,所以要在 Path 中添加 D:\MinGW\bin。要使用 MinGW 下的命令行操作,进入 D:/MinGW/msys/1.0,运行 msys.bat,弹出的终端就等效于 linux/unix 下的终端,后面的 x264 编译,ffmpeg 编译,opencv_ffmpeg.dll 的生成都需要 msys 下的终端。
为了能够顺利编译 x264 和 ffmpeg,还需要安装 yasm 和 pkg-config。
yasm可以在 http://yasm.tortall.net/Download.html 下载,选择 Win32.exe(for general use on 32-bit Windows) 一项进行下载,下载好的文件重命名为 yasm.exe,放到 D:/MinGW/bin 文件夹中。
pkg-config 可以在 gnome 的网站上下载,
本人采用的是 http://zengwu3915.blog.163.com/blog/static/278348972014628101514288/ 上给出的两个链接
http://ftp.gnome.org/pub/gnome/binaries/win32/dependencies/pkg-config_0.23-3_win32.zip
http://ftp.gnome.org/pub/gnome/binaries/win32/glib/2.18/glib_2.18.4-1_win32.zip
将 pkg-config.exe 和 libglib-2.0-0.dll 放到 D:/MinGW/bin 路径下。
注:本人在另一台电脑上实验的时候,发现上述 exe 和 dl l并不够,运行 configure 命令时缺少 intl.dll。
后来在 http://stackoverflow.com/questions/1710922/how-to-install-pkg-config-in-windows 上看到,为了成功运行 pkg-config,依赖项不仅仅有 glib,还有 gettext-runtime。上面给出的 pkg-config 和附加依赖项的下载链接是
http://ftp.gnome.org/pub/gnome/binaries/win32/dependencies/pkg-config_0.26-1_win32.zip
http://ftp.gnome.org/pub/gnome/binaries/win32/glib/2.28/glib_2.28.8-1_win32.zip
http://ftp.gnome.org/pub/gnome/binaries/win32/dependencies/gettext-runtime_0.18.1.1-2_win32.zip
至此准备工作完成。
在网页 http://www.videolan.org/developers/x264.html 下载 x264 源代码,解压。解压得到文件件 x264-snapshot-20150908-2245,其中的时间表示了版本信息。打开 msys 终端,将当前路径切换到 x264 源码路径。编译x264静态库,安装到/usr/local/x264中,运行命令为:
./configure --enable-win32thread --enable-static --prefix=/usr/local/x264
make
make install
注意,msys 终端中的 /usr/local 路径实际上是 D:/MinGW/msys/1.0/local
在网页 http://ffmpeg.org/download.html 下载 ffmpeg 源代码,解压。当前 ffmpeg 版本是 2.8。打开 msys 终端,将当前路径切换到 ffmpeg 源码路径,编译支持 x264 的ffmpeg 静态库,安装到 /usr/local/ffmpeg 中,运行命令为:
./configure --enable-w32threads --enable-gpl --enable-libx264 --extra-cflags=-I/usr/local/x264/include --extra-ldflags=-L/usr/local/x264/lib --prefix=/usr/local/ffmpeg
make
make install
在我的电脑上运行 make 的时候现编译错误,并终止。LOG 如下
CC libavformat/cache.o
In file included from libavformat/cache.c:41:0:
d:\mingw\include\unistd.h:79:1: error: expected ',' or ';' before 'int'
int __mingw_sleep( unsigned long, unsigned long );
^
In file included from libavformat/cache.c:41:0:
d:\mingw\include\unistd.h:105:1: error: expected ',' or ';' before 'int'
int nanosleep( const struct timespec *, struct timespec * );
^
d:\mingw\include\unistd.h:125:28: error: expected ',' or ';' before 'usleep'
int _cdecl __MINGW_NOTHROW usleep( useconds_t )__MINGW_ATTRIB_DEPRECATED;
^
d:\mingw\include\unistd.h:138:10: error: conflicting types for '_cdecl'
unsigned _cdecl __MINGW_NOTHROW sleep( unsigned );
^
d:\mingw\include\unistd.h:125:5: note: previous declaration of '_cdecl' was here
int _cdecl __MINGW_NOTHROW usleep( useconds_t )__MINGW_ATTRIB_DEPRECATED;
^
d:\mingw\include\unistd.h:138:33: error: expected ',' or ';' before 'sleep'
unsigned _cdecl __MINGW_NOTHROW sleep( unsigned );
^
d:\mingw\include\unistd.h:153:12: error: expected '=', ',', ';', 'asm' or '__attribute__' before 'ftruncate'
int _cdecl ftruncate( int, off_t );
^
libavformat/cache.c: In function 'cache_seek':
libavformat/cache.c:230:17: warning: format '%d' expects argument of type 'int', but argument 4 has type 'int64_t' [-Wformat=]
av_log(h, AV_LOG_ERROR, "Inner protocol failed to seekback end : %"PRId64"\n", pos);
^
libavformat/cache.c: In function 'cache_close':
libavformat/cache.c:290:12: warning: format '%d' expects argument of type 'int', but argument 4 has type 'int64_t' [-Wformat=]
c->cache_hit, c->cache_miss);
^
libavformat/cache.c:290:12: warning: format '%d' expects argument of type 'int', but argument 5 has type 'int64_t' [-Wformat=]
make: *** [libavformat/cache.o] Error 1
经过搜索发现了解决办法:
第一种方法是某 CSDN 博主给出的
http://blog.csdn.net/u013699869/article/details/45894083
解决方案是在文中列出的文件中 #include
之前,加上 #include
我没尝试过这种方法。
第二种方法是 sourceforge 上 MinGW 的 bug 网页上给出的
http://sourceforge.net/p/mingw/bugs/2256/
Stefano Sabatini 在 2015-05-07 的一个回复中指出 I confirm that replacing “_cdecl” with “__cdecl” in the file MINGW_PATH/usr/include/unistd.h will fix the compilation issue.
我参考了这种方法,将 D:/MinGW/include/unistd.h 中的 _cdecl
替换成 __cdecl
解决了 ffmpeg 编译错误的问题。
然后,按照 D:/OpenCV2.4.5/3rdparty/ffmpeg/readme.txt 文件的说法,运行
cd /usr/local/ffmpeg
strip -g *.a
这样得到的 *.a 文件变小。
为了成功连接 opencv_ffmpeg.dll,还需要 zlib 的支持。
在 http://zlib.net/ 下载 zlib 的压缩包,目前是 zlib-1.2.8.tar.gz,解压到 D:/zlib-1.2.8。
打开 msys 终端,将当前路径切换为 zlib 源码路径 D:/zlib-1.2.8,D:/zlib-1.2.8/win32 里面的 Makefile.gcc 有 MinGW 下编译和安装方法。事实上,在 D:/zlib-1.2.8 下运行
make -fwin32/Makefile.gcc
能够在 D:/zlib-1.2.8 下得到libz.a
将 D:/MinGW/msys/1.0\local\ffmpeg\include 下的所有文件夹复制粘贴到 D:/OpenCV2.4.5/3rdparty/include中,将 D:/MinGW/msys/.0/local/ffmpeg/lib 下的库文件,D:/MinGW/msys/1.0/local/x264/lib下的 libx264.a,D:/zlib-1.2.8 下的 libz.a,D:/MinGW/lib 下的libiconv.a 复制粘贴到 D:/OpenCV2.4.5/3rdparty/lib中。
将 D:/OpenCV2.4.5/3rdparty/ffmpeg/make.bat 修改为
set path=D:\MinGW\msys\1.0\bin;%path% & gcc -Wall -shared -o opencv_ffmpeg.dll -O2 -x c++ -I../include -I../include/ffmpeg_ -I../../modules/highgui/src ffopencv.c -L../lib -lavformat -lavcodec -lavdevice -lswscale -lswresample -lavutil -lwsock32 -lx264 -lz -liconv
msys 终端切换路径至 D:/OpenCV2.4.5/3rdparty/ffmpeg,运行 make.bat,当前路径下得到编译好的 opencv_ffmpeg.dll。将 opencv_ffmpeg.dll 重命名为 opencv_ffmpeg245.dll,替换 OpenCV 动态库路径下的原 dll。
确实如此,重命名并替换,新的 opencv_ffmpeg245.dll 就能配合 opencv_highgui245.dll 工作。本文 OpenCV 和 ffmpeg 一节中已经指出,opencv_ffmpeg245.dll 这个库是动态加载的。至于如何加载,我们可以看 D:/OpenCV2.4.5/modules/highgui/src/cap_ffmpeg.cpp 中 icvInitFFMPEG
类的构造函数。另一方面,我们可以研究一下本地重编译的 OpenCV 的动态库是如何生成的。我们用 CMake 生成 OpenCV 的工程,打开 opencv.sln,在 Solution Explorer 中选中 opencv_highgui,右键进入 Properties->Configuration Propeprties->Build Events->Post-Build Event->Command Line,我们看到如下命令
setlocal
"C:\Program Files (x86)\CMake\bin\cmake.exe" -E copy D:/OpenCV2.4.5/3rdparty/ffmpeg/opencv_ffmpeg.dll E:/Projects/OpenCV2.4.5sln/bin/Release/opencv_ffmpeg245.dll
if %errorlevel% neq 0 goto :cmEnd
"C:\Program Files (x86)\CMake\bin\cmake.exe" -E copy D:/OpenCV2.4.5/3rdparty/ffmpeg/opencv_ffmpeg.dll E:/Projects/OpenCV2.4.5sln/bin/Debug/opencv_ffmpeg245.dll
if %errorlevel% neq 0 goto :cmEnd
:cmEnd
endlocal & call :cmErrorLevel %errorlevel% & goto :cmDone
:cmErrorLevel
exit /b %1
:cmDone
if %errorlevel% neq 0 goto :VCEnd
其实就是把 D:/OpenCV2.4.5/3rdparty/ffmpeg/opencv_ffmpeg.dll 拷贝到新的路径然后重命名而已,而且这个 dll 是不区分 release 和 debug 模式的。
新的 opencv_ffmpeg245.dll 替换好后,运行本文开头的的程序,会出现以下错误
[libx264 @ 006ac9e0] broken ffmpeg default settings detected
[libx264 @ 006ac9e0] use an encoding preset (e.g. -vpre medium)
[libx264 @ 006ac9e0] preset usage: -vpre -vpre
[libx264 @ 006ac9e0] speed presets are listed in x264 --help
[libx264 @ 006ac9e0] profile is optional; x264 defaults to high
Could not open codec 'libx264': Unspecified error请按任意键继续. . .
网上搜索能提供一些解决方案:
http://blog.csdn.net/cffishappy/article/details/7680097
http://blog.csdn.net/vblittleboy/article/details/8511894
http://rosoo.net/a/201012/10576.html
这几个国内网站链接中的内容似乎都指向同一份国外网站上的讨论,但是目前我没有搜到原始讨论内容。
解决方法是,在调用 avcodec_open
之前,对 AVCodecContext
结构体的的几个成员进行初始化。
对应到 opencv_ffmpeg.dll 的编译,就是打开 D:/OpenCV2.4.5/modules/highgui/src/cap_ffmpeg_impl.hpp,在 1610 行增加如下代码
c->me_range = 16;
c->max_qdiff = 4;
c->qmin = 10;
c->qmax = 51;
c->qcompress = 0.6;
这段代码位于 bool CvVideoWriter_FFMPEG::open
函数内,在调用 avcodec_open
前。
修改后重新运行 make.bat 得到新的 opencv_ffmpeg.dll。然后程序能够成功运行。
但是上面这组参数生成的视频体积小,视觉质量很差,修改为以下参数
c->me_range = 16;
c->max_qdiff = 4;
c->qmin = 1;
c->qmax = 30;
c->qcompress = 0.9;
c->qcompress = 0.6;
能够得到较好的视觉质量。这部分参数如何设置还需要进一步研究。
windows 下使用 OpenCV 编码 h.264 视频的编译过程已经说完。其中还有很多细节需要进一步分析和理解。视频处理是一项很专业的工作,如果需要对视频编码做更详细的控制,需要研究 ffmpeg 中相关结构体的设置方法,使用 OpenCV 的接口是很难做到的。