前言
本文将详细如何使用C#联合Halcon实现圆形检测的功能。
代码实现图像加载、绘制ROI(感兴趣区域)、圆查找功能,并开放了边缘极性、边缘阈值、卡尺数量、卡尺长度等参数供调整。其他参数的开放方式类似,只需复制粘贴并修改参数名即可。
正文
1、操作步骤
加载图像:从文件中加载待处理的图像。
绘制矩形ROI:在图像上绘制一个矩形区域作为感兴趣区域(ROI)。
调整参数:设置边缘极性、边缘阈值、卡尺数量、卡尺长度等参数。
执行查找:根据设定的参数进行圆形查找。
2、实现步骤
创建计量模型:
初始化一个新的计量模型。
添加圆形计量模型:
使用 AddMetrologyObjectCircleMeasure 函数添加一个圆形测量对象。
获取计量模型测量轮廓:提取用于测量的轮廓数据。
应用计量模型到图像:将计量模型应用于输入图像。
获取计量模型结果对象:获取经过计算后的计量模型结果。
获取计量模型测量结果:包括圆形卡尺和匹配结果。
AddMetrologyObjectCircleMeasure 算子
AddMetrologyObjectCircleMeasure 是 Halcon 中用于添加圆形测量对象的重要函数,属于 Halcon 的计量工具集的一部分。
该函数用于创建一个圆形测量对象,以便后续进行精确的圆形边缘检测和测量。
主要参数
num_measures:指定测量点的数量。
measure_transition:定义边缘过渡的方向,可选值包括:
"positive":表示从背景到前景(从黑到白)。
"negative":表示从前景到背景(从白到黑)。
"all":包括所有类型的边缘过渡方向(正负边缘)。
例如,在实际案例中,可以根据需要选择全部边缘、仅从黑到白或仅从白到黑的边缘过渡方向。
参数开放说明
本文开放了以下参数供用户调整:
边缘极性
边缘阈值
卡尺数量
卡尺长度
其他参数的开放方式类似,只需复制粘贴现有参数并修改相应的参数名即可。
运行环境
操作系统:Window 11编程软件:Visual Studio 2022.Net 版本:.Net Framework 4.8.0Halcon版本:HALCON 20.11
运行效果
示例代码
MainForm代码
此代码负责通过控件进行参数变更,以及触发一些操作,如绘制ROI,执行圆查找。
using CustomControls;
using HalconDotNet;
using System.Windows.Forms;
using System;
using System.Drawing;
namespace CS学习之Halcon卡尺找圆
{
public partial class MainForm : WinFormBase
{
#region 字段
private HWindow WindowID; //窗口ID
private HObject hImage = null; //原图
private HObject grayImage = null; //灰度图
private Point startPoint = new Point(100, 100); //起始点
private HDrawingObject hDrawingObject = new HDrawingObject(); //绘制对象
private string[] drawingParams = { "row", "column", "radius" }; //绘制参数名:圆形(输入)
private HTuple Roi = new HTuple(); //绘制参数值:圆形(输出)
private bool drawing = false; //是否绘制
CircleFindTool circleFindTool; //圆查找工具类
#endregion
#region 初始化、加载
public MainForm()
{
InitializeComponent();
this.CenterToParent();
}
private void MainForm_Load(object sender, System.EventArgs e)
{
BindEvents(HSWindow);
WindowID = HSWindow.HalconWindow;
circleFindTool = new CircleFindTool();
LoadImage("source.png");
cbx_EdgesPolarity.DataSource = circleFindTool.PolarityList;
cbx_EdgesPolarity.SelectedIndex = 0;
UpdateControlParams();
}
#endregion
#region 滚轮缩放
private void BindEvents(HSmartWindowControl control)
{
control.MouseWheel += new MouseEventHandler(CustomMouseWheel);
control.HMouseDown += CustomHMouseDown; ;
}
private void CustomHMouseDown(object sender, HMouseEventArgs e)
{
if (e.Button == MouseButtons.Left && drawing)
{
HOperatorSet.SetColor(WindowID, "blue");
startPoint = new Point((int)e.X, (int)e.Y);
hDrawingObject = HDrawingObject.CreateDrawingObject(HDrawingObject.HDrawingObjectType.CIRCLE, new HTuple[] { startPoint.Y, startPoint.X, 20 });
HOperatorSet.AttachDrawingObjectToWindow(WindowID, hDrawingObject);
drawing = false;
}
}
/// <summary>
/// 放大缩小图像
/// </summary>
private void CustomMouseWheel(object sender, MouseEventArgs e)
{
System.Drawing.Point pt = this.Location;
int leftBorder = HSWindow.Location.X;
int rightBorder = HSWindow.Location.X + HSWindow.Size.Width;
int topBorder = HSWindow.Location.Y;
int bottomBorder = HSWindow.Location.Y + HSWindow.Size.Height;
//判断鼠标指针是否在控件内部
if (e.X > leftBorder && e.X < rightBorder && e.Y > topBorder && e.Y < bottomBorder)
{
MouseEventArgs newe = new MouseEventArgs(e.Button, e.Clicks, e.X - pt.X, e.Y - pt.Y, e.Delta);
HSWindow.HSmartWindowControl_MouseWheel(sender, newe);
}
}
#endregion
#region 事件方法
/// <summary>
/// 加载图像
/// </summary>
private void LoadImage(string path)
{
//1、加载图像
HOperatorSet.ReadImage(out hImage, path);
HOperatorSet.DispObj(hImage, WindowID);
HOperatorSet.CountChannels(hImage, out HTuple channel);
grayImage = hImage.Clone();
if (channel.I == 3)
{
HOperatorSet.Rgb1ToGray(hImage, out grayImage);
}
}
private void btn_LoadImage_Click(object sender, EventArgs e)
{
OpenFileDialog openFile = new OpenFileDialog();
openFile.InitialDirectory = Application.StartupPath;
if (openFile.ShowDialog()== DialogResult.OK)
{
LoadImage(openFile.FileName);
}
}
/// <summary>
/// 绘制圆
/// </summary>
private void btn_Draw_Click(object sender, EventArgs e)
{
if (hDrawingObject.ID < 1)
{
drawing = true;
HOperatorSet.DispObj(hImage, WindowID);
HOperatorSet.DispText(WindowID, "鼠标点击绘制!!!", "window", 10, 10, "red", new HTuple(), new HTuple());
}
}
/// <summary>
/// 执行圆查找
/// </summary>
private void btn_ExecuteFind_Click(object sender, EventArgs e)
{
HOperatorSet.DispObj(hImage, WindowID);
if (hDrawingObject.ID < 1) return;
//获取绘制对象参数
HOperatorSet.GetDrawingObjectParams(hDrawingObject, drawingParams, out HTuple roi);
circleFindTool.ExecuteMeasure(roi.DArr[0], roi.DArr[1], roi.DArr[2], grayImage, WindowID);
if (circleFindTool.Contour != null)
{
HOperatorSet.SetColor(WindowID, "blue");
HOperatorSet.DispObj(circleFindTool.Contours, WindowID);
HOperatorSet.SetLineWidth(WindowID, 2);
HOperatorSet.SetColor(WindowID, "green");
HOperatorSet.DispObj(circleFindTool.Contour, WindowID);
}
}
#endregion
#region 参数变更
private void UpdataMeasuresParams()
{
if (hDrawingObject.ID < 1) return;
HOperatorSet.DispObj(hImage, WindowID);
HOperatorSet.GetDrawingObjectParams(hDrawingObject, drawingParams, out Roi);
circleFindTool.UpdataMeasuresParams(Roi.DArr[0], Roi.DArr[1], Roi.DArr[2], grayImage, WindowID);
HOperatorSet.SetLineWidth(WindowID, 1);
HOperatorSet.SetColor(WindowID, "blue");
HOperatorSet.DispObj(circleFindTool.Contours, WindowID);
}
private void cbx_EdgesPolarity_SelectedIndexChanged(object sender, EventArgs e)
{
circleFindTool.Transition = cbx_EdgesPolarity.SelectedItem.ToString();
btn_ExecuteFind_Click(sender, e);
}
private void nudx_CallipersNum_ValueChanged(object sender, EventArgs e)
{
circleFindTool.MetrologyModel.MeasureNumber = (int)nudx_CallipersNum.Value;
UpdataMeasuresParams();
}
private void nudx_Threshold_ValueChanged(object sender, EventArgs e)
{
circleFindTool.MetrologyModel.MeasureThreshold = (int)nudx_Threshold.Value;
UpdataMeasuresParams();
}
private void trackB_Length1_Scroll(object sender, EventArgs e)
{
circleFindTool.MetrologyModel.MeasureLength1 = (int)trackB_Length1.Value;
label_Length1.Text = $"L1:({trackB_Length1.Value})";
UpdataMeasuresParams();
}
private void trackB_Length2_Scroll(object sender, EventArgs e)
{
circleFindTool.MetrologyModel.MeasureLength2 = (int)trackB_Length2.Value;
label_Length2.Text = $"L2:({trackB_Length2.Value})";
UpdataMeasuresParams();
}
/// <summary>
/// 更新控件参数
/// </summary>
private void UpdateControlParams()
{
nudx_CallipersNum.Value = circleFindTool.MetrologyModel.MeasureNumber;
nudx_Threshold.Value = circleFindTool.MetrologyModel.MeasureThreshold;
trackB_Length1.Value = circleFindTool.MetrologyModel.MeasureLength1;
label_Length1.Text = $"L1:({trackB_Length1.Value})";
trackB_Length2.Value = circleFindTool.MetrologyModel.MeasureLength2;
label_Length2.Text = $"L2:({trackB_Length2.Value})";
}
#endregion
}
}
圆查找工具代码
代码主要负责创建一些圆查找方法,及参数修改方法。
public class CircleFindTool
{
private HObject contour; //轮廓
private HObject contours; //卡尺
private Dictionary<string,string> DicPolarity = new Dictionary<string,string>();
public List<string> PolarityList { get => DicPolarity.Keys.ToList(); }
public string Transition {
get => MetrologyModel.Transition;
set
{
string text = value.ToString();
int leng = text.Length;
if (text.Equals("从黑到白")|| text.Equals("从白到黑"))
{
MetrologyModel.Transition = DicPolarity[text];
}else
{
MetrologyModel.Transition = DicPolarity["全部"];
}
}
}
/// <summary>
/// 计量模型
/// </summary>
public MetrologyModel MetrologyModel { get; set; }
public HObject Contour { get=> contour; set=> contour = value; }
public HObject Contours { get => contours; set => contours = value; }
public CircleFindTool()
{
MetrologyModel = new MetrologyModel();
//1、创建计量模型
HOperatorSet.CreateMetrologyModel(out MetrologyModel.handle);
DicPolarity.Add("全部","all");
DicPolarity.Add("从黑到白", "negative");
DicPolarity.Add("从白到黑", "positive");
}
/// <summary>
/// 执行测量
/// </summary>
public void ExecuteMeasure(double row, double column, double radius,HObject hImage, HTuple windowID)
{
UpdataMeasuresParams(row, column, radius, hImage, windowID);
//7、获取计量模型对象结果轮廓:匹配结果
HOperatorSet.GetMetrologyObjectResultContour(out contour, MetrologyModel.handle, MetrologyModel.index, "all", 1.5);
}
/// <summary>
/// 更新测量参数
/// </summary>
public void UpdataMeasuresParams(double row, double column, double radius, HObject hImage, HTuple windowID)
{
//2、添加圆形计量模型
HOperatorSet.AddMetrologyObjectCircleMeasure(MetrologyModel.handle,
row, column, radius,
MetrologyModel.MeasureLength1, MetrologyModel.MeasureLength2,
MetrologyModel.MeasureSigma, MetrologyModel.MeasureThreshold,
MetrologyModel.GenParamName, MetrologyModel.GenParamValue,
out MetrologyModel.index);
SetMeasureNumber();
SetMeasureTransition();
//3、获取计量模型测量轮廓
HOperatorSet.GetMetrologyObjectModelContour(out contour, MetrologyModel.handle, MetrologyModel.index, 1.5);
HOperatorSet.SetColor(windowID, "blue");
//4、应用计量模型到图像
HOperatorSet.ApplyMetrologyModel(hImage, MetrologyModel.handle);
//5、获取计量模型结果对象
HOperatorSet.GetMetrologyObjectResult(MetrologyModel.handle, MetrologyModel.index,
"all", "result_type", "all_param", out _);
//6、获取计量模型测量结果:圆形卡尺
HOperatorSet.GetMetrologyObjectMeasures(out contours,
MetrologyModel.handle, MetrologyModel.index, MetrologyModel.Transition, out _, out _);
}
/// <summary>
/// 设置测量点数
/// </summary>
private void SetMeasureNumber()
{
HOperatorSet.SetMetrologyObjectParam(MetrologyModel.handle,"all", "num_measures", MetrologyModel.MeasureNumber);
}
/// <summary>
/// 设置极性
/// </summary>
private void SetMeasureTransition()
{
HOperatorSet.SetMetrologyObjectParam(MetrologyModel.handle, "all", "measure_transition", MetrologyModel.Transition);
}
}
计量模型代码
计量对象模型,参数数值保存。
/// <summary>
/// 计量模型
/// </summary>
public class MetrologyModel
{
public HTuple handle = new HTuple(); //(输入): 计量模型的句柄
public HTuple index = new HTuple(); //新创建的计量对象的索引
private HTuple row = new HTuple(); //圆形中心的初始行坐标(y坐标)
private HTuple column = new HTuple(); //圆形中心的初始列坐标(x坐标)
private HTuple radius = new HTuple(); //圆形的初始半径
private HTuple measureLength1 = 20; //垂直于边缘的测量区域长度(通常设为15-50)
private HTuple measureLength2 = 5; //平行于边缘的测量区域长度(通常设为5-10)
private HTuple measureSigma = 1; //高斯滤波的sigma值(通常设为1)
private HTuple measureThreshold = 30; //边缘检测的阈值(通常设为30)
private HTuple genParamName = new HTuple(); //通用参数名称(可选)
private HTuple genParamValue = new HTuple(); //通用参数值(可选)
private HTuple resolution = 1.5; //解释度
//开放参数
private HTuple transition = "all"; //边缘极性:即边缘过渡方向(所有、从黑到白、从白到黑)
private HTuple measureNumber = 30;
public HTuple Row { get => row; set => row = value; }
public HTuple Column { get => column; set => column = value; }
public HTuple Radius { get => radius; set => radius = value; }
public HTuple MeasureLength1 { get => measureLength1; set => measureLength1 = value; }
public HTuple MeasureLength2 { get => measureLength2; set => measureLength2 = value; }
public HTuple MeasureSigma { get => measureSigma; set => measureSigma = value; }
public HTuple MeasureThreshold { get => measureThreshold; set => measureThreshold = value; }
public HTuple GenParamName { get => genParamName; set => genParamName = value; }
public HTuple GenParamValue { get => genParamValue; set => genParamValue = value; }
public HTuple Index { get => index; set => index = value; }
public HTuple Resolution { get => resolution; set => resolution = value; }
public HTuple Transition { get => transition; set => transition = value; }
public HTuple MeasureNumber { get => measureNumber; set => measureNumber = value; }
~ MetrologyModel()
{
index?.Dispose();
handle?.Dispose();
row?.Dispose();
column?.Dispose();
radius?.Dispose();
measureLength1?.Dispose();
measureLength2?.Dispose();
measureSigma?.Dispose();
measureThreshold?.Dispose();
measureLength1?.Dispose();
measureLength2?.Dispose();
measureSigma?.Dispose();
transition?.Dispose();
MeasureNumber.Dispose();
}
}
项目地址
Gitee:gitee.com/incodenotes…
总结
卡尺没有像海康VM那样可以拖动调整大小,这里偷了一下懒,使用的是Halcon的圆形ROI,后面有时间会更新一下。
最后
如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。
也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!
优秀是一种习惯,欢迎大家留言学习!
作者:编程笔记in
出处:mp.weixin.qq.com/s/kBpQrmVYUQReo9ZsGNTjHQ
声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!