【 Emgu CV教程】1.1、Emgu CV 简介及使用
作者在工作中偶然接触到了Emgu CV这个视觉处理封装包,并对它的具体功能做了比较全面的试验,为了方便广大C#程序员也能愉快的体验到视觉处理的乐趣,我决定通过一系列的文章和代码演示,来一步步的实现Emgu CV,或者说是OpenCV的基础功能。
由于作者代码水平有限,以及是一个视觉处理方便的业余爱好者,因此只能从门外汉的角度进行编程和文字描述,不足之处希望大家不要介意。另外介绍的代码、功能描述、章节划分,甚至是理论的介绍都有可能出现错误之处,请大家多多指正。
本系列文章适用于C#程序员,对计算机视觉有一定爱好,并且理论基础比较薄弱。提供的代码也仅限于相互交流和技术验证,请勿用于商业目的。
1、OpenCV
在计算机视觉处理领域,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。
2、OpenCvSharp
好像是一位日本程序员开发并维护的OpenCV封装包,具体的使用方法不在本系列课程的介绍范围内,据说是使用方式上更接近原始的OpenCV,有兴趣的朋友可以单独了解一下。其项目网址如下:
3、Emgu CV
其官方介绍如下: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应有的以下功能:
- 图像处理,比如滤波、形态学处理、二值化、色彩空间转换、图像增强等。
- 计算机视觉:支持各种计算机视觉算法,如特征提取、目标检测、跟踪、人脸识别等。
- 模式识别:提供了模式识别算法,如支持向量机(SVM)、随机森林等。
- 深度学习:支持深度学习算法,如卷积神经网络(CNN)、循环神经网络(RNN)等。
- OCR:可以结合Tesseract OCR引擎实现光学字符识别(OCR)功能,将图像中的文字转换为可编辑和可搜索的文本。
- 视频处理:提供了视频处理功能,如视频帧提取、视频编解码等。
- 几何变换:支持各种几何变换算法,如平移、旋转、缩放等。
- 图像分割:提供了各种图像分割算法,如基于阈值的分割、基于区域的分割等。
上面这个功能介绍也是我抄的,据说是有这些功能,但是我也还没有全部实现呢。
4、WPF项目引用Emgu CV
本系列课程都以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应用如下图所示:
5、简单应用
Emgu CV用于图像分析,最简单的应用当然是读取出一副原始图片并显示。在实际过程中分为四种情况:
1、读取.jpg、.bmp、png类的图片文件;
2、读取本地.mp4类的视频文件并显示每一帧图像;
3、读取计算机的摄像头并显示;
4、读取网络上的视频文件并显示。
补充资料:视频其实就是在一秒内连续播放n幅图片,然后下一秒再不停的播放,直到结束为止。
5.1、显示图片文件
在MainWindow.xmal中建立一个Image控件,代码如下:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Image Name="Image1" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="1" HorizontalAlignment="Left" Margin="10,10,10,10" VerticalAlignment="Top"/>
</Grid>
窗体的Load事件中执行如下代码:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Image<Bgr, Byte> image = new Image<Bgr, byte>("lena.jpg"); // 创建Image类的变量image,并从文件加载图片
Image1.Source = image.ToBitmapSource(); // Image控件显示加载到的图片
}
其实就定义一个Emgu CV内的Image类,加载本地 lena.jpg 文件,然后让WPF的Image1控件显示。运行项目,结果如下:
5.2、显示本地mp4文件
窗体的Load事件中要先建立一个VideoCapture类(Emgu CV中用于操作视频的类),用于打开本地 J20.mp4 文件,打开成功后,通过
ComponentDispatcher.ThreadIdle += new EventHandler(ProcessFrame)
去执行ProcessFrame函数内的代码。而ProcessFrame函数就是在Image1控件中显示出 J20.mp4 文件的每一帧,全部代码如下:
/// <summary>
/// Window窗体加载
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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);
}
/// <summary>
/// 显示每一帧.
/// </summary>
/// <param name="sender">触发事件的控件对象,就是当前的对象.</param>
/// <param name="e">触发事件的控件对象,记录事件传递过来的额外信息.</param>
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帧,每一帧中间会有短暂的间隔;而上面的代码则是显示完一帧马上就显示下一帧,中间没有间隔。
5.3、显示本地计算机的摄像头
显示本地文件的代码是
videoCapture = new VideoCapture("J20.mp4");
只需要改成
videoCapture = new VideoCapture(0);
就可以打开本地计算机的摄像头。可以简单的理解为:new VideoCapture(0) 的意思就是打开本地计算机的第0个摄像头。如果您的计算机上有多个外接摄像机,可以用
videoCapture = new VideoCapture(1);
videoCapture = new VideoCapture(2);
分别打开不同的外接摄像头。
5.3、读取网络上的视频文件并显示
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进行初始化。
- 如果是本地文件,就在括号内写文件路径。
- 如果是计算机连接的外接摄像头,就在括号内写摄像头的数字编号(编号顺序和规则是什么,作者不知道,您可以试一试)。
- 如果是网络视频,就在括号内写视频地址,RTSP、RTMP、HLS各种格式的都可以播放。
这篇文章全部代码如下,MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="540" Width="800" Loaded="Window_Loaded">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Image Name="Image1" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="1" HorizontalAlignment="Left" Margin="10,10,10,10" VerticalAlignment="Top"/>
</Grid>
</Window>
MainWindow.xaml.cs
using Emgu.CV;
using System;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Interop;
namespace WpfApp1
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
private static VideoCapture videoCapture = null;
private Mat frame; // 视频播放的n帧
public MainWindow()
{
InitializeComponent();
}
/// <summary>
/// Windowc窗体加载
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// 1.显示图片文件
// Image<Bgr, Byte> image = new Image<Bgr, byte>("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);
}
/// <summary>
/// 显示每一帧.
/// </summary>
/// <param name="sender">触发事件的控件对象,就是当前的对象.</param>
/// <param name="e">触发事件的控件对象,记录事件传递过来的额外信息.</param>
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原始函数相差不多,接下来的文章会把几十个主要的函数,或者图像/视频处理方式介绍给大家。
原创不易,请勿抄袭。共同进步,相互学习。
更多推荐
所有评论(0)