4.3 使用OpenCV创建视频
对于简单的视频创建工作,可以使用OpenCV内建的
VideoWriter 类来完成。
视频文件的结构:
每个视频文件本质上是一个容器,文件的扩展名(avi,mov,mkv等)表示容器的类型。容器中包含了很多元素,如 视频流,音频流或其他轨道(如字幕)。这些流的存储方式(格式)由每个源对应的编解码器(codec)决定。一般情况下音轨通常用的编解码器是 mp3 或 aac 。而视频格式更多,如 XVID,DIVX,H264,LAGS等等。在一个系统中你可以使用的编解码器依赖于你在系统中是否安装过编解码器。
OpenCV作为一个计算机视觉库而不是视频处理编解码库,力求在这方便精简,所以暂时只支持 avi 扩展名的视频(注意:avi 只是表明了视频文件的类型,具体的视频格式还要根据采用的编解码器类型确定)。不能创建超过2GB的单个视频,每个文件里只能创建一个视频流,不支持音轨和其他轨道。尽管如此,使用任何系统支持的编解码器应该都能工作。如果在视频处理这方面受到了限制,建议使用一些专门的视频处理库如 FFMpeg或更多的编解码器如 HuffYUV,CorePNG 和 LCL。作为替代方案,可以使用OpenCV创建一个原始的视频流,把它和音轨合并起来,或者使用视频处理软件如 VirtualDub 或 AviSynth 用它创建其他视频格式。
VideoWriter 类:
要创建视频文件,需要创建一个
VideoWriter 类的对象,并且可以通过构造函数或
open 成员函数的参数来设置对象的属性。两种方法的参数相同。
VideoWriter::VideoWriter(const string& filename, int fourcc, double fps, Size frameSize, bool isColor=true)
bool VideoWriter::open(const string& filename, int fourcc, double fps, Size frameSize, bool is-Color=true)
1.
filename:
输出的文件名包含了扩展名的类型,暂时只支持 avi 格式,使用输入文件名+通道名+avi来创建输出文件名:
const string source = "../images/a1.avi"; // 原视频文件名
string::size_type pAt = source.find_last_of('.'); // 找到扩展名的位置
const string NAME = source.substr(0, pAt) + "chnl" + ".avi"; // 创建新的视频文件名
2.
fourcc:现在所有的视频编解码器都有一个不超过四个字符的短名字,如 XVID,DIVX,H264等。这被称为
fourcc(four character code)。你可以使用
get 函数从输入视频中获取编解码器类型。
get 函数会返回一个 double 值,fourcc 位于double 值的低四字节,所以可以直接强制转换成 int 值。
VideoCapture inputVideo(source); // 打开视频输入
int ex = static_cast(inputVideo.get(CV_CAP_PROP_FOURCC)); // 得到编码器的int表达式
OpenCV内部使用这个 int 值作为第二个参数
fourcc 的实参。可以通过两种方法把 int 转换成 string 类型:位操作和
union 方法。
位操作使用 & 操作符和 移位操作结合,具体如下:
char EXT[] = {ex & 0XFF , (ex & 0XFF00) >> 8,(ex & 0XFF0000) >> 16,(ex & 0XFF000000) >> 24, 0};
也可以使用union方法,使用它的好处是指定了编解码器类型后转换就自动完成了,而位操作还需要在修改编解码器类型时再进行转换操作。如果你知道事前知道编解码器对应的fourcc编码是什么,你可以使用
CV_FOURCC 宏定义来构建这个 int 数。
union { int v; char c[5];} uEx ;
uEx.v = ex; // 通过联合体来分解字符串,考虑内存模型
uEx.c[4]='\0';
如果传入的参数是是负值的话,会在运行的时候弹出一个窗口,包含你系统安装的所有编解码器供你选择一个使用。
3.
fps:输出视频的帧率。这里和输入视频的帧率保持一致,实际参数使用
get 函数从输入视频中获得。
4.
frameSize:输出视频一帧的大小。这里仍和输入视频保持一致,实际参数使用
get 函数从输入视频中获得。
5.
isColor:可选参数。默认为 true ,表示输出是彩色视频(所以需要传给它三通道图像)。如果传递实参 false ,则创建灰度视频。
具体实现如下:
VideoWriter outputVideo;
Size S = Size((int) inputVideo.get(CV_CAP_PROP_FRAME_WIDTH), //获取输入尺寸
(int) inputVideo.get(CV_CAP_PROP_FRAME_HEIGHT));
outputVideo.open(NAME , ex, inputVideo.get(CV_CAP_PROP_FPS), S, true);
然后,使用
isOpened() 函数验证能否成功打开创建的视频。当
VideoWriter 对象被销毁时,视频文件会自动关闭。成功打开视频后,可以使用
write 成员函数向创建的视频按照序列连续传送视频帧了。也可以使用重载操作符 << 来完成相同的功能:
outputVideo.write(res); //或者
outputVideo << res;
抽取RGB图像的颜色通道意味着将未被选中的通道的值全部置为0。这个操作可以用到手动遍历整个图像完成,也可以使用
split 和
merge 操作:首先把图像的分离成三个单通道图像,然后把未被选中的两个单通道图像置为全0图像,然再重新把三个图像合并成一个三通道图像。
split(src, spl); // 分离三个通道
for( int i =0; i < 3; ++i)
if (i != channel)
spl[i] = Mat::zeros(S, spl[0].type()); //创建相同大小的黑色图像
merge(spl, res); //重新合并