使用OpenCV創(chuàng)建視頻

2018-10-03 09:40 更新

目標(biāo)

每當(dāng)您使用視頻Feed時(shí),您最終都可能想要以新的視頻文件的形式保存您的圖像處理結(jié)果。對于簡單的視頻輸出,您可以使用為此設(shè)計(jì)的OpenCV內(nèi)置cv :: VideoWriter類。

  • 如何使用OpenCV創(chuàng)建視頻文件
  • 您可以使用OpenCV創(chuàng)建什么類型的視頻文件
  • 如何從視頻中提取給定的顏色通道

作為一個(gè)簡單的演示,我只需將一個(gè)輸入視頻文件的BGR顏色通道之一提取到一個(gè)新的視頻中。您可以從其控制臺行參數(shù)控制應(yīng)用程序的流量:

  • 第一個(gè)參數(shù)指向要處理的視頻文件
  • 第二個(gè)參數(shù)可以是以下字符之一:RG B.這將指定要提取哪些通道。
  • 最后一個(gè)參數(shù)是字符Y(是)或N(否)。如果不是,輸入視頻文件的編解碼器將與輸出相同。否則,將彈出一個(gè)窗口,并允許您選擇要使用的編解碼器。

例如,有效的命令行將如下所示:

video-write.exe video/Megamind.avi R Y

源代碼

您也可以在samples/cpp/tutorial_code/videoio/video-write/OpenCV源庫的文件夾中找到源代碼和這些視頻文件,或從這里下載。

#include <iostream> // for standard I/O
#include <string>   // for strings
#include <opencv2/core.hpp>     // Basic OpenCV structures (cv::Mat)
#include <opencv2/videoio.hpp>  // Video write
using namespace std;
using namespace cv;
static void help()
{
    cout
        << "------------------------------------------------------------------------------" << endl
        << "This program shows how to write video files."                                   << endl
        << "You can extract the R or G or B color channel of the input video."              << endl
        << "Usage:"                                                                         << endl
        << "./video-write <input_video_name> [ R | G | B] [Y | N]"                          << endl
        << "------------------------------------------------------------------------------" << endl
        << endl;
}
int main(int argc, char *argv[])
{
    help();
    if (argc != 4)
    {
        cout << "Not enough parameters" << endl;
        return -1;
    }
    const string source      = argv[1];           // the source file name
    const bool askOutputType = argv[3][0] =='Y';  // If false it will use the inputs codec type
    VideoCapture inputVideo(source);              // Open input
    if (!inputVideo.isOpened())
    {
        cout  << "Could not open the input video: " << source << endl;
        return -1;
    }
    string::size_type pAt = source.find_last_of('.');                  // Find extension point
    const string NAME = source.substr(0, pAt) + argv[2][0] + ".avi";   // Form the new name with container
    int ex = static_cast<int>(inputVideo.get(CAP_PROP_FOURCC));     // Get Codec Type- Int form
    // Transform from int to char via Bitwise operators
    char EXT[] = {(char)(ex & 0XFF) , (char)((ex & 0XFF00) >> 8),(char)((ex & 0XFF0000) >> 16),(char)((ex & 0XFF000000) >> 24), 0};
    Size S = Size((int) inputVideo.get(CAP_PROP_FRAME_WIDTH),    // Acquire input size
                  (int) inputVideo.get(CAP_PROP_FRAME_HEIGHT));
    VideoWriter outputVideo;                                        // Open the output
    if (askOutputType)
        outputVideo.open(NAME, ex=-1, inputVideo.get(CAP_PROP_FPS), S, true);
    else
        outputVideo.open(NAME, ex, inputVideo.get(CAP_PROP_FPS), S, true);
    if (!outputVideo.isOpened())
    {
        cout  << "Could not open the output video for write: " << source << endl;
        return -1;
    }
    cout << "Input frame resolution: Width=" << S.width << "  Height=" << S.height
         << " of nr#: " << inputVideo.get(CAP_PROP_FRAME_COUNT) << endl;
    cout << "Input codec type: " << EXT << endl;
    int channel = 2; // Select the channel to save
    switch(argv[2][0])
    {
    case 'R' : channel = 2; break;
    case 'G' : channel = 1; break;
    case 'B' : channel = 0; break;
    }
    Mat src, res;
    vector<Mat> spl;
    for(;;) //Show the image captured in the window and repeat
    {
        inputVideo >> src;              // read
        if (src.empty()) break;         // check if at end
        split(src, spl);                // process - extract only the correct channel
        for (int i =0; i < 3; ++i)
            if (i != channel)
                spl[i] = Mat::zeros(S, spl[0].type());
       merge(spl, res);
       //outputVideo.write(res); //save or
       outputVideo << res;
    }
    cout << "Finished writing" << endl;
    return 0;
}

