WinForm+ Halcon 实现高精度圆查找测量

485 阅读7分钟

前言

本文将详细如何使用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(100100);                 //起始点
        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"1010"red"new HTuple(), new HTuple());
            }
        }
        /// <summary>
        /// 执行圆查找
        /// </summary>
        private void btn_ExecuteFind_Click(object sender, EventArgs e)
        {
            HOperatorSet.DispObj(hImage, WindowID);
            if (hDrawingObject.ID < 1return;
            //获取绘制对象参数
            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 < 1return;
            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 { getset; }
     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

声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!