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,如下所示
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();
}
}));
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,不进行优化。
/// <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();
播放视频文件:
UI线程中响应监控类的事件:
定义异步调用的委托:
添加事件委托:
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
C#使用EmguCV实现视频读取和播放,及多个视频一起播放的问题的更多相关文章
- 机器学习进阶-背景建模-(帧差法与混合高斯模型) 1.cv2.VideoCapture(进行视频读取) 2.cv2.getStructureElement(构造形态学的卷积) 3.cv2.createBackgroundSubtractorMOG2(构造高斯混合模型) 4.cv2.morpholyEx(对图像进行形态学的变化)
1. cv2.VideoCapture('test.avi') 进行视频读取 参数说明:‘test.avi’ 输入视频的地址2. cv2.getStructureElement(cv2.MORPH_E ...
- OpenCV视频读取播放,视频转换为图片
转载请注明出处!!! http://blog.csdn.net/zhonghuan1992 OpenCV视频读取播放,视频转换为图片 介绍几个有关视频读取的函数: VideoCapture::Vide ...
- ijkplayer阅读笔记02-创建音视频读取,解码,播放线程
本节主要介绍音视频读取和解码线程的创建及启动,代码流程例如以下: IjkMediaPlayer_prepareAsync{ ijkmp_prepare_async_l{ ijkmp_change_st ...
- 基于OpenCV之视频读取,处理和显示框架的搭建(一)
主要包括以下内容: 1.使用的主要函数的说明. 2.两个实例:视频读取和显示.搭建视频读取和处理框架,调用canny函数提取边缘并显示. 3.一些注意事项和代码说明. 一.使用的主要函数 1.延时函数 ...
- OpenCV的视频读取
现在找一个能拍摄视频的设备真是太容易了.结果大家都用视频来代替以前的序列图像.视频可能由两种形式得到,一个是像网络摄像头那样实时视频流,或者由其他设备产生的压缩编码后的视频文件.幸运的是,OpenCV ...
- [转载]matlab视频读取函数VideoReader
看到以前matlab中读取视频多 使用mmreader等(参考<matlab读取/播放视频的函数>),而现在matlab有一个专门的视频读取类VideoReader完成视频读取的功能. 相 ...
- OpenCV计算机视觉学习(1)——图像基本操作(图像视频读取,ROI区域截取,常用cv函数解释)
1,计算机眼中的图像 我们打开经典的 Lena图片,看看计算机是如何看待图片的: 我们点击图中的一个小格子,发现计算机会将其分为R,G,B三种通道.每个通道分别由一堆0~256之间的数字组成,那Ope ...
- Atitit.android播放smb 网络邻居视频文件解决方案
Atitit.android播放smb 网络邻居视频文件解决方案 Android4.4 1.1. Android4视频播放器不能直接地支持smb协议..子好先转换成个http流 1.2. ES文件浏览 ...
- web网页中使用vlc插件播放相机rtsp流视频
可参考: 使用vlc播放器做rtsp服务器 使用vlc播放器播放rtsp视频 使用vlc进行二次开发做自己的播放器 vlc功能还是很强大的,有很多的现成的二次开发接口,不需配置太多即可轻松做客户端播放 ...
- Windows Media Player安装了却不能播放网页上的视频
前段时间遇到Windows Media Player安装了却不能播放网页上的视频的问题,在网上查找资料时,发现大部分资料都没能解决我这个问题.偶尔试了网上一牛人的方法,后来竟然解决了.现在再找那个网页 ...
随机推荐
- linux ascii艺术与ansi艺术
Linux终端下的ASCII艺术 http://zh.wikipedia.org/zh-tw/%E9%9B%BB%E5%AD%90%E9%81%8A%E6%88%B2%E5%8F%B2 电子游戏史 h ...
- linux包之procps之sysctl命令
概述 [root@localhost ~]# rpm -qf /sbin/sysctlprocps-3.2.8-25.el6.x86_64 我们常常在 Linux 的 /proc/sys 目录下,手动 ...
- Redis分布式部署,一致性hash
一致性哈希 由于hash算法结果一般为unsigned int型,因此对于hash函数的结果应该均匀分布在[0,2^32-1]区间,如果我们把一个圆环用2^32 个点来进行均匀切割,首先按照hash( ...
- python file operation
file.open(name[,mode[,buffering]]) 模式的类型有: r 默认只读 w 以写方式打开,如果文件不存在则会先创建,如果文件存在则先把文件内容清空(truncate ...
- html之span标签
对于文档中的行内元素最好使用span来组合它们,这样就可以通过样式来格式化它们. span没有任何的样式,当对它应用样式时,才会产生变化 id和class属性是span标签的好伴侣,这样做既可以增加适 ...
- HDP2.4安装(四):ambari安装
ambari是apache基金会的开源项目,它的优势在于巧妙溶合已有的开源软件,提供集群自动化安装.中心化管理.集群监控.报警等功能.据Hortonwork官方资料介绍,不同的HDP版本,对ambar ...
- Debian 环境下安装Tomcat记录
1.安装JAVA运行环境 Debian默认带了OpenJDK,有人说不好用,我没有验证就从ORACLE官网上下载了最新的JDK安装包,直接解压并设置环境变量就行了: # tar zxvf jdk-8u ...
- (转)LitJson 遍历key
本文转载自:http://blog.csdn.net/inlet511/article/details/47127579 用LitJson插件获取到的对象,如果想遍历对象中包含的子对象的key,可以用 ...
- Redis主从同步介绍
Redis主从同步命令和配置项 启动主从复制:master无需任何操作,slave中使用以下任意一种开启复制功能 (1).通过配置文件启动主从复制: 在redis.conf中加入"slave ...
- bzoj1201: [HNOI2005]数三角形
Description Input 大三角形的所有短边可以看成由(n+1)*n/2个单位三角形的边界组成.如下图的灰色三角形所示.其中第1排有1个灰色三角形,第2排有2个灰色三角形,……,第n排有n个 ...