65.9K
CodeProject 正在变化。 阅读更多。
Home

易于使用的 Intel OpenCV 库的包装器 (DLL) 及示例

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (64投票s)

2008年8月8日

CPOL

19分钟阅读

viewsIcon

1423221

downloadIcon

38899

本文介绍了英特尔OpenCV库的易用包装器及示例。

cvlib 版本: A.B.C

  • A = 主版本;在新版本(例如使用SWIG构建的版本)发布之前不会更改。
  • B = 次版本;添加新的子库(如ML或cvaux)时会更改。
  • C = 构建版本;每次发布新版本(任何更改)时都会更改。

最新版本是1.0.3。
请在此处查看频繁更新: 下载

Content

  1. 简介 - 英特尔OpenCV库和包装器
  2. 包装器的特性
  3. 环境和IDE
  4. 安装
    1. 包装器DLL的常用方式
    2. 示例二进制文件
    3. 示例源代码/Visual Studio项目
    4. 重新编译包装器库
  5. 开始 - 简短教程
  6. 重载代替默认参数
  7. 错误处理
  8. 封送处理 - P/Invoke
    1. 内部包装器函数调用
    2. 已使用的转换表
    3. cvtools - 类
  9. 像素和序列的低级访问
  10. 当前实现状态和已知问题
  11. 示例
    1. “BigSample”应用程序
    2. 光流示例
    3. 方块示例
  12. 历史
  13. 联系方式和频繁更新
  14. 个人事宜 :-) 

一、简介 - 英特尔OpenCV库和包装器

opencv.jpg

OpenCV 库(或开放计算机视觉库)是英特尔开发的一个开源计算机视觉库,于2002年首次发布到开源领域。它包含许多用于图像处理和机器视觉的算法和示例。一些关于资源和信息的最新链接是:

所呈现的包装器以动态链接库 (DLL) 的形式提供,使您能够从现代 .NET 语言 C# 中调用库函数。移植我的软件并不是最困难的障碍,但我对用于适配英特尔库所需的互操作机制并不熟悉。在网上搜索后,我找到了两种可能的解决方案。一方面是 SharperCV,另一方面是 EmGuCv。这两个项目都相当广泛,涵盖了丰富的功能集,我不得不承认我从中获得了许多提示,并也使用了一些小段代码。我决定编写自己的库的根本原因是为了更多地了解平台调用 (P/invoke),并创建一个可用的库,其中可以以“C 风格”方式编写方法调用。理想情况下,用户学习包装器应该尽可能少地付出努力,并且只需阅读原始的 OpenCV 文档,当然还有我的一些文档就足够了。

OpenCV 库拥有大量高度发达的算法(超过400个函数,数百个宏,无数常量等),因此一个人封装所有这些似乎几乎不可能。此外,还有大量的重载和默认值。我的想法是编写一个简单的HTML解析器,它一方面能够从OpenCV HTML文档中提取所有函数原型和描述,另一方面能够通过使用预定义规则生成必要的代码。通过这种方式,我可以节省大量时间,但由于我没有时间编写一个完美的转换器,并且生成的代码并不完美,因此需要大量额外的手工工作。
实际上,我学会了使用 swig 转换器,它将使我能够轻松包装像 ml(机器学习)这样的类。

二、包装器的特性

  • 该包装器以动态链接库 (DLL) 的形式提供。
  • 所有导出的方法(例如 cvCanny,...)、最重要的宏、构造函数、常量定义和回调函数都通过一个类可见。(例如: cvlib.FunctionName(...), cvlib.CV_LOAD_IMAGE_COLOR)。
  • 所有结构定义都以熟悉的方式使用(例如 IplImage img, CvMat mat)。
  • 一些特殊的函数参数通过 IntPtr 传递,并且有额外的转换支持(一个辅助工具库参见 cvtools)。
  • Bitmap 等绘图类型之间的转换已集成到库中,并且与 Bitmap 之间的强制转换运算符/函数也可用。
  • 对于错误处理,可以使用 OpenCV 库提供的机制。目前不支持异常(仍然不支持)。
  • 回调由预定义的委托处理。以熟悉的方式使用子窗口中的跟踪条、鼠标回调和错误处理程序
  • 用户必须负责释放非托管内存。这只需通过调用相应的 OpenCV 函数即可完成,例如 cvlib.CvReleaseImage(...)
  • 提供了大量的示例,并且由于反响热烈,到目前为止已经修复了许多错误。在此非常感谢社区。
  • 应要求,我现已添加了对迭代(例如,遍历 CvSeq 或树)和无需使用不安全代码的低级访问的支持。

