一、前言
視訊摘要又稱視訊濃縮,是對視訊內容的一個簡單概括,先通過運動目標分析,提取運動目標,然後對各個目標的運動軌跡進行分析,將不同的目標拼接到一個共同的背景場景中,並將它們以某種方式進行組合。視訊摘要在視訊分析和基於內容的視訊檢索中扮演著重要角色。
視訊摘要主要運用在對長時間的監控視訊的壓縮上,它可以將不同時刻場景內目標的運動顯示在同一時刻,這樣大量減少了整個場景事件的時間跨度。一般的視訊摘要的步驟可以總結為:
視訊讀取→背景建模 → 前景提取→ 目標軌跡跟蹤→ 目標的時序與空間規劃 → 生成濃縮視訊
但是本文並不討論上面的這些主題,這裡我只想通過一個簡單的去除視訊裡非運動幀來實現一個簡單的視訊壓縮的功能。視訊摘要不是本文的主題,文章想通過做一個簡單的視訊摘要程式對OpenCV下面幾個功能進行介紹:
1)OpenCV與XML資料通訊
2)視訊的讀取與寫入
3)如何在沒有OpenCV的環境中執行編譯好的程式
二、與XML的互動
很多程式都需要有一個配置檔案,可以手動的去調整一些執行中的引數,xml檔案格式就是我們常用到的一種配置檔案格式。opencv中提供了一個處理xml的類用來與xml檔案進行簡單的資料儲存與讀取通訊。但這個類的功能有限,如果需要更多的功能可以利用第三方的庫,比如libxml等。
我們所設計的視訊摘要程式,跟常規的視訊摘要不同,這裡只是通過刪除一些無運動目標的幀來達到視訊壓縮的目的,所以我們的演算法可以設計如下:
1 2 3 4 5 |
1,定義一個目標運動的興趣區域,作為檢測區域。 2,遍歷指定目錄下的所有視訊檔案,並逐一的進行視訊處理。 3,針對視訊的每一幀,在檢測區域內運用幀差法檢測前景移動。 4,如果檢測區域內前景的面積超過區域面積的10%,則說明有運動物體,則此幀進行保留,寫入壓縮視訊。否則,該幀直接捨棄。 5,所有視訊處理結束,則程式終止。 |
麼,我們需要一個配置檔案,這個檔案裡需要儲存下面幾個內容:
1,檢測區域的引數
2,視訊檔案的目錄
3,視訊檔案的字尾格式
4,生存視訊的儲存目錄
1 2 3 4 5 6 7 |
<?xml version="1.0"?> <opencv_storage> <roi> 3 460 1250 480</roi> <videoReadPath>D:\ExtractKeyImages\video\</videoReadPath> <videoSuffix>*.mp4</videoSuffix> <videoSavePath>../result.avi</videoSavePath> </opencv_storage> |
注意所有的節點都儲存在opencv_storage節點下。
在OpenCV中定義了一個叫FileStorage的類,提供了一些簡單的開啟與讀取xml檔案內容的操作。
我們先來看xml檔案資料的讀取:
1,用FileStorage的建構函式可以開啟一個xml或yml檔案,也可以用FileStorage::open()來開啟一個資料檔案。
1 2 |
FileStorage::FileStorage(); // 預設建構函式 FileStorage::FileStorage(const string& source, int flags, const string& encoding = string()); |
上面第二個建構函式中有三個引數。
第一個引數source指定讀取檔案的路徑。
第二個引數flag指定操作的模式,可以設定為READ說明以只讀的方式開啟一個檔案,或者設定為WRITE,這種情況下,如果檔案不存在,則建立一個檔案,如果檔案已經存在,則會清空當前檔案裡的內容。還可以設定為APPEND用來開啟一個存在的檔案,並且可以在原來基礎上寫入。
第三個引數用來指定檔案的編碼格式,一般都為UTF-8。
而open成員函式的介面與第二個建構函式介面一致。
1 |
bool FileStorage::open(const string& filename, int flags, const string& encoding=string()) |
2,讀取檔案內的資料,FileStorage過載的操作符[],用來獲得指定的節點內容。
1 2 |
FileNode FileStorage::operator[](const string& nodename) const FileNode FileNode::operator[](const string& nodename) const |
上面兩個操作符都返回FileNode型別,它是一個子節點型別。
比如:我們想讀取<book>結點下的<name>結點,則可以:
1 2 3 |
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:
1 2 |
string book_name=”theOpenCV”; fs<<”book”<<book_name; |
最後給出我們程式中讀取配置引數的程式碼,我們需要4項配置項,上面已經介紹過了:
1 2 3 4 5 6 7 8 9 10 11 |
FileStorage fs("../config.xml", FileStorage::WRITE); string videoPath; string videoSuffix; Rect roiRect; string imgSavePath; fs["videoReadPath"] >> videoPath; fs["videoSuffix"] >> videoSuffix; fs["imgSavePath"] >> imgSavePath; fs["roi"] >> roiRect; |
二、檢測區域的運動檢測
這裡我們要進行簡單的視訊壓縮就是想把完全靜止不動的視訊幀從原視訊裡刪除,我們的興趣目標一般是在移動的視訊裡。所以我們可以用幀差法來檢測移動物體,它的原理是利用視訊中物體的移動將引起相鄰視訊幀內容的不同,從而顯示出移動的前景。
兩幀之間的幀差影象可以這樣定義:
其中imgCur代表當前幀的影象,imgPre代表前一幀影象
在得到幀差影象後,我們並不能得到很明顯的判斷條件,所以我們需要對幀差影象進行二值化,我們設定一個閾值T
然後我們只需遍歷影象求出影象中所有白點的個數,即是運動前景的面積,計算一下面積比例即可以確定當前幀是否有物體移動。
當然我們得到的前景目標並不移動的物體的輪廓,而是與前一幀相比目標移動的部分。
下面為這一部分的OpenCV實現,相關的視訊讀取和寫入的操作可以參考OpenCV成長之路中的相關文章。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
// 查詢檔案目錄下的所有視訊檔案 vector<string> videoPathStr = FindAllFile((videoPath + videoSuffix).c_str(), true); // 先讀取一個視訊檔案,用於獲取相關的引數 VideoCapture capture(videoPathStr[0]); // 視訊大小 Size videoSize(capture.get(CV_CAP_PROP_FRAME_WIDTH), capture.get(CV_CAP_PROP_FRAME_HEIGHT)); // 建立一個視訊寫入物件 VideoWriter writer("../result.avi", CV_FOURCC('M', 'J', 'P', 'G'), 25.0, videoSize); for (auto videoName : videoPathStr) { capture.open(videoName); // 讀入路徑下的視訊 Mat preFrame; bool stop(false); double totleFrameNum = capture.get(CV_CAP_PROP_FRAME_COUNT); // 獲取視訊總幀數 for (int frameNum = 0; frameNum < totleFrameNum; frameNum++) { Mat imgSrc; capture >> imgSrc; // 讀一視訊的一幀 if (!imgSrc.data) break; Mat frame; cvtColor(imgSrc, frame, CV_BGR2GRAY); ++frameNum; if (frameNum == 1) { preFrame = frame; } Mat frameDif; absdiff(frame, preFrame, frameDif); // 幀差法 preFrame = frame; threshold(frameDif, frameDif, 30, 255, THRESH_BINARY); // 二值化 Mat imgRoi = frameDif(roiRect); double matArea = computeMatArea(imgRoi); // 計算區域面積 if (matArea / (imgRoi.rows*imgRoi.cols) > 0.1) // 面積比例大於10% { writer << frameDif;// 寫入視訊 } } } capture.release(); writer.release(); |
三、在沒有OpenCV的環境下執行程式
這裡是指基於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
最後我們需要在連結器->輸入->附加依賴項中加入一些常用到的庫檔案
1 2 3 4 5 6 7 8 9 10 11 |
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你還安裝對應的執行庫。