視頻的結(jié)構(gòu)

首先,您應(yīng)該了解視頻文件的外觀。每個(gè)視頻文件本身就是容器。容器的類型在文件擴(kuò)展名(例如avi,mov或mkv)中表示。它包含多個(gè)元素,如:視頻饋送,音頻饋送或其他音軌(例如字幕)。這些饋送是如何存儲的由它們中的每一個(gè)使用的編解碼器決定。在音頻通道使用的編解碼器是mp3或aac的情況下。對于視頻文件,列表以某種方式更長,包括諸如XVID,DIVX,H264或LAGS(Lagarith Lossless Codec)。您可能在系統(tǒng)上使用的編解碼器的完整列表取決于您已安裝的編解碼器。

使用OpenCV創(chuàng)建視頻

正如您可以看到的事情可能會讓視頻變得非常復(fù)雜。然而,OpenCV主要是一個(gè)計(jì)算機(jī)視覺庫,而不是視頻流,編解碼器和寫入。因此,開發(fā)人員試圖讓這部分盡可能的簡單。由于OpenCV視頻容器僅支持avi擴(kuò)展,它的第一個(gè)版本。這樣做的一個(gè)直接的限制是您無法保存大于2 GB的視頻文件。此外,您只能創(chuàng)建和擴(kuò)展容器內(nèi)的單個(gè)視頻軌道。沒有音頻或其他曲目編輯支持。不過,系統(tǒng)上存在的任何視頻編解碼器都可能正常工作 如果您遇到這些限制,您將需要查看更專門的視頻編寫庫,如FFMpeg或編解碼器,如HuffYUV,CorePNG和LCL。作為替代方案,使用OpenCV創(chuàng)建視頻軌道,并使用聲軌擴(kuò)展,或通過使用視頻操作程序(如VirtualDub或AviSynth)將其轉(zhuǎn)換為其他格式。

VideoWriter類

這里寫的內(nèi)容建立在你已經(jīng)通過OpenCV讀取視頻輸入和相似度測量教程的假設(shè),你知道如何讀取視頻文件。要?jiǎng)?chuàng)建視頻文件,您只需要?jiǎng)?chuàng)建一個(gè)cv :: VideoWriter類的實(shí)例。您可以通過構(gòu)造函數(shù)中的參數(shù)或稍后通過cv :: VideoWriter :: open函數(shù)指定其屬性。無論哪種方式,參數(shù)是相同的:1.在其擴(kuò)展名中包含容器類型的輸出的名稱。目前只支持avi。我們從輸入文件構(gòu)造這個(gè),添加到要使用的通道的名稱,并使用容器擴(kuò)展名完成它。

const string source      = argv[1];            // the source file name
string::size_type pAt = source.find_last_of('.');   // Find extension point
const string NAME = source.substr(0, pAt) + argv[2][0] + ".avi";   // Form the new name with container

  • 用于視頻軌道的編解碼器?,F(xiàn)在所有的視頻編解碼器都有一個(gè)唯一的短名稱,最多四個(gè)字符。因此,XVID,DIVXH264的名稱。這被稱為四字符代碼。您也可以通過使用其獲取功能從輸入視頻中查詢。因?yàn)?em>get函數(shù)是一個(gè)通用函數(shù),它總是返回double值。雙精度值存儲在64位上。四個(gè)字符是四個(gè)字節(jié),意味著32位。這四個(gè)字符被編碼在雙倍的低32位中。丟棄高32位的一個(gè)簡單方法是將此值轉(zhuǎn)換為int