三、环境和IDE

  • Visual Studio 2005 标准版
  • 版本 8.050727.762 (SP.050727-7600)
  • Microsoft .NET Framework 1.1. / 2.0 / 3.0

四、安装

我决定根据您想要做什么将此部分分为几个部分。因此,有些行只是简单地复制了。在任何情况下,都必须从 sourceforge 的官方网站下载原始的英特尔 OpenCV DLL,或者您可以使用提供的链接。

1. 包装器 DLL 的常见用法

  1. 从上面的链接下载“cvlib_bin.zip”和 openCV 库 (DLL),例如从这里下载。
  2. 将两个 zip 文件解压到您选择的文件夹中。
  3. 从文件夹中选择调试 DLL 或发布版本,并将其复制到您的项目 DebugRelease 文件夹。重要的是不要混合调试和发布版本。这可能会导致您的应用程序崩溃。
  4. 在您的 Visual Studio 项目引用选项卡中添加对包装器 DLL 的引用(如何操作:请参阅下面的第 3.6 节:示例源/Visual Studio 项目的用法)

2. 示例二进制文件

  1. 如果尚未完成,请从以上链接下载“cvlib_bin.zip”、“samples_bin.zip”以及 openCV 库 (DLL),例如从 这里 下载。将 zip 文件解压到您选择的文件夹中。对于包装器 DLL,您将为每个示例获得一个 DebugRelease 文件夹,外加一个 zip 文件。选择您想要的示例并解压。
  2. 将包装器 DLL 的发布版本和所有发布 openCV DLL 复制到包含 EXE 文件的示例文件夹中。
  3. 运行示例。如果您想尝试相机校准(在“bigexample 应用程序”中),EXE 文件夹旁边有一些图像。您可以将其用于教学和演示去畸变。

3. 示例源代码/Visual Studio 项目

  1. 如果尚未完成,请从以上链接下载“cvlib_bin.zip”、“samples_src.zip”以及 openCV 库 (DLL),例如从 这里 下载。将 zip 文件解压到您选择的文件夹中。对于包装器 DLL,您将为每个示例源代码获得一个 DebugRelease 文件夹,外加一个 zip 文件。选择您想要的示例并解压。每个示例源代码 zip 都包含一个完整的 Visual Studio 解决方案。
  2. 转到 Visual Studio 的项目文件夹,创建 bin\Debugbin\Release 目录。
  3. 将“cvlib_bin”中的 DLL 文件复制到您刚创建的 debugrelease 文件夹。如果您想读取由 VS intellisense 功能生成的方法工具提示消息,也请复制 XML 文件。
  4. 转到您的项目的工作室环境,查看左侧的引用,右键单击并选择“添加引用”。
  5. 从菜单中选择“浏览”选项卡,进入项目的 bin/debug 文件夹(取决于您是处于调试模式还是发布模式),只添加 cvlib.dll

    cvlib2.jpg

  6. 对于您自己的项目,您还需要在要使用该库的 *.cs 文件的顶部添加一个 using 引用(“using OpenCV ”)。

4. 重新编译包装器库

  1. 从以上链接下载“cvlib_src.zip”并解压到您选择的目录。
  2. 双击“openCV.csproj”文件打开 Visual Studio 项目。
  3. 根据需要更改代码。
  4. 在调试或发布模式下编译库。
  5. 别忘了向社区报告您的扩展或修复 :-)

