Elsaの技術日記(徒然なるままに)

主に自分で作ったアプリとかの報告・日記を記載

MENU

Gtk# + OpenCVSharpでGUIアプリにて動画再生

前回、GtkSharp + OpenCVSharpを用いてGUIアプリに画像を表示してみました。
elsammit-beginnerblg.hatenablog.com

今回はGtkSharp + OpenCVSharpで動画再生させてみたいと思います。



■条件

今回の条件ですが下記になります。
・OS:Ubuntu20.04
dotnet:.NET 5.0

今回は、
 ・dotnetでGtkSharpが生成されていること
 ・OpenCVSharpがインストールされていること
を前提とします。
もし生成出来ていない場合にはこちらをご参考ください。
elsammit-beginnerblg.hatenablog.com

■GtkSharp + OpenCVSharpで動画再生してみる

では動画再生用GUIアプリを実装してみたいと思います。
今回はボタン押下すると動画が再生するようにしてみます。

全体のコードはこちら。

using System;
using Gtk;
using OpenCvSharp;
using OpenCvSharp.Extensions;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Runtime.InteropServices;
using UI = Gtk.Builder.ObjectAttribute;

namespace test2
{
    class MainWindow : Gtk.Window
    {
        [UI] private Label _label1 = null;
        [UI] private Button _button1 = null;
        [UI] private Gtk.Image testImg = null;

        private int _counter;

        public MainWindow() : this(new Builder("MainWindow.glade")) { }

        private MainWindow(Builder builder) : base(builder.GetRawOwnedObject("MainWindow"))
        {
            builder.Autoconnect(this);
         	Thread thread = new Thread(new ThreadStart(()=>{
                Application.Invoke(delegate {
				    ShowMovie();
			    });
            }));
            thread.Start();
            
            DeleteEvent += Window_DeleteEvent;
            _button1.Clicked += Button1_Clicked;
        }

        private void Window_DeleteEvent(object sender, DeleteEventArgs a)
        {
            Application.Quit();
        }

        private void ShowMovie(){
            VideoCapture vcap = new VideoCapture("/home/hiro/dotnet/test2/img/Forest.mp4");
            while (vcap.IsOpened())
            {
                Mat mat = new Mat();
                Mat dist = new Mat();

                if (vcap.Read(mat)){
                    if (mat.IsContinuous()){
                        Cv2.Resize(mat,dist, new OpenCvSharp.Size(240,160),0,0, InterpolationFlags.Cubic);
                        MemoryStream ms = new MemoryStream ();
                        var bmp = BitmapConverter.ToBitmap(dist);
                        bmp.Save(ms, ImageFormat.Png);
                        ms.Position = 0;
                        testImg.Pixbuf = new Gdk.Pixbuf(ms);
                    }else{
                        break;
                    }
                    while (GLib.MainContext.Iteration ()){}
                }else{
                    break;
                }
                Thread.Sleep((int)(1000 / vcap.Fps));
                mat.Dispose();
            }
            vcap.Dispose();
        }
    }
}

動画を読み出してImage Widgetにフレーム画像を書き込む制御は、

private void ShowMovie(){
    VideoCapture vcap = new VideoCapture("/home/hiro/dotnet/test2/img/Forest.mp4");
    while (vcap.IsOpened())
    {
        Mat mat = new Mat();
        Mat dist = new Mat();

        if (vcap.Read(mat)){
            if (mat.IsContinuous()){
                Cv2.Resize(mat,dist, new OpenCvSharp.Size(240,160),0,0, InterpolationFlags.Cubic);
                MemoryStream ms = new MemoryStream ();
                var bmp = BitmapConverter.ToBitmap(dist);
                bmp.Save(ms, ImageFormat.Png);
                ms.Position = 0;
                testImg.Pixbuf = new Gdk.Pixbuf(ms);
            }else{
                break;
            }
            while (GLib.MainContext.Iteration ()){}
        }else{
            break;
        }
        Thread.Sleep((int)(1000 / vcap.Fps));
        mat.Dispose();
    }
    vcap.Dispose();
}

となります。
実施していることは、
 ・OpenCVSharpで動画からフレーム画像を読み出し。
 ・読み出したフレーム画像をビットマップデータとしてメモリに書き込み
 ・メモリに書き込んだビットマップデータをGtkのImage Widgetに書き込み
です。

この、
 ・読み出したフレーム画像をビットマップデータとしてメモリに書き込み
 ・メモリに書き込んだビットマップデータをGtkのImage Widgetに書き込み
GtkSharp + OpenCVSharpで実装した時と同じコードになります。

ここで、各フレーム毎にビットマップデータを書き込んだ後に、

while (GLib.MainContext.Iteration ()){}

を実行する必要があります。
このglib.MainContext.iteration()がないと、応答なしの状態で画面がフリーズしてしまいます。

glib.MainContext.iteration()について詳細はこちらをご参照ください。
https://developer.gnome.org/pygobject/stable/class-glibmaincontext.html
どうやら、重い処理を実行している場合定期的にキューにたまったデータを吐き出すようにしないと、
画面の更新・描画が発生しないためフリーズしてしまうようです。

■ボタン押下による動画再生

次にボタン押下時に動画再生させる方法ですが、

_button1.Clicked += Button1_Clicked;

といった形でボタン押下時に実行するイベントに対して、

private void Button1_Clicked(object sender, EventArgs a)
{
    Thread thread = new Thread(new ThreadStart(()=>{
        Application.Invoke(delegate {
		    ShowMovie();
	    });
    }));
    thread.Start();
    _counter++;
    _label1.Text = "Hello World! This button has been clicked " + _counter + " time(s).";
}

といったように、ShowMovie()を別スレッドとして実行すればOKです。

■ボタン押下で動画再生させてみる
では実際に動かしてみます。
結果はこちら!!
f:id:Elsammit:20210428231632g:plain

ボタン押下したら動画が再生されることが確認できるかと思います。
※動画少し小さすぎました。。。

■注意

一旦ビットマップデータに変換させていることが要因なのか、
動画サイズが大きくなると動画再生が遅くなってしまう現象が発生していました。。。
こちらの方法で動画再生させる場合、あまり動画サイズは大きくしない方がいいかもです。。

■最後に

以前できなかった動画再生を実現させることが出来ました!!
結構自分的には満足!!
もう少しGtkSharpで遊んでみたいと思います。
もし新しく分かったことがあったら備忘録も兼ねてブログに載せていきたいと思います。