VideoCapture inputVideo(source);                                // Open input
int ex = static_cast<int>(inputVideo.get(CAP_PROP_FOURCC));     // Get Codec Type- Int form

OpenCV內(nèi)部使用這種整數(shù)類型,并期望它作為其第二個(gè)參數(shù)?,F(xiàn)在要從整數(shù)形式轉(zhuǎn)換為字符串,我們可以使用兩種方法:一個(gè)按位運(yùn)算符和一個(gè)聯(lián)合方法。第一個(gè)從int中提取字符看起來像(“和”操作,一些移動,并在結(jié)尾添加一個(gè)0以關(guān)閉字符串):

char EXT [] = {ex&0XFF,(ex&0XFF00)>> 8,(ex&0XFF0000)>> 16,(ex&0XFF000000)>> 24,0};

你可以和工會做同樣的事情:

union { int v; char c[5];} uEx ;
uEx.v = ex;                              // From Int to char via union
uEx.c[4]='\0';

這樣做的優(yōu)點(diǎn)是轉(zhuǎn)換在分配后自動完成,而對于按位運(yùn)算符,您需要在更改編解碼器類型時(shí)執(zhí)行操作。如果事先知道編解碼器有四個(gè)字符代碼,可以使用CV_FOURCC宏構(gòu)建整數(shù):

CV_FOURCC('P','I','M,'1') // this is an MPEG1 codec from the characters to integer

如果您傳遞此參數(shù)減去一個(gè)而不是窗口將在運(yùn)行時(shí)彈出,其中包含系統(tǒng)上安裝的所有編解碼器,并要求您選擇要使用的編解碼器:

使用OpenCV創(chuàng)建視頻

  • 輸出視頻的每秒幀數(shù)。再次,這里我通過使用get函數(shù)來保持每秒的輸入視頻幀。
  • 輸出視頻幀的大小。這里也使用get函數(shù)保留每秒的輸入視頻幀大小。
  • 最后一個(gè)參數(shù)是可選的。默認(rèn)情況下是真的,并且輸出將是一個(gè)多彩的輸出(所以寫你會發(fā)送三通道圖像)。要?jiǎng)?chuàng)建一個(gè)灰度級視頻,在這里傳遞一個(gè)虛假參數(shù)。

這是我在示例中如何使用它:
VideoWriter outputVideo;
Size S = Size((int) inputVideo.get(CAP_PROP_FRAME_WIDTH),    //Acquire input size
              (int) inputVideo.get(CAP_PROP_FRAME_HEIGHT));
outputVideo.open(NAME , ex, inputVideo.get(CAP_PROP_FPS),S, true);
之后,您可以使用cv :: VideoWriter :: isOpened()函數(shù)來查看打開的操作是否成功。VideoWriter對象銷毀時(shí),視頻文件會自動關(guān)閉。在成功打開對象后,您可以使用該類cv :: VideoWriter :: write函數(shù)按順序發(fā)送視頻的幀?;蛘?,您可以使用其重載運(yùn)算符<<:
outputVideo.write(res);  //or
outputVideo << res;
從BGR圖像提取顏色通道意味著將其他通道的BGR值設(shè)置為零。您可以使用圖像掃描操作或使用拆分和合并操作來執(zhí)行此操作。您首先將通道分成不同的圖像,將其他通道設(shè)置為相同尺寸和類型的零圖像,最后將其合并:
split(src, spl);                 // process - extract only the correct channel
for( int i =0; i < 3; ++i)
   if (i != channel)
      spl[i] = Mat::zeros(S, spl[0].type());
merge(spl, res);

把所有這一切放在一起,你會得到更高的源代碼,其運(yùn)行時(shí)結(jié)果將顯示如下圖像:

使用OpenCV創(chuàng)建視頻


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號