1. 程式人生 > >OpenCV入門教程(8)-視訊檔案的讀取和儲存

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;
}

下面是視訊截圖:
這裡寫圖片描述