版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。
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知识,请移步: .net>vb.net 教程 目录_vb中如何用datagridview-CSDN博客
学习C#知识,请移步:C# 教程 目录_c#教程目录-CSDN博客
9.2 VideoWriter类
VideoWriter类提供了将帧图像数据保存为视频文件的功能。
9.2.1 构造函数
VideoWriter类常用的1个构造函数:
public VideoWriter(
string fileName,
int compressionCode,
double fps,
Size size,
bool isColor
)
参数说明:
- fileName:保存的视频文件名。如果需要保存的视频文件已经存在,那么videowriter类将会删除原文件,并创建一个新的视频文件。
- codecId:视频编解码器的代码,详见9.2.2节【Fourcc方法】。
- fps:视频的帧率。
- size:视频的宽度和高度。
- isColor:是否保存彩色视频。
以下是VideoWriter构造函数的示例代码:
VideoWriter vw = new VideoWriter("C:\\ saved-movie2.mp4", codecId, 25, new Size(640, 480), true);
9.2.2 Fourcc方法
Four cc是一个用于指定视频编解码器的4字节代码,是一个由四个ASCII字符组成的标识符。Four cc的作用是告诉计算机如何编解码视频文件并正确地显示它。
常见的编解码器格式对应Four cc如下:
编码 | Four cc | 编码 | Four cc | 编码 | Four cc |
MPEG-4 | DIVX | MPEG-1 | PTM1 | MPEG-4.2 | MP42 |
MPEG-4.3 | DIV3 | H263 | U263 | H263I | I263 |
H.264 | AVC1 | H.265 | HEVC | FLV1 | FLV1 |
编码不同,对电脑性能要求不同,生成文件大小也不同。具体需要哪种编码,要根据实际需求进行综合考虑。
VideoWriter类提供了fourcc静态方法,通过传入的4字符返回一个编解码器的代码。声明如下:
public static int Fourcc(
char c1,
char c2,
char c3,
char c4
)
9.2.3 Write方法
Write方法用于将一帧图像写入视频文件中。该方法声明如下:
public void Write(
IInputArray frame
)
参数说明:
- frame:要写入视频文件的帧,类型为Mat。
write方法只能将一帧图像写入视频文件中。如果需要将多帧图像写入视频文件中,可以在write方法的调用中使用循环来实现。
【代码位置:frmChapter9_1】Button11_Click
//写入视频文件
private void Button11_Click(object sender, EventArgs e)
{
VideoCapture vc = new VideoCapture("C:\\learnEmgucv\\movie1.mp4");
if (vc.IsOpened == false)
return;
int codecId;
//Mpeg-4.2编码
codecId = VideoWriter.Fourcc('M','P', '4', '2');
//宽度,同源视频文件
int width = (int)vc.Get(CapProp.FrameWidth);
//高度,同源视频文件
int height = (int)vc.Get(CapProp.FrameHeight);
//帧率,同源视频文件
Double movieFps = vc.Get(CapProp.Fps);
//使用Mpeg-4.2来编码
VideoWriter vw = new VideoWriter("C:\\learnEmgucv\\saved-movie.mp4", codecId, 50, new Size(width, height), true);
Mat m = new Mat();
while (true)
{
m = vc.QueryFrame();
if (m == null)
break;
if (m.IsEmpty)
break;
ImageBox1.Image = m;
ImageBox1.Refresh();
//将帧图像输出到文件
vw.Write(m);
System.Threading.Thread.Sleep((int)(1000 / movieFps));
}
vc.Dispose();
vw.Dispose();
Label1.Text = "保存完毕";
}
【代码位置:frmChapter9_1】Button12_Click、Button13_Click
//是否停止录制视频标记
Boolean stopRecord;
//开始录制摄像头视频
private void Button12_Click(object sender, EventArgs e)
{
VideoCapture vc = new VideoCapture(0);
if (vc.IsOpened == false)
return;
int codecId;
//Mpeg-4.2编码
codecId = VideoWriter.Fourcc('M', 'P', '4', '2');
//使用Mpeg-4.2来编码
VideoWriter vw = new VideoWriter("C:\\learnEmgucv\\saved-movie1.mp4", codecId, 25, new Size(640, 480), true);
Mat m = new Mat();
stopRecord = false;
while (stopRecord == false)
{
m = vc.QueryFrame();
if (m == null)
break;
if (m.IsEmpty)
break;
Mat mout = new Mat();
CvInvoke.Canny(m, mout, 160, 250, 3);
ImageBox1.Image = mout;
ImageBox1.Refresh();
//输出到文件
vw.Write(mout);
//需要增加doevents,否则会出现不响应
System.Windows.Forms.Application.DoEvents();
}
//必须释放资源
vc.Dispose ();
vw.Dispose();
Label1.Text = "保存完毕";
}
//停止录制摄像头视频
private void Button13_Click(object sender, EventArgs e)
{
stopRecord = true;
}
事实上,在录制摄像头视频时,即使在循环中加了Application.DoEvents(),程序运行时也会出现卡顿的情况。在实际中最好是在ImageGrabbed事件中进行处理。
【代码位置:frmChapter9_1】Button14_Click、vc3_ImageGrabbed、Button15_Click
VideoCapture vc3;
VideoWriter vw3;
//是否停止录制
Boolean stopRecord3 = false;
//调用ImageGrabbed进行录制视频
private void Button14_Click(object sender, EventArgs e)
{
vc3 = new VideoCapture(0);
if (vc3.IsOpened == false)
{
MessageBox.Show("打开摄像头失败");
return;
}
//stopRecord3 = false
int codecId;
//Mpeg-4.2编码
codecId = VideoWriter.Fourcc('M', 'P', '4', '2');
vw3 = new VideoWriter("C:\\learnEmgucv\\saved-movie2.mp4", codecId, 25, new Size(640, 480), true);
//添加ImageGrabbed事件
vc3.ImageGrabbed += vc3_ImageGrabbed;
//启动
vc3.Start();
}
//在ImageGrabbed事件里面进行录制视频
private void vc3_ImageGrabbed(object sender, EventArgs e)
{
Mat nextframe = new Mat();
if (stopRecord3 == true)
{
//取消事件
vc3.ImageGrabbed -= vc3_ImageGrabbed;
//释放资源
vc3.Dispose();
vw3.Dispose();
Label1.Text = "录制结束";
}
else
{
//获得视频图像
vc3.Retrieve(nextframe);
//输出
vw3.Write(nextframe);
ImageBox1.Image = nextframe;
System.Threading.Thread.Sleep(40);
}
}
private void Button15_Click(object sender, EventArgs e)
{
stopRecord3 = true;
}
【代码位置:frmChapter9_1】Button16_Click、getMask
//模拟实现绿幕视频和其他视频合并并输出
//1、为了简化说明,未采用在ImageGrabbed事件中进行处理
//2、为了取得更好的效果,在实际中还需要考虑对扣取的部分进行边缘处理
private void Button16_Click(object sender, EventArgs e)
{
//前景是一个绿幕视频
VideoCapture vc1 = new VideoCapture("c:\\learnEmgucv\\前景.wmv");
if (vc1.IsOpened == false)
{
MessageBox.Show("打开前景文件失败");
return;
}
//获得前景视频的帧率
Double fps1 = vc1.Get(CapProp.Fps);
//获得前景文件的帧数
int frames1 = (int)vc1.Get(CapProp.FrameCount);
//背景视频
VideoCapture vc2 = new VideoCapture("c:\\learnEmgucv\\背景.mp4");
if (vc2.IsOpened == false)
{
MessageBox.Show("打开背景文件失败");
return;
}
//获得背景视频的帧率
Double fps2 = vc2.Get(CapProp.Fps);
//获得背景文件的帧数
int frames2 = (int)vc2.Get(CapProp.FrameCount);
//输出编码,使用MPEG-4.3
int vfourcc = Emgu.CV.VideoWriter.Fourcc('D', 'I', 'V', '3');
VideoWriter vw = new VideoWriter("c:\\learnEmgucv\\output-movie.avi", vfourcc, 25, new Size(vc1.Width, vc1.Height), true);
//输出帧数为两个视频帧数相比较最小的
int maxframecount = frames1 > frames2 ? frames2 : frames1;
for (int i = 0; i < maxframecount; i++)
{
Console.WriteLine("处理:" + i);
//读取前景视频的一帧
Mat m1 = new Mat();
vc1.Read(m1);
Mat mmask1 = new Mat();
mmask1 = getMask(m1);
Mat mfront = new Mat();
CvInvoke.BitwiseAnd(m1, mmask1, mfront);
//读取背景视频的一帧
Mat m2 = new Mat();
vc2.Read(m2);
Mat mmask2 = new Mat();
mmask2 = ~mmask1;
Mat mback = new Mat();
CvInvoke.BitwiseAnd(m2, mmask2, mback);
Mat mout = new Mat();
mout = mfront + mback;
vw.Write(mout);
//代码会不定位置出现错误提示:
//OpenCV: Failed to allocate xxxxx bytes”
//错误的原因主要是提供的内存不足,无法加载更多数据。
//解决方法:
//有些网站提出需要切换到64位编译
//但是经过测试仍然会出现上述错误
//最好是把所有资源都释放了(如下)。经测试没有发生错误。
mmask1.Dispose();
mmask2.Dispose();
mfront.Dispose();
mback.Dispose();
mout.Dispose();
System.Threading.Thread.Sleep(40);
}
vc1.Dispose();
vc2.Dispose();
Label1.Text = "输出视频完成";
}
//将获得的图像根据颜色范围二值化。
private Mat getMask(Mat inputMat)
{
Mat mhsv = new Mat();
CvInvoke.CvtColor(inputMat, mhsv, ColorConversion.Bgr2Hsv);
//这里测试的是在这两个颜色范围之间
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);
return m3channel;
}
输出结果如下图所示:
图9-4 模拟绿幕抠图生成视频