
みなさんこんにちは! ガノー(Ganohr)です! (≧▽≦)
前回公開した『MovieToImage』(解説・無料ダウンロード)、導入したでしょうか?
今回は、開発したアプリケーションの中核となる処理である、
C#で動画をループしながら複数画像をキャプチャする方法を、解説します!



更新履歴
2019/07/04 公開
OpenCVやOpenCvSharpとはなにか?

OpenCVとはなにか?
‘OpenCV’は、画像処理用のマルチプラットフォームなフレームワークです。
※ より詳細な解説は以下からお読みください。

OpenCvSharpとはなにか?
‘OpenCvSharp’は、日本人開発者であるSchima氏(ブログ)による、OpenCVのC#用ラッパーフレームワークです。
OpenCVに習ったコーディングとともに、C#らしいコーディングが可能になったフレームワークです。
※ より詳細な解説は以下からお読みください。

OpenCvSharpで動画から画像をキャプチャーする方法

OpenCVが画像認識技術のためのフレームワークであることは前述の通りです。
OpenCVはカメラからの画像取り込みや、動画ファイルからの画像取り込みなども可能になっています。
今回はその特徴である「動画ファイルからの画像取り込みを行う機能」を利用して、動画ファイルからのキャプチャを行います。
OpenCVで動画ファイルから画像を取り込むための手順は、非常に簡単です。
そのラッパーフレームワークであるOpenCvSharpも、同様に簡単です。
具体的には以下のステップで行います。
Mat
型で、画像を取り込むための変数を定義するVideoCapture
クラスのインスタンスを生成するVideoCapture
のインスタンスを用いて、動画を読み込むVideoCapture
のインスタンスに画像を取り込む位置やオプションを指定するVideoCapture
のインスタンスで実際に画像をキャプチャする- 定義した
Mat
型のインスタンスと、VideoCapture
インスタンスを開放する
※ より詳細な解説は以下からお読みください。

OpenCvSharpで動画をループして任意の位置の画像をキャプチャする

では、実際に動画をループしながらキャプチャーしてみましょう。
ここでは簡単にするため「動画の先頭1000フレーム目から、最後の1000フレーム手前までを、動画の10秒分のフレーム毎にキャプチャーする」ことにします。
サンプルコード
実際の実行例

using定義
using System;
using OpenCvSharp;
メイン処理
static void Main(string[] args)
{
using (var img = new Mat())
using (var capture = new VideoCapture(@"d:\test.mp4"))
{
for (int pos = 1000; pos < capture.FrameCount - 1000; pos += (int)capture.Fps * 10)
{
capture.PosFrames = pos;
capture.Read(img);
img.SaveImage(string.Format(@"d:\temp\img\img_{0}.png", pos));
}
}
}
解説
Mat
型を定義し、画像をキャプチャし保存する際に使用しますVideoCapture
を生成し、動画のキャプチャを行う際に使用しますMat
型もVideoCapture
型も、usingブロック(using () {~}
)を用いることで、自動解放されます- ファイルパスの文字列指定には「半角アットマーク記号」(@)を用いることで、エスケープシーケンスを無効化しています
- ループ処理は、フレーム番号を直接指定するため、開始フレームを1000フレーム、終了フレームを全体フレーム数を表す
capture.FrameCount
の値から1000を減算した範囲を指定しています。 - ループ間隔は
capture.Fps
により取得しています。 capture.Fps
はdouble
型の値を返すため、int
型へキャストしています。- ファイルへの保存は
img.SaveImage(~);
を用いています。 - ファイル名の生成には、
string.Format(書式文字列, 変数)
を用いています。 - 「拡張子」として「.png」を指定することで、‘PNG画像’として保存しています。
- これを「.bmp」等に変えれば、任意の画像形式で保存できます。
留意点
OpenCvSharpでの動画再生位置の指定は、大まかに分けて以下の2つから選択できます。
- フレーム番号による指定
- ミリ秒による指定
動画の全体時間を示すcapture.FrameCount
ですが、実はバグがあります。
このバグに関しては以下の記事をお読みください。

ミリ秒による指定を行う場合、このバグについて理解していないとなりません。
ミリ秒による指定の仕方は、次節にサンプルを提示します。
ミリ秒を指定して動画をループしながらキャプチャーする方法
フレーム番号を用いず、時間を指定して動画をループしながらキャプチャーしてみましょう。
方法は先の例と同じですので、using
による参照の定義は省略します。
また、簡単にするため「動画の先頭10秒目のフレームから、最後から10秒手前のフレームまでを、30秒おきにキャプチャーする」こととします。
サンプルコード
実際の実行例

コード
const int MSEC_FOR_TENSEC = 10000;
static void Main(string[] args)
{
using (var img = new Mat())
using (var capture = new VideoCapture(@"d:\test.mp4"))
{
capture.PosFrames = capture.FrameCount - 2;
var lastms = capture.PosMsec - MSEC_FOR_TENSEC;
for (int pos_ms = 0; pos_ms < lastms; pos_ms += MSEC_FOR_TENSEC * 3)
{
capture.PosMsec = pos_ms;
capture.Read(img);
img.SaveImage(
string.Format(
@"d:\temp\img\img_{0}.png"
, new TimeSpan(0, 0, 0, 0, pos_ms).ToString(@"hh\hmm\mss\sfff\m\s")));
}
}
}
解説
capture.FrameCount - 2
が、キャプチャ可能な動画の最終フレームを示します。capture.PosFrames
に最終フレーム番号を指定し、その後にcapture.PosMsec
の値を取得することで、最終フレームの時間を取得しています。pos_ms
に、ミリ秒単位によるキャプチャ位置が記憶されますTimeSpan
は、時間差を扱うための標準的なクラスです。5つの引数を受け取るコンストラクタの5番目の引数に、ミリ秒を指定できます。TimeSpan.ToString(~)
は、ミリ秒指定を独自の書式で文字列へ変換するための定義です。ここでは、「時間」・「分」・「秒」・「ミリ秒」をそれぞれ「h・m・s・ms」という単位を付けて文字列化しています。


最後に
今回の内容は、以下のツールを開発する上で基幹となる技術を解説しています。
本記事が、私と同様に使いやすいツールを開発してくれる方のもとへ届けば幸いです。

コメントを書く