五、开始 - 简短教程

对于本教程,如果您不想编写小型 GUI,可以使用下载中提供的“SmallExample” - Visual Studio 项目。该示例演示了如何在图片框控件和 OpenCV 图像窗口中实现一个简单的 Canny 边缘检测器及其图像输出。稍后将通过错误处理和跟踪条进行扩展,并将展示如何从外部 OpenCV 窗口检索鼠标坐标。

您的第一步是创建一个 Windows 窗体应用程序,并按照上述安装部分中的说明添加所有 DLL 和对包装器的引用。向您的窗体添加两个按钮和一个图片框控件。一个按钮用于退出应用程序,另一个按钮用于在应用程序中加载图像文件。从工具箱中添加一个“openFileDialog”控件。创建按钮的事件处理程序(通过双击控件)。在“打开文件”按钮事件处理程序中,添加以下代码:

private void buttonFile_Click(object sender, EventArgs e)
{
IplImage img
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
// Load image from file
img = cvlib.CvLoadImage(openFileDialog.FileName, cvlib.CV_LOAD_IMAGE_COLOR);
// Create external window
cvlib.CvNamedWindow("MyWindow", cvlib.CV_WINDOW_AUTOSIZE);
// Show the image in external Window
cvlib.CvShowImage( "MyWindow", ref img );
// Show the image additional in the Picture Box Control
this.pictureBox1.Image = (Bitmap)img;
}
}

假设 Visual Studio 代码生成器为文件对话框添加名称“openFileDialog1”。如您所见,使用库调用非常简单。输入前缀“cvlib.”,然后点击点后,可以从出现的列表中选择所需的操作(在启用了智能感知的 Visual Studio 中)。大多数 openCV 操作都以“cv”前缀开头。与包装器中的原始库调用不同,所有操作符都以大写字母开头。在文件事件处理程序中,我们从文件加载图像,创建一个外部窗口,并在该窗口中显示图像。我们经常通过引用关键字传递参数。这是因为 openCV 库期望指向该数据类型的指针,并希望对该对象执行读写操作。您可能注意到,目前我没有注意释放非托管内存(IplImagebitmap 图像的处置)。您有责任在每次需要释放图像(或任何其他非托管内存)时通过使用所需的调用来完成此操作(参见下文)。因为我们将图像保存在类变量中,所以我们将在窗体的 Close 事件中释放内存(此处未显示)。
在下一步中,我们将添加边缘检测器。但要实现此目标,我们首先必须创建一个灰度图像并应用颜色转换,并且我们将记住现在有一个需要释放的第二个图像。

private void buttonFile_Click(object sender, EventArgs e)
{
IplImage img, gray;
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
// Load image from file
img = cvlib.CvLoadImage(openFileDialog.FileName, cvlib.CV_LOAD_IMAGE_COLOR);
// create grey channel image
gray = cvlib.CvCreateImage(new CvSize(img.width, img.height), 
				(int)cvlib.IPL_DEPTH_8U, 1);
// color conversion
cvlib.CvCvtColor(ref img, ref gray, cvlib.CV_BGR2GRAY);
// apply the operator
cvlib.CvCanny(ref gray, ref gray, 100, 100, 3);
// Create external window
cvlib.CvNamedWindow("MyWindow", cvlib.CV_WINDOW_AUTOSIZE);
// Show the edge image in external Window
cvlib.CvShowImage( "MyWindow", ref gray );
// Show the input image in the Picture Box Control
this.pictureBox1.Image = (Bitmap)img;
}
}

现在您可以运行应用程序查看结果了。成功了吗?希望如此。

我们现在要向外部窗口添加一个跟踪条控件并接收鼠标事件。我们将在一个标签中显示鼠标坐标。所以请在您的窗体窗口的任意位置添加一个用于文本输出的标签控件。

