OpenCV 4.13.0
Open Source Computer Vision
読み込み中...
検索中...
見つかりません
🤖 AIによる機械翻訳(非公式) — これは OpenCV 4.13.0 公式リファレンス(英語)を AI (Claude) で自動翻訳したものです。訳に誤りを含む場合があります。正確な情報は 公式英語版(原文) を参照してください。
OpenCVによる動画の作成

前のチュートリアル: OpenCVによる動画入力と類似度の計測
次のチュートリアル: Kinectおよびその他のOpenNI互換深度センサーの利用

原著者Bernát Gábor
互換性OpenCV >= 3.0

目標

動画フィードを扱う際には、画像処理の結果を新しい動画ファイルとして保存したくなることがある。単純な動画出力には、このために設計されたOpenCV組み込みの cv::VideoWriter クラスを使用できる。

  • OpenCVで動画ファイルを作成する方法
  • OpenCVで作成できる動画ファイルの種類
  • 動画から指定した色チャンネルを抽出する方法

簡単なデモとして、入力動画ファイルのBGRカラーチャンネルのうちの1つを抽出し、新しい動画にする。アプリケーションの動作はコマンドライン引数から制御できる:

  • 第1引数は処理対象の動画ファイルを指す
  • 第2引数は R G B のいずれかの文字である。これにより、どのチャンネルを抽出するかを指定する。
  • 最後の引数は文字 Y(Yes)または N(No)である。これがnoの場合、出力に使用するコーデックは入力動画ファイルと同じものになる。そうでなければ、ウィンドウがポップアップし、使用するコーデックを自分で選択できる。

たとえば、有効なコマンドラインは次のようになる:

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

ソースコード

ソースコードとこれらの動画ファイルは、OpenCVソースライブラリの samples/cpp/tutorial_code/videoio/video-write/ フォルダ内でも見つけられるほか、ここからダウンロードすることもできる。

#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;
}
n-dimensional dense array class
Definition mat.hpp:840
Template class for specifying the size of an image or rectangle.
Definition types.hpp:335
_Tp height
the height
Definition types.hpp:363
_Tp width
the width
Definition types.hpp:362
Class for video capturing from video files, image sequences or cameras.
Definition videoio.hpp:786
Video writer class.
Definition videoio.hpp:1085
virtual bool open(const String &filename, int fourcc, double fps, Size frameSize, bool isColor=true)
Initializes or reinitializes video writer.
virtual bool isOpened() const
Returns true if video writer has been successfully initialized.
void split(const Mat &src, Mat *mvbegin)
Divides a multi-channel array into several single-channel arrays.
void merge(const Mat *mv, size_t count, OutputArray dst)
Creates one multi-channel array out of several single-channel ones.
int main(int argc, char *argv[])
Definition highgui_qt.cpp:3
Definition core.hpp:107
STL namespace.

動画の構造

まず、動画ファイルがどのようなものかを把握しておくべきである。すべての動画ファイルは、それ自体がコンテナである。コンテナの種類はファイルの拡張子で表される(たとえば avimovmkv)。これは、動画フィード、音声フィード、その他のトラック(たとえば字幕など)といった複数の要素を含む。これらのフィードがどのように格納されるかは、それぞれに使用されるコーデックによって決まる。音声トラックの場合、一般的に使用されるコーデックは mp3aac である。動画ファイルの場合、その一覧はやや長く、XVIDDIVXH264LAGSLagarith Lossless Codec)などの名前を含む。システム上で使用できるコーデックの全一覧は、何がインストールされているかに依存する。

見ての通り、動画に関しては事態が非常に複雑になり得る。しかし、OpenCVは主にコンピュータビジョンのライブラリであり、動画ストリーム・コーデック・書き込みのためのライブラリではない。そのため、開発者はこの部分を可能な限り単純に保とうとしてきた。このため、OpenCVは動画コンテナとして avi 拡張子(その最初のバージョン)のみをサポートする。これによる直接的な制限として、2 GBを超える動画ファイルは保存できない。さらに、コンテナ内に作成・拡張できる動画トラックは1つだけである。音声やその他のトラック編集のサポートはここにはない。それでも、システム上にある任意の動画コーデックは動作する可能性がある。これらの制限に遭遇した場合は、FFmpeg のようなより専門的な動画書き込みライブラリや、HuffYUVCorePNGLCL のようなコーデックを検討する必要がある。代替として、OpenCVで動画トラックを作成し、VirtualDubAviSynth のような動画操作プログラムを使ってそれに音声トラックを追加したり、他の形式に変換したりするとよい。

