視頻摘要又稱視頻濃縮,是對視頻內容的一個簡單歸納,先經過運動目標分析,提取運動目標,而後對各個目標的運動軌跡進行分析,將不一樣的目標拼接到一個共同的背景場景中,並將它們以某種方式進行組合。視頻摘要在視頻分析和基於內容的視頻檢索中扮演着重要角色。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你還安裝對應的運行庫。