很多时候做工业视觉的都喜欢用C#+halcon,纯粹只想做做应用的话,采用该模式搞下去也没啥问题,但是如果想提升一下自己对图像算法的底层开发能力,一般都还是会选择使用C++搭配Opencv进行开发,对于喜欢用C#开发上位机,结合C++做算法的话,那么C++的托管模式是一个不错的选择。
首先说下我为什么要用C++托管模式:
1、托管C++可以使用非托管内容;
2、托管模式下可以断点调试,非托管模式本人暂未找到可以断点调试的方法(如果喜欢非托管可以使用DebugView);
3、在调用c++的dll的时候,就像使用C#导出的dll一样去使用,不需要写dllexport和dllimport之类的玩意;
缺点:变量类型转换会稍微麻烦点
本次C#/C++混合模式测试思路为:
1、C#端作为图像源的获取端,便于测试直接使用halcon进行图像读取和传递;
2、C++端作为图像的接收端,接收C#传过来的图像;
3、图像显示:将C#端的panel控件的句柄传递到C++端,C++端通过halcon进行处理和显示;
下面具体介绍托管模式下C#端和C++端的详细配置和代码测试例程:
C++端配置
1、新建空白工程,添加一个需要供C#端进行调用的类,将配置类型设置为“动态库(.dll)”
2、在配置属性里,将公共语言运行时支持设置为clr模式,见下图
3、在配置属性里,选择C/C++下语言,找到“符合模式”一项,设置为“否(permissive)”
如果设置为“是(permissive-)”的话,编译的时候会出现以下错误
4、添加类库mscorlib和System,添加方式同C#一样
5、如果需要在断点的时候查看变量值的变化,需要进行一下操作
C++端设置:将调试器类型设置为混合模式
C#端设置:勾选“启用本地代码调试”
6、C++端测试代码
C++端采用opencv和halcon的混用模式,可根据具体情况去选择,halcon和Opencv的配置可自行配置
6.1 在类的前面加上ref
6.2 .h文件
static HalconCpp::HWindow g_hwin;
public ref class MyAlgorithmPro
{
public:
MyAlgorithmPro();
~MyAlgorithmPro(void);
public:
void initWinHandle(long handle, int width, int height);//初始化窗口句柄
void getImageByHalcon(unsigned char* R, unsigned char* G, unsigned char* B, int width, int height, int channel);//halcon取图
void getImageByOpencv(unsigned char* R, unsigned char* G, unsigned char* B, int width, int height, int channel);//opencv取图
};
6.3 .cpp文件
#include "MyAlgorithmPro.h"
MyAlgorithmPro::MyAlgorithmPro()
{
}
MyAlgorithmPro::~MyAlgorithmPro(void)
{
}
void MyAlgorithmPro::initWinHandle(long handle, int width, int height)
{
g_hwin = HalconCpp::HWindow(0, 0, width, height, handle, "visible", "");
}
void MyAlgorithmPro::getImageByHalcon(unsigned char* R, unsigned char* G, unsigned char* B,
int width, int height, int channel)
{
HObject img;
switch (channel)
{
case 1:
GenImage1(&img, "byte", width, height, (Hlong)R);
break;
case 3:
GenImage3(&img, "byte", width, height, (Hlong)R, (Hlong)G, (Hlong)B);
break;
default:
break;
}
g_hwin.SetPart(0, 0, height, width);
g_hwin.DispObj(img);
g_hwin.SetColor("red");
g_hwin.DispCircle( height*0.5, width * 0.5, 50);
}
void MyAlgorithmPro::getImageByOpencv(unsigned char* R, unsigned char* G, unsigned char* B,
int width, int height, int channel)
{
//cv::merge()
cv::Mat img, rimg, gimg, bimg;
std::vector<cv::Mat> vc;
switch (channel)
{
case 1:
img = cv::Mat(height, width, CV_8UC1, R);
cv::circle(img, cv::Point(width*0.5, height*0.5), 200, cv::Scalar(100), -1);
break;
case 3:
img = cv::Mat(height, width, CV_8UC3);
rimg = cv::Mat(height, width, CV_8UC1, R);
gimg = cv::Mat(height, width, CV_8UC1, G);
bimg = cv::Mat(height, width, CV_8UC1, B);
//halcon是RGB模式,opencv是BGR模式
vc.push_back(bimg);
vc.push_back(gimg);
vc.push_back(rimg);
cv::merge(vc, img);
break;
default:
break;
}
HObject dstimg = MyClass::Mat2Hobject(img);
g_hwin.SetPart(0, 0, height, width);
g_hwin.DispObj(dstimg);
g_hwin.SetColor("red");
g_hwin.DispCircle(height * 0.5, width * 0.5, 50);
}
7、编写完代码后,会生成一个dll,记得将C++端和C#的生成目录都设置为同一个目录,否则是无法进行断点调试的
C#端配置
1、新建一个窗体应用程序,添加一个panel控件和按钮控件
2、在应用处添加C++端导出得dll
3、具体代码见下
public partial class Form1 : Form
{
MyAlgorithmPro alg = new MyAlgorithmPro();
public Form1()
{
InitializeComponent();
initialWinHandle();
}
private void initialWinHandle()
{
alg.initWinHandle((int)panel1.Handle, panel1.Width, panel1.Height);
}
private void button1_Click(object sender, EventArgs e)
{
string curFilename = "";
OpenFileDialog opndlg = new OpenFileDialog();
opndlg.Filter = "所有图像文件|*.bmp;*.pcx;*.png;*.jpg;*.gif";
opndlg.Title = "打开图片文件";
if(opndlg.ShowDialog()==DialogResult.OK)
{
curFilename = opndlg.FileName;
}
HObject img = null;
HTuple pointers = new HTuple(), type = new HTuple(), width = new HTuple(), height = new HTuple();
HTuple redPointers = new HTuple(), greenPointers = new HTuple(), bluePointers = new HTuple();
HTuple channels = new HTuple();
HOperatorSet.GenEmptyObj(out img);
HOperatorSet.ReadImage(out img, curFilename);
HOperatorSet.CountChannels(img, out channels);
if(channels.I==1)
{
HOperatorSet.GetImagePointer1(img, out pointers, out type, out width, out height);
unsafe
{
byte* ptr = (byte*)pointers.L;
alg.getImageByHalcon(ptr, ptr, ptr, width, height, channels);
//alg.getImageByOpencv(ptr, ptr, ptr, width, height, channels);
}
}
else if(channels.I==3)
{
HOperatorSet.GetImagePointer3(img, out redPointers, out greenPointers, out bluePointers, out type, out width, out height);
unsafe
{
byte* bptr = (byte*)(redPointers.L);
byte* R = (byte*)redPointers.L;
byte* G = (byte*)greenPointers.L;
byte* B = (byte*)bluePointers.L;
alg.getImageByHalcon(R, G, B, width, height, channels);
//alg.getImageByOpencv(R, G, B, width, height, channels);
}
}
}
}
4、显示处理后的灰度图像
5、显示处理后的彩色图像
结束语:以上混编模式适合喜欢折腾的人,其实用QT就没上面的事了!
当然除了传图片,肯定也少不了要进行变量的互传,下面再介绍下托管模式下各类型变量的互传:
C++/clr托管模式下同C#端进行各类型变量的转换和互传
托管模式和非托管模式的变量类型的传递还是有很大区别的,本文暂不介绍非托管的内容,有兴趣的可以自行百度。
C#传递各类型变量到C++端
除了字符串之外,其它类型的变量按照正常的变量类型进行传递即可,但C++端需要在引用处添加System.dll
比如传递int、double、float、bool类的时候,C++端的测试代码如下:
.h文件:
int getMyInt(int n);//接收和返回int变量
double getMyDouble(double n);//接收和返回double变量
float getMyFloat(float n);//接收和返回float变量
bool getMyBool(bool n);//接收和返回bool变量
.cpp文件:
int MyAlgorithmPro::getMyInt(int n){ return n;}
double MyAlgorithmPro::getMyDouble(double n){ return n;}
float MyAlgorithmPro::getMyFloat(float n){ return n;}
bool MyAlgorithmPro::getMyBool(bool n){ return n;}
C#端测试代码:
double t = alg.getMyDouble(12.3);
this.textBox1.Text = t.ToString();
int t = alg.getMyInt(123);
this.textBox1.Text = t.ToString();
float t = alg.getMyFloat(12.2f);
this.textBox1.Text = t.ToString();
bool b = alg.getMyBool(false);
this.textBox1.Text = b.ToString();
但是在传字符串的时候需要稍作修改
C++端:
#include <msclr\marshal_cppstd.h>
System::String^ MyAlgorithmPro::getMyString(System::String^ str)
{
string ss = msclr::interop::marshal_as<std::string>(str);
System::String^ res = gcnew String(ss.c_str());
return res;
}
参考链接:system::String ^转std::string - 懒人福利 - 博客园
C#和C++互传各类型变量的数组(其实就是用于计算结果回传)
C++端测试代码:
.h文件:
void getMyStringArray(cli::array<System::String^, 1>^ res);//字符串数组传参
void getMyDoubleArray(cli::array<System::Double^, 1>^ res);//double数组传参
void getMyIntArray(cli::array<System::Int32^, 1>^ res);//int数组传参
void getMyFloatArray(cli::array<System::Single^, 1>^ res);//Single(单精度)数组传参
.cpp文件:
void MyAlgorithmPro::getMyDoubleArray(cli::array<System::Double^, 1>^ res)
{
double t = 9.99;
res[0] = t;
res[1] = 2.2;
}
void MyAlgorithmPro::getMyIntArray(cli::array<System::Int32^, 1>^ res)
{
res[0] = 12;
res[1] = 13;
res[2] = 14;
}
void MyAlgorithmPro::getMyFloatArray(cli::array<System::Single^, 1>^ res)
{
float t1 = 11.1;
float t2 = 11.2;
//res[0] = t1;
res[0] = (float)22.3;
res[1] = t2;
}
void MyAlgorithmPro::getMyStringArray(cli::array<System::String^, 1>^ res)
{
string ss = "123456";
//res[0]= "111";
res[0] = gcnew String(ss.c_str());
res[1] = "222";
}
C#端测试代码:除了字符串之外,C#端的数组均为ValueType类型,只需要再做一个显示转换即可
private void button2_Click(object sender, EventArgs e)
{
string[] tt = { "1", "2" };
alg.getMyStringArray(tt);
this.textBox1.Text = tt[0].ToString();
}
private void button3_Click(object sender, EventArgs e)
{
ValueType[] v = { 1, 2, 3 };
alg.getMyDoubleArray(v);
double t = (double)v[0];
this.textBox1.Text =t.ToString();
}
private void button4_Click(object sender, EventArgs e)
{
ValueType[] v = { 0, 0, 0 };
alg.getMyIntArray(v);
int v2 = (int)v[0];
this.textBox1.Text = v2.ToString();
}
private void button5_Click(object sender, EventArgs e)
{
ValueType[] v = { 0, 0, 0 };
alg.getMyFloatArray(v);
float t = (float)v[0];
this.textBox1.Text = t.ToString();
}
具体测试效果可自行测试,不做具体展示