OpenCV(Emgu)入門系列(9):在C#中,使用Emgu+PictureBox實現一個簡易的視頻播放器


有了之前使用Emgu讀取圖片並顯示在C#的PictureBox中的實踐,今天使用相同的思路實現一個視頻播放器。

任務

使用C#與Emgu實現一個簡單的視頻播放器,有以下功能:

  • 可播放avi,rmvb等格式視頻
  • 有暫停、繼續、停止、上一幀、下一幀等功能
  • 有刻度條顯示播放進度,並且可通過拖動刻度條來改來視頻進度

要求:

  • VS 2012 RC,代碼及庫全部采用64位
  • 使用C#的PictureBox,而不是Emgu的ImageBox :我想實際體驗一下PictureBox與ImageBox之間的性能差距
  • 采用System.Windows.Forms.Timer取幀:據說當間隔小於100ms時Timer會很不准確。在實際運行時,發現打開一個正常的視頻文件(幀頻為30/s)時,播放很慢,像在慢放一樣,可證明的確有這個問題。不過我這么做是為了熟悉C#的API,下一次將換用更高效的方法。
  • 使用自定義事件來控制按鈕與刻度條的狀態(如文字)

效果圖

image

布局

布局還是比較簡單的,主要是要用好以下幾點:

  • AutoSize
  • Dock
  • Anchor
  • MinimumSize

就可以設計出一個窗口可隨視頻大小自動變化,按鈕位置不會錯位的布局出來。

Emgu相關API

使用Emgu封裝好的方法讀取視頻文件,及獲取相關信息:

// 讀取視頻文件(可以為AVI,rmvb等,只要系統安裝了解碼器)
IntPtr capture = CvInvoke.cvCreateFileCapture(file);
// 得到總幀數
CvInvoke.cvGetCaptureProperty(capture, Emgu.CV.CvEnum.CAP_PROP.CV_CAP_PROP_FRAME_COUNT);
// 視頻寬度
CvInvoke.cvGetCaptureProperty(capture, Emgu.CV.CvEnum.CAP_PROP.CV_CAP_PROP_FRAME_WIDTH);
// 視頻高度
CvInvoke.cvGetCaptureProperty(capture, Emgu.CV.CvEnum.CAP_PROP.CV_CAP_PROP_FRAME_HEIGHT);
// 當前幀位置
CvInvoke.cvGetCaptureProperty(capture, Emgu.CV.CvEnum.CAP_PROP.CV_CAP_PROP_POS_FRAMES);
// 幀頻
CvInvoke.cvGetCaptureProperty(capture, Emgu.CV.CvEnum.CAP_PROP.CV_CAP_PROP_FPS);

使用以下方法,將Emgu取得的圖片變為PictureBox認識的Bitmap:

// 讀取下一幀
var frame = CvInvoke.cvQueryFrame(capture);
// 生成一個新的Image容器
Image<Bgr, byte> dest = new Image<Bgr, byte>(movieInfo.width, movieInfo.height);
// 把數據復制過去
CvInvoke.cvCopy(frame, dest, IntPtr.Zero);
// 轉換為Bitmap
dest.ToBitmap();

如果想讓視頻定位到某一幀,使用以下方法:

int newPos = 23;
CvInvoke.cvSetCaptureProperty(capture, Emgu.CV.CvEnum.CAP_PROP.CV_CAP_PROP_POS_FRAMES, newPos);

Emgu的代碼就是以上這些,其它的都是C#代碼了。

視頻信息

通過定義一個叫MovieInfo的struct,來保存與視頻相關的信息供使用:

struct MovieInfo {
    public String filename;
    public int frameCount;
    public int width;
    public int height;
    public int currentFrame;
    public int fps;
}

Timer

創建Timer,根據幀頻設好Interval,以及Tick對應的操作。如下:

Timer myTimer = new Timer();
myTimer.Interval = 1000 / Convert.ToInt32(movieInfo.fps);
myTimer.Tick += new EventHandler(MyTimer_Tick);
myTimer.Start();

在MyTimer_Tick方法中讀取下一幀畫面,顯示在PictureBox中

事件

各按鈕與刻度條的狀態,與視頻的播放狀態,是互相對應的。比如只有打開了一個視頻文件,“開始/停止/前一幀/后一幀”按鈕才可用,並且刻度條上的指針也隨着播放向右移動;同時拖動刻度指針,視頻也會隨之變化。點擊“前一幀/后一幀”時,畫面也會跳動。它們之間是通過自定義的事件來協調的。

關於事件,可參考這篇文件:C#事件(event)解析

首先自定義了一個MovieEvent,它有多種不同的狀態,用以區分不同的事件:

class MovieEvent : EventArgs {
    public State EventState { get; set; }
    public enum State {
        NewMovie, Started, Stopped, Paused, Playing, Scroll, PrevNext
    }
    public MovieEvent(State state) : base() {
        this.EventState = state;
    }
}

然后在Form1中定義了一個delegate和一個event handler:

delegate void MovieHandler(object sender, MovieEvent e);
event MovieHandler MovieHandlers;

再寫一個handler方法,用於捕獲事件並處理。這里寫的有點亂,不知道有沒有更好的做法:

 void handler(object sender, MovieEvent e) {
    switch (e.EventState) {
        case MovieEvent.State.NewMovie:
            btnStart.Enabled = true;
            btnStop.Enabled = true;
            btnPrev.Enabled = true;
            btnNext.Enabled = true;
            btnStart.Text = "暫停";
            break;
        case MovieEvent.State.Paused:
            btnStart.Enabled = true;
            btnStop.Enabled = true;
            btnPrev.Enabled = true;
            btnNext.Enabled = true;
            btnStart.Text = "開始";
            break;
        // 更多
}

在Form1的構造函數中,把handler注冊上去:

this.MovieHandlers += new MovieHandler(this.handler);

然后就是在程序的不同地方,執行了不同操作時,創建事件並散播出去:

MovieHandlers(this, new MovieEvent(MovieEvent.State.Stopped));
MovieHandlers(this, new MovieEvent(MovieEvent.State.Playing));

項目源代碼

放在了github上:https://github.com/freewind/opencv_emgu_learning/tree/master/AviPlayer_PictureBox

這是一個VS 2012 RC的項目,應該也可以在低版本運行。如果要運行,需要手動安裝OpenCV/Emgu等,具體做法可參考我之前的文章。

改進

下一步將對該項目進行如下改進:

  • 使用Emgu的ImageBox,測一下兩者的性能差距
  • 使用其它方式代替PictureBox
  • 尋找進一步優化代碼的方法

后記

(2012-08-09)

該程序有一個嚴重的問題:無法按幀頻精准的播放視頻。比如當幀頻為30時,timer的間隔時間為1000/30=33,而准確的應該是33.3333,這樣實際上就慢了一點。另外,如果視頻比較大,解析一幅畫面比較費時的時候,Timer就更加不准確了,播放起來像在慢放。

這應該是一個難以解決的問題,精准的控制時間很難做到,參見How to use c# to write a simple video player which plays the video with accurate fps?

由於我的目的是通過這個例子來學習OpenCV,所以就不往下鑽了,做到現在的程度已經達到了目的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM