C#使用EmguCV實現視頻讀取和播放,及多個視頻一塊兒播放的問題

 

你們知道WPF中多線程訪問UI控件時會提示UI線程的數據不能直接被其餘線程訪問或者修改,該怎樣來作呢?

分下面兩種狀況算法

1.WinForm程序安全

1)第一種方法,使用委託:
private delegate void SetTextCallback(string text); private void SetText(string text) { // InvokeRequired須要比較調用線程ID和建立線程ID // 若是它們不相同則返回true if (this.txt_Name.InvokeRequired) { SetTextCallback d = new SetTextCallback(SetText); this.Invoke(d, new object[] { text }); } else { this.txt_Name.Text = text; } } 2)第二種方法,使用匿名委託 private void SetText(Object obj) { if (this.InvokeRequired) { this.Invoke(new MethodInvoker(delegate { this.txt_Name.Text = obj; })); } else { this.txt_Name.Text = obj; } } 這裏說一下BeginInvoke和Invoke和區別:BeginInvoke會當即返回,Invoke會等執行完後再返回。

 

2.WPF程序多線程

1)可使用Dispatcher線程模型來修改框架

若是是窗體自己可以使用相似以下的代碼:異步

this.lblState.Dispatcher.Invoke(new Action(delegate
{
     this.lblState.Content = "狀態:" + this._statusText;
}));

那麼假如是在一個公共類中彈出一個窗口、播放聲音等呢?這裏咱們可使用:System.Windows.Application.Current.Dispatcher,以下所示async

 System.Windows.Application.Current.Dispatcher.Invoke(new Action(() =>
 {
     if (path.EndsWith(".mp3") || path.EndsWith(".wma") || path.EndsWith(".wav"))
     {
        _player.Open(new Uri(path));
        _player.Play();
    }
 }));
 
關鍵問題:多個視頻同時播放,以上幾種方法不足以解決,多個視頻播放中主界面卡死和播放顯示刷新不了的問題。
目前筆者的解決方法是
 pinturebox.CreateGraphics().DrawImage(imgSrc.Bitmap, new System.Drawing.Rectangle(0, 0, pinturebox.Width, pinturebox.Height));

 

EmguCV中的Capture類能夠完成視頻文件的讀取,並捕捉每一幀,能夠利用Capture類完成實現WinForm中視頻檢測跟蹤環境的搭建。本文只實現最簡陋的WinForm + EmguCV上的avi文件讀取和播放框架,複雜的檢測和跟蹤算法在以後添加進去。ide

        這裏使用WinForm實現視頻的播放,主要是PictureBox類,它是支持基於事件的異步模式的典型組件,不使用EmguCV自帶的UI控件等。優化

圖1.效果圖ui

 

        直接在UI線程中完成視頻的播放的話整個程序只有一個線程,因爲程序只能同步執行,播放視頻的時候UI將中止響應用戶的輸入,形成界面的假死。因此視頻的播放須要實現異步模式。主要有三種方法:第一是使用異步委託;第二種是使用BackgroundWorker組件;最後一種就是使用多線程(不使用CheckForIllegalCrossThreadCalls =false的危險作法)。this

        Windows窗體控件,惟一能夠從建立它的線程以外的線程中調用的是Invoke()、BegionInvoke()、EndInvoke()方法和InvokeRequired屬性。其中BegionInvoke()、EndInvoke()方法是Invoke()方法的異步版本。這些方法會切換到建立控件的線程上,以調用賦予一個委託參數的方法,該委託參數能夠傳遞給這些方法。

        (一)   使用多線程
        首先定義監控的類及其對應的事件參數類和異常類:
        判斷是否繼續執行的布爾型成員會被調用線程改變,所以聲名爲volatile,不進行優化。

/// <summary>
/// 紅外檢測子。
/// </summary>
public class ThermalSurveillant
{
    #region Private Fields

    /// <summary>
    /// 是否中止線程,此變量供多個線程訪問。
    /// </summary>
    private volatile bool shouldStop = false;

    #endregion
    #region Public Properties

    #endregion
    #region Public Events

    /// <summary>
    /// 幀刷新事件。
    /// </summary>
    public EventHandler<FrameRefreshEventArgs> FrameRefresh;

    /// <summary>
    /// 播放完成。
    /// </summary>
    public EventHandler<CompletedEventArgs> Completed;

    #endregion
    #region Protected Methods

    /// <summary>
    /// 處理幀刷新事件。
    /// </summary>
    /// <param name="e"></param>
    protected virtual void OnFrameRefresh(FrameRefreshEventArgs e)
    {
        if (this.FrameRefresh != null)
        {
            this.FrameRefresh(this, e);
        }
    }

