上一個教程: 使用OpenCV進行影片輸入和相似性測量
下一個教程: 使用Kinect和其他OpenNI相容的深度感測器
| |
| 原始作者 | Bernát Gábor |
| 相容性 | OpenCV >= 3.0 |
目標
每當你處理影片流時,你最終可能會希望將影像處理結果儲存為新的影片檔案。對於簡單的影片輸出,你可以使用OpenCV內建的 cv::VideoWriter 類,它就是為此而設計的。
- 如何使用OpenCV建立影片檔案
- 你可以用OpenCV建立哪些型別的影片檔案
- 如何從影片中提取給定顏色通道
作為一個簡單的演示,我將把輸入影片檔案的一個BGR顏色通道提取到一個新影片中。你可以透過控制檯命令列引數來控制應用程式的流程
- 第一個引數指向要處理的影片檔案
- 第二個引數可以是字元:R G B 中的一個。這將指定要提取哪個通道。
- 最後一個引數是字元 Y (Yes) 或 N (No)。如果為 N,則輸入影片檔案使用的編解碼器將與輸出相同。否則,將彈出一個視窗,允許你自行選擇要使用的編解碼器。
例如,一個有效的命令列會是這樣
video-write.exe video/Megamind.avi R Y
原始碼
你也可以在OpenCV源庫的 samples/cpp/tutorial_code/videoio/video-write/ 資料夾中找到原始碼和這些影片檔案,或者從這裡下載。
#include <iostream>
#include <string>
static void help()
{
cout
<< "------------------------------------------------------------------------------" << endl
<< "本程式演示瞭如何寫入影片檔案。" << endl
<< "你可以提取輸入影片的 R、G 或 B 顏色通道。" << endl
<< "用法:" << 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 << "引數不足" << endl;
return -1;
}
const string source = argv[1];
const bool askOutputType = argv[3][0] =='Y';
if (!inputVideo.isOpened())
{
cout << "無法開啟輸入影片:" << source << endl;
return -1;
}
string::size_type pAt = source.find_last_of('.');
const string NAME = source.substr(0, pAt) + argv[2][0] + ".avi";
int ex = static_cast<int>(inputVideo.get(CAP_PROP_FOURCC));
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),
(int) inputVideo.get(CAP_PROP_FRAME_HEIGHT));
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);
{
cout << "無法開啟輸出影片進行寫入:" << source << endl;
return -1;
}
<< " 幀數:" << inputVideo.get(CAP_PROP_FRAME_COUNT) << endl;
cout << "輸入編解碼器型別:" << EXT << endl;
int channel = 2;
switch(argv[2][0])
{
case 'R' : channel = 2; break;
case 'G' : channel = 1; break;
case 'B' : channel = 0; break;
}
vector<Mat> spl;
for(;;)
{
inputVideo >> src;
if (src.empty()) break;
for (int i =0; i < 3; ++i)
if (i != channel)
spl[i] = Mat::zeros(S, spl[0].type());
outputVideo << res;
}
cout << "寫入完成" << endl;
return 0;
}
用於指定影像或矩形大小的模板類。
Definition types.hpp:335
_Tp height
高度
Definition types.hpp:363
_Tp width
寬度
Definition types.hpp:362
用於從影片檔案、影像序列或攝像機捕獲影片的類。
Definition videoio.hpp:772
影片寫入類。
定義 videoio.hpp:1071
virtual bool open(const String &filename, int fourcc, double fps, Size frameSize, bool isColor=true)
初始化或重新初始化影片寫入器。
virtual bool isOpened() const
如果影片寫入器已成功初始化,則返回 true。
void split(const Mat &src, Mat *mvbegin)
將多通道陣列分割成幾個單通道陣列。
void merge(const Mat *mv, size_t count, OutputArray dst)
將多個單通道數組合併成一個多通道陣列。
int main(int argc, char *argv[])
定義 highgui_qt.cpp:3
影片的結構
首先,你應該瞭解影片檔案的樣子。每個影片檔案本身都是一個容器。容器的型別透過檔案擴充套件名錶示(例如 *avi*、*mov* 或 *mkv*)。它包含多個元素,如:影片流、音訊流或其他軌道(例如字幕)。這些流的儲存方式由它們各自使用的編解碼器決定。對於音訊軌道,常用的編解碼器是 *mp3* 或 *aac*。對於影片檔案,列表則更長一些,包括 *XVID*、*DIVX*、*H264* 或 *LAGS* (*Lagarith Lossless Codec*) 等名稱。你可以在系統上使用的編解碼器完整列表取決於你安裝了哪些。
如你所見,影片處理會變得非常複雜。然而,OpenCV 主要是一個計算機視覺庫,而不是一個影片流、編解碼和寫入庫。因此,開發者試圖使這部分儘可能簡單。正因如此,OpenCV 對於影片容器只支援 *avi* 副檔名,這是它的第一個版本。一個直接的限制是,你不能儲存大於 2 GB 的影片檔案。此外,你只能在容器內建立和擴充套件單個影片軌道。不支援音訊或其他軌道的編輯。儘管如此,你係統上存在的任何影片編解碼器都可能有效。如果你遇到這些限制,你需要研究更專業的影片寫入庫,如 *FFmpeg*,或編解碼器如 *HuffYUV*、*CorePNG* 和 *LCL*。作為替代方案,你可以使用 OpenCV 建立影片軌道,然後使用 *VirtualDub* 或 *AviSynth* 等影片處理程式新增音軌或將其轉換為其他格式。
VideoWriter 類
此處內容基於你已經閱讀了使用OpenCV進行影片輸入和相似性測量教程並知道如何讀取影片檔案的假設。要建立影片檔案,你只需建立一個 cv::VideoWriter 類的例項。你可以透過建構函式中的引數或稍後透過 cv::VideoWriter::open 函式指定其屬性。無論哪種方式,引數都是相同的:1. 輸出檔案的名稱,其副檔名包含容器型別。目前只支援 *avi*。我們從輸入檔案構建此名稱,新增要使用的通道名稱,並以容器副檔名結尾。
const string source = argv[1];
string::size_type pAt = source.find_last_of('.');
const string NAME = source.substr(0, pAt) + argv[2][0] + ".avi";
- 影片軌道使用的編解碼器。現在所有的影片編解碼器都有一個唯一的短名稱,最多四個字元。因此,有 *XVID*、*DIVX* 或 *H264* 等名稱。這被稱為四字元程式碼。你也可以透過輸入影片的 *get* 函式獲取此資訊。由於 *get* 函式是一個通用函式,它總是返回雙精度浮點值。一個雙精度浮點值儲存在 64 位中。四個字元是四個位元組,意味著 32 位。這四個字元編碼在 *double* 的低 32 位中。一個簡單的方法是,直接將此值轉換為 *int*,以丟棄高 32 位
int ex = static_cast<int>(inputVideo.get(CAP_PROP_FOURCC));
OpenCV 內部使用此整數型別並期望其作為第二個引數。現在,要從整數形式轉換為字串,我們可以使用兩種方法:位運算子和聯合體方法。第一種從 int 中提取字元的方法看起來像(一個“與”操作,一些移位,並在末尾新增一個 0 來關閉字串)char EXT[] = {ex & 0XFF , (ex & 0XFF00) >> 8,(ex & 0XFF0000) >> 16,(ex & 0XFF000000) >> 24, 0};
你也可以使用 *union* 實現相同的功能,如下所示union { int v; char c[5];} uEx ;
uEx.v = ex;
uEx.c[4]='\0';
這樣做的好處是轉換在賦值後自動完成,而對於位運算子,你需要在每次更改編解碼器型別時都執行操作。如果你預先知道編解碼器的四字元程式碼,你可以使用 *CV_FOURCC* 宏來構建整數
int CV_FOURCC(char c1, char c2, char c3, char c4)
構造“fourcc”程式碼,用於影片編解碼器和許多其他地方。只需用4個字元呼叫它即可……
定義 cvdef.h:934
如果你為此引數傳遞 -1,那麼在執行時將彈出一個視窗,其中包含系統上安裝的所有編解碼器,並要求你選擇要使用的一個
- 輸出影片的每秒幀數。同樣,這裡我使用 *get* 函式來保持輸入影片的每秒幀數。
- 輸出影片的幀尺寸。這裡我也使用 *get* 函式來保持輸入影片的每秒幀尺寸。
- 最後一個引數是可選的。預設情況下為 true,表示輸出將是彩色的(因此寫入時會發送三通道影像)。要建立灰度影片,請在此處傳遞 false 引數。
這就是我在示例中如何使用它的方式
Size S =
Size((
int) inputVideo.get(CAP_PROP_FRAME_WIDTH),
(int) inputVideo.get(CAP_PROP_FRAME_HEIGHT));
outputVideo.
open(NAME , ex, inputVideo.get(CAP_PROP_FPS),S,
true);
之後,你可以使用 cv::VideoWriter::isOpened() 函式來判斷開啟操作是否成功。當 *VideoWriter* 物件被銷燬時,影片檔案會自動關閉。成功開啟物件後,你可以使用該類的 cv::VideoWriter::write 函式按順序傳送影片幀。另外,你也可以使用其過載的運算子 <<
outputVideo << res;
virtual void write(InputArray image)
寫入下一個影片幀。
從BGR影像中提取顏色通道意味著將其他通道的BGR值設定為零。你可以透過影像掃描操作或使用分割和合並操作來完成。你首先將通道分割成不同的影像,將其他通道設定為相同大小和型別的零影像,最後將它們合併回來
split(src, spl);
for( int i =0; i < 3; ++i)
if (i != channel)
spl[i] = Mat::zeros(S, spl[0].type());
merge(spl, res);
把所有這些放在一起,你就會得到上面的原始碼,它的執行時結果將大致展示出這個想法
你可以在YouTube上觀看此程式的執行例項。