您的位置:首页 > 运维架构

OpenCV入门教程(8)-视频文件的读取和存储

2017-01-16 23:52 751 查看
1 编解码器

视频的压缩算法称为编码器;那么,与之对应的解压缩算法就是解码器。想要了解详细的codec编解码器知识,可以参考FOURCC网站。

在了解编解码器之前,我们先来学习一个概念:FourCC。它的全称是Four Charactors Code,称为四字符码,是一种独立标示视频数据流格式。在FOURCC网站你可以得到完整的基于FourCC的各种各样的编解码器。

我们通过这个标识符,就可以寻找与 FourCC 代码相关联的视频解码器来播放特定的视频流。

FOURCC的定义如下:

typedef unsigned int FOURCC;


一般用宏生成FOURCC,FOURCC是由4个字符拼接而成的,生成FOURCC的传统方法是:

#define MAKE_FOURCC(a,b,c,d) \
(((uint32_t)d) | (((uint32_t)c) << 8) | (((uint32_t)b) << 16) | (((uint32_t)a) << 24))


这种方法的意思显而易见,我们可以使用下面一个模型进行操作:

switch(val)
{
case MAKE_FOURCC('f','m','t',' '):
.....
break;
case MAKE_FOURCC('Y','4','4','2'):
....
break;
...
}


因为宏能生成常量,符合case 的条件。

难道要退回古老的宏?当然不是,C++的模板机制给程序员带来的无限的空间,它不光能让类型作为参数,还能将常量作为参数(这一点常常被人遗忘),而且这一切都是编译期决定的!这是我们用它来生成FOURCC的第1个基础。

于是,我们迫不及待地利用模板来改写上面的函数:

template <char ch0, char ch1, char ch2, char ch3>inline FOURCC MakeFOURCC()
{
return (ch0 << 0) + (ch1 << 8) + (ch2 << 16) + (ch3 << 24);
}


可是错误照旧。虽然这次可以保证返回值能在编译期计算出来,但可惜的是那个return语句却要等到运行才能运行(也有可能在优化阶段就能消除这个语句,但肯定不能再编译期就全部完成)。

别急,还有第2个基础才可以。那是什么?是一个从C语言继承来的东西――enum。很多朋友认为,它不是很重要,因为很多情况下可以用别的方法来取代它,比如const。但是它有一个经常被人忽略的特性,而且这个特性非常重要,那就是――它的值必须在编译期就得出,即它是个编译期常量!这不是正符合我们的需要吗?请看下面的模板:

template <char ch0, char ch1, char ch2, char ch3> struct MakeFOURCC
{
enum
{
value = (ch0 << 0) + (ch1 << 8) + (ch2 << 16) + (ch3 << 24)
};
};


核心还是和上面一样,通过表达式(ch0 << 0) + (ch1 << 8) + (ch2 << 16) + (ch3 << 24)计算FOURCC(那当然是一样的)。但是计算的时机从运行期或者优化期移到了编译期。编译器在编译时,通过模板带入的char常量计算出表达式的值,并把它保存在枚举值value里。看看现在的代码:

const FOURCC fccFMT = MakeFOURCC<'f', 'm', 't', ' '>::value;
const FOURCC fccDATA = MakeFOURCC<'d', 'a', 't', 'a'>::value;
switch (val)
{
case fccFMT:
//...
break;
case fccDATA:
//...
break;
}


成功了,MakeFOURCC模板顺利地完成了任务。FOURCC的模板生成法既让我们抛弃了那个不安全的宏,又让我们看到了inline的局限性,还让我们重新认识了enum的一些特性。其它许多类似的问题也能通过template + enum来解决。

OpenCV 2 中提供了两个类来实现视频的读写:读视频的类是 VideoCapture;写视频的类是 VideoWriter。

2 读视频

VideoCapture 既可以从视频文件读取图像,也可以从摄像头读取图像。可以使用该类的构造函数打开视频文件或者摄像头。如果 VideoCapture 对象已经创建,也可以使VideoCapture::open()打开,VideoCapture::open()函数会自动调用VideoCapture::release()函数,先释放已经打开的视频,然后再打开新视频。

如果要读一帧,可以使用 VideoCapture::read()函数。VideoCapture 类重载了>>操作符,实现了读视频帧的功能。下面的例程演示了使用 VideoCapture 类读视频。

#include <QCoreApplication>
#include <iostream>
#include "opencv2/opencv.hpp"
using namespace std;
using namespace cv;
int main(void)
{
//打开第一个摄像头
VideoCapture cap(0);
//打开视频文件
//VideoCapture cap("myvideo.avi");

//检查是否成功打开
if(!cap.isOpened())
{
cerr << "Can not open a camera or file." << endl;
return -1;
}

Mat edges;
//创建窗口
namedWindow("edges",1);
for(;;)
{
Mat frame;
//从 cap 中读一帧,存到 frame
cap >> frame;
//如果未读到图像
if(frame.empty())
break;
//将读到的图像转为灰度图
cvtColor(frame, edges, CV_BGR2GRAY);
//进行边缘提取操作
Canny(edges, edges, 0, 30, 3);
//显示结果
imshow("edges", edges);
//等待 30 秒,如果按键则推出循环
if(waitKey(30) >= 0)
break;
}
//退出时会自动释放 cap 中占用资源
return 0;
}


下图是运行结果,打开视频和摄像头是一样的(下图是我本人的图像):



3 写视频

使用 OpenCV 创建视频也非常简单,与读视频不同的是,你需要在创建视频时设置一系列参数,包括:文件名,编解码器,帧率,宽度和高度等。编解码器使用四个字符表示,可以是CV_FOURCC(‘M’,’J’,’P’,’G’)、

CV_FOURCC(‘X’,’V’,’I’,’D’)及CV_FOURCC(‘D’,’I’,’V’,’X’)等。如果使用某种编解码器无法创建视频文件,请尝试其他的编解码器。

将图像写入视频可以使用 VideoWriter::write()函数,VideoWriter 类中也重载了<<操作符,使用起来非常方便。另外需要注意:待写入的图像尺寸必须与创建视频时指定的尺寸一致。

下面例程演示了如何写视频文件。本例程将生成一个视频文件,视频的第 0帧上是一个红色的“0”,第 1 帧上是个红色的“1”,以此类推,共 100 帧。生成视频的播放效果如下图所示。

#include <stdio.h>
#include <iostream>
#include "opencv2/opencv.hpp"
using namespace std;
using namespace cv;

// 25帧/s
#define FRM_PER_SEC         (25)

int main(void)
{
//定义视频的宽度和高度
Size s(320, 240);
//创建 writer,并指定 FOURCC 及 FPS 等参数
VideoWriter  writer = VideoWriter("myvideo.avi",CV_FOURCC('M','J','P','G'), FRM_PER_SEC, s);
//检查是否成功创建
if(!writer.isOpened())
{
cerr << "Can not create video file.\n" << endl;
return -1;
}

//视频帧
Mat frame(s, CV_8UC3);
for(int i = 0; i < 100; i++)
{
//将图像置为黑色
frame = Scalar::all(0);
//将整数 i 转为 i 字符串类型
char text[128];
snprintf(text, sizeof(text), "%d", i);

//将数字绘到画面上
putText(frame, text, Point(s.width/3, s.height/3), FONT_HERSHEY_SCRIPT_SIMPLEX, 3,Scalar(0,0,255), 3, 8);

//将图像写入视频
writer << frame;
}
//退出程序时会自动关闭视频文件
return 0;
}


下面是视频截图:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息