    /// <summary>
    /// 處理視頻讀完事件。
    /// </summary>
    /// <param name="e"></param>
    protected virtual void OnCompleted(CompletedEventArgs e)
    {
        if (this.Completed != null)
        {
            this.Completed(this, e);
        }
    }

    #endregion
    #region Public Methods

    /// <summary>
    /// 視頻監控。
    /// </summary>
    /// <param name="capture">捕捉。</param>
    public void DoSurveillance(Object oCapture)
    {
        Capture capture = oCapture as Capture;
        int id = 1;
        if (capture == null)
        {
            throw new InvalidCaptureObjectException("傳遞的Capture類型無效。");
        }
        while (!shouldStop)
        {
            Image<Bgr, byte> frame = capture.QueryFrame();
            if (frame != null)
            {
                FrameRefreshEventArgs e = new FrameRefreshEventArgs(frame.ToBitmap(), id++);
                // 觸發刷新事件
                this.OnFrameRefresh(e);
            }
            else
            {
                break;
            }
        }
        // 觸發完成事件
        this.OnCompleted(new CompletedEventArgs(id));
    }

    /// <summary>
    /// 請求中止線程。
    /// </summary>
    public void Cancel()
    {
        this.shouldStop = true;
    }

    #endregion
}

  

 

        UI線程中啓動播放線程:

聲明:

/// <summary>
/// 監控線程。
/// </summary>
private Thread threadSurveillance = null;

/// <summary>
/// 捕獲視頻幀。
/// </summary>
private Capture captureSurveillance;

/// <summary>
/// 監控子。
/// </summary>
private ThermalSurveillant surveillant = new ThermalSurveillant();
 

讀入視頻文件:

captureSurveillance = new Capture(this.videoFilePath);
captureSurveillance.SetCaptureProperty(CAP_PROP.CV_CAP_PROP_FRAME_WIDTH, this.width);
captureSurveillance.SetCaptureProperty(CAP_PROP.CV_CAP_PROP_FRAME_HEIGHT, this.height);
Image<Bgr, byte> frame = captureSurveillance.QueryFrame();
this.pictureBox.Image = frame.ToBitmap();

 

 

  

播放視頻文件:

// 啓動播放線程
threadSurveillance = new Thread(new ParameterizedThreadStart(surveillant.DoSurveillance));
threadSurveillance.Name = 「Surveillance」;
threadSurveillance.IsBackground = true;
threadSurveillance.Start(captureSurveillance);

 

  

        UI線程中響應監控類的事件:

定義異步調用的委託:

#region Delegate

/// <summary>
/// 爲了調用UI線程的方法聲明的委託。
/// </summary>
/// <param name=」frame」>要刷新的幀。</param>
public delegate void FrameRefreshDelegate(Bitmap frame);

/// <summary>
/// 爲了調用UI線程的方法聲明的委託。
/// </summary>
/// <param name=」frame」>要刷新的幀。</param>
/// <param name=」id」>幀序號。</param>
public delegate void FrameStatusRefreshDelegate(Bitmap frame, int id);

/// <summary>
/// 爲了調用UI線程的方法聲明的委託。
/// </summary>
/// <param name=」message」>要輸出的信息。</param>
public delegate void CompletedDelegate(string message);

#endregion

 

  

添加事件委託:

this.surveillant.FrameRefresh += OnRefreshFrame;
this.surveillant.Completed += OnCompleted;
 

        如下方法中都是由監控線程中的事件委託方法,應該使用BeginInvoke方法,這樣能夠優雅的結束線程,若是使用Invoke方法,則調用方式爲同步調用,此時若是使用Thread.Join()方法終止線程將引起死鎖(正常播放沒有問題),Thread.Join()方法的使用使調用線程阻塞等待當前線程完成,在這裏即UI線程阻塞等待監控線程完成,而監控線程中又觸發UI線程中pictureBox的刷新,使用Invoke方法就形成了監控線程等待UI線程刷新結果,而UI線程已經阻塞,造成了死鎖。死鎖時只能用Thread.Abort()方法才能結束線程。或者直接強制結束應用程序。

        使用BeginInvoke方法時爲異步調用,監控線程不等待刷新結果直接繼續執行,能夠正常結束。結束後UI才進行刷新,不會形成死鎖。

圖2.線程關係

/// <summary>
/// 刷新UI線程的pixtureBox的方法。
/// </summary>
/// <param name="frame">要刷新的幀。</param>
private void RefreshFrame(Bitmap frame)
{
    this.pictureBox.Image = frame;
    // 這裏必定不能刷新!2012年8月2日1:50:16
    //this.pictureBox.Refresh();
}