由于 C# 语言使用所谓的委托来接收事件,因此我们必须遵循这些约定。在所呈现的 cvlib.dll 中,预定义的委托已经实现,因此只需实例化它即可。我们通过添加两个成员并在窗体构造函数中分配它们来完成此操作。

public partial class Form1 : Form
{
...
private cvlib.OnTrackbarChangeCallback onChange;
private cvlib.OnMouseCallback onMouse;
...
public Form1()
{
InitializeComponent();
...
onChange = new cvlib.OnTrackbarChangeCallback(OnChange);
onMouse = new cvlib.OnMouseCallback(OnMouse);
...
}
...
}

现在我们可以为回调函数添加 eventhandler。处理程序(方法名称)的名称不受限制。您可以随意选择一个名称。在示例中,我使用了“OnChange”和“OnMouse”。当 trackbar 事件发生时将调用“OnChange”,当鼠标移动时将调用“OnMouse”。

比事件处理程序的名称更重要的是它们的参数列表。您可以通过在 Visual Studio 中启用智能感知后键入开括号时弹出的行来查看,或者从 OpenCV 文档中获取。

// this Method is called when Trackbar value changed
private void OnChange(int value)
{
// Set the labels text with trackbar value
labelValue.Text = value.ToString();
}

// This Method is called when the mouse is moved around the child window
private void OnMouse(int evnt, int x, int y, int flags, IntPtr param)
{
labelValue.Text =
"Event: " + evnt.ToString() +

"\nx=" + x.ToString() +
"\ny=" + y.ToString() +
"\nFlags: " + flags.ToString();
}

例如,“x,y”变量将在鼠标每次移动时更新。
现在用以下几行扩展您的文件对话框方法

// Position of the trackbar
int value = 0;
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
...
// Create external window
cvlib.CvNamedWindow("MyWindow", cvlib.CV_WINDOW_AUTOSIZE);
// Create the trackbar
cvlib.CvCreateTrackbar
	("TrackbarName", "MyWindow", ref value, 100, onChange); //  -- add
// Set the mouse callback eventhandler
cvlib.CvSetMouseCallback("MyWindow", onMouse, IntPtr.Zero);    // < -- add
...
}

回调例程的标识符作为参数传递,不带 ref 关键字。TrackbarName 是标尺的开头string,并指定其功能。因为 OpenCV 通过strings 识别窗口之间的关系,所以窗口名称“MyWindow”是一个附加参数。在示例代码中,我将处理部分放在一个名为 Process() 的单独方法中,并将外部窗口的创建移到了窗体构造函数中。

六、重载代替默认参数

openVC 库的特点之一是支持重载和默认参数。这在 C# 中会导致问题,因为不支持默认参数,而且结构类型和默认值的组合数量可能很多。因此,我决定只支持少数几种组合,并为默认值添加了部分重载。也许当我有了更好的想法时,我会为无处不在的 CvArr 类型提供更好的支持。

以下示例演示如何为 CvMat 组件分配数据。

七、错误处理

OpenCV 库提供两种主要的、不同的方法来获取错误发生时的信息。第一种方法是查询错误状态,或者您可以在回调中获得有关错误的通知。此外,还有一种方法可以区分您是想继续程序执行还是暂停应用程序。

重要的是要知道,当程序内部终止时,可能会发生一些意想不到的行为,当然,如果您继续,也会发生这种情况,因为某些值是未定义的。OpenCV 不支持异常。因此,如果您放置一个 try-catch 块,并且在函数调用中发生错误,默认情况下会弹出一个消息框,并且根据您选择的按钮(取消、重试、忽略),应用程序在大多数情况下会退出或崩溃,并且 try-catch 块将被忽略。

您首先需要决定的是错误模式。您有三个选项(部分文本复制自 OpenCV 文档):

  • 叶子 (Leaf)
    调用错误处理程序后,程序将终止。这是默认值。它对于调试很有用,因为错误发生后会立即发出信号。然而,对于生产系统,其他两种方法可能更可取,因为它们提供更多的控制。
    您可以定义自己的错误处理程序或使用预定义的处理程序(参见下文)
  • Parent
    程序不会终止,但会调用错误处理程序。堆栈会展开(不使用 C++ 异常机制)。用户可以在调用 CxCore 函数后通过 cvlib.CvGetErrStatus() 检查错误代码并作出反应。
  • 静默 (Silent)
    类似于父模式,但不会调用错误处理程序。

如果您使用静默模式,您可以在调用函数后使用 cvlib.CvGetErrStatus() 进行检查并作出反应。为了定义您自己的错误处理程序(这是建议的),您必须通过使用 cvlib.RedirectError(...) 方法将错误重定向,并将您自己的错误处理程序作为参数传递。其用法类似于鼠标或跟踪条回调(带委托)。

以下代码片段演示了这些变体(使用上面给出的示例)。

public partial class Form1 : Form
{
// define the error delegate member
cvlib.OnErrorCallback onError;
// Constructor
public Form1()
{
InitializeComponent();
// create a instance and pass your error handler method name
onError = new cvlib.OnErrorCallbackDelegate(OnError);
// advise the CvLib to work in Parent mode (call error handler but don't exit)
cvlib.CvSetErrMode(cvlib.CV_ErrModeParent);
// redirect the error to use your error handler against the predefined handler
// (Message Box)
cvlib.CvRedirectError(onError);
}
private void buttonFile_Click(object sender, EventArgs e)
{
...
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
...
// apply the operator
cvlib.CvCanny(ref gray, ref gray, 100, 100, 3);
// check for error (variant 1)
if (cvlib.CvGetErrStatus() != 0)
{
// do some clean up
cvlib.CvReleaseImage(ref img);
// !! important set error status back
cvlib.CvSetErrStatus(0);
// return for example or throw exception
return;
}
}
...
}
// your error handler
private int OnError(int status, string func_name,
string err_msg, string file_name, int line)
{
MessageBox.Show(
"Status: " + cvlib.CvErrorStr(status) +
"\nIn Function: " + func_name +
"\nMessage: " + err_msg +
"\nIn File: " + file_name +
"\nOn Line: " + line.ToString(), "CV-Error", 
	MessageBoxButtons.OK, MessageBoxIcon.Error);
// a return value   0 means application is suspended, avoid this!!
return 0;
}

八、封送处理 - P/Invoke

1. 内部包装器函数调用

大多数包装器内部 DLL 调用都遵循简单的规则。如果被调用的函数返回一个结构指针,它会通过使用 Marshal.PointerToStructure 转换转换为所需的结构定义。返回的指针作为附加参数保存在结构定义中,并在处理相应的释放调用(如 cvlib.CvReleaseImage(...))时用于释放已分配的内存。所有基本数据类型,包括一维数组,都直接或通过引用传递。Strings 通过使用所需的约定(LPStr)进行转换。结构(如 IPL 图像结构)通过引用传递,以避免读/写访问冲突。Void* 指针通过 IntPtr 传递。要转换这些参数,请使用 cvtool 类中可用的函数。为了访问非托管函数,已使用 DllImportAttribute 类。由于此类的类只能处理 static 库函数,因此目前仍不支持 ml 类等类。以下几行是 highgui.cs 的简短摘录,用于说明。

/// <summary>
/// The function cvShowImage shows the image
/// in the specified window. If the window was created
/// with CV_WINDOW_AUTOSIZE flag then the image is shown
/// with its original size, otherwise the image is scaled
/// to fit the window.
/// </summary>
/// <param name="name">The name of the window.</param>
/// <param name="image">The image to be shown.</param>
public static void CvShowImage( string name, ref IplImage image)
{
  cvShowImage(name, ref image);
}
[DllImport(HIGHGUI_LIBRARY)]

private static extern void cvShowImage([MarshalAs(UnmanagedType.LPStr)] 
					String name, ref IplImage image);

2. 已使用的转换表

该表显示了在 C# 中声明 OpneCV API 类型时使用的类比。它仅考虑 OpenCV 库方法调用中的参数,尚未对常见的 Windows API 调用进行测试。所解释的是参数是如何传递的,并提供了何时可以使用 cvtool 类进行适配的提示。

API-类型 在 C# 中作为 (输入和/或输出参数) 传递 解释/提示
int, uint, uint64, float, double int, uint, UInt64, float, double uint64 = cvRNG 是随机数生成器的种子
int* ref int, int[] ref 用于读/写单个值,int[] 用于读/写一维数组
float* ref float, float[] ref 用于读/写单个值,float[] 用于读/写一维数组
结构 结构体 对于只包含原始datatypes(用于读取,如CvPoint)的struct
结构体* ref 结构体, IntPtr 对于包含指针的结构(如 IplImage (ref))和结构数组(应使用 cvtools 进行转换)。
结构体** ref IntPtr 通常情况下,数据将被释放。
char* [MarshalAs(UnmanagedType.LPStr)] String CharSet = CharSet.Ansi。在函数读取 string 的情况下。
char* ref StringBuilder CharSet = CharSet.Ansi。在函数分配 string 并返回它的情况下。
void* IntPtr 使用 cvtools.Convert1DArrayToPtr() 或所需的函数
void** ref IntPtr 使用 cvtools.Convert2DArrayToPtr()。或者使用 ref 关键字传递结构。

还有额外的返回值转换。大多数情况下,使用了 Marshal.PointerToStructure 转换,或者返回值未经更改地传递给调用者。Microsoft 在 .NET Framework 文档中,在关键词“Programing with .NET Framework”、“Communication with unmanaged code”、“Interop-Marshalling”、“Marshalling data with Platform invoke”和“Datatypes for Platform invoke”下描述了这些类比。

3. cvtool 类

通过使用 cvtool 类的方法,处理 C 指针类型非常简单。对于大多数方法,非托管内存中值的变化将在托管环境中可见。以下列表显示了可用的转换例程。

  • Convert2DArrToPtr(byte[][] arr, out GCHandle[] handles)
  • Convert2DArrToPtr(int[][] arr, out GCHandle[] handles)
  • Convert2DArrToPtr(float[][] arr, out GCHandle[] handles)
  • Convert2DArrToPtr(double[][] arr, out GCHandle[] handles)
  • Convert1DArrToPtr(byte[] arr, out GCHandle handle)
  • Convert1DArrToPtr(int[] arr, out GCHandle handle)
  • Convert1DArrToPtr(float[] arr, out GCHandle handle)
  • Convert1DArrToPtr(double[] arr, out GCHandle handle)
  • Convert1DArrToPtr(CvPoint2D32f[] arr, out GCHandle handle)
  • ConvertStructureToPtr(object structure, out GCHandle handle)
  • ConvertPtrToStructure(IntPtr p, Type typeOfStructure)
  • ConvertPtrToArray(IntPtr data, byte[] arr)
  • ConvertPtrToArray(IntPtr data, float[] arr)
  • ConvertPtrToArray(IntPtr data, double[] arr)
  • ConvertPtrToArray(IntPtr data, int[] arr)
  • ReleaseHandels(GCHandle[] h)
  • ReleaseHandel(GCHandle h)

九、像素和序列的低级访问

低级操作在下一个示例中进行说明。它将展示如何通过使用宏或通过直接访问原始数据(使用安全或不安全代码)来访问和操作图像/矩阵数据。对于不安全操作,必须在 Visual Studio 中启用此选项。您可以通过单击项目选项“构建”选项卡中的“允许不安全代码”复选框来完成此操作。为了访问原始数据,OpenCV 库提供了一些宏。这些宏是

  • CvPtr*D
  • CvSet*D
  • CvGet*D
  • CvGetReal*D
  • CvSetReal*D

以下是使用其中一个宏读取/写入数据的第一个示例

// Create a matrix
CvMat m = cvlib.CvCreateMat(3, 3, cvlib.CV_64FC1);
// set data
cvlib.CvSetReal2D(ref m, 0, 0, 12.4);
cvlib.CvSetReal2D(ref m, 0, 1, 2.4);
...
// read data at index  0, 0
double res = cvlib.CvGetReal2D(ref m, 0, 0);

第二个例子展示了如何为 CvMat 组件分配数据。请注意,cvtools 类的成员会输出一个连接内存的句柄。在不再需要内存之前,请勿提前释放该句柄。释放句柄后,内存可供垃圾回收器使用。

// a array with doubles
double[] a = { 1.1, 2.2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };

// create a matrix with 3 rows and 4 columns
CvMat ma = cvlib.CvCreateMat(3, 4, cvlib.CV_64FC1);

// as long as the handle (h) is not disposed,
// the garbage collector will not touch the double array
GCHandle h;

// fill our matrix with data
cvlib.CvInitMatHeader(ref ma, 3, 4, cvlib.CV_64FC1, 
	cvtools.Convert1DArrToPtr(a, out h), cvlib.CV_AUTO_STEP);

//...
// do anything with the matrix
//...

// dispose
cvtools.ReleaseHandel(h);

现在我们想要迭代一个序列。下一个示例源代码展示了如何实现此目标。

// Create a Sequence , say that will be a set of contours
CvSeq contours = new CvSeq();

// Get a pointer to the sequence
GCHandle h;
IntPtr p = cvtools.ConvertStructureToPtr(contours, out h);

// Do anything with that pointer for example find contours
cvlib.CvFindContours(ref gray, ref storage, ref p, 
	Marshal.SizeOf(typeof(CvContour)),
cvlib.CV_RETR_LIST, cvlib.CV_CHAIN_APPROX_SIMPLE, new CvPoint(0, 0));

// iterate
while (p != IntPtr.Zero)
{
  // Do anything with the partial contour for example approximate polygonal  
  CvSeq result = cvlib.CvApproxPoly
		(p, Marshal.SizeOf(typeof(CvContour)), ref storage,
    cvlib.CV_POLY_APPROX_DP, 10, 0);

  // Get the next contour element
  contours = (CvSeq)cvtools.ConvertPtrToStructure(p, typeof(CvSeq));
  p = contours.h_next;
}

cvtools.ReleaseHandel(h);

最后,我们将展示如何通过不安全访问来给图像区域着色。假设我们有一个彩色图像 (IplImage image),并想用 P(10, 10) 处左上角、宽度和高度均为 (100, 100) 的矩形区域填充它。图像数据基地址由 imageData IntPtr 类给出。我们将通过偏移此基地址来获取实际的写入数据地址。这类似于:image[y * imageWidthStep + x]。由于装箱或 ROI,imageWidthStep 可能比实际图像宽度短。

unsafe
{
...
// get base address
int baseAddress = image.imageData.ToInt32();
// iterate
for (int x = 10; x   100; x++)
{
for (int y = 10; y   100; y++)
{
byte* dst =  (((byte*)(baseAddress + image.widthStep * y))[x * 3]);
dst[0] = 0; dst[1] = 0; dst[2] = 255;
}
}
...
}

至少是一个与上述类似但没有不安全访问且读取浮点数的例子。请注意“4 * x”,这意味着 float 数据类型在内部由 4 字节数据表示。

...
// get base address
int baseAddress = image.imageData.ToInt32();

// iterate
for (int x = 10; x   100; x++)
{
  for (int y = 10; y   100; y++)
  {
    float result = (float)Marshal.PtrToStructure(new IntPtr(baseAddress +
			baseAddress .widthStep * y + 4 * x), typeof(float));

  }
}
...

十、当前实施状态和已知问题

  • cxcore 最完整
  • cv 最完整
  • highgui 最完整
  • 机器学习cvaux 待开发

