视频摘要又称视频浓缩,是对视频内容的一个简单归纳,先经过运动目标分析,提取运动目标,而后对各个目标的运动轨迹进行分析,将不一样的目标拼接到一个共同的背景场景中,并将它们以某种方式进行组合。视频摘要在视频分析和基于内容的视频检索中扮演着重要角色。node
视频摘要主要运用在对长时间的监控视频的压缩上,它能够将不一样时刻场景内目标的运动显示在同一时刻,这样大量减小了整个场景事件的时间跨度。通常的视频摘要的步骤能够总结为:算法
视频读取$ \to $背景建模 $\to$ 前景提取$ \to$ 目标轨迹跟踪$ \to$ 目标的时序与空间规划 $\to$ 生成浓缩视频windows
可是本文并不讨论上面的这些主题,这里我只想经过一个简单的去除视频里非运动帧来实现一个简单的视频压缩的功能。视频摘要不是本文的主题,文章想经过作一个简单的视频摘要程序对OpenCV下面几个功能进行介绍:ide
1)OpenCV与XML数据通讯函数
2)视频的读取与写入ui
3)如何在没有OpenCV的环境中运行编译好的程序编码
不少程序都须要有一个配置文件,能够手动的去调整一些运行中的参数,xml文件格式就是咱们经常使用到的一种配置文件格式。opencv中提供了一个处理xml的类用来与xml文件进行简单的数据存储与读取通讯。但这个类的功能有限,若是须要更多的功能能够利用第三方的库,好比libxml等。spa
咱们所设计的视频摘要程序,跟常规的视频摘要不一样,这里只是经过删除一些无运动目标的帧来达到视频压缩的目的,因此咱们的算法能够设计以下:debug
1,定义一个目标运动的兴趣区域,做为检测区域。
2,遍历指定目录下的全部视频文件,并逐一的进行视频处理。
3,针对视频的每一帧,在检测区域内运用帧差法检测前景移动。
4,若是检测区域内前景的面积超过区域面积的10%,则说明有运动物体,则此帧进行保留,写入压缩视频。不然,该帧直接舍弃。
5,全部视频处理结束,则程序终止。
那么,咱们须要一个配置文件,这个文件里须要保存下面几个内容:设计
1,检测区域的参数
2,视频文件的目录
3,视频文件的后缀格式
4,生存视频的保存目录
1 <?xml version="1.0"?> 2 <opencv_storage> 3 <roi> 3 460 1250 480</roi> 4 <videoReadPath>D:\ExtractKeyImages\video\</videoReadPath> 5 <videoSuffix>*.mp4</videoSuffix> 6 <videoSavePath>../result.avi</videoSavePath> 7 </opencv_storage>
注意全部的节点都保存在opencv_storage节点下。
在OpenCV中定义了一个叫FileStorage的类,提供了一些简单的打开与读取xml文件内容的操做。
咱们先来看xml文件数据的读取:
1,用FileStorage的构造函数能够打开一个xml或yml文件,也能够用FileStorage::open()来打开一个数据文件。
FileStorage::FileStorage(); // 默认构造函数 FileStorage::FileStorage(const string& source, int flags, const string& encoding = string());
上面第二个构造函数中有三个参数。
第一个参数source指定读取文件的路径。
第二个参数flag指定操做的模式,能够设置为READ说明以只读的方式打开一个文件,或者设置为WRITE,这种状况下,若是文件不存在,则建立一个文件,若是文件已经存在,则会清空当前文件里的内容。还能够设置为APPEND用来打开一个存在的文件,而且能够在原来基础上写入。
第三个参数用来指定文件的编码格式,通常都为UTF-8。
而open成员函数的接口与第二个构造函数接口一致。
bool FileStorage::open(const string& filename, int flags, const string& encoding=string())
2,读取文件内的数据,FileStorage重载的操做符[],用来得到指定的节点内容。
FileNode FileStorage::operator[](const string& nodename) const FileNode FileNode::operator[](const string& nodename) const
上面两个操做符都返回FileNode类型,它是一个子节点类型。
好比:咱们想读取<book>结点下的<name>结点,则能够:
FileStorage fs("../config.xml", FileStorage::READ); string book_name; fs["book"] ["name"]>> book_name;
若是要取出A节点下的B结点下的C结点则为fs["A"]["B"]["C"]>>content;要记住全部节点都是在根结点opencv_storage下的,可是访问时忽略它。
而若是须要将数据写入,则简单的写入能够直接用<<运算符,好比增长一个节点为book,内容为theOpenCV:
string book_name=”theOpenCV”; fs<<”book”<<book_name;
最后给出咱们程序中读取配置参数的代码,咱们须要4项配置项,上面已经介绍过了:
1 FileStorage fs("../config.xml", FileStorage::WRITE); 2 3 string videoPath; 4 string videoSuffix; 5 Rect roiRect; 6 string imgSavePath; 7 8 fs["videoReadPath"] >> videoPath; 9 fs["videoSuffix"] >> videoSuffix; 10 fs["imgSavePath"] >> imgSavePath; 11 fs["roi"] >> roiRect;
这里咱们要进行简单的视频压缩就是想把彻底静止不动的视频帧从原视频里删除,咱们的兴趣目标通常是在移动的视频里。因此咱们能够用帧差法来检测移动物体,它的原理是利用视频中物体的移动将引发相邻视频帧内容的不一样,从而显示出移动的前景。
两帧之间的帧差图像能够这样定义:
$$imgDif(x,y)=abs(imgCur(x,y)-imgPre(x,y))$$
其中imgCur表明当前帧的图像,imgPre表明前一帧图像。
在获得帧差图像后,咱们并不能获得很明显的判断条件,因此咱们须要对帧差图像进行二值化,咱们设置一个阈值T
$$imgBw(i,j)=
\left\{
\begin{array}{c}
1 ,\ if\ \ imgDif(x,y)\ge T\\
0 ,\ if\ \ imgDif(x,y)\lt T
\end{array}
\right.
$$
而后咱们只需遍历图像求出图像中全部白点的个数,便是运动前景的面积,计算一下面积比例便可以肯定当前帧是否有物体移动。
固然咱们获得的前景目标并不移动的物体的轮廓,而是与前一帧相比目标移动的部分。
下面为这一部分的OpenCV实现,相关的视频读取和写入的操做能够参考OpenCV成长之路中的相关文章。
1 // 查找文件目录下的全部视频文件 2 vector<string> videoPathStr = FindAllFile((videoPath + videoSuffix).c_str(), true); 3 // 先读取一个视频文件,用于获取相关的参数 4 VideoCapture capture(videoPathStr[0]); 5 // 视频大小 6 Size videoSize(capture.get(CV_CAP_PROP_FRAME_WIDTH), capture.get(CV_CAP_PROP_FRAME_HEIGHT)); 7 // 建立一个视频写入对象 8 VideoWriter writer("../result.avi", CV_FOURCC('M', 'J', 'P', 'G'), 25.0, videoSize); 9 10 for (auto videoName : videoPathStr) 11 { 12 capture.open(videoName); // 读入路径下的视频 13 14 Mat preFrame; 15 bool stop(false); 16 17 double totleFrameNum = capture.get(CV_CAP_PROP_FRAME_COUNT); // 获取视频总帧数 18 19 for (int frameNum = 0; frameNum < totleFrameNum; frameNum++) 20 { 21 Mat imgSrc; 22 capture >> imgSrc; // 读一视频的一帧 23 if (!imgSrc.data) 24 break; 25 Mat frame; 26 cvtColor(imgSrc, frame, CV_BGR2GRAY); 27 ++frameNum; 28 if (frameNum == 1) 29 { 30 preFrame = frame; 31 } 32 Mat frameDif; 33 absdiff(frame, preFrame, frameDif); // 帧差法 34 preFrame = frame; 35 36 threshold(frameDif, frameDif, 30, 255, THRESH_BINARY); // 二值化 37 38 Mat imgRoi = frameDif(roiRect); 39 double matArea = computeMatArea(imgRoi); // 计算区域面积 40 41 if (matArea / (imgRoi.rows*imgRoi.cols) > 0.1) // 面积比例大于10% 42 { 43 writer << frameDif;// 写入视频 44 } 45 } 46 } 47 capture.release(); 48 writer.release();
这里是指基于windows系统下VS平台的程序,不少时候咱们编译好的程序须要在别人的电脑上运行,而别人电脑上是没有OpenCV的基本库的,而咱们的编译的opencv程序通常是动态连接一些dll的。
有两种方法:一种是拷贝用到的dll到release目录下,另外一种是把相关的源文件加入工程中一块儿编译。
下面主要介绍第一种方法,由于看起来简单,不少人仍是运行不了。
咱们从openCV的环境配置开始提及:
首先,咱们先找到咱们下载并解压后的OpenCV目录下的这几个目录:
头文件目录:F:\EvProjects\OpenCV\OpenCV248\build\include
运行库目录:F:\EvProjects\OpenCV\OpenCV248\build\x86\vc12\lib
上面的vc12指定你的vs的版本,这里是vs2013
而后咱们在咱们新建的工程中找到属性管理器:
而后分别在DeBug和Release下配置属性表:
咱们能够新建一个名字为opencv248_debug.props的属性表,之后新建的工程,直接拷贝添加便可。
而后右键配置opencv248_debug.props的属性,在VC目录下配置两项:
一项是包含目录,加入:F:\EvProjects\OpenCV\OpenCV248\build\include
第二项是在库目录下加入:F:\EvProjects\OpenCV\OpenCV248\build\x86\vc12\lib
最后咱们须要在连接器->输入->附加依赖项中加入一些经常使用到的库文件
opencv_core248d.lib
opencv_imgproc248d.lib
opencv_highgui248d.lib
opencv_ml248d.lib
opencv_video248d.lib
opencv_features2d248d.lib
opencv_calib3d248d.lib
opencv_objdetect248d.lib
opencv_contrib248d.lib
opencv_legacy248d.lib
opencv_flann248d.lib
注意上面的248说明了个人opencv版本,你的多是246或247。
也能够把F:\EvProjects\OpenCV\OpenCV248\build\x86\vc12\lib目录里的lib文件都加入,注意只加入带d的表示debug库。
这样的话debug下就配置完了,咱们按相同方法,在release下配置一个属性表opencv248_release.props,与debug不一样的是,在连接器的配置里加入的库名,都是不包含d的。
OK,属性表都配置好后,咱们把当前的编译环境改成Release:
在解决方案里,右键项目名->属性->配置管理器
而后把活动解决方案配置改成release便可。
全部的环境配置好后,只须要编译好程序,而后在release下找到exe文件,这个就是咱们的可执行文件,可是它不能单独运行,咱们须要把它须要依赖的一些dll拷贝过来,dll在opencv的F:\EvProjects\OpenCV\OpenCV248\build\x86\vc12\bin目录下,若是你不肯定你的程序里须要哪些库,你就把所有都拷贝过来。或者能够用一个依赖库查看软件查看你的程序所依赖的库,把对应的dll拷贝过来便可。
另外值得注意,若是是VS的较高版本,如VS2012,VS2013你还安装对应的运行库。