C#使用EmguCV实现视频读取和播放,及多个视频一起播放的问题
- WinForm程序
1)第一种方法,使用委托:
1 |
|
2)第二种方法,使用匿名委托
1 | private void SetText(Object obj) |
这里说一下BeginInvoke和Invoke和区别:BeginInvoke会立即返回,Invoke会等执行完后再返回。
- WPF程序
1)可以使用Dispatcher线程模型来修改
如果是窗体本身可使用类似如下的代码:
this.lblState.Dispatcher.Invoke(new Action(delegate
{
this.lblState.Content = “状态:” + this._statusText;
}));
那么假如是在一个公共类中弹出一个窗口、播放声音等呢?这里我们可以使用:System.Windows.Application.Current.Dispatcher,如下所示

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文件读取和播放框架,复杂的检测和跟踪算法在之后添加进去。
这里使用WinForm实现视频的播放,主要是PictureBox类,它是支持基于事件的异步模式的典型组件,不使用EmguCV自带的UI控件等。
图1.效果图
直接在UI线程中完成视频的播放的话整个程序只有一个线程,由于程序只能同步执行,播放视频的时候UI将停止响应用户的输入,造成界面的假死。所以视频的播放需要实现异步模式。主要有三种方法:第一是使用异步委托;第二种是使用BackgroundWorker组件;最后一种就是使用多线程(不使用CheckForIllegalCrossThreadCalls =false的危险做法)。
Windows窗体控件,唯一可以从创建它的线程之外的线程中调用的是Invoke()、BegionInvoke()、EndInvoke()方法和InvokeRequired属性。其中BegionInvoke()、EndInvoke()方法是Invoke()方法的异步版本。这些方法会切换到创建控件的线程上,以调用赋予一个委托参数的方法,该委托参数可以传递给这些方法。
(一) 使用多线程
首先定义监控的类及其对应的事件参数类和异常类:
判断是否继续执行的布尔型成员会被调用线程改变,因此声名为volatile,不进行优化。
1 | /// <summary> |
UI线程中启动播放线程:
声明:
1 | /// <summary> |
读入视频文件:
1 | captureSurveillance = new Capture(this.videoFilePath); |
播放视频文件:
UI线程中响应监控类的事件:
定义异步调用的委托:
添加事件委托:
1 | this.surveillant.FrameRefresh += OnRefreshFrame; |
以下方法中都是由监控线程中的事件委托方法,应该使用BeginInvoke方法,这样可以优雅的结束线程,如果使用Invoke方法,则调用方式为同步调用,此时如果使用Thread.Join()方法终止线程将引发死锁(正常播放没有问题),Thread.Join()方法的使用使调用线程阻塞等待当前线程完成,在这里即UI线程阻塞等待监控线程完成,而监控线程中又触发UI线程中pictureBox的刷新,使用Invoke方法就造成了监控线程等待UI线程刷新结果,而UI线程已经阻塞,形成了死锁。死锁时只能用Thread.Abort()方法才能结束线程。或者直接强制结束应用程序。
使用BeginInvoke方法时为异步调用,监控线程不等待刷新结果直接继续执行,可以正常结束。结束后UI才进行刷新,不会造成死锁。
图2.线程关系
1 | /// <summary> |
(二) 使用异步委托
创建线程的一个更简单的方法是定义一个委托,并异步调用它。委托是方法的类型安全的引用。Delegate类还支持异步地调用方法。在后台,Delegate类会创建一个执行任务的线程。
1 | // asynchronous by using a delegate |
(三) 使用BackgroundWorker组件
BackgroundWorker类是异步事件的一种实现方案,异步组件可以选择性的支持取消操作,并提供进度信息。RunWorkerAsync()方法启动异步调用。CancelAsync()方法取消。
图3.BackgroundWorker组件
1 | /// <summary> |
%E2%80%94quartz.net%E5%8A%A8%E6%80%81%E6%B7%BB%E5%8A%A0job%E8%AE%BE%E8%AE%A1/181839415731718.png)
%E2%80%94quartz.net%E5%8A%A8%E6%80%81%E6%B7%BB%E5%8A%A0job%E8%AE%BE%E8%AE%A1/181849136677228.png)
%E2%80%94quartz.net%E5%8A%A8%E6%80%81%E6%B7%BB%E5%8A%A0job%E8%AE%BE%E8%AE%A1/181853563397152.png)
%E2%80%94quartz.net%E5%8A%A8%E6%80%81%E6%B7%BB%E5%8A%A0job%E8%AE%BE%E8%AE%A1/181858070583283.png)
%E2%80%94quartz.net%E6%8C%81%E4%B9%85%E5%8C%96%E5%92%8C%E9%9B%86%E7%BE%A4/181125329017601.png)
%E2%80%94quartz.net%E6%8C%81%E4%B9%85%E5%8C%96%E5%92%8C%E9%9B%86%E7%BE%A4/181503535266416.png)
%20%E2%80%94%20Quartz.Net%E8%BF%9B%E9%98%B6/172151135196757.png)