OpenCvSharpをつかう その7 (WindowsFormsで動画の再生)
OpenCVのhighguiではなくWindowsFormsのウィンドウ上で動画を再生したいということは多いようです。OpenCvSharpに限っても、以下のページなどで試みられています。
http://d.hatena.ne.jp/Guernsey/20081207/1228649067
http://blog.livedoor.jp/embed_life/archives/267250.html
C#はGUIアプリケーションの開発の容易さや強力さが大きなウリだと思うので、当然のことです。しかしサンプルには入れてなかった気がするので、ここで様々な方法を試してみます。
前提
「Windows フォーム アプリケーション」でプロジェクトを新規作成し、Form1にPictureBoxを貼り付けます。この時点でのForm1.csのコードはこのようになります。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using OpenCvSharp; namespace OpenCvSharpTest { public partial class Form1 : Form { public Form1() { InitializeComponent(); } } }
シングルスレッドのループ
何も考えず、表示された瞬間からループを回していくコードです。
public partial class Form1 : Form { private CvCapture capture; public Form1() { InitializeComponent(); capture = new CvCapture("test.avi"); } private void Form1_Shown(object sender, EventArgs e) { int interval = (int)(1000 / capture.Fps); IplImage image; while ((image = capture.QueryFrame()) != null) { pictureBox1.Image = image.ToBitmap(); System.Threading.Thread.Sleep(interval); Application.DoEvents(); } } }
Application.DoEventsがミソです。これを入れないと固まります。
QueryFrameは動画の最後まで行くとnullを返すので、これを利用してループを停止させています。フレーム数をカウントしても良いと思います。
欠点としては、動画の再生以外何もできなくなることでしょうか。
Timerを使う
System.Windows.Forms.Timerクラスを使い、定期的にPictureBoxの画像を入れ替えます。
public partial class Form2 : Form { private CvCapture capture; private Timer timer; public Form2() { InitializeComponent(); capture = new CvCapture("test.avi"); timer = new Timer(); timer.Interval = (int)(1000 / capture.Fps); timer.Tick += new EventHandler(timer_Tick); } private void Form2_Load(object sender, EventArgs e) { timer.Start(); } private void timer_Tick(object sender, EventArgs e) { IplImage image = capture.QueryFrame(); if (image != null) { pictureBox1.Image = image.ToBitmap(); } else { timer.Stop(); } } }
簡単な割に使い勝手も良く、悪くない方法です。処理が追いつかない際の挙動にやや不安材料がありますが、最近のPCならば普通は大丈夫でしょう。
BackgroundWorkerによるマルチスレッド
System.ComponentModel.BackgroundWorkerを用いて、動画からの毎フレームの画像の取得を別スレッドで行います。
WindowsFormsのコントロールは基本的にシングルスレッドで扱うようにできており、普通のThreadを用いる際はInvokeを使ったりしなければならずそのあたりがやや面倒になります。BackgroundWorkerはReportProgressを使うとそのあたりが楽に記述できます。
public partial class Form3 : Form { private BackgroundWorker worker; public Form3() { InitializeComponent(); worker = new BackgroundWorker(); worker.WorkerReportsProgress = true; worker.DoWork += new DoWorkEventHandler(worker_DoWork); worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged); } private void Form3_Load(object sender, EventArgs e) { worker.RunWorkerAsync(); } private void worker_DoWork(object sender, DoWorkEventArgs e) { BackgroundWorker bw = (BackgroundWorker)sender; using (CvCapture capture = new CvCapture("test.avi")) { int interval = (int)(1000 / capture.Fps); IplImage image; while ( (image = capture.QueryFrame()) != null ) { bw.ReportProgress(0, image); System.Threading.Thread.Sleep(interval); } } } private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e) { IplImage image = (IplImage)e.UserState; pictureBox1.Image = image.ToBitmap(); } }
他にもまだ方法はあると思いますが、この辺で。
また、OpenCvSharp.UserInterface.PictureBoxIplを使うと、画像の更新処理は以下のように書けます。
pictureBoxIpl1.RefreshIplImage(image);
この方法を使うと、毎フレームごとのBitmapオブジェクトの再生成を行わなくなるので、若干効率が良いと思います。

- 作者: 北山洋幸
- 出版社/メーカー: カットシステム
- 発売日: 2013/07
- メディア: 単行本
- この商品を含むブログ (1件) を見る