作者在工作中偶然接触到了Emgu CV这个视觉处理封装包,并对它的具体功能做了比较全面的试验,为了方便广大C#程序员也能愉快的体验到视觉处理的乐趣,我决定通过一系列的文章和代码演示,来一步步的实现Emgu CV,或者说是OpenCV的基础功能。
由于作者代码水平有限,以及是一个视觉处理方便的业余爱好者,因此只能从门外汉的角度进行编程和文字描述,不足之处希望大家不要介意。另外介绍的代码、功能描述、章节划分,甚至是理论的介绍都有可能出现错误之处,请大家多多指正。
本系列文章适用于C#程序员,对计算机视觉有一定爱好,并且理论基础比较薄弱。提供的代码也仅限于相互交流和技术验证,请勿用于商业目的。
在计算机视觉处理领域,OpenCV可是说是大名鼎鼎。其用C++语言编写,它具有C ++,Python,Java和MATLAB接口,并支持Windows,Linux,Android和Mac OS,OpenCV主要倾向于实时视觉应用,并在可用时利用MMX和SSE指令, 如今也提供对于C#、Ch、Ruby,GO的支持(来自百度百科)。
对于 .NET 程序员来说,直接使用OpenCV很不方便。如果程序员想用WinForm或者WPF开发一些视觉应用的程序,此时就有两个选择,分别是OpenCvSharp和Emgu CV。
好像是一位日本程序员开发并维护的OpenCV封装包,具体的使用方法不在本系列课程的介绍范围内,据说是使用方式上更接近原始的OpenCV,有兴趣的朋友可以单独了解一下。其项目网址如下:
Github网址OpenCV wrapper for .NET. Contribute to shimat/opencvsharp development by creating an account on GitHub.https://github.com/shimat/opencvsharp
其官方介绍如下:Emgu CV is a cross platform .Net wrapper to the OpenCV image processing library. Allowing OpenCV functions to be called from .NET compatible languages. The wrapper can be compiled by Visual Studio and Unity, it can run on Windows, Linux, Mac OS, iOS and Android.
Emgu CV: OpenCV in .NET (C#, VB, C++ and more)https://emgu.com/其在版本的更新上,紧随OpenCV,截止到2023年12月11日,其最新版本为Emgu CV-4.8.0
从具体功能上来说,它实现了OpenCV应有的以下功能:
上面这个功能介绍也是我抄的,据说是有这些功能,但是我也还没有全部实现呢。
本系列课程都以WPF项目为例,来进行Emgu CV的演示,WinForm的使用方法应该差不多,读者可以自行试验。
首先创建WPF应用,基于.NET Framework 4.7.2
然后在NuGet中引用以下四个包:
Emgu.CV
Emgu.CV.runtime.windows
Emgu.CV.Bitmap
Emgu.CV.UI
引用并安装好NuGet包,WPF应用如下图所示:
Emgu CV用于图像分析,最简单的应用当然是读取出一副原始图片并显示。在实际过程中分为四种情况:
1、读取.jpg、.bmp、png类的图片文件;
2、读取本地.mp4类的视频文件并显示每一帧图像;
3、读取计算机的摄像头并显示;
4、读取网络上的视频文件并显示。
补充资料:视频其实就是在一秒内连续播放n幅图片,然后下一秒再不停的播放,直到结束为止。
在MainWindow.xmal中建立一个Image控件,代码如下:
窗体的Load事件中执行如下代码:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Image image = new Image("lena.jpg"); // 创建Image类的变量image,并从文件加载图片
Image1.Source = image.ToBitmapSource(); // Image控件显示加载到的图片
}
其实就定义一个Emgu CV内的Image类,加载本地 lena.jpg 文件,然后让WPF的Image1控件显示。运行项目,结果如下:
窗体的Load事件中要先建立一个VideoCapture类(Emgu CV中用于操作视频的类),用于打开本地 J20.mp4 文件,打开成功后,通过
ComponentDispatcher.ThreadIdle += new EventHandler(ProcessFrame)
去执行ProcessFrame函数内的代码。而ProcessFrame函数就是在Image1控件中显示出 J20.mp4 文件的每一帧,全部代码如下:
///
/// Window窗体加载
///
///
///
private void Window_Loaded(object sender, RoutedEventArgs e)
{
videoCapture?.Dispose(); // 先释放现有的VideoCapture类
videoCapture = new VideoCapture("J20.mp4");
// 进行异常判断
if (!videoCapture.IsOpened)
{
System.Windows.MessageBox.Show("流媒体视频加载出错!", "提示", (MessageBoxButton)MessageBoxButtons.OK);
return;
}
// 开始捕捉并显示每一帧
ComponentDispatcher.ThreadIdle += new EventHandler(ProcessFrame);
}
///
/// 显示每一帧.
///
/// 触发事件的控件对象,就是当前的对象.
/// 触发事件的控件对象,记录事件传递过来的额外信息.
private void ProcessFrame(object sender, EventArgs e)
{
try
{
// 进行异常判断,如果打不开流媒体,则终止视频分析
if (videoCapture == null || videoCapture.Ptr == IntPtr.Zero || !videoCapture.IsOpened)
{
return;
}
frame = videoCapture.QueryFrame();
if (frame == null)
{
return;
}
if (frame.IsEmpty)
{
return;
}
Image1.Source = frame.ToBitmapSource();
}
catch (Exception)
{
}
}
程序运行效果如下图所示,视频在播放时速度比利用VLC等软件播放的要快。原因是VLC会调整播放速度,比如一秒钟显示24帧,每一帧中间会有短暂的间隔;而上面的代码则是显示完一帧马上就显示下一帧,中间没有间隔。
显示本地文件的代码是
videoCapture = new VideoCapture("J20.mp4");
只需要改成
videoCapture = new VideoCapture(0);
就可以打开本地计算机的摄像头。可以简单的理解为:new VideoCapture(0) 的意思就是打开本地计算机的第0个摄像头。如果您的计算机上有多个外接摄像机,可以用
videoCapture = new VideoCapture(1);
videoCapture = new VideoCapture(2);
分别打开不同的外接摄像头。
5.2章节已经可以利用VideoCapture显示出本地文件,是这样写的
videoCapture = new VideoCapture(0);
此时,只需要把数字 0 ,换成网络视频的地址,就可以播放出对应的视频,代码及效果如下:
videoCapture?.Dispose(); // 先释放现有的VideoCapture类
videoCapture = new VideoCapture("http://gcalic.v.myalicdn.com/gc/sgns01_1/index.m3u8");
1、WPF的Image1控件显示时,或是 image.ToBitmapSource(),或是 Image1.Source = frame.ToBitmapSource()。这个.ToBitmapSource()方法来自于Emgu CV的一个官方文件,可以通过这个路径下载:
emgucv/Emgu.CV.NativeImage/BitmapSourceExtension.cs at master · emgucv/emgucv · GitHub
2、当Emgu CV要操作视频时,需要用到VideoCapture进行初始化。
这篇文章全部代码如下,MainWindow.xaml
MainWindow.xaml.cs
using Emgu.CV;
using System;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Interop;
namespace WpfApp1
{
///
/// MainWindow.xaml 的交互逻辑
///
public partial class MainWindow : Window
{
private static VideoCapture videoCapture = null;
private Mat frame; // 视频播放的n帧
public MainWindow()
{
InitializeComponent();
}
///
/// Windowc窗体加载
///
///
///
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// 1.显示图片文件
// Image image = new Image("lena.jpg"); // 创建Image类的变量image,并从文件加载图片
// Image1.Source = image.ToBitmapSource(); // Image控件显示加载到的图片
// 2.显示本地mp4文件
// videoCapture?.Dispose(); // 先释放现有的VideoCapture类
// videoCapture = new VideoCapture("J20.mp4");
// 3.显示本地计算机的摄像头
// videoCapture?.Dispose(); // 先释放现有的VideoCapture类
// videoCapture = new VideoCapture(0);
// 3.显示本地计算机的摄像头
videoCapture?.Dispose(); // 先释放现有的VideoCapture类
videoCapture = new VideoCapture("http://gcalic.v.myalicdn.com/gc/sgns01_1/index.m3u8");
// 进行异常判断
if (!videoCapture.IsOpened)
{
System.Windows.MessageBox.Show("流媒体视频加载出错!", "提示", (MessageBoxButton)MessageBoxButtons.OK);
return;
}
// 开始捕捉并显示每一帧
ComponentDispatcher.ThreadIdle += new EventHandler(ProcessFrame);
}
///
/// 显示每一帧.
///
/// 触发事件的控件对象,就是当前的对象.
/// 触发事件的控件对象,记录事件传递过来的额外信息.
private void ProcessFrame(object sender, EventArgs e)
{
try
{
// 进行异常判断,如果打不开流媒体,则终止视频分析
if (videoCapture == null || videoCapture.Ptr == IntPtr.Zero || !videoCapture.IsOpened)
{
return;
}
frame = videoCapture.QueryFrame();
if (frame == null)
{
return;
}
if (frame.IsEmpty)
{
return;
}
Image1.Source = frame.ToBitmapSource();
}
catch (Exception)
{
}
}
}
}
Emgu CV总体上使用起来比较简单,而且所有的函数都和OpenCV原始函数相差不多,接下来的文章会把几十个主要的函数,或者图像/视频处理方式介绍给大家。
原创不易,请勿抄袭。共同进步,相互学习。