How to Create an OpenCV C++ DLL and To be Used in C# Windows Form Project?
如果你跟我一樣:
1. 有點厭倦使用EmguCV, 理由很簡單: 因為當你google 關鍵字OpenCV找到的範例絕大部分應該是C++為大宗,
所以你得即時翻譯成EmguCV語法, 無法享受Copy-Paste的快樂;
雖然話說習慣成自然, 但是久沒有使用, 時間是會沖淡一切….
2. 當你使用 C++, 又開始懷念C#拉UI的快樂時, 你不得不考慮兩者加在一起使用….
3. 綜合1.2兩點, 希望可以在C# windows form快快樂樂拉UI, 同時簡簡單單將不同參數傳給OpenCV,
最重要的是可以快速複製貼上OpenCV的範例程式….哈哈!!!
4. 當OpenCV C++函式庫有變動時, 只需要編譯整個方案(solution)就可以全部一次更新完成
所以接下來要寫一個簡單範例,
1. 在C++ DLL專案, 載入一張圖片並顯示
2. 在C# Windows Form專案, 加入C++ DLL參考, 將檔案名稱傳給該DLL函式
===================================================================
ImageProcLib
選擇DLL
按下<完成>後如下
ImageProcLib.cpp 空空如也~
加入現有屬性工作表
接下來新增 ImageProcLib.hpp
名稱: imageProcLib.h
先宣告ImageProcLib.h
#include <wchar.h>
#pragma once
namespace CVision
{
public ref class ImageProc
{
public:
ImageProc(){};
bool imread(wchar_t *filename);
bool showImages();
};
}
回到ImageProcLib.cpp, 實作ImageProcLib.h
// ImageProcLib.cpp : 定義 DLL 應用程式的匯出函式。
//
#include "stdafx.h"
#include "ImageProcLib.h"
using namespace std;
using namespace cv;
namespace CVision
{
string filename;
Mat img;
bool ImageProc::imread(wchar_t* txt)
{
wstring ws(txt);
filename = WstringToString(ws);
img = cv::imread( filename );
if( !img.data)
return false;
else
return true;
}
std::string ImageProc::WstringToString(const std::wstring str)
{
unsigned len = str.size() * 4;
setlocale(LC_CTYPE, "cht");
char *p = new char[len];
wcstombs(p,str.c_str(),len);
std::string str1(p);
delete[] p;
return str1;
}
void ImageProc::showImage()
{
namedWindow( filename, CV_WINDOW_NORMAL );
imshow(filename, img );
}
}
組態管理員->選擇x64平台
下拉選擇支援/clr, 讓C#可以呼叫C++ DLL
若成功編譯的話, 會在x64\Release資料夾輸出ImageProcLib.dll
==========================================================
準備撰寫C# windows form, 首先在原來ImageProLib Solution加入新專案(C# windows form project)
在C# GUI加入一個menuStrip元件
準備加入<參考>
選擇剛才C++製作的DLL元件
在C# windows form加入
using CVision;
記得勾選: 允許Unsafe程式碼
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using CVision;
namespace ImageProcLibClient
{
public partial class Form1 : Form
{
ImageProc imgProc = new ImageProc();
public Form1()
{
InitializeComponent();
}
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
using (OpenFileDialog ofd = new OpenFileDialog())
{
if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
unsafe
{
fixed (char* filename = ofd.FileName)
{
imgProc.imread(filename);
imgProc.showImage();
}
}
}
}
}
}
}
選擇ImageProcLibClient為起始專案, 並編譯執行
ImageProcLibClient c# Window Form專案也必須是x64平台, 才能呼叫c++ x64 imageProcLib函式庫
測試載入一張圖來試試看囉
=================================================================
更新 imageProcLib.h
#include <wchar.h>
#include <opencv2\highgui\highgui.hpp>
#pragma once
using namespace std;
namespace CVision
{
public ref class ImageProc
{
public:
ImageProc(){};
bool imread(string filename);
bool imread(wchar_t *filename);
std::string WstringToString(const std::wstring str);
void showImage();
};
}
更新 imageProcLib.cpp
// ImageProcLib.cpp : 定義 DLL 應用程式的匯出函式。
//
#include "stdafx.h"
#include "ImageProcLib.h"
using namespace std;
using namespace cv;
namespace CVision
{
string filename;
Mat img;
bool ImageProc::imread(string filename)
{
const char * file = filename.c_str();
img = cv::imread( file );
if( !img.data)
return false;
else
return true;
}
bool ImageProc::imread(wchar_t* txt)
{
wstring ws(txt);
filename = WstringToString(ws);
img = cv::imread( filename );
if( !img.data)
return false;
else
return true;
}
std::string ImageProc::WstringToString(const std::wstring str)
{
unsigned len = str.size() * 4;
setlocale(LC_CTYPE, "cht");
char *p = new char[len];
wcstombs(p,str.c_str(),len);
std::string str1(p);
delete[] p;
return str1;
}
void ImageProc::showImage()
{
namedWindow( filename, CV_WINDOW_NORMAL );
imshow(filename, img );
}
}
=================================================================
接下來, 加入一個簡單的形態學運算
將結果的視窗名稱命名 std::string filenameDilate = s + " Dilate"
How to concatenate two strings in C++?
接下來測試Copy-Paste的功能
google “opencv dilate” 輕鬆找到一個範例[4]來測試
先更新 imageProcLib.h
只是多宣告一行 ImageDilate();
#include <wchar.h>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#pragma once
using namespace std;
namespace CVision
{
public ref class ImageProc
{
public:
ImageProc(){};
bool imread(string filename);
bool imread(wchar_t *filename);
std::string WstringToString(const std::wstring str);
void ImageProc::ImageDilate();
void showImage();
};
}
更新 imageProcLib.cpp
幾乎是Copy Paste改幾個字就打完收工
// ImageProcLib.cpp : 定義 DLL 應用程式的匯出函式。
//
#include "stdafx.h"
#include "ImageProcLib.h"
using namespace std;
using namespace cv;
namespace CVision
{
string filename;
Mat img;
Mat imgDst;
bool ImageProc::imread(string filename)
{
const char * file = filename.c_str();
img = cv::imread( file );
if( !img.data)
return false;
else
return true;
}
bool ImageProc::imread(wchar_t* txt)
{
wstring ws(txt);
filename = WstringToString(ws);
img = cv::imread( filename );
if( !img.data)
return false;
else
return true;
}
std::string ImageProc::WstringToString(const std::wstring str)
{
unsigned len = str.size() * 4;
setlocale(LC_CTYPE, "cht");
char *p = new char[len];
wcstombs(p,str.c_str(),len);
std::string str1(p);
delete[] p;
return str1;
}
void ImageProc::ImageDilate()
{
// Create a structuring element
int erosion_size = 6;
Mat element = getStructuringElement(cv::MORPH_CROSS ,
cv::Size(2 * erosion_size + 1, 2 * erosion_size + 1),
cv::Point(erosion_size, erosion_size) );
// Apply erosion or dilation on the image
erode(img,imgDst,element); // dilate(image,dst,element);
}
void ImageProc::showImage()
{
namedWindow( filename, CV_WINDOW_NORMAL );
imshow(filename, img );
std::string filenameDilate = filename + " Dilate"; //concatenation easy!
namedWindow( filenameDilate, CV_WINDOW_NORMAL );
imshow( filenameDilate, imgDst );
}
}
更新完C++ DLL, C# Windows Form會自動偵測到ImageProcLib.dll異動
C# 額外呼叫 imgProc.ImageDilate(), 其他不變(夠簡單吧!)
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
using (OpenFileDialog ofd = new OpenFileDialog())
{
if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
unsafe
{
fixed (char* filename = ofd.FileName)
{
imgProc.imread(filename);
imgProc.ImageDilate();
imgProc.showImage();
}
}
}
}
}
接著按一下編譯, 將C#專案再次編譯就可以
測試結果如下:
每開一張圖, 就會以filename開新的視窗, 所以意外地可以同時看多張跑出來的結果, 真是太棒了!!!
===============================================================
如果要C#對erosion size參數, 該如何做到?
更新 imageProcLib.h
void ImageProc::ImageDilate(int erosion_size);
更新 imageProcLib.cpp
void ImageProc::ImageDilate(int erosion_size)
{
// Create a structuring element
Mat element = getStructuringElement(cv::MORPH_CROSS ,
cv::Size(2 * erosion_size + 1, 2 * erosion_size + 1),
cv::Point(erosion_size, erosion_size) );
// Apply erosion or dilation on the image
erode(img,imgDst,element); // dilate(image,dst,element);
}
c# windows form更新如下
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using CVision;
namespace ImageProcLibClient
{
public partial class Form1 : Form
{
ImageProc imgProc = new ImageProc();
public Form1()
{
InitializeComponent();
}
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
using (OpenFileDialog ofd = new OpenFileDialog())
{
if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
unsafe
{
fixed (char* filename = ofd.FileName)
{
imgProc.imread(filename);
int erosionSize = Convert.ToInt16(numericUpDown1.Value);
imgProc.ImageDilate(erosionSize);
imgProc.showImage();
}
}
}
}
}
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
int erosionSize = Convert.ToInt16(numericUpDown1.Value);
imgProc.ImageDilate(erosionSize);
imgProc.showImage();
}
}
}
測試結果如下
很明顯這樣可以輕輕鬆鬆不同Erosion Size參數大小,
而不用像以前在c++ Console模式下,
寫一堆有的沒有程式碼或是每次更動參數後重新Compile
參考資料:
1. Calling OpenCV C++ Code in C# Windows Form Applications
2. char* pointer from string in C#
留言列表