VideoWriterクラス

ここで述べる内容は、すでに OpenCVによる動画入力と類似度の計測 チュートリアルを読み、動画ファイルの読み込み方を知っていることを前提としている。動画ファイルを作成するには、cv::VideoWriter クラスのインスタンスを作成するだけでよい。そのプロパティはコンストラクタの引数で指定するか、後から cv::VideoWriter::open 関数で指定できる。いずれの方法でも引数は同じである: 1. コンテナの種類を拡張子に含む出力の名前。現時点では avi のみがサポートされている。これは入力ファイルから構築し、使用するチャンネルの名前を加え、最後にコンテナの拡張子で仕上げる。

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
  1. 動画トラックに使用するコーデック。すべての動画コーデックは、最大4文字の固有の短い名前を持つ。したがって XVIDDIVXH264 という名前になる。これはfour character code(4文字コード)と呼ばれる。これは入力動画から get 関数を使って取得することもできる。get 関数は汎用的な関数であるため、常にdouble値を返す。double値は64ビットで格納される。4文字は4バイト、つまり32ビットを意味する。これらの4文字は double の下位32ビットにコード化されている。上位32ビットを捨てる簡単な方法は、この値を int に変換することである:
    VideoCapture inputVideo(source); // Open input
    int ex = static_cast<int>(inputVideo.get(CAP_PROP_FOURCC)); // Get Codec Type- Int form
    OpenCVは内部でこの整数型を扱い、2番目の引数としてこれを期待する。整数形式から文字列に変換するには、2つの方法を使える。ビット演算子による方法とunionによる方法である。最初の方法、つまりintから文字を取り出す方法は次のようになる(「and」演算、いくつかのシフト、そして文字列を閉じるための末尾への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; // From Int to char via union
    uEx.c[4]='\0';
    この利点は、代入後に変換が自動的に行われることである。一方、ビット演算子の場合は、コーデックの種類を変更するたびに演算を行う必要がある。コーデックの4文字コードを事前に知っている場合は、CV_FOURCC マクロを使って整数を構築できる:
    CV_FOURCC('P','I','M,'1') // this is an MPEG1 codec from the characters to integer
    int CV_FOURCC(char c1, char c2, char c3, char c4)
    Constructs the 'fourcc' code, used in video codecs and many other places. Simply call it with 4 chars...
    Definition cvdef.h:936
    この引数にマイナス1を渡すと、実行時にウィンドウがポップアップし、システムにインストールされているすべてのコーデックが表示され、使用するものを選択するよう求められる:
  1. 出力動画のフレームレート(fps)。ここでも、get 関数を使って入力動画のフレームレートをそのまま保持している。
  2. 出力動画のフレームサイズ。ここでも、get 関数を使って入力動画のフレームサイズをそのまま保持している。
  3. 最後の引数は省略可能である。デフォルトはtrueで、出力がカラーになることを意味する(したがって書き込みには3チャンネル画像を渡す)。グレースケール動画を作成するには、ここにfalseの引数を渡す。

サンプルでの使い方は次の通りである:

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() 関数を使って、open操作が成功したかどうかを調べる。動画ファイルは VideoWriter オブジェクトが破棄されると自動的に閉じられる。オブジェクトのオープンに成功したら、クラスの cv::VideoWriter::write 関数を使って、動画のフレームを順番に送ることができる。代わりに、オーバーロードされた演算子 << を使うこともできる:

outputVideo.write(res); //or
outputVideo << res;
virtual void write(InputArray image)
Writes the next video frame.

BGR画像から色チャンネルを抽出するとは、他のチャンネルのBGR値をゼロに設定することを意味する。これは画像走査操作で行うか、splitとmerge操作を使って行える。まずチャンネルを別々の画像に分割し、他のチャンネルを同じサイズ・型のゼロ画像に設定し、最後にそれらを再びマージする:

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

これらをすべてまとめると、上のソースコードが得られる。その実行時の結果は、おおよそ次のような内容を示す:

この実行時の様子は こちらのYouTube で確認できる。