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

image

選擇DLL

image

按下<完成>後如下

image

ImageProcLib.cpp 空空如也~

image

加入現有屬性工作表

VS2010OpenCV249X64Release

image

接下來新增 ImageProcLib.hpp

image

名稱: imageProcLib.h

image

先宣告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平台

image

image

下拉選擇支援/clr, 讓C#可以呼叫C++ DLL

image

若成功編譯的話, 會在x64\Release資料夾輸出ImageProcLib.dll

image

==========================================================

準備撰寫C# windows form, 首先在原來ImageProLib Solution加入新專案(C# windows form project)

image

image

在C# GUI加入一個menuStrip元件

image

準備加入<參考>

image

選擇剛才C++製作的DLL元件

image

在C# windows form加入

using CVision;

image

記得勾選: 允許Unsafe程式碼

image

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為起始專案, 並編譯執行

image

ImageProcLibClient c# Window Form專案也必須是x64平台, 才能呼叫c++ x64 imageProcLib函式庫

image

測試載入一張圖來試試看囉

image

=================================================================

更新 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]來測試

image

先更新 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#專案再次編譯就可以

image

測試結果如下:

每開一張圖, 就會以filename開新的視窗, 所以意外地可以同時看多張跑出來的結果, 真是太棒了!!!

image

===============================================================

如果要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更新如下

image

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

image


參考資料:

1. Calling OpenCV C++ Code in C# Windows Form Applications

2. char* pointer from string in C#

3. How to concatenate two strings in C++?

4. Erosion or dilation (Morphological operations)

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 me1237guy 的頭像
    me1237guy

    天天向上

    me1237guy 發表在 痞客邦 留言(6) 人氣()