版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。
EmguCV是一个基于OpenCV的开源免费的跨平台计算机视觉库,它向C#和VB.NET开发者提供了OpenCV库的大部分功能。
教程VB.net版本请访问:EmguCV学习笔记 VB.Net 目录-CSDN博客
教程C#版本请访问:EmguCV学习笔记 C# 目录-CSDN博客
笔者的博客网址:https://blog.csdn.net/uruseibest
教程配套文件及相关说明以及如何获得pdf教程和代码,请移步:EmguCV学习笔记
学习VB.Net知识,请移步: vb.net 教程 目录_vb中如何用datagridview-CSDN博客
学习C#知识,请移步:C# 教程 目录_c#教程目录-CSDN博客
9.1 VideoCapture类
VideoCapture类是用于处理视频输入的类,它提供了多种方法用于从摄像头、视频文件、网络流等不同的源读取视频数据。
9.1.1 构造函数
VideoCapture类提供了2个构造函数:
1、public VideoCapture(
int camIndex = 0,
VideoCapture. API captureApi = VideoCapture.API.Any,
)
参数说明:
- camIndex:要打开的摄像头的编号,例如0表示打开第一个摄像头
- captureApi:指定videocapture对象使用的视频捕获API,这是一个VideoCapture.API枚举,Windows下常用的成员有:
- Any:自动选择最佳的视频捕获API
- DShow:使用DirectShow视频捕获API。
- Vfw:使用Video for Windows视频捕获API。
- Ffmpeg:使用FFmpeg视频捕获API,但是需要安装相应的FFmpeg库。
2、public VideoCapture(
string fileName,
VideoCapture. API captureApi = VideoCapture.API.Any,
)
参数说明:
- fileName:要打开的视频文件路径或网络流地址。
- captureApi:指定videocapture对象使用的视频捕获API。
由于这个构造函数的两个参数均有默认值,所以在实际使用中,可以使用以下方法:
public void VideoCapture ();
public void VideoCapture (int camIndex);
public void VideoCapture (string fileName);
通常可以采用以下方法来从摄像头或文件获得视频:
1、打开本地第一个摄像头,
VideoCapture vc = new VideoCapture(0);
2、打开本地视频文件
VideoCapture vc = new VideoCapture("D:\\video.mp4");
3、打开网络流
VideoCapture vc = new VideoCapture("http://example.com/video.m3u8");
在EmguCV中,VideoCapture类支持许多网络视频协议,包括RTSP、HTTP、FTP、MMS等。通过使用不同的网络视频协议,可以从远程摄像头、网络摄像头或者网络视频流中获取视频数据。下面分别介绍一下这几种网络视频协议的使用方法。
1. RTSP协议
RTSP协议是一种用于实时数据传输的协议,常用于远程视频监控。
// 打开RTSP视频流
VideoCapture vc = new VideoCapture("rtsp://192.168.1.100:554/live.sdp");
VideoCapture vc = new VideoCapture("rtsp://admin:***@ 192.168.1.100:554");
其中的网址是RTSP视频流的URL地址,需要根据实际修改。
2. HTTP协议
HTTP协议是一种广泛应用于互联网的协议,可以通过HTTP视频流获取网络摄像头或者网络视频流的视频数据。例如:
VideoCapture vc = new VideoCapture("http://192.168.1.100:8080/video");
VideoCapture vc = new VideoCapture("http://username:pass@cam_address/video.cgi? ");
其中的网址是HTTP视频流的URL地址,需要根据实际修改。
3. FTP协议
FTP协议是一种文件传输协议,可以通过FTP视频流获取网络摄像头或者网络视频流的视频数据。
VideoCapture vc = new VideoCapture("ftp://192.168.1.100:21/video.mp4");
其中网址是FTP视频流的URL地址,需要根据实际修改。
4. MMS协议
MMS协议是一种多媒体流传输协议,可以通过MMS视频流获取网络摄像头或者网络视频流的视频数据。
VideoCapture vc = new VideoCapture("mms://192.168.1.100:1755/video.wmv");
其中网址是MMS视频流的URL地址,需要根据实际修改。
注意:并非所有的网络视频协议都适用于所有的视频输入设备,有些视频输入设备可能不支持某些协议。在使用VideoCapture类打开网络视频流时,需要确认网络视频协议是否被支持。可以通过使用VideoCapture的BackendName属性获取VideoCapture类的后端名称,以判断是否支持某种网络视频协议。
【代码位置:frmChapter9_1】Button1_Click
private void Button1_Click(object sender, EventArgs e)
{
//下面测试视频的网址来自https://rtsp.stream/ ,可以用以下方法获得
//打开https://rtsp.stream/,点击Free下的"Get started"填写邮箱,打开邮箱,打开指定的链接后,会给出2个免费的有效地址。
VideoCapture vc = new VideoCapture("rtsp://zephyr.rtsp.stream/pattern?streamKey=3c1b3e15c8904e1b7eea13468a085b3d");
//rtsp://zephyr.rtsp.stream/movie?streamKey=e44ecf50adae5c720543462d14a4a69a
string backendName = vc.BackendName;
if( backendName == "FFMPEG") // 判断是否支持FFMPEG
{
//如果支持FFMPEG,可以使用RTSP、HTTP、FTP、MMS等网络视频协议
}
}
9.1.2 Get和Set方法
Get和Set方法用于获取或设置视频输入源的相关参数,例如视频分辨率、帧率、亮度、对比度等。这两个方法的重要参数是一个CapProp枚举,它包含很多重要的成员。例如:
1、获得VideoCapture对象视频相关属性:
VideoCapture.Get(CapProp.Fps); //获取视频帧率
VideoCapture.Get(CapProp.FrameWidth); //获取视频宽度
VideoCapture.Get(CapProp.FrameHeight); //取视频高度
VideoCapture.Get(CapProp.FrameCount); //获取视频总帧数
VideoCapture.Get(CapProp.PosFrames); //获取视频当前帧数
VideoCapture.Get(CapProp.PosAvti); //获取视频当前帧的位置的属性值。
2、设置VideoCapture对象视频相关属性:
VideoCapture.Set(CapProp.FrameWidth, 640); //设置视频宽度为640像素
VideoCapture.Set(CapProp. FrameHeight, 480); //设置视频高度为480像素
VideoCapture.Set(CapProp. Fps,, 30); //设置视频帧率为30帧/秒
VideoCapture.Set(CapProp. Brightness, 50); //设置视频亮度为50
VideoCapture.Set(CapProp. Contrast, 50); //设置视频对比度为50
【代码位置:frmChapter9_1】Button2_Click
//获得视频信息
private void Button2_Click(object sender, EventArgs e)
{
VideoCapture vc = new VideoCapture("C:\\learnEmgucv\\movie1.mp4");
Double width = vc.Get(CapProp.FrameWidth);
Double height = vc.Get(CapProp.FrameHeight);
Double fps = vc.Get(CapProp.Fps);
Double FrameCount = vc.Get(CapProp.FrameCount);
Double Brightness = vc.Get(CapProp.Brightness);
Double Contrast = vc.Get(CapProp.Contrast);
Console.WriteLine("视频宽度:" + width);
Console.WriteLine("视频高度:" + height);
Console.WriteLine("视频帧率:" + fps);
Console.WriteLine("视频总帧数:" + FrameCount);
Console.WriteLine("视频亮度:" + Brightness);
Console.WriteLine("视频对比度:" + Contrast);
vc.Dispose();
}
注意:并非所有的视频属性都适用于所有的视频输入设备,有些视频输入设备可能不支持某些属性,比如Brightness、Contrast等属性只适合相机。在使用Set方法设置视频捕获属性时,最好先使用Get方法获取当前视频捕获属性的值,以便确认是否支持该属性。
【代码位置:frmChapter9_1】Button3_Click
private void Button3_Click(object sender, EventArgs e)
{
VideoCapture vc = new VideoCapture("C:\\learnEmgucv\\movie1.mp4");
//获取当前视频宽度属性的值
int width =(int) vc.Get(CapProp.FrameWidth);
//判断是否支持该属性
if (width > 0)
//设置视频宽度为640像素
vc.Set(CapProp.FrameWidth, 640);
vc.Dispose();
}
9.1.3 Retrieve、Read和Queryframe方法
这三种方法都是从视频中获得一幅图像。但是各有区别。
1、Retrieve不能自动读取下一帧,需要与Grab方法一起使用。而Grab方法只是获取下一帧的数据到一个用户无法访问的内存区,然后使用Retrieve方法根据通道号进行编码读取相应的图像。Read和Queryframe方法取出图像后可以自动转到下一帧图像
2、Retrieve可以返回多通道图像,比如使用立体摄像机,每一帧会返回两个图像;而Kinect会返回四个图像。Read和Queryframe方法不能读取多通道图像。
3、三种方法读取完视频(文件)最后一帧,返回的状态(详细请看示例代码):
- QueryFrame:m == null(其中m为获得的Mat对象,下同),读取时,m所指向的数据指针DataPointer会变。
- Read:m != null,m.IsEmpty=true,读取时,m所指向的数据指针DataPointer不会变。
- Retrieve:m != null,m.IsEmpty= false,仍然会一直返回最后一帧的图像。因此需要判断是否达到最后一帧,即播放的当前帧是否是最后一帧。读取时,m所指向的数据指针DataPointer不会变。
由于上述三个方法以及Grab方法的使用都很简单,这里不再详述。
【代码位置:frmChapter9_1】Button4_Click
//播放视频
private void Button4_Click(object sender, EventArgs e)
{
VideoCapture vc = new VideoCapture("C:\\learnEmgucv\\movie1.mp4");
//判断是否已经打开
if (vc.IsOpened == false)
return;
//获得帧率
Double fps = vc.Get(CapProp.Fps);
//获得总的帧数
long framecount = (long)vc.Get(CapProp.FrameCount);
Mat m = new Mat();
long i = 0;
while( true)
{
//读取视频帧图像,三种方式:
//==1、使用Grab+Retrieve
if (vc.Grab() == false)
break;
if (vc.Retrieve(m) == false)
break;
//==2、使用Read
//vc.Read(m);
//==3、使用QueryFrame
//m = vc.QueryFrame();
//如果m是空
if (m == null)
break;
//如果m未包含数据
if (m.IsEmpty)
break;
//播放视频,两种方式
//==1、在imagebox1里面播放
//显示帧画面
ImageBox1.Image = m;
//必须强制刷新,否则只会在结束时显示最后一帧画面
ImageBox1.Refresh();
//按照播放的帧率设置间隔时间
System.Threading.Thread.Sleep((int)(1000 / fps));
//==2、使用Imshow来播放
显示帧画面
//CvInvoke.Imshow("video", m);
等待按键
//CvInvoke.WaitKey((int)(1000 / fps));
//判断是否已经是结束的一帧
if (i >= framecount)
break;
i += 1;
Label1.Text = i + "/" + framecount;
//必须强制刷新,否则只会看到最后的结果
Label1.Update();
}
//如果上述代码中使用了CvInvoke.Imshow来显示视频,那么这里应该销毁这个窗口
//CvInvoke.DestroyWindow("video");
vc.Dispose();
Label1.Text = "播放结束";
}
输出结果如下图所示:
图9-1 视频播放
在播放时设置视频播放的起始点,可以使用两种方式,都需要用到Set方法:
1、设置指定帧位置作为起始点:
vc.Set(CapProp.PosFrames, 帧位置);
2、设置指定时间位置作为起始点:
vc.Set(CapProp.PosMsec, 时间位置);
【代码位置:frmChapter9_1】Button5_Click
//设置播放起始点
private void Button5_Click(object sender, EventArgs e)
{
VideoCapture vc = new VideoCapture("C:\\learnEmgucv\\movie1.mp4");
vc.Set(CapProp.FrameWidth, 640);
vc.Set(CapProp.FrameHeight, 480);
Double fps = vc.Get(CapProp.Fps);
Console.WriteLine(vc.Get(CapProp.FrameCount));
if (vc.IsOpened == false)
return;
long framecount = (long)vc.Get(CapProp.FrameCount);
//以下两种方式选其一:
//==1、设置要播放的起始帧的位置
int frameIndex = 50;
vc.Set(CapProp.PosFrames, frameIndex);
//==2、设置要播放的事件点的位置,注意Set()的第二个参数是毫秒
//Double frameTime = 3.0;
//vc.Set(CapProp.PosMsec, frameTime * 1000);
Mat m = new Mat();
while (true)
{
//读取视频帧图像,使用Read
vc.Read(m);
if (m == null)
break;
if (m.IsEmpty)
break;
CvInvoke.Imshow("video", m); // 显示图像
CvInvoke.WaitKey((int)(1000 / fps)); // 等待按键
}
CvInvoke.DestroyWindow("video");
vc.Dispose();
Label1.Text = "播放结束";
}
事实上,当获得视频帧图像后,就可以对这个图像进行处理,再显示处理的结果。
【代码位置:frmChapter9_1】Button6_Click
//简单示例处理图像后输出视频
private void Button6_Click(object sender, EventArgs e)
{
VideoCapture vc = new VideoCapture("C:\\learnEmgucv\\movie1.mp4");
Double fps = vc.Get(CapProp.Fps);
if (vc.IsOpened == false)
return;
long framecount = (long)vc.Get(CapProp.FrameCount);
Mat m = new Mat();
while (true)
{
vc.Read(m);
if (m==null)
break;
if( m.IsEmpty)
break;
//这里使用canny简单示例处理图像
Mat mcanny = new Mat();
CvInvoke.Canny(m, mcanny, 100, 250, 3);
CvInvoke.Imshow("video", mcanny); // 显示图像
CvInvoke.WaitKey((int)(1000 / fps)); // 等待按键
}
CvInvoke.DestroyWindow("video");
vc.Dispose();
Label1.Text = "播放结束";
}
输出结果如下图所示:
图9-2 处理视频帧并播放
9.1.4 Start、Stop和Pause方法
在EmguCV中,videocapture类提供了Start、Stop、Pause等方法,用于控制视频的播放和暂停。
Start方法:开始播放视频,实际是启动grab到一个指定线程。该方法并不会立即开始播放视频,而是会将视频设置为播放状态,此时将会触发ImageGrabbed事件。
Stop方法:停止播放视频,实际是停止grabbing线程。
Pause方法:暂停播放视频,实际是暂停正在运行的grab线程。该方法并不会立即暂停视频播放,而是会将视频设置为暂停状态。在Pause方法调用后,可以使用read或Grab方法读取视频帧进行处理,但是读取的视频帧是暂停状态下的帧图像,而不是实时的帧图像。
上述三个方法的使用,请参看9.1.5节【ImageGrabbed事件】的示例代码。
9.1.5 ImageGrabbed事件
ImageGrabbed事件会在每一帧图像数据被抓取时触发。使用该事件可以实现对视频流的实时处理。需要注意的是,1、ImageGrabbed事件实际已经是在进行Grab,所以,如果使用Retrieve方法获得图像,可以不使用Grab方法;2、由于ImageGrabbed事件是在后台线程中执行的,因此在事件处理方法中不能直接访问窗体或控件的属性。
【代码位置:frmChapter9_1】frmChapter9_Load、Button7_Click、vc1_ImageGrabbed、Button8_Click、Button9_Click
VideoCapture vc1;
//帧率
Double fps = 0;
private void frmChapter9_1_Load(object sender, EventArgs e)
{
//不检查跨线程
CheckForIllegalCrossThreadCalls = false;
}
//调用VideoCapture的ImageGrabbed事件
private void Button7_Click(object sender, EventArgs e)
{
vc1 = new VideoCapture("C:\\learnEmgucv\\movie1.mp4");
if (vc1.IsOpened == false)
{
MessageBox.Show("打开文件失败");
return;
}
//获得帧率
fps = vc1.Get(CapProp.Fps);
//添加ImageGrabbed事件
vc1.ImageGrabbed += vc1_ImageGrabbed;
//启动
vc1.Start();
}
//ImageGrabbed事件,这里面进行播放
private void vc1_ImageGrabbed(object sender, EventArgs e)
{
Mat nextframe = new Mat();
//如果使用Retrieve,那么需要检查视频当前播放的位置
vc1.Retrieve(nextframe);
//判断是否到达视频结束帧
if (vc1.Get(CapProp.PosFrames) >= vc1.Get(CapProp.FrameCount))
{
//停止
vc1.Stop();
//释放资源
vc1.Dispose();
//取消事件
vc1.ImageGrabbed -= vc1_ImageGrabbed;
//需要跨线程
Label1.Text = "播放结束";
return;
}
//如果使用read,只需要检查该方法的返回值
//if (vc1.Read(nextframe) == false)
//{
// //停止
// vc1.Stop();
// //释放资源
// vc1.Dispose();
// //取消事件
// vc1.ImageGrabbed-= vc1_ImageGrabbed;
// //需要跨线程
// Label1.Text = "播放结束";
// return;
//}
ImageBox1.Image = nextframe;
System.Threading.Thread.Sleep((int)(1000 / fps));
}
//暂停
private void Button8_Click(object sender, EventArgs e)
{
vc1.Pause();
}
//停止
private void Button9_Click(object sender, EventArgs e)
{
vc1.Stop();
}
以下代码模拟了电影中常用的扣除绿幕。
【代码位置:frmChapter9_1】Button10_Click、vc2_ImageGrabbed
VideoCapture vc2;
private void Button10_Click(object sender, EventArgs e)
{
vc2 = new VideoCapture("C:\\learnEmgucv\\前景.wmv");
if (vc2.IsOpened == false)
{
MessageBox.Show("打开文件失败");
return;
}
fps = vc2.Get(CapProp.Fps);
vc2.ImageGrabbed += vc2_ImageGrabbed;
vc2.Start();
}
//模拟绿幕
private void vc2_ImageGrabbed(object sender, EventArgs e)
{
Mat nextframe = new Mat();
//这里是对视频文件进行检查,所以使用read,同时检查是否播放完毕
if (vc2.Read(nextframe) == false)
{
vc2.Stop();
vc2.Dispose();
vc2.ImageGrabbed -= vc2_ImageGrabbed;
//跨线程了
Label1.Text = "播放结束";
return;
}
Mat mhsv = new Mat();
CvInvoke.CvtColor(nextframe, mhsv, ColorConversion.Rgb2Hsv);
ScalarArray lower = new ScalarArray(new MCvScalar(35, 43, 46));
ScalarArray upper = new ScalarArray(new MCvScalar(77, 255, 255));
//提取图像中某个颜色范围内的像素
//颜色值在范围内,则将其设置为白色(255),否则将其设置为黑色(0)
Mat mmask = new Mat();
CvInvoke.InRange(mhsv, lower, upper, mmask);
//根据实际需要判断是否反转颜色
Mat mreversalmask = new Mat();
mreversalmask = ~mmask;
//以下代码输出二值图作为mask的彩色图,也就是原图去除了绿色背景
Mat m3channel = new Mat();
CvInvoke.CvtColor(mreversalmask, m3channel, ColorConversion.Gray2Bgr);
Mat mout = new Mat();
CvInvoke.BitwiseAnd(nextframe, m3channel, mout);
System.Threading.Thread.Sleep((int)(1000 / fps));
ImageBox1.Image = mout;
}
输出结果如下图所示:
图9-3 模拟去除绿幕