本文的初衷是希望帮助那些有其它平台视觉算法开发经验的人能快速转入Halcon平台下,通过文中的示例开发者能快速了解一个Halcon项目开发的基本步骤,让开发者能把精力完全集中到算法的开发上面。
首先,你需要安装Halcon,HALCON 18.11.0.1的安装包会放在文章末尾。安装包分开发和运行时两个版本,运行时版本一般用于生产环境。
注:开发版本自带运行时可替代运行时版本,但安装的东西会比较多。
然后,你需要学会查看Halcon的帮助手册,这是很重要的一件事。
本文涉及到帮助文档的主要章节如下:
原文 HALCON 18.11.0.1 / Programmer's Guide / Programming With HALCON/.NET
翻译 HALCON 18.11.0.1/程序员指南/使用 HALCON/.NET 编程
原文 HALCON 18.11.0.1 / HALCON Operator Reference
翻译 HALCON 18.11.0.1/ HALCON 运算符参考
文中的示例是我第一次接触Halcon时的学习测试用例,在电脑里面躺了一年,最近才有时间整理一下发出来,希望能对你有所帮助。
注:运行本文示例程序前至少安装Halcon的运行时,否则Halcon的dll无法正常使用。
将 HALCON/.NET 添加到应用程序#
添加控件#
右键单击工具箱,然后选择“选择项”,弹出的对话框选择“.NET Framework组件”,单击下面的“浏览”,导航到HALCON安装目录下的\bin\dotnet35(VS2008以下版本的选择dotnet20) ,然后选择halcondotnet.dll。
完成上述操作后,HSmartWindowControl和HWindowControl控件就会出现在工具箱中,其中HWindowControl控件已经过时官方不再推荐使用。
与HWindowControl相比,HSmartWindowControl控件具有以下几个优点:
- 可以像任何其他控件一样使用
- 提供预定义的鼠标交互(移动窗口内容并使用鼠标滚轮进行缩放), 可以通过双击窗口来重置视图
- 控件会自动重新缩放,而不会闪烁
注:与HSmartWindowControlWPF 相反,HSmartWindowControl需要一个回调才能使用鼠标滚轮进行缩放。
引用dll#
在HALCON安装目录下的\bin\dotnet35中,引用以下dll:
- hdevenginedotnet.dll
- halcondotnet.dll
注:使用 HALCON XL 开发应用程序时,必须选择以xl结尾的dll,hhdevelop xl适用于大分辨率的图像(大于 32k x 32k )。
引用以下命名空间:
- HalconDotNet:控件所在的命名空间
- HalconType:Line、Rectangle2等数据类型所在的命名空间
调用Halcon算子#
以ReadImage操作为例,函数原型如下:
static void HOperatorSet.ReadImage(out HObject image, HTuple fileName)
public HImage(HTuple fileName)
public HImage(string fileName)
void HImage.ReadImage(HTuple fileName)
void HImage.ReadImage(string fileName)
注:这些内容帮助手册上都有,在文章开头列出来的章节。
在C#调用HALCON 算子有两种选择:函数式、对象式,前值通过HOperatorSet调用算子并通过out关键字传入关键对象,后者直接在关键对象上调用对应的方法。
两种方法完全等价,C#是一门面向对象的语言,建议使用对象式的方式调用算子会好一点。
程序示例#
本示例只实现下面几种关键功能:
- 加载、保存图片
- 画线、框并保存
- 抓边算法、测宽算法
注:项目的解决方案平台不能使用AnyCPU,只能根据安装的Halcon位数选择x64或x86,我使用的是x64平台。
HSmartWindowControl控件使用#
将HSmartWindowControl控件拖入主界面即可,在窗体类里面定义一个HWindow类型的成员引用控件内部的窗体,同时设置控件的回调函数(WPF则不需要)。代码如下:
//窗口实例
private HWindow hwindow;
public Form1()
{
InitializeComponent();
hwindow = hSmartWindowControl1.HalconWindow;//初始化窗口变量
hSmartWindowControl1.MouseWheel += HSmartWindow_MouseWheel;
}
//鼠标滚轮回调
private void HSmartWindow_MouseWheel(object sender, MouseEventArgs e)
{
Point pt = this.Location;
MouseEventArgs newe = new MouseEventArgs(e.Button, e.Clicks, e.X - pt.X, e.Y - pt.Y, e.Delta);
hSmartWindowControl1.HSmartWindowControl_MouseWheel(sender, newe);
}
加载、保存图像#
加载、保存图像也比较简单,我们需要先定义一个HImage实例,然后按钮单击事件在该实例上调用对应的算子,代码如下:
//图片变量
private HImage image = new HImage();
//加载图片
private void button_ReadImage_Click(object sender, EventArgs e)
{
string imagePath = "TestRead.bmp";
image.ReadImage(imagePath);
hwindow.DispImage(image);
//自动适应图片(相当于控件上面的双击操作)
hwindow.SetPart(0, 0, -2, -2);
}
//保存图片
private void button_WriteImage_Click(object sender, EventArgs e)
{
string imagePath = "TestWrite.bmp";
image.WriteImage("bmp", 0, imagePath);
hwindow.DispImage(image);
}
上面代码是从程序启动目下加载TestRead.bmp图片,保存图片到程序启动目下的TestWrite.bmp,实际路径可以根据项目情况自己定义。
上面的图片是自己生成的,不是生产环境下的产品图片,仅用于程序演示。
扩展:加载相机图像#
大部分项目都是从相机加载图片,但这涉及到相机驱动的一些知识,全部介绍一边会偏移文章主题。
简单来说,加载相机图像分两步:
- 将相机图像保存到内存
- 将内存中的图像传入Halcon
将相机图像保存到内存是相机驱动的工作,下面只讨论怎么将内存中的图像传入Halcon,代码如下:
private void GenImageByPtr()
{
//这三个参数都可以通过相机驱动得到
byte[] imageBuf = null; //图像缓存数组
int width = 0; //图像宽度
int heigth = 0; //图像高度
//获取内存图像中间的指针
IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(imageBuf, 0);
//加载内存中的图像
image.GenImage1("byte", width, heigth, ptr);
hwindow.DispImage(image);
}
这里只列一个简单的示例,类似的算子还有copy_image、gen_image3等。
画线、画框并保存#
在图像上画线、框是机器视觉里面常见的需求,根据线、框确定算法搜索的区域和特征。
在窗体类中定义一个HDrawingObject对象并附加到现有窗口用于交互,同时定义好Line对象、Rectangle2对象用于保存绘图的结果。
先在图像窗口上面画出线和框,然后再用鼠标手动调整大小、位置,代码如下:
//绘图对象
private HDrawingObject drawingObject = new HDrawingObject();
//线ROI
private Line line = new Line();
//框ROI
private Rectangle2 rectangle2 = new Rectangle2();
private void button_DrawLine_Click(object sender, EventArgs e)
{
drawingObject.CreateDrawingObjectLine(100, 100, 200, 200);
//将绘图对象关联到Halcon窗口
hwindow.AttachDrawingObjectToWindow(drawingObject);
}
private void button_SaveLine_Click(object sender, EventArgs e)
{
HTuple paramName, param;
paramName = new HTuple(new string[] { "row1", "column1", "row2", "column2" });
param = drawingObject.GetDrawingObjectParams(paramName);
//保存参数
line.SetValue(param.ToDArr());
paramName.Dispose();
param.Dispose();
//清除绘图内容
drawingObject.ClearDrawingObject();
}
private void button_DrawRect_Click(object sender, EventArgs e)
{
drawingObject.CreateDrawingObjectRectangle2(300, 400, 0, 300, 200);
//将绘图对象关联到Halcon窗口
hwindow.AttachDrawingObjectToWindow(drawingObject);
}
private void button_SaveRect_Click(object sender, EventArgs e)
{
HTuple paramName, param;
paramName = new HTuple(new string[] { "row", "column", "phi", "length1", "length2" });
param = drawingObject.GetDrawingObjectParams(paramName);
//保存参数
rectangle2.SetValue(param.ToDArr());
paramName.Dispose();
param.Dispose();
//清除绘图内容
drawingObject.ClearDrawingObject();
}
上面的paramName可以取以下值,里面包含了Line、Rectangle2类属性名:
"color", "column", "column1", "column2", "end_angle", "font", "length1", "length2", "line_style",
"line_width", "phi", "radius", "radius1", "radius2", "row", "row1", "row2", "start_angle", "string", "type"
检测算法#
用Halcon开发检测算法一般有两种方法:
- 根据直接调用Halcon在对应语言平台下的算子接口
- 用Halcon自带的脚本语言开发算法然后转成C#类
第一种自由度比较高,代码看起来也比较简洁易懂,但上手比较困难。第二种更简单,但生成的类很难看,而且与程序集成的时候需要做一些改动。
两种方法并不是绝对对立的,一般会先用Halcon验证算法,然后参考导出的C#类实现自己的检测算法。
抓边算法#
抓变算法直接调用的是Halcon的C#算子接口,里面有用到2D 测量模型:
2D测量模型
简述一下2D 测量的使用步骤:
-
创建测量模型并指定图像大小:首先必须使用create_metrology_model创建测量模型,然后使用set_metrology_model_image_size指定测量结果所在的图像的大小。
-
提供近似值:将测量对象添加到测量模型中,每个测量对象由图像中相应对象的近似形状参数和控制测量的参数组成,控制测量的参数包括例如指定测量区域的尺寸和分布的参数,测量对象有以下几种:
- 圆:add_metrology_object_circle_measure
- 椭圆:add_metrology_object_ellipse_measure
- 矩形:add_metrology_object_rectangle2_measure
- 线:add_metrology_object_line_measure
- 使用一个运算符创建不同形状:add_metrology_object_generic
要直观检查定义的度量对象,可以使用运算符get_metrology_object_model_contour访问其XLD轮廓。要直观检查创建的测量区域,可以使用运算符get_metrology_object_measures访问其XLD轮廓。
-
修改模型参数:如果已执行相机校准,则可以使用set_metrology_model_param,没有就忽略(本示例没有使用)。
-
修改对象参数:当将测量对象添加到测量模型时,可以设置许多参数,之后还可以使用运算符set_metrology_object_param修改其中的一些(本示例是在添加时设置的参数,所以没有此步骤)。
-
调整测量模型:在执行下一次测量之前平移和旋转测量模型,可以使用操作员align_metrology_model。通常使用基于形状的匹配来获得对准参数,相当于测量前的位置就纠偏(本示例比较简单没有此步骤)。
-
应用测量:使用apply_metrology_model执行测量过程。
-
访问结果:测量后,可以使用get_metrology_object_result访问结果,也可以使用get_metrology_object_measures获取定位边的行坐标和列坐标再进一步处理(本示例使用前者)。
代码实现
抓变算法的C#代码如下:
private void button_FindEdge_Click(object sender, EventArgs e)
{
//创建测量对象
HMetrologyModel hMetrologyModely = new HMetrologyModel();
//设置图片大小
image.GetImageSize(out int width, out int height);
hMetrologyModely.SetMetrologyModelImageSize(width, height);
//添加直线测量
double measureLength1= 30, measureLength2=30, measureSigma=1, measureThreshold=30;
HTuple genParamName = new HTuple(), genParamValue = new HTuple();
hMetrologyModely.AddMetrologyObjectLineMeasure(line.Row1, line.Column1,line.Row2, line.Column2, measureLength1, measureLength2, measureSigma, measureThreshold, genParamName, genParamValue);
//执行并获取结果
hMetrologyModely.ApplyMetrologyModel(image);
//获取测量区域
HTuple mRow = new HTuple(), mCol = new HTuple();
HXLDCont mContours = hMetrologyModely.GetMetrologyObjectMeasures("all", "all", out mRow, out mCol); //检测区域轮廓
HXLDCont mmContours = hMetrologyModely.GetMetrologyObjectModelContour("all", 1); //测量对象轮廓
//参数顺序 ["row_begin", "column_begin", "row_end", "column_end"]
HTuple lineRet =hMetrologyModely.GetMetrologyObjectResult("all", "all", "result_type", "all_param");
double[] retAry = lineRet.DArr;
//打印结果
hwindow.SetLineWidth(2);
hwindow.SetColor("green");
hwindow.DispLine(retAry[0], retAry[1], retAry[2], retAry[3]);
hwindow.SetColor("blue");
hwindow.DispXld(mContours);
hwindow.SetColor("yellow");
hwindow.DispXld(mmContours);
//清空测量对象
hMetrologyModely.ClearMetrologyModel();
//清理对象
hMetrologyModely?.Dispose();
genParamName?.Dispose();
genParamValue?.Dispose();
mRow.Dispose();
mCol.Dispose();
mContours.Dispose();
mmContours.Dispose();
}
Halcon的代码如下:
*读取图片
read_image (Image, 'D:/test.bmp')
dev_get_window (WindowHandle)
*画线
Row1:=1218.79
Column1:=1002.95
Row2:=1242.07
Column2:=2786.18
*draw_line (WindowHandle, Row1, Column1, Row2, Column2)
*gen_region_line (RegionLines, Row1, Column1, Row2, Column2)
*创建测量几何形状所需的数据结构
create_metrology_model (MetrologyHandle)
get_image_size (Image, Width, Height)
set_metrology_model_image_size (MetrologyHandle, Width, Height)
add_metrology_object_line_measure (MetrologyHandle, Row1, Column1, Row2, Column2, 100, 50, 1, 30, [], [], Index)
apply_metrology_model (Image, MetrologyHandle)
get_metrology_object_result (MetrologyHandle, 'all', 'all', 'result_type','all_param', Parameter)
get_metrology_object_measures(Contours, MetrologyHandle, 'all', 'all', Row, Column)
get_metrology_object_model_contour (Contour, MetrologyHandle, 0, 1.5)
*清空测量对象,否则会导致内存泄露
clear_metrology_model (MetrologyHandle)
*可视化
dev_clear_window ()
dev_display(Image)
dev_set_color('green')
dev_set_line_width(1)
disp_line (WindowHandle, Parameter[0], Parameter[1], Parameter[2], Parameter[3])
dev_display (Contours)
dev_display (Contour)
使用方法
直接在界面上点击“打开图片”->“画线ROI”(默认位置我都调好了,你也可以自己调整大小、位置)->“抓边”,过程如下:
测宽算法#
测宽算法使用一维测量中的measure_pairs算子提取直边对,然后计算两个直边的距离。代码太长这里就不贴了,完整的项目源码会在文章末尾给出。
需要注意,measure_pairs算子的搜索框必须和目标边缘完全垂直,否则宽度数据会不准确,算子原理如下:
直接在界面上点击“打开图片”->“画框ROI”(默认位置我都调好了,你也可以自己调整大小、位置)->“测宽”,过程如下:
上面的箭头就是框的方向,测量边必须与框的方向接近垂直否则会运算失败,实际项目中还是建议用2D测量单独抓两个边来测宽度。
源码里面显示边缘的DispEdgeMarker方法,是直接从measure_pairs算子示例里面导出转C#的,所以风格会比较奇怪。