OpenCV入門教程(8)-視訊檔案的讀取和儲存
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;
}
下面是視訊截圖: