先確保您已經(jīng)安裝了OpenCV庫,到鏈接下載:
https://opencv.org/releases/

C#調(diào)用OpenCV函數(shù)的實現(xiàn)步驟:
1、C++編寫調(diào)用OpenCV函數(shù)的方法,編譯成dll;
2、C#引用C++生成的dll,調(diào)用其中的方法。
詳細步驟如下:
1、C++編寫調(diào)用OpenCV函數(shù)的方法,編譯成dll
C++生成dll的實現(xiàn)方法可參考文章C++ 如何生成一個DLL動態(tài)鏈接庫
a.新建一個Visual C++的空項目,名稱為OpenCVCPPDLL


b.在界面右邊目錄(頭文件)右鍵添加>>新建項>>代碼>>頭文件.h:OpenCVMethod.h,


編寫代碼如下:
#pragma once
//這段代碼是防止報錯——“鏈接規(guī)范與前面的xx不兼容”
#undef _M_CEE
#include<opencv2\opencv.hpp>
#define _M_CEE
//導(dǎo)入圖片路徑,傳遞圖片數(shù)據(jù)供C#使用
//imagePath圖片文件完整的路徑名,data圖片數(shù)據(jù)輸出,size圖片數(shù)據(jù)大小輸出
extern "C" _declspec(dllexport) void loadImage(const wchar_t* imagePath, uchar *data, size_t& size);
//導(dǎo)入圖片路徑,處理圖片,把灰度值大于該閾值的像素變?yōu)?55(0黑,255白)
//imagePath圖片文件完整的路徑名,thresh閾值門限,data圖片數(shù)據(jù)輸出,size圖片數(shù)據(jù)大小輸出
extern "C" _declspec(dllexport) void threshold(const wchar_t* imagePath, double thresh, uchar *data, size_t& size);
//導(dǎo)入圖片數(shù)據(jù),把灰度值大于該閾值的像素變?yōu)?55(0黑,255白)
//dataIn圖片數(shù)據(jù)輸入,sizeIn圖片數(shù)據(jù)大小,thresh閾值門限,dataOut圖片數(shù)據(jù)輸出,size圖片數(shù)據(jù)大小輸出
extern "C" _declspec(dllexport) void threshold1(uchar *dataIn, size_t sizeIn, double thresh, uchar *dataOut, size_t& size);
//_decspec(dllexport)將函數(shù)聲名為導(dǎo)出函數(shù)被其他程序調(diào)。
//extern "C" _declspec(dllexport)的目的是為了使用DllImport調(diào)用非托管C++的DLL文件。因為使用DllImport只能調(diào)用由C語言函數(shù)做的DLL。
c.在界面右邊目錄(源文件)右鍵添加>>新建項>>代碼>>C++文件.cpp:OpenCVMethod.cpp
編寫代碼如下
#include "OpenCVMethod.h"
#include<opencv2\opencv.hpp>
#include<windows.h>
using namespace std;
using namespace cv;
//導(dǎo)入圖片路徑,傳遞圖片數(shù)據(jù)供C#使用
//imagePath圖片文件完整的路徑名,data圖片數(shù)據(jù)輸出,size圖片數(shù)據(jù)大小輸出
void loadImage(const wchar_t * imagePath, uchar * data, size_t & size)
{
if (imagePath != NULL)
{
//1.把文件路徑轉(zhuǎn)化為OpenCV的函數(shù)可識別的格式
//string與wchar_t*轉(zhuǎn)換
//第一次調(diào)用確認轉(zhuǎn)換后單字節(jié)字符串的長度,用于開辟空間
int pathSize = WideCharToMultiByte(CP_OEMCP, 0, imagePath, wcslen(imagePath), NULL, 0, NULL, NULL);
char* imagePathChar = new char[pathSize + 1];
//第二次調(diào)用將雙字節(jié)字符串轉(zhuǎn)換成單字節(jié)字符串
WideCharToMultiByte(CP_OEMCP, 0, imagePath, wcslen(imagePath), imagePathChar, pathSize, NULL, NULL);
imagePathChar[pathSize] = '\0';
string pattern = imagePathChar;
delete imagePathChar;//釋放內(nèi)存
//2.根據(jù)圖片路徑,讀取圖片數(shù)據(jù)到OpenCV的Mat
Mat image = imread(pattern);
//3.轉(zhuǎn)換Mat為C#可識別的byte[]數(shù)據(jù)輸出
vector<uchar> buf;
imencode(".bmp", image, buf);//將Mat以bmp格式存入內(nèi)存中,轉(zhuǎn)換為uchar數(shù)組
size = buf.size();
for each (uchar var in buf)//將buf拷貝到C#的輸出byte[]內(nèi)存中
{
*data = var;
data++;
}
}
}
//導(dǎo)入圖片路徑,處理圖片,把灰度值大于該閾值的像素變?yōu)?55(0黑,255白)
void threshold(const wchar_t * imagePath, double thresh, uchar * data, size_t & size)
{
if (imagePath != NULL)
{
//1.把文件路徑轉(zhuǎn)化為OpenCV的函數(shù)可識別的格式
string pattern = "";
//第一次調(diào)用確認轉(zhuǎn)換后單字節(jié)字符串的長度,用于開辟空間
int pathSize = WideCharToMultiByte(CP_OEMCP, 0, imagePath, wcslen(imagePath), NULL, 0, NULL, NULL);
char* imagePathChar = new char[pathSize + 1];
//第二次調(diào)用將雙字節(jié)字符串轉(zhuǎn)換成單字節(jié)字符串
WideCharToMultiByte(CP_OEMCP, 0, imagePath, wcslen(imagePath), imagePathChar, pathSize, NULL, NULL);
imagePathChar[pathSize] = '\0';
pattern = imagePathChar;
delete imagePathChar;//釋放內(nèi)存
//2.讀取圖片數(shù)據(jù)到Mat中
Mat sourceMat = imread(pattern);//圖片處理前的Mat
Mat destMat;//圖片處理后的Mat
//3.調(diào)用OpenCV函數(shù)進行處理
threshold(sourceMat, destMat, thresh, 255, THRESH_BINARY);
//4.將Mat轉(zhuǎn)化為C#可識別的byte[]格式數(shù)據(jù)
vector<uchar> buf;
imencode(".bmp", destMat, buf);//將Mat以bmp格式存入內(nèi)存中,轉(zhuǎn)換為uchar數(shù)組
size = buf.size();
for each (uchar var in buf)//將buf拷貝到C#的輸出byte[]內(nèi)存中
{
*data = var;
data++;
}
}
}
//導(dǎo)入圖片數(shù)據(jù),把灰度值大于該閾值的像素變?yōu)?55(0黑,255白)
//dataIn圖片數(shù)據(jù)輸入,sizeIn圖片數(shù)據(jù)大小,thresh閾值門限,dataOut圖片數(shù)據(jù)輸出,size圖片數(shù)據(jù)大小輸出
void threshold1(uchar *dataIn, size_t sizeIn, double thresh, uchar * dataOut, size_t & size)
{
//1.把C#輸入的byte數(shù)組數(shù)據(jù)拷貝到bufIn中,通過cv::imdecode方法進而轉(zhuǎn)化為OpenCV的Mat
vector<uchar> bufIn;
for (size_t i = 0; i < sizeIn; i++)
{
bufIn.push_back(dataIn[i]);
}
Mat sourceMat = cv::imdecode(bufIn,1);//與cv::imread方法中的額flags=1一致,圖片處理前的Mat
Mat destMat;//圖片處理后的Mat
//2.調(diào)用OpenCV中的函數(shù),cv::threshold
cv::threshold(sourceMat, destMat, thresh, 255, THRESH_BINARY);
//3.將OpenCV的Mat轉(zhuǎn)化為C#可識別的byte[]內(nèi)存中
vector<uchar> buf;
cv::imencode(".bmp", destMat, buf);//將Mat以bmp格式存入內(nèi)存中,轉(zhuǎn)換為uchar數(shù)組
size = buf.size();
for each (uchar var in buf)//將buf拷貝到C#的輸出byte[]內(nèi)存中
{
*dataOut = var;
dataOut++;
}
}
d.在界面右邊目錄(源文件)右鍵添加>>新建項>>代碼>>模塊定義文件.def:OpenCVMethod.def

編寫代碼如下:
LIBRARY "OpenCVMethod"
EXPORTS
;loadImage函數(shù)
loadImage
;threshold函數(shù)
threshold
;threshold1函數(shù)
threshold1
e.編譯生成dll之前需要先安裝OpenCV庫,并部署好環(huán)境變量,其實現(xiàn)方法可參考文章OpenCV安裝部署詳細教程
f.在界面右邊項目文件OpenCVCPPDLL右鍵選擇屬性>>配置屬性>>常規(guī)>>目標(biāo)文件擴展名的.exe修改為為.dll,解決方案平臺為x64(因為OpenCV庫有用到64),編譯生成,之后在...\OpenCVCPPDLL\x64\Debug目錄下找到生成的dll。



二、C#引用C++生成的dll,調(diào)用其中的方法
a.創(chuàng)建一個C#的窗體程序OpenCVCSharp.sln,打開VS,新建項目>>Visual C#>>Windows窗體應(yīng)用程序
b.界面放置兩個PictureBox控件
c.雙擊窗體,編寫代碼如下:
using System;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace OpenCVCSharp
{
public partial class Form1 : Form
{
/// <summary>
/// 外部調(diào)用C++的dll,導(dǎo)入圖片路徑,通過Mat傳遞數(shù)據(jù)到C#
/// </summary>
/// <param name="imagePath">圖片完整路徑</param>
/// <param name="data">圖片數(shù)據(jù)輸出</param>
/// <param name="size">圖片數(shù)據(jù)大小輸出</param>
[DllImport("OpenCVCPPDLL.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
private extern static void loadImage(string imagePath, ref byte data, out ulong size);
/// <summary>
/// 外部調(diào)用C++的dll,導(dǎo)入圖片路徑,處理圖片,把灰度值大于該閾值的像素灰度值改為255(0黑,255白)
/// </summary>
/// <param name="imagePath">圖片完整路徑</param>
/// <param name="thresh">閾值門限</param>
/// <param name="data">圖片數(shù)據(jù)輸出</param>
/// <param name="size">圖片數(shù)據(jù)大小輸出</param>
[DllImport("OpenCVCPPDLL.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
private extern static void threshold(string imagePath, double thresh, ref byte data, out ulong size);
/// <summary>
/// 外部調(diào)用C++的dll,導(dǎo)入圖片數(shù)據(jù)及大小,處理圖片,把灰度值大于該閾值的像素灰度值改為255(0黑,255白)
/// </summary>
/// <param name="dataIn">圖片數(shù)據(jù)輸入</param>
/// <param name="sizeIn">圖片數(shù)據(jù)大小輸入</param>
/// <param name="thresh">閾值門限</param>
/// <param name="data">圖片數(shù)據(jù)輸出</param>
/// <param name="size">圖片數(shù)據(jù)大小輸出</param>
[DllImport("OpenCVCPPDLL.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
private extern static void threshold1(byte[] dataIn, ulong sizeIn, double thresh, ref byte data, out ulong size);
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
//圖片文件路徑
string picPath = @"D:\Work\OpenCVCPPCSharp\image\testPic.png";
//盡可能大的byte[],存圖片數(shù)據(jù)
byte[] ptrData = new byte[2048 * 2048 * 3];
//存儲byte的長度
ulong size = new ulong();
//導(dǎo)入圖片路徑,通過Mat傳遞數(shù)據(jù)到C#,將C++的內(nèi)存數(shù)據(jù)存入C#的內(nèi)存中
loadImage(picPath, ref ptrData[0], out size);
//將byte[]轉(zhuǎn)化為MemoryStream再傳遞給控件image顯示
pictureBox1.Image = Image.FromStream(new MemoryStream(ptrData, 0, (int)size));
//自適應(yīng)控件窗口
pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
//盡可能大的byte[],存圖片數(shù)據(jù)
byte[] ptrData1 = new byte[2048 * 2048 * 3];
//存儲byte的長度
ulong size1 = new ulong();
////根據(jù)圖片路徑輸入處理圖片
//threshold(picPath, 80, ref ptrData1[0], out size1);//212814
//根據(jù)圖片數(shù)據(jù)輸入處理圖片
threshold1(ptrData,(ulong)size, 80, ref ptrData1[0], out size1);
//將byte[]轉(zhuǎn)化為MemoryStream再傳遞給控件image顯示
pictureBox2.Image = Image.FromStream(new MemoryStream(ptrData1, 0, (int)size1));
//自適應(yīng)控件窗口
pictureBox2.SizeMode = PictureBoxSizeMode.StretchImage;
}
}
}
d.同樣配置解決方案平臺為x64,編譯生成;
e.在目錄...\OpenCVCPPDLL\x64\Debug中拷貝C++生成的OpenCVCPPDLL.dll到 目錄...\OpenCVCSharp\OpenCVCSharp\bin\x64\Debug下,運行C#程序效果如下圖:

至此,C#調(diào)用OpenCV函數(shù)庫的方法完成。歡迎大家提出問題一起探討。
總結(jié)本文技術(shù)要點:
1.C++生成dll文件方法;
2.C++OpenCV的Mat與C#的Image互換;
3.C++中方法的輸入?yún)?shù)、輸出參數(shù)的實現(xiàn)方式;
4.C++OpenCV的環(huán)境配置;
5.OpenCV方法imencode與imdecode的使用;
6.C#調(diào)用C++非托管dll的實現(xiàn)方法;