/// <summary>
/// 響應pictureBox刷新。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnRefreshFrame(object sender, FrameRefreshEventArgs e)
{
    // 判斷是否須要跨線程調用
    if (this.pictureBox.InvokeRequired == true)
    {
        FrameRefreshDelegate fresh = this.RefreshFrame;
        this.BeginInvoke(fresh, e.Frame);
    }
    else
    {
        this.RefreshFrame(e.Frame);
    }
}


/// <summary>
/// 響應Label刷新信息。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnCompleted(object sender, CompletedEventArgs e)
{
    // 判斷是否須要跨線程調用
    CompletedDelegate fresh = this.RefreshStatus;
    string message = "視頻結束,共 " + e.FrameCount + " 幀。";
    this.BeginInvoke(fresh, message);
}
  

        關閉時須要停止播放線程以後再退出:

/// <summary>
/// 關閉窗體時發生。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnFormClosed(object sender, FormClosedEventArgs e)
{
    // 檢測子算法請求終止
    surveillant.Cancel();

    // 阻塞調用線程直到檢測子線程終止
    if (threadSurveillance != null)
    {
        if (threadSurveillance.IsAlive == true)
        {
            threadSurveillance.Join();
        }
    }
}

 

  

 

        (二)   使用異步委託

        建立線程的一個更簡單的方法是定義一個委託,並異步調用它。委託是方法的類型安全的引用。Delegate類還支持異步地調用方法。在後臺,Delegate類會建立一個執行任務的線程。

// asynchronous by using a delegate
PlayVideoDelegate play = this.PlayVideoFile;
IAsyncResult status = play.BeginInvoke(null, null);

/// <summary>
/// 播放視頻文件。
/// </summary>
private void PlayVideoFile()
{
    while (true)
    {
        Image<Bgr, byte> frame = capture.QueryFrame();
        if (frame != null)
        {
            Image<Gray, byte> grayFrame = frame.Convert<Gray, byte>();
            grayFrame.Resize(this.width, this.height, INTER.CV_INTER_CUBIC);
            RefreshPictureBoxDelegate fresh = this.RefreshPictureBox;
            try
            {
                this.BeginInvoke(fresh, grayFrame.ToBitmap());
            }
            catch (ObjectDisposedException ex)
            {
                Thread.CurrentThread.Abort();
            }
        }
        else
        {
            break;
        }
    }
}

/// <summary>
/// 刷新UI線程的pixtureBox的方法。
/// </summary>
/// <param name="frame">要刷新的幀。</param>
private void RefreshPictureBox(Bitmap frame)
{
    this.pictureBox.Image = frame;
}

  

 

        (三)   使用BackgroundWorker組件

        BackgroundWorker類是異步事件的一種實現方案,異步組件能夠選擇性的支持取消操做,並提供進度信息。RunWorkerAsync()方法啓動異步調用。CancelAsync()方法取消。

圖3.BackgroundWorker組件

/// <summary>

/// 播放視頻文件。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void detectItemPlay_Click(object sender, EventArgs e)
{
    if (this.videoFilePath != null)
    {
        // run async
        this.backgroundWorker.RunWorkerAsync(capture);
    }
}

/// <summary>
/// 異步調用。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnDoWork(object sender, DoWorkEventArgs e)
{
    Emgu.CV.Capture capture = e.Argument as Emgu.CV.Capture;
    while (!e.Cancel)
    {
        Image<Bgr, byte> frame = capture.QueryFrame();
        if (frame != null)
        {
            Image<Gray, byte> grayFrame = frame.Convert<Gray, byte>();
            grayFrame.Resize(this.width, this.height, INTER.CV_INTER_CUBIC);
            if (this.backgroundWorker.CancellationPending == true)
            {
                e.Cancel = true;
                break;
            }
            else
            {
                if (this.pictureBox.InvokeRequired == true)
                {
                    RefreshPictureBoxDelegate fresh = this.RefreshPictureBox;
                    this.BeginInvoke(fresh, grayFrame.ToBitmap());
                }
                else
                {
                    this.RefreshPictureBox(grayFrame.ToBitmap());
                }
            }
        }
        else
        {
            break;
        }
    }
}

/// <summary>
/// 關閉窗體時發生。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
{
    if (this.backgroundWorker.IsBusy)
    {
        this.backgroundWorker.CancelAsync();
    }
}轉自http://blog.csdn.net/azkabannull/article/details/7827673
相關文章
相關標籤/搜索