Carsten C 报告了 CvMemStorage 释放问题

for (int i = 0; i   100000; i++)
{
  CvMemStorage storage = cvlib.CvCreateMemStorage(0);
  CvSeq sequence = cvlib.CvCreateSeq(0, Marshal.SizeOf(typeof (CvSeq)), 
			Marshal.SizeOf(typeof (CvPoint)), ref storage);
  // Do some funny things with the sequence
  cvlib.CvReleaseMemStorage(ref storage); //  == does actually not release sequence
}

解决方案在此(感谢 Carsten)

CvMemStorage parentStorage = cvlib.CvCreateMemStorage(0);
for (int i = 0; i   100000; i++)
{
    CvMemStorage storage = cvlib.CvCreateChildMemStorage(ref parentStorage);
    CvSeq sequence = cvlib.CvCreateSeq(0, Marshal.SizeOf(typeof(CvSeq)), 
			Marshal.SizeOf(typeof(CvPoint)), ref storage);
    // Do some funny things with the sequence
    cvlib.CvClearMemStorage(ref storage);    //  == needed
    cvlib.CvReleaseMemStorage(ref storage);  //  == seems not to be needed
}
cvlib.CvReleaseMemStorage(ref parentStorage);

十一、示例

1. “BigSample”应用程序

cvlib.jpg

特点

  • 加载和保存 OpenCV 库支持的不同图像格式的图像。
  • 从一套直接改编自最新 OpenCV 软件包附带示例的图像处理运算符中进行选择。
  • 使用 HighGUI 函数显示来自网络摄像头或文件的实时视频。简单的实时处理您自己的代码。
  • 了解如何实现一个非模态动态可配置对话框

包含的图像处理示例

  • Canny 边缘检测器
  • 膨胀滤镜
  • 腐蚀滤镜
  • Harris 角点检测器
  • Sobel 滤镜
  • 拉普拉斯滤镜
  • 分水岭变换
  • 金字塔分割
  • 校准(教学)
  • 校准(修正)
  • HAAR 分类器(人脸)
  • 轮廓查找器
  • 霍夫线
  • 霍夫圆
  • Convert2Gray
  • 线近似
  • 椭圆近似
  • 几种直方图
  • 直方图均衡化
  • 色相-饱和度直方图
  • 好特征跟踪
  • 奇异值变换
  • 矩阵乘法
  • 透视变换
  • 随机数生成器演示
  • 所有矩和胡矩
  • 泛洪填充
  • 模板匹配

工作原理

所提供的应用程序旨在作为参考或作为构建您自己的应用程序的良好起点。在 mainForm.cs 文件中,所有属性和方法都按区域排列。在 Visual Studio IDE 中,您应该首先折叠所有定义以获得良好的概览。最重要的是,定义包括:

  • 进程运行
  • 进程菜单
  • 视频开始/停止/计时器

在“Process Run”下,您会找到当“Run”按钮被按下时执行的 eventhandlerif 构造检查“operations”组合框中实际选定的条目,并分支到相应的图像处理方法。

“操作”组合框的 SelectedIndexChanged 方法已放置在“进程菜单”区域中。每次索引更改时,此方法都会创建一个动态对话框,其中包含与所选图像处理任务相对应的控件。

对于每个图像处理操作,我都编写了一个单独的类,放置在不同的文件中。因此,查找相应的代码以便重用非常简单。每个函数只接受一个参数,即当前加载的 IPL 格式图像。根据所选模式,操作的结果图像将显示在新选项卡或当前选项卡页中。

十二、历史

  • v 1.0.0, 2008年8月: 库首次发布
  • v 1.0.3, 2009年2月: 库更新,新增示例,CodeProject文章更新

十三、联系方式和频繁更新

  • cvlib@cardijn.zzn.com
  • 主页

十四、个人事宜

如果您在图像处理领域的任何项目需要帮助、建议或协助,请随时与我联系...(请使用上述电子邮件)。

© . All rights reserved.