CSharp联合halcon实现模板匹配

前言

1、加载并显示图像功能。
2、图像拖动缩放功能。
3、绘制ROI:矩形、方向矩形、圆形、椭圆形。
4、创建模板:参数修改、模板轮廓显示。
5、匹配模板:参数修改、匹配轮廓显示、匹配结果显示。

案例实操

在这里插入图片描述

代码结构

HalconModelSet_Ex: 该目录空间下存放halcon 算子相关模型(算子参数)。
HalconTools :该目录空间下存放图像转换、ROI相关(类、方法、事件)。
Models:该目录空间存放所有相关模型(参数);
MainForm:将所有控件放到了一个界面(应该拆分放到创建的Views目录)。
在这里插入图片描述

代码

MainForm

窗体类

设计器中添加的控件

private System.Windows.Forms.Panel panel_OperatorBar;
private System.Windows.Forms.Panel panel_MessageBar;
private System.Windows.Forms.Panel panel_ImageWindowBar;
private System.Windows.Forms.GroupBox GroupBox_Message;
private System.Windows.Forms.RichTextBox rtbx_MessageLog;
private System.Windows.Forms.GroupBox groupBox_Operator;
private System.Windows.Forms.GroupBox groupBox_ImageWin;
private System.Windows.Forms.TextBox tbx_LoadImage;
private System.Windows.Forms.Button btn_ImageLoad;
private System.Windows.Forms.Panel panel_ImageListBar;
private System.Windows.Forms.Panel panel_ImageWindow;
private System.Windows.Forms.Panel panel_ImageInfo;
private HalconDotNet.HSmartWindowControl ImageWindow;
private System.Windows.Forms.TextBox tbx_WindowState;
private System.Windows.Forms.ComboBox cbx_ImageList;
private System.Windows.Forms.Button btn_DrawROI;
private System.Windows.Forms.ContextMenuStrip cmsx_RightMenu;
private System.Windows.Forms.ToolStripMenuItem 清空内容ToolStripMenuItem;
private System.Windows.Forms.ComboBox cbx_DrawObjectList;
private System.Windows.Forms.ContextMenuStrip cmsx_RoiRightMenu;
private System.Windows.Forms.ToolStripMenuItem 删除ROItoolStripMenuItem;
private System.Windows.Forms.Panel panel_LoadImage;
private System.Windows.Forms.Panel panel_Match;
private System.Windows.Forms.TabControl tabControl_Match;
private System.Windows.Forms.TabPage tabPage_RoiList;
private System.Windows.Forms.ListBox lbx_RoiList;
private System.Windows.Forms.TabPage tabPage_RoiParams;
private System.Windows.Forms.TabPage tabPage_MatchParams;
private System.Windows.Forms.Button btn_CreateTemplate;
private System.Windows.Forms.Button btn_FindTeamplate;
private System.Windows.Forms.Panel panel_CreateTemplateOperate;
private System.Windows.Forms.Panel panel_CreateTemplateParam;
private System.Windows.Forms.Button btn_Eraser;
private System.Windows.Forms.NumericUpDown numUD_EraserSize;
private System.Windows.Forms.Panel panel_FindTemplateOperate;
private System.Windows.Forms.Panel panel_FindTemplateParam;
private System.Windows.Forms.TabPage tabPage_MatchResult;
private System.Windows.Forms.Panel panel_MatchItem;
private System.Windows.Forms.Panel panel_MathingResult;
private System.Windows.Forms.Button btn_ConfirmParam;
private System.Windows.Forms.Button btn_FlushCreateingParams;
private System.Windows.Forms.TextBox tbx_AngleExtent_Creating;
private System.Windows.Forms.TextBox tbx_AngleStep_Creating;
private System.Windows.Forms.TextBox tbx_AngleStart_Creating;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.ComboBox cbx_Metric_Creating;
private System.Windows.Forms.Label label5;
private System.Windows.Forms.ComboBox cbx_Optimization_Creating;
private System.Windows.Forms.Label label6;
private System.Windows.Forms.Label label7;
private System.Windows.Forms.Label label8;
private System.Windows.Forms.DomainUpDown dudx_Contrast_Creating;
private System.Windows.Forms.DomainUpDown dudx_MinContrast_Creating;
private System.Windows.Forms.DomainUpDown dudx__NumLevels_Creating;
private System.Windows.Forms.DataGridView dgvx_MatchResult;
private System.Windows.Forms.DomainUpDown dudx_NumLevels_Matching;
private System.Windows.Forms.DomainUpDown dudx_MinScore_Matching;
private System.Windows.Forms.Label label9;
private System.Windows.Forms.Label label10;
private System.Windows.Forms.Label label11;
private System.Windows.Forms.ComboBox cbx_SubPixel_Matching;
private System.Windows.Forms.Label label12;
private System.Windows.Forms.Label label15;
private System.Windows.Forms.Label label16;
private System.Windows.Forms.TextBox tbx_AngleStart_Matching;
private System.Windows.Forms.TextBox tbx_AngleExtent_Matching;
private System.Windows.Forms.NumericUpDown nudx_NumMatches_Matching;
private System.Windows.Forms.DomainUpDown dudx_MaxOverlap_Matching;
private System.Windows.Forms.Label label13;
private System.Windows.Forms.DomainUpDown dudx_Greediness_Matching;
private System.Windows.Forms.Label label17;
private System.Windows.Forms.ComboBox cbx_DrawFindRegion;
private System.Windows.Forms.Button btn_DrawFindRegion;
private System.Windows.Forms.DomainUpDown dudx_MixScale_Matching;
private System.Windows.Forms.Label label14;
private System.Windows.Forms.DomainUpDown dudx_MaxScale_Matching;
private System.Windows.Forms.Label label18;
private System.Windows.Forms.ComboBox cbx_MatchingModelMethod;
private System.Windows.Forms.Label label19;
private System.Windows.Forms.DomainUpDown dudx_ScaledMax_Creating;
private System.Windows.Forms.Label label20;
private System.Windows.Forms.DomainUpDown dudx_ScaledMin_Creating;
private System.Windows.Forms.Label label21;
private System.Windows.Forms.Label label22;
private System.Windows.Forms.ComboBox comboBox1;
private System.Windows.Forms.DataGridViewTextBoxColumn Column_Index;
private System.Windows.Forms.DataGridViewTextBoxColumn Column_Row;
private System.Windows.Forms.DataGridViewTextBoxColumn Column_Column;
private System.Windows.Forms.DataGridViewTextBoxColumn Column_Score;
private System.Windows.Forms.DataGridViewTextBoxColumn Column_Scaled;

字段、属性、集合

#region 字段、属性、集合
private string InitImagePath = Environment.CurrentDirectory + "\\1.png";    //初始图像路径
private string CurrentImagePath;                    //当前图像路径
private HWindow WindowID = null;                    //窗口ID
private HImage _sourceImage;                        //原图
private HImage _grayImage;                          //灰度图
private HImage _currentImage;                       //当前图像
private HImage _backgroudImage;                      //空白背景图
private HImage _matchResultImage;                      //空白背景图
private int _imageChannels = 1;                     //图像通道
private int _drawObjIndex = -1;                     //绘制对象索引
private HTuple ModelID;                             //模板ID
private HObject ModelImage;                         //模板图像
private List<HObject> MatchingContoursAffinTrans;   //匹配轮廓
private HObject CreateContours;                     //创建的模板轮廓
private HObject MatchUnitContours;                  //匹配到的合并的轮廓
private string removeItemID = "";                   //移除ROI?
private string[] _imageName = { "原图", "灰度图","模板图像", "模板轮廓图", "匹配结果图" };           //图像选择显示
private string[] _drawObjectList = { "矩形1", "矩形2", "圆形", "椭圆", "多边形" };  //ROI显示选择
private RoiTools RoiTools = new RoiTools();         //ROI工具
private bool _isGrayConvert = true;         //是否获取灰度图
//消息日志显示颜色
private Color[] MessageColors = new Color[] {System.Drawing.Color.Black, System.Drawing.Color.Aqua, System.Drawing.Color.DodgerBlue, System.Drawing.Color.DarkGreen };
/// <summary>
/// 当前图像
/// </summary>
public HImage CurrentImage
{
    get { return _currentImage; }
    private set { 
        _currentImage = value;
        if (_imageChannels==1){
            HImage tempImage = _currentImage.Clone();
            HOperatorSet.GetImagePointer1(tempImage, out HTuple pointer, out HTuple type, out HTuple width, out HTuple height);
            _matchResultImage = new HImage();
            _backgroudImage = new HImage("byte", width, height);
            _matchResultImage.GenImage3( type, width, height, pointer, pointer, pointer);
        }else{
            _matchResultImage = _currentImage.Clone();
            HOperatorSet.GetImageSize(_currentImage, out HTuple width, out HTuple height);
            _backgroudImage = new HImage("byte", width, height);
        }
    }
}
/// <summary>
/// 原图
/// </summary>
public HImage SourceImage
{
    get { return _sourceImage; }
    private set { _sourceImage = value; }
}
/// <summary>
/// 灰度图
/// </summary>
public HImage GrayImage
{
    get { return _grayImage; }
    private set { _grayImage = value; }
}
public HImage MatchResultImage { get => _matchResultImage; set => _matchResultImage = value; }
/// <summary>
/// 图像集合
/// </summary>
public List<ModelsBase> ImageModelList = new List<ModelsBase>();
/// <summary>
/// 模板参数模型(创建模板时的参数)
/// </summary>
private CreateScaledShapeModel createScaledShapeModel = new CreateScaledShapeModel();
/// <summary>
/// 模板匹配参数模型(查找时的参数)
/// </summary>
private FindScaledShapeModel findScaledShapeModel = new FindScaledShapeModel();
#endregion

窗体初始化加载

#region 窗体初始化、加载
public MainForm()
{
    InitializeComponent();
    this.Height = 800;
    this.CenterToParent();
    this.CenterToScreen();
    WindowID = ImageWindow.HalconWindow;
}
private void MainForm_Load(object sender, EventArgs e)
{
    //事件绑定
    this.MouseWheel += new System.Windows.Forms.MouseEventHandler(CustomMouseWheel);
    this.ImageWindow.HMouseMove += ImageWindow_HMouseMove;
    this.RoiTools.MessageUpdateEvents += OnUpdateMessageCallback;
    this.RoiTools.DrawObjectChangedEvents += OnDrawObjectChangedCallback;
    ///初始化控件值
    cbx_ImageList.DataSource = _imageName;
    cbx_ImageList.SelectedIndex = 1;
    cbx_DrawObjectList.DataSource = _drawObjectList;
    cbx_DrawObjectList.SelectedIndex = 0;
    tbx_LoadImage.Text = InitImagePath;
    SetValueCreating();
}
/// <summary>
/// 窗体关闭
/// </summary>
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
    RoiTools.UnEventBinding();
}
#endregion

获取窗体灰度值

#region 获取灰度值
/// <summary>
/// 图像信息显示:移动显示鼠标位置及其灰度值
/// </summary>
private void MoveShowImageInfo(HMouseEventArgs e)
{
    if (SourceImage == null) return;
    try
    {
        // 获取鼠标位置及所在位置的灰度值
        if (GrayImage != null)
        {
            GetGrayValue((int)e.X, (int)e.Y);
        }
    }
    catch (Exception ex)
    {
        UpdateMessage($"Mouse Move 异常:{ex.Message}");
    }
}
/// <summary>
/// 获取鼠标位置,及所在位置的灰度值。
/// </summary>
private void GetGrayValue(int mouseX, int mouseY)
{
    HOperatorSet.GetImageSize((HObject)GrayImage, out HTuple width, out HTuple height);
    // 确保鼠标位置在图像范围内
    if (mouseX >= 0 && mouseX < width && mouseY >= 0 && mouseY < height)
    {
        HTuple hv_GrayValue;
        HOperatorSet.GetGrayval((HObject)GrayImage, mouseY, mouseX, out hv_GrayValue);
        // 显示当前点位和灰度值
        string text = $"$(W:{width},H:{height})\t" +
            $"X: ({mouseX},Y: {mouseY})   " +
            $"(R:{hv_GrayValue},G:{hv_GrayValue},B:{hv_GrayValue})";
        Console.WriteLine(text);
        tbx_WindowState.Text = text;
    }
}
#endregion

消息栏更新消息

 #region 消息栏更新方法
 /// <summary>
 /// 更新消息
 /// </summary>
 private void UpdateMessage(string message)
 {
     rtbx_MessageLog.Invoke(new Action(() =>
     {
         if (rtbx_MessageLog.Lines.Length > 5000)
         {
             rtbx_MessageLog.Clear();
         }
         
         int index = rtbx_MessageLog.Lines.Length;
         string msg = $"{(index == 0 ? 1 : index)}\t" + $"{DateTime.Now.ToString()}{message}{Environment.NewLine}";
         rtbx_MessageLog.AppendText(msg); ;
         rtbx_MessageLog.SelectionStart = rtbx_MessageLog.Text.Length;
         rtbx_MessageLog.Focus();
     }));
 }
 /// <summary>
 /// 更新消息,异常显示红色(error level = 1)。正常显示其他颜色
 /// </summary>
 private void UpdateMessage(string message,int errorLevel)
 {
     rtbx_MessageLog.Invoke(new Action(() =>
     {
         if (rtbx_MessageLog.Lines.Length > 5000)
         {
             rtbx_MessageLog.Clear();
         }
         int index = rtbx_MessageLog.Lines.Length;
         string msg = $"{(index == 0 ? 1 : index)}\t" + $"{DateTime.Now.ToString()}{message}{Environment.NewLine}";

         int startIndex = rtbx_MessageLog.Text.Length;
         rtbx_MessageLog.AppendText(msg);
         int length = rtbx_MessageLog.Text.Length;
         if (errorLevel == 1) //异常定义
         {
             // 设置选定文本的颜色为红色
             rtbx_MessageLog.Select(startIndex, length); // 选择前 lengthToHighlight 个字符
             rtbx_MessageLog.SelectionColor = Color.Red; // 设置颜色为红色
             // 取消选择
             rtbx_MessageLog.Select(rtbx_MessageLog.Text.Length, 0); // 取消选择
             rtbx_MessageLog.SelectionColor = Color.Black; // 恢复后续文本颜色
         }
         else
         {
             Random random = new Random();
             int indexColor = random.Next(MessageColors.Length);
             // 设置选定文本的颜色为红色
             rtbx_MessageLog.Select(startIndex, length); // 选择前 lengthToHighlight 个字符
             rtbx_MessageLog.SelectionColor = MessageColors[indexColor]; // 设置颜色为红色
             // 取消选择
             rtbx_MessageLog.Select(rtbx_MessageLog.Text.Length, 0); // 取消选择
             rtbx_MessageLog.SelectionColor = Color.Black; // 恢复后续文本颜色
         }
         rtbx_MessageLog.SelectionStart = rtbx_MessageLog.Text.Length;
         rtbx_MessageLog.Focus();
     }));
 }

 /// <summary>
 ///  消息更新回调
 /// </summary>
 private void OnUpdateMessageCallback(object sender, string message)
 {
     UpdateMessage(message,0);
 }
 /// <summary>
 /// 响应:绘制对象变更回调(事件被触发后响应)
 /// </summary>
 private void OnDrawObjectChangedCallback(object sender, DrawingObjectModel drawObj)
 {
     if (drawObj != null)
     {
         switch (drawObj.Operate)
         {
             case RoiOperateModel.None:
                 break;
             case RoiOperateModel.添加:
                 lbx_RoiList.Items.Add($"ROI_{drawObj.DrawID.ToString()}");
                 break;
             case RoiOperateModel.删除:
                 string id = drawObj.DrawID.ToString();

                 foreach (var item in lbx_RoiList.Items)
                 {
                     UpdateMessage(item.ToString());
                 }
                 lbx_RoiList.Items.Remove(id);

                 break;
             case RoiOperateModel.修改:
                 break;
             case RoiOperateModel.查询:
                 if (lbx_RoiList.Items.Contains($"ROI_{drawObj.DrawID}"))
                 {
                     lbx_RoiList.SelectedItem = $"ROI_{drawObj.DrawID}";
                 }
                 break;
             default:
                 break;
         }
     }
 }
 #endregion

控件触发

#region 控件触发
/// <summary>
/// 加载图像按钮
/// </summary>
private void btn_ImageLoad_Click(object sender, EventArgs e)
{
    LoadImage(tbx_LoadImage.Text);
}
/// <summary>
/// 选择显示的图像
/// </summary>
private void cbx_ImageList_SelectedIndexChanged(object sender, EventArgs e)
{
    if (CurrentImage == null) return;
    switch (cbx_ImageList.SelectedIndex)
    {
        case 0:
            CurrentImage = SourceImage.Clone();
            UpdateMessage("设置当前图像为原图!");
            break;
        case 1:
            CurrentImage = GrayImage.Clone();
            UpdateMessage("设置当前图像为灰度图!");
            break;
        case 2:
            if (ModelImage == null) return;
            HOperatorSet.DispObj(_backgroudImage, WindowID);
            HOperatorSet.DispObj(ModelImage, WindowID);
            UpdateMessage("设置当前图像为模板图图像!");
            return;
        case 3:
            HOperatorSet.DispObj(CurrentImage, WindowID);
            DispContoursRegion(CreateContours);
            UpdateMessage("设置当前图像为匹配结果图像!");
            return;
        case 4:
            CurrentImage = MatchResultImage.Clone();
            DispContoursRegion(MatchUnitContours);
            UpdateMessage("设置当前图像为匹配结果图像!");
            return;
        default:
            break;
    }
    HOperatorSet.DispObj(CurrentImage, WindowID);
}

/// <summary>
/// 清空log消息
/// </summary>
private void 清空内容ToolStripMenuItem_Click(object sender, EventArgs e)
{
    rtbx_MessageLog.Clear();
}

/// <summary>
/// 消息栏区域鼠标按下:显示右键菜单
/// </summary>
private void rtbx_MessageLog_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Right)
    {
        cmsx_RightMenu.Show(rtbx_MessageLog, e.X, e.Y);
    }
}
/// <summary>
/// 绘制ROI
/// </summary>
private void btn_DrawROI_Click(object sender, EventArgs e)
{
    if (SourceImage == null) return;
    switch (_drawObjIndex)
    {
        case 0:
            RoiTools.DrawROI(RoiTypeModel.RECTANGLE1, WindowID);
            break;
        case 1:
            RoiTools.DrawROI(RoiTypeModel.RECTANGLE2, WindowID);
            break;
        case 2:
            RoiTools.DrawROI(RoiTypeModel.CIRCLE, WindowID);
            break;
        case 3:
            RoiTools.DrawROI(RoiTypeModel.ELLIPSE, WindowID);
            break;
        case 4:
            RoiTools.DrawROI(RoiTypeModel.XLD_CONTOUR, WindowID);
            break;
        default:
            RoiTools.DrawROI(RoiTypeModel.RECTANGLE1, WindowID);
            break;
    }
}
/// <summary>
/// 绘制对象(ROI)类型索引
/// </summary>
private void cbx_DrawObjectList_SelectedIndexChanged(object sender, EventArgs e)
{
    _drawObjIndex = cbx_DrawObjectList.SelectedIndex;
}
/// <summary>
/// 删除ROI
/// </summary>
private void 删除ROItoolStripMenuItem_Click(object sender, EventArgs e)
{
    Int64 id = Int64.Parse(removeItemID.Split('_')[1]);
    var draw = RoiTools.RoiList.FirstOrDefault(obj => obj.DrawObject.ID == id);
    if (draw != null)
    {
        lbx_RoiList.Items.Remove(removeItemID);
        RoiTools.DetachtROI(WindowID, id);
    }
}
/// <summary>
/// ROI列表区域鼠标按下
/// </summary>
private void lbx_RoiList_MouseDown(object sender, MouseEventArgs e)
{
    // 检查是否是右键按下
    if (e.Button == MouseButtons.Right)
    {
        // 获取鼠标点击的位置
        int index = lbx_RoiList.IndexFromPoint(e.Location);
        if (index != System.Windows.Forms.ListBox.NoMatches) // 确保点击的是有效项
        {
            if (index != System.Windows.Forms.ListBox.NoMatches) // 确保点击的是有效项
            {
                // 记录选中的项
                removeItemID = lbx_RoiList.Items[index].ToString();
                // 在鼠标位置显示右键菜单
                cmsx_RoiRightMenu.Show(lbx_RoiList, e.Location);
            }
        }
    }
}
/// <summary>
/// 创建模板
/// </summary>
private void btn_CreateTemplate_Click(object sender, EventArgs e)
{
    UpdateMessage("创建模板", 0);
    if (GrayImage == null) return;
    if (RoiTools.RoiList.Count < 1) return;
    HOperatorSet.DispObj(CurrentImage, WindowID);
    int result = -1;
    switch (cbx_MatchingModelMethod.SelectedIndex)
    {
        case 0:
            createScaledShapeModel.ModelCreatingMethod = 0;
            result = RoiTools.CreateTeamplate(GrayImage, createScaledShapeModel, out CreateContours, out ModelID, out ModelImage);
            break;
        case 1:
            createScaledShapeModel.ModelCreatingMethod = 1;
            result = RoiTools.CreateTeamplate(GrayImage, createScaledShapeModel, out CreateContours,
        out ModelID, out ModelImage);
            break;
        default:
            break;
    }
    if (result == 0)
    {
        MatchingContoursAffinTrans = null;
        DispContoursRegion(CreateContours);
    }
}
/// <summary>
/// 查找模板
/// </summary>
private void btn_FindTeamplate_Click(object sender, EventArgs e)
{
    //执行模板匹配
    UpdateMessage("执行模板匹配", 0);
    if (ModelImage == null || ModelID == null) return;
    try
    {
        SetValueMatching();
        HOperatorSet.DispObj(CurrentImage, WindowID);
        HTuple row = new HTuple();
        HTuple column = new HTuple();
        HTuple angle = new HTuple();
        HTuple score = new HTuple();
        HTuple scaled = new HTuple();
        switch (createScaledShapeModel.ModelCreatingMethod)
        {
            case 0:
                findScaledShapeModel.ModelCreatingMethod = 0;
                RoiTools.FindTeamplate(CurrentImage, ModelID, findScaledShapeModel, out MatchingContoursAffinTrans, out row, out column, out angle, out scaled, out score);
                break;
            case 1:
                findScaledShapeModel.ModelCreatingMethod = 1;
                RoiTools.FindTeamplate(CurrentImage, ModelID, findScaledShapeModel, out MatchingContoursAffinTrans, out row, out column, out angle, out scaled, out score);
                break;
            default:
                break;
        }
        dgvx_MatchResult.Rows.Clear();
        if (score.Length > 0)
        {

            if (scaled != null)
            {
                for (int i = 0; i < row.Length; i++)
                {
                    UpdateMessage($"匹配结果:[{row[i].D.ToString("0.000")},{column[i].D.ToString("0.000")},{angle[i].D.ToString("0.000")}],分数: {score[i].D.ToString("0.000")},缩放:[{scaled.D.ToString("0.000")}]", 0);
                    dgvx_MatchResult.Rows.Add((i), row[i].D.ToString("0.000"), column[i].D.ToString("0.000"), score[i].D.ToString("0.000"), scaled[i].D.ToString("0.000"));
                }
            }
            else
            {
                for (int i = 0; i < row.Length; i++)
                {
                    UpdateMessage($"匹配结果:[{row[i].D.ToString("0.000")},{column[i].D.ToString("0.000")},{angle[i].D.ToString("0.000")}],分数: {score[i].D.ToString("0.000")}", 0);
                    dgvx_MatchResult.Rows.Add((i), row[i].D.ToString("0.000"), column[i].D.ToString("0.000"), score[i].D.ToString("0.000"), scaled[i].D.ToString("0.000"));
                }
            }
            HOperatorSet.GenEmptyRegion(out MatchUnitContours);
            foreach (var contours in MatchingContoursAffinTrans)
            {
                //生成轮廓xld区域
                HOperatorSet.GenRegionContourXld(contours, out HObject Region, "margin");
                HOperatorSet.Union2(MatchUnitContours, Region, out MatchUnitContours);
            }
            HOperatorSet.DispRegion(MatchUnitContours, WindowID);
        }
        else
            UpdateMessage($"匹配失败,模板ID:{ModelID}。请检查参数?", 1);
    }
    catch (Exception ex)
    {
        UpdateMessage($"匹配失败,{ex.Message}", 1);
    }
}

/// <summary>
/// 刷新参数显示
/// </summary>
private void btn_FlushCreateingParams_Click(object sender, EventArgs e)
{
    DisplyValueCreating();
}
/// <summary>
/// 确认创建模板参数
/// </summary>
private void btn_ConfirmParam_Click(object sender, EventArgs e)
{
    SetValueCreating();
}

/// <summary>
/// ROI列表控件选择变更
/// </summary>
private void lbx_RoiList_SelectedIndexChanged(object sender, EventArgs e)
{
    Int64 drawID = long.Parse(lbx_RoiList.SelectedItem.ToString().Split('_')[1]);
    var drawObj = RoiTools.RoiList.FirstOrDefault(x => x.DrawObject.ID.Equals(drawID));
    //HOperatorSet.DetachDrawingObjectFromWindow(WindowID, drawObj.DrawObject);
}
/// <summary>
/// 图像窗口移动鼠标
/// </summary>
private void ImageWindow_HMouseMove(object sender, HMouseEventArgs e)
{
    MoveShowImageInfo(e);
}
/// <summary>
/// 滚动缩放图像
/// </summary>
private void CustomMouseWheel(object sender, MouseEventArgs e)
{
    System.Drawing.Point pt = this.Location;
    int leftBorder = ImageWindow.Location.X;
    int rightBorder = ImageWindow.Location.X + ImageWindow.Size.Width;
    int topBorder = ImageWindow.Location.Y;
    int bottomBorder = ImageWindow.Location.Y + ImageWindow.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);
        ImageWindow.HSmartWindowControl_MouseWheel(sender, newe);
    }
}
#endregion

ROI 参数的获取与设置

#region ROI参数获取与设置
/// <summary>
/// 设置创建模板参数
/// </summary>
private void SetValueCreating()
{
    createScaledShapeModel.AngleStart = double.Parse(tbx_AngleStart_Creating.Text);
    createScaledShapeModel.AngleExtent = double.Parse(tbx_AngleExtent_Creating.Text);
    createScaledShapeModel.AngleStep = double.Parse(tbx_AngleStep_Creating.Text);
    createScaledShapeModel.Optimization = cbx_Optimization_Creating.Text;
    createScaledShapeModel.Metric = cbx_Metric_Creating.Text;
    createScaledShapeModel.NumLevels = int.Parse(dudx__NumLevels_Creating.Text);
    createScaledShapeModel.Contrast = int.Parse(dudx_Contrast_Creating.Text);
    createScaledShapeModel.MinContrast = int.Parse(dudx_MinContrast_Creating.Text);
    createScaledShapeModel.ScaleMin = double.Parse(dudx_ScaledMin_Creating.Text);
    createScaledShapeModel.ScaleMax = double.Parse(dudx_ScaledMax_Creating.Text);
    createScaledShapeModel.ScaleStep = 0.01;
    UpdateMessage($"设置对象参数:{createScaledShapeModel.GetType().Name}", 0);
}
/// <summary>
/// 获取创建模板参数
/// </summary>
private void DisplyValueCreating()
{
    tbx_AngleStart_Creating.Text = createScaledShapeModel.AngleStart.ToString();
    tbx_AngleExtent_Creating.Text = createScaledShapeModel.AngleExtent.ToString();
    tbx_AngleStep_Creating.Text = createScaledShapeModel.AngleStep.ToString();
    cbx_Optimization_Creating.Text = createScaledShapeModel.Optimization;
    cbx_Metric_Creating.Text = createScaledShapeModel.Metric;
    dudx__NumLevels_Creating.Text = createScaledShapeModel.NumLevels.ToString();
    dudx_Contrast_Creating.Text = createScaledShapeModel.Contrast.ToString();
    dudx_MinContrast_Creating.Text = createScaledShapeModel.MinContrast.ToString();
    dudx_ScaledMin_Creating.Text = createScaledShapeModel.ScaleMin.ToString();
    dudx_ScaledMax_Creating.Text = createScaledShapeModel.ScaleMax.ToString();
    UpdateMessage($"更新参数值到UI:{createScaledShapeModel.GetType().Name}", 0);
}
/// <summary>
/// 设置查找模板参数
/// </summary>
private void SetValueMatching()
{
    findScaledShapeModel.AngleStart = double.Parse(tbx_AngleStart_Matching.Text);
    findScaledShapeModel.AngleExtent = double.Parse(tbx_AngleExtent_Matching.Text);
    findScaledShapeModel.MinScore = double.Parse(dudx_MinScore_Matching.Text);
    findScaledShapeModel.NumMatches = int.Parse(nudx_NumMatches_Matching.Value.ToString());
    if (int.TryParse(dudx_NumLevels_Matching.Text, out int reuslt))
        findScaledShapeModel.NumLevels = reuslt;
    findScaledShapeModel.SubPixel = cbx_SubPixel_Matching.Text;
    findScaledShapeModel.MaxOverlap = double.Parse(dudx_MaxOverlap_Matching.Text);
    findScaledShapeModel.Greediness = double.Parse(dudx_Greediness_Matching.Text);
    findScaledShapeModel.ScaleMin = double.Parse(dudx_MixScale_Matching.Text);
    findScaledShapeModel.ScaleMax = double.Parse(dudx_MaxScale_Matching.Text);
}
#endregion

图像加载

#region 图像加载
/// <summary>
/// 加载图像
/// </summary>
public void LoadImage(string path)
{
    CurrentImagePath = path;
    OpenFileDialog openFile = new OpenFileDialog();
    //判断设置初始目录
    openFile.InitialDirectory = Environment.CurrentDirectory;
    if (Directory.Exists(CurrentImagePath)) openFile.InitialDirectory = CurrentImagePath;
    if (Directory.Exists("D:\\Image")) openFile.InitialDirectory = "D:\\Image";
    if (Directory.Exists(CurrentImagePath)) openFile.InitialDirectory = CurrentImagePath;
    if (File.Exists(CurrentImagePath)) openFile.InitialDirectory = System.IO.Path.GetDirectoryName(CurrentImagePath);
    //判断读取图像
    if (openFile.ShowDialog() == DialogResult.OK)
    {
        CurrentImagePath = openFile.FileName;
        tbx_LoadImage.Text = openFile.FileName;
        try
        {
            ImageFormatTools.ReadImage(CurrentImagePath, out _sourceImage, out string info);
            if (info != null)
            {
                UpdateMessage($"图像直接加载异常{info}!!");
                UpdateMessage($"使用Bitmap转换图像,图像路径:{CurrentImagePath}");
            }
            //设置当前图像显示:原图
            CurrentImage = SourceImage.Clone();
            //灰度转换
            if (_isGrayConvert)
            {
                _grayImage = _sourceImage.Rgb1ToGray();
                CurrentImage = _grayImage.Clone();  //设置当前图像显示:原图
                UpdateMessage($"执行图像灰度转换:{CurrentImagePath}");
            }
            HOperatorSet.DispObj(CurrentImage, WindowID);
            UpdateMessage($"图像加载完成,图像路径:{CurrentImagePath}");
            GetImageChannel(CurrentImage);
            ImageModelList.Clear();     //先清空图像
            //添加图像到集合
            ImageModel imageModels = new ImageModel();
            imageModels.FileName = CurrentImagePath;
            imageModels.Directory = System.IO.Path.GetDirectoryName(CurrentImagePath);
            imageModels.SourceImage = _sourceImage.Clone();
            imageModels.GrayImage = _grayImage?.Clone();
            imageModels.ID = ImageModelList.Count;
            ImageModelList.Add(imageModels);
        }
        catch (Exception ex)
        {
            UpdateMessage($"图像加载失败:{ex.Message}");
        }
    }
}
#endregion

其他方法

 #region 其他方法
 /// <summary>
 /// 显示轮廓
 /// </summary>
 public void DispContoursRegion(HObject contours)
 {
     if (contours == null) return;
     HOperatorSet.SetColor(WindowID, "green");
     HOperatorSet.SetLineWidth(WindowID, 3);
     if (!(contours is HRegion))
     {
         HOperatorSet.GenRegionContourXld(contours, out HObject region, "margin");
         HOperatorSet.DispRegion(region, WindowID);
     }
     else
     {
         HOperatorSet.DispRegion(contours, WindowID);
     }
 }

 /// <summary>
 /// 获取图像通道数
 /// </summary>
 private void GetImageChannel(HImage hImage)
 {
     HOperatorSet.CountChannels(hImage, out HTuple channels);
     _imageChannels = channels;
 }
 #endregion

RoiTools

ROI工具类

事件

#region 事件定义、声明、绑定、触发方法
/// <summary>
/// 消息更新事件
/// </summary>
public event EventHandler<string> MessageUpdateEvents;
/// <summary>
/// 绘制对象(ROI)变更事件
/// </summary>
public event EventHandler<DrawingObjectModel> DrawObjectChangedEvents;

/// <summary>
/// 取消所有事件绑定
/// </summary>
public void UnEventBinding()
{
    MessageUpdateEvents = null;
    DrawObjectChangedEvents = null;
}
#region 触发事件
/// <summary>
/// 触发事件:消息更新
/// </summary>
public void OnMessageUpdateEvents(string message)
{
    MessageUpdateEvents?.Invoke(this, message);
}
/// <summary>
/// 触发事件:绘制对象变更
/// </summary>
public void OnDrawObjectChangedEvents(DrawingObjectModel drawObj)
{
    DrawObjectChangedEvents?.Invoke(this, drawObj);
}
#endregion
#endregion

字段、属性、集合

#region 字段、属性、集合
//ROI模型集合
private List<DrawingObjectModel> _roiList = new List<DrawingObjectModel>();
/// <summary>
/// ROI模型集合
/// </summary>
public List<DrawingObjectModel> RoiList
{
    get => _roiList;
    private set => _roiList = value;
}
#endregion

ROI添加、删除

#region ROI添加、删除
/// <summary>
/// 绘制ROI:根据ROI类型,绘制ROI到指定窗体ID
/// </summary>
public void DrawROI(RoiTypeModel type, HWindow windowID)
{
    HTuple[] param;
    HOperatorSet.SetColor(windowID, "blue");
    HOperatorSet.SetLineWidth(windowID, 1);
    switch (type)
    {
        case RoiTypeModel.RECTANGLE1:
            param = new HTuple[] { 100, 100, 200, 200 };
            GenROI(type, param, windowID);
            break;
        case RoiTypeModel.RECTANGLE2:
            param = new HTuple[] { 200, 200, 0, 100, 100 };
            GenROI(type, param, windowID);
            break;
        case RoiTypeModel.CIRCLE:
            param = new HTuple[] { 100, 100, 100 };
            GenROI(type, param, windowID);
            break;
        case RoiTypeModel.ELLIPSE:
            param = new HTuple[] { 100, 100, 50, 50, 50 };
            GenROI(type, param, windowID);
            break;
        case RoiTypeModel.CIRCLE_SECTOR:
            break;
        case RoiTypeModel.ELLIPSE_SECTOR:
            break;
        case RoiTypeModel.LINE:
            break;
        case RoiTypeModel.XLD_CONTOUR:
            param = new HTuple[] { 200, 200 };
            param[0].Append(new HTuple(100, 200));
            param[1].Append(new HTuple(300, 300));
            GenROI(type, param, windowID);
            break;
        case RoiTypeModel.TEXT:
            break;
        default:
            param = new HTuple[] { 100, 100, 200, 200 };
            GenROI(type, param, windowID);
            break;
    }
}

/// <summary>
/// 移除ROI对象: 移除指定窗体上的指定ROI句柄。
/// </summary>
public void DetachtROI(HWindow windowID, Int64 drawID)
{
    var drawObj = RoiList.FirstOrDefault(drawobj => drawobj.DrawID == drawID);
    if (drawObj != null)
    {
        HOperatorSet.DetachDrawingObjectFromWindow(windowID, drawObj.DrawObject);
        RoiList.Remove(drawObj);
        OnDrawObjectChangedEvents(new DrawingObjectModel(RoiOperateModel.删除, drawID));
    }
}

/// <summary>
/// 重新加载ROI
/// </summary>
public void ReAttachROI(HWindow windowID, Int64 drawID)
{
    var drawObj = RoiList.FirstOrDefault(drawobj => drawobj.DrawID == drawID);
    if (drawObj != null)
    {
        HOperatorSet.AttachDrawingObjectToWindow(windowID, drawObj.DrawObject);
        OnDrawObjectChangedEvents(new DrawingObjectModel(RoiOperateModel.重画, drawID));
    }
}
/// <summary>
/// 生成ROI:根据ROI类型和ROI参数,在指定窗体中生成ROI
/// </summary>
public void GenROI(RoiTypeModel type, HTuple[] param, HWindow windowID)
{
    if (RoiList.Count < 10)
    {
        HDrawingObject hDrawObject;
        if (type == RoiTypeModel.XLD_CONTOUR)
        {
            HOperatorSet.CreateDrawingObjectXld(param[0], param[1], out HTuple drawID);
            hDrawObject = new HDrawingObject(drawID.H);
        }
        else
        {
            hDrawObject = CreateROIObject(type, param);
        }
        ///绑定事件:ROI:拖拽、调整大小、解除附加、选中
        hDrawObject.OnDrag(OnDragDrawObjectCallback);
        hDrawObject.OnResize(OnResizeDrawObjectCallback);
        hDrawObject.OnDetach(OnDetachDrawObjectCallback);
        hDrawObject.OnSelect(OnSelectDrawObjectCallback);
        
        //创建并实例化绘制模型对象
        DrawingObjectModel drawModelObject = new DrawingObjectModel(type, hDrawObject, hDrawObject.ID, param);
        drawModelObject.Operate = RoiOperateModel.添加;
        ///添加到集合
        RoiList.Add(drawModelObject);
        //附加对象到窗体
        HOperatorSet.AttachDrawingObjectToWindow(windowID, hDrawObject);

        //打印日志
        OnMessageUpdateEvents($"添加一个{type}ROI:ID = [{hDrawObject.ID}]");
        OnDrawObjectChangedEvents(drawModelObject);
    }
    else
    {
        OnMessageUpdateEvents($"添加矩形ROI失败,最多创建10个,当前个数:{RoiList.Count}");
    }
}

/// <summary>
/// 创建ROI对象:根据ROI类型及参数,返回一个ROI(Halcon 绘制)对象。
/// </summary>
public HDrawingObject CreateROIObject(RoiTypeModel type,  HTuple[] param)
{
    return HDrawingObject.CreateDrawingObject(EnumConvert(type), param);
}

/// <summary>
/// 枚举转换:传入自定义ROI类型,转换成Halcon的ROI类型(Halcon的调用太长了...)。
/// </summary>
private HDrawingObject.HDrawingObjectType EnumConvert(RoiTypeModel type)
{
    return (HDrawingObject.HDrawingObjectType)type;
}
#endregion

获取ROI参数

#region 获取ROI参数
/// <summary>
/// 获取ROI参数:输入:绘制对象,ROI类型; 输出:ROI参数结果,ROI形状。
/// </summary>
public void GetROIParams(HDrawingObject drawingObject, RoiTypeModel roiType,
    out double[] RoiShapeParamResult, out HObject RoiShapeObj)
{
    HTuple paramName = new HTuple();
    HTuple paramValue = new HTuple();
    switch (roiType)
    {
        case RoiTypeModel.RECTANGLE1:  //矩形1
            paramName = DrawObjectParamModel.RECT1;
            paramValue = drawingObject.GetDrawingObjectParams(paramName);
            HOperatorSet.GenRectangle1(out RoiShapeObj, paramValue.DArr[0], paramValue.DArr[1], paramValue.DArr[2], paramValue.DArr[3]);
            RoiShapeParamResult = paramValue.DArr;
            break;
        case RoiTypeModel.RECTANGLE2:  //方向矩形
            paramName = DrawObjectParamModel.RECT2;
            paramValue = drawingObject.GetDrawingObjectParams(paramName);
            HOperatorSet.GenRectangle2(out RoiShapeObj, paramValue.DArr[0], paramValue.DArr[1], paramValue.DArr[2], paramValue.DArr[3], paramValue.DArr[4]);
            RoiShapeParamResult = paramValue.DArr;
            break;
        case RoiTypeModel.CIRCLE:  //圆形
            paramName = DrawObjectParamModel.CIRCLE;
            paramValue = drawingObject.GetDrawingObjectParams(paramName);
            HOperatorSet.GenCircle(out RoiShapeObj, paramValue.DArr[0], paramValue.DArr[1], paramValue.DArr[2]);
            RoiShapeParamResult = paramValue.DArr;
            break;
        case RoiTypeModel.ELLIPSE:  //圆形
            paramName = DrawObjectParamModel.ELLIPSE;
            paramValue = drawingObject.GetDrawingObjectParams(paramName);
            HOperatorSet.GenEllipse(out RoiShapeObj, paramValue.DArr[0], paramValue.DArr[1], paramValue.DArr[2], paramValue.DArr[3], paramValue.DArr[4]);
            RoiShapeParamResult = paramValue.DArr;
            break;
        case RoiTypeModel.XLD_CONTOUR:  //圆形
            paramName = DrawObjectParamModel.ELLIPSE;
            paramValue = drawingObject.GetDrawingObjectParams(paramName);
            HOperatorSet.GenEllipse(out RoiShapeObj, paramValue.DArr[0], paramValue.DArr[1], paramValue.DArr[2], paramValue.DArr[3], paramValue.DArr[4]);
            RoiShapeParamResult = paramValue.DArr;
            break;
        default:
            RoiShapeParamResult = new double[] { 0 };
            RoiShapeObj = null;
            break;
    }
}

/// <summary>
/// 获取ROI参数:输入=>ROI的ID(句柄);输出<=ROI参数数组;返回:ROI形状对象
/// </summary>
public HObject GetROIParams(Int64 roiID, out double[] RoiShapeParamResul)
{
    //遍历获取集合中首个,与输入的ROI的ID相同绘制对象,如果没找到,返回null
    var drawObj = RoiList.FirstOrDefault(id => id.DrawObject.ID.Equals(roiID));
    if (drawObj != null)
    {
        GetROIParams(drawObj.DrawObject, drawObj.RoiType, out RoiShapeParamResul, out HObject roiShapeObj);
        //消息:参数
        string data = "[";
        for (int i = 0; i < RoiShapeParamResul.Length; i++)
        {
            if (i != RoiShapeParamResul.Length - 1) data += RoiShapeParamResul[i].ToString("0.000") + ",";
            else data += RoiShapeParamResul[i].ToString("0.000") + "]";
        }
        OnMessageUpdateEvents($"{drawObj.RoiType} ROI 参数 {data}");
        return roiShapeObj;
    }
    else
    {
        RoiShapeParamResul = new double[] { 0 };
        return null;
    }
}

/// <summary>
/// 获取ROI参数:输入=>ROI的ID(句柄);输出<=ROI参数数组、ROI形状对象;返回:ROI类型
/// </summary>
public RoiTypeModel  GetRoiObjParams(Int64 roiID,out double[] RoiShapeParamResul,out HObject roiShapeObj)
{
    //遍历获取集合中首个,与输入的ROI的ID相同绘制对象,如果没找到,返回null
    var drawObj = RoiList.FirstOrDefault(id => id.DrawObject.ID.Equals(roiID));
    if (drawObj != null)
    {
        GetROIParams(drawObj.DrawObject, drawObj.RoiType, out RoiShapeParamResul, out  roiShapeObj);
        string data = "[";
        for (int i = 0; i < RoiShapeParamResul.Length; i++)
        {
            if (i != RoiShapeParamResul.Length - 1) data += RoiShapeParamResul[i].ToString("0.000") + ",";
            else data += RoiShapeParamResul[i].ToString("0.000") + "]";
        }
        OnMessageUpdateEvents($"{drawObj.RoiType} ROI 参数 {data}");
        return drawObj.RoiType;
    }
    else
    {
        RoiShapeParamResul = new double[] { 0 };
        roiShapeObj =null;
        return RoiTypeModel.NONE;
    }
}
#endregion

ROI信息变更回调

#region ROI信息变更回调(拖拽、调整大小、选择...)
/// <summary>
/// 移除ROI时触发
/// </summary>
private void OnDetachDrawObjectCallback(HDrawingObject drawid, HWindow window, string type)
{
    OnMessageUpdateEvents($"移除矩形ROI:ID = [{drawid.ID}]");
}
/// <summary>
/// 选中ROI时触发
/// </summary>
private void OnSelectDrawObjectCallback(HDrawingObject drawid, HWindow window, string type)
{
    OnMessageUpdateEvents($"选择矩形ROI:ID = [{drawid.ID}]");
    var drawObj = RoiList.FirstOrDefault(drawobj => drawobj.DrawID == drawid.ID);
    if (drawObj != null)
    {
        OnDrawObjectChangedEvents(new DrawingObjectModel(RoiOperateModel.查询, drawObj.DrawID));
    }
}
/// <summary>
/// 拖拽ROI时触发
/// </summary>
private void OnDragDrawObjectCallback(HDrawingObject drawid, HWindow window, string type)
{
    
}
/// <summary>
/// 调整ROI大小时触发
/// </summary>
private void OnResizeDrawObjectCallback(HDrawingObject drawid, HWindow window, string type)
{
    
}
#endregion

模板工具

#region 模板工具
/// <summary>
/// 创建模板:输入 图像()
/// </summary>
public int CreateTeamplate(HObject grayImage, TemplateBase templateObj, out HObject contoursAffinTrans,
    out HTuple modelID, out HObject reduceImage )
{
    modelID = null;
    reduceImage =null;
    RoiTypeModel roiType = RoiTypeModel.NONE ;
    double[] drawObjResultParam=null;
    foreach (DrawingObjectModel drawObj in RoiList)
    {
        roiType = GetRoiObjParams(drawObj.DrawObject.ID, out drawObjResultParam, out HObject region);
        HOperatorSet.ReduceDomain(grayImage, region,out reduceImage);
    }
    CreateScaledShapeModel modelParam = templateObj as CreateScaledShapeModel;
    if (templateObj.ModelCreatingMethod==1)
    {
        HOperatorSet.CreateShapeModel(reduceImage,
                                modelParam.NumLevels,
                                modelParam.AngleStart, modelParam.AngleExtent, modelParam.AngleStep,
                                modelParam.Optimization, modelParam.Metric,
                                modelParam.Contrast, modelParam.MinContrast,
                                out modelID);
    }
    else if(templateObj.ModelCreatingMethod == 0)
    {
        HOperatorSet.CreateScaledShapeModel(reduceImage, modelParam.NumLevels,
            modelParam.AngleStart, modelParam.AngleExtent, modelParam.AngleStep, 
            modelParam.ScaleMin, modelParam.ScaleMax, modelParam.ScaleStep, 
            modelParam.Optimization, modelParam.Metric, 
            modelParam.Contrast, modelParam.MinContrast, out modelID);
    }
    HTuple row =new HTuple();
    HTuple column = new HTuple();
    HTuple angle = 0;
    switch (roiType)
    {
        case RoiTypeModel.RECTANGLE1:
            row = (drawObjResultParam[0] + drawObjResultParam[2]) / 2.0;
            column = (drawObjResultParam[1] + drawObjResultParam[3]) / 2.0;
            break;
        case RoiTypeModel.RECTANGLE2:
            row = drawObjResultParam[0];
            column = drawObjResultParam[1];
            angle = drawObjResultParam[2];
            break;
        case RoiTypeModel.CIRCLE:
            row = drawObjResultParam[0];
            column = drawObjResultParam[1];
            break;
        case RoiTypeModel.ELLIPSE:
            row = drawObjResultParam[0];
            column = drawObjResultParam[1];
            angle = (drawObjResultParam[2] * 180 / Math.PI);
            break;
        case RoiTypeModel.XLD_CONTOUR:
            break;
        default:
            break;
    }
    //创建变换矩阵
    HOperatorSet.VectorAngleToRigid(0, 0, 0, row, column, angle, out HTuple hv_HomMat2D);
    //获取轮廓
    HOperatorSet.GetShapeModelContours(out HObject ho_ModelContours, modelID, 1);
    //根据轮廓和变换矩阵获得变换后的轮廓。
    HOperatorSet.AffineTransContourXld(ho_ModelContours, out contoursAffinTrans, hv_HomMat2D);
    OnMessageUpdateEvents($"模板创建成功:ID = {modelID}");
    return 0;
}
/// <summary>
/// 查找模板
/// </summary>
public void FindTeamplate(HObject findImage,  HTuple modelID,TemplateBase templateObj ,out List<HObject> contoursAffinTrans,
    out HTuple row, out HTuple column, out HTuple angle, out HTuple scale, out HTuple score)
{
    HOperatorSet.SetShapeModelParam(modelID,"timeout",5000);         //设置匹配超时时间5s
    FindScaledShapeModel matchModel = templateObj as FindScaledShapeModel;
    contoursAffinTrans = new List<HObject>();
    if (templateObj.ModelCreatingMethod==1)
    {
        HOperatorSet.FindShapeModel(findImage, modelID,
            matchModel.AngleStart, matchModel.AngleExtent,
            matchModel.MinScore, matchModel.NumMatches, matchModel.MaxOverlap,
            matchModel.SubPixel, matchModel.NumLevels, matchModel.Greediness,
            out row, out column, out angle, out score);
            scale = null;
    }
    else if(templateObj is FindScaledShapeModel)
    {
        HOperatorSet.FindScaledShapeModel(findImage, modelID, 
            matchModel.AngleStart,matchModel.AngleExtent,
            matchModel.ScaleMin,matchModel.ScaleMax,
            matchModel.MinScore,matchModel.NumMatches,matchModel.MaxOverlap,
            matchModel.SubPixel,matchModel.NumLevels,matchModel.Greediness,
            out row,out column,out angle,out scale,out score);
    }
    else
    {
        row = new HTuple();
        column = new HTuple();
        angle = new HTuple();
        score = new HTuple();
        scale =new HTuple();
    }

    
    if (row.Length > 0)
    {
        try
        {
            for (int i = 0; i < row.Length; i++)
            {
                HOperatorSet.VectorAngleToRigid(0, 0, 0, row[i], column[i], angle[i], out HTuple hv_HomMat2D);
                HOperatorSet.GetShapeModelContours(out HObject ho_ModelContours, modelID, 1);
                HOperatorSet.AffineTransContourXld(ho_ModelContours, out HObject contours, hv_HomMat2D);
                contoursAffinTrans.Add(contours);
            }
        }
        catch (HOperatorException ex)
        {
            throw new Exception($"仿射变换参数异常: {ex.Message}");
        }
    }
}
#endregion

图像转换类

/// <summary>
/// 图像转换类
/// </summary>
public class ImageFormatTools
{
    #region 判断图像的正确格式
    /// <summary>
    /// 图像格式工具:获取正确的图像格式,通过图像文件的二进制头部图像格式标识。
    /// </summary>
    public static ImageFormat GetImageFormat(string filePath)
    {
        using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
        {
            using (BinaryReader br = new BinaryReader(fs))
            {
                // 读取文件的前几个字节
                byte[] headerBytes = br.ReadBytes(16);

                // 根据文件的前几个字节判断图像的实际格式
                if (IsJpeg(headerBytes))
                {
                    return ImageFormat.Jpeg;
                }
                else if (IsPng(headerBytes))
                {
                    return ImageFormat.Png;
                }
                else if (IsGif(headerBytes))
                {
                    return ImageFormat.Gif;
                }
                else if (IsBmp(headerBytes))
                {
                    return ImageFormat.Bmp;
                }
                else
                {
                    // 默认返回未知格式
                    return null;
                }
            }
        }
    }

    /// <summary>
    /// 判断是否为 Jpeg 图像
    /// </summary>
    private static bool IsJpeg(byte[] headerBytes)
    {
        // JPEG 文件的前两个字节是 0xFF, 0xD8
        return headerBytes.Length >= 2 && headerBytes[0] == 0xFF && headerBytes[1] == 0xD8;
    }
    /// <summary>
    /// 判断是否为 Png 图像
    /// </summary>
    private static bool IsPng(byte[] headerBytes)
    {
        // PNG 文件的前八个字节是固定的签名:137 80 78 71 13 10 26 10
        return headerBytes.Length >= 8 && headerBytes[0] == 137
                && headerBytes[1] == 80 && headerBytes[2] == 78
                && headerBytes[3] == 71 && headerBytes[4] == 13
                && headerBytes[5] == 10 && headerBytes[6] == 26
                && headerBytes[7] == 10;
    }

    /// <summary>
    /// 判断是否为 gif 图像
    /// </summary>
    private static bool IsGif(byte[] headerBytes)
    {
        // GIF 文件的前三个字节是 "GIF"
        return headerBytes.Length >= 3 && headerBytes[0] == 71
                && headerBytes[1] == 73 && headerBytes[2] == 70;
    }

    /// <summary>
    /// 判断是否为 bmp 图像
    /// </summary>
    private static bool IsBmp(byte[] headerBytes)
    {
        // BMP 文件的前两个字节是 "BM"
        return headerBytes.Length >= 2 && headerBytes[0] == 66
            && headerBytes[1] == 77;
    }
    #endregion

    /// <summary>
    /// 获取图像通道数、图像像素格式。
    /// </summary>
    public static int GetImageChannelCount(string imagePath ,out Bitmap bitmap,out PixelFormat format)
    {
        try
        {
            Image image = Image.FromFile(imagePath);
            bitmap = new Bitmap(image);
            format = bitmap.PixelFormat;  // 获取图像像素格式
            image.Dispose();       
            switch (format)//据PixelFormat判断通道数
            {
                case PixelFormat.Format8bppIndexed: // 灰度图
                    return 1;
                case PixelFormat.Format24bppRgb: // RGB图
                    return 3;
                case PixelFormat.Format32bppRgb: // 
                case PixelFormat.Format32bppArgb: // RGBA图 RGB + Alpha
                    return 4;
                default:
                    return -1;
            }
        }
        catch (Exception ex)
        {
            throw new ArgumentException($"获取图像通道数->参数异常:{ex.Message}");
        }
    }

    /// <summary>
    /// 读取图像,并返回读取信息:输入图像路径,返回HImage图像(8,24,32位(1,3,4通道))
    /// </summary>
    public static void ReadImage(string path,out HImage hImage,out string info)
    {
        hImage = new HImage();
        info = null;
        GetImageChannelCount(path, out Bitmap bitmap, out PixelFormat pixelFormat);
        ImageFormat imageFormat = GetImageFormat(path);
        switch (pixelFormat)
        {
            case PixelFormat.Format8bppIndexed: //8位单通道
                try
                {
                    hImage.ReadImage(path);
                }
                catch (Exception ex)
                {
                    BitmapToHImageBpp8(bitmap, out hImage);
                    info = $"图像原格式:{imageFormat},当前后缀:{Path.GetExtension(path)}{ex.Message}";
                }
                break;
            case PixelFormat.Format24bppRgb://243通道
                try
                {
                    hImage.ReadImage(path);
                }
                catch (Exception ex)
                {
                    BitmapToHImageBpp24(bitmap, out hImage);
                    info = $"图像原格式:{imageFormat},当前后缀:{Path.GetExtension(path)}{ex.Message}";
                }
                break;
            case PixelFormat.Format32bppRgb://323通道
            case PixelFormat.Format32bppArgb://324通道
                try
                {
                    hImage.ReadImage(path);
                }
                catch (Exception ex)
                {
                    BitmapToHImageBpp32(bitmap, out hImage, pixelFormat);
                    info = $"图像原格式:{imageFormat},当前后缀:{Path.GetExtension(path)}{ex.Message}";
                }
                break;

            default: //8位单通道
                try
                {
                    hImage.ReadImage(path);
                }
                catch (Exception ex)
                {
                    BitmapToHImageBpp8(bitmap, out hImage);
                    info = $"图像原格式:{imageFormat},当前后缀:{Path.GetExtension(path)}{ex.Message}";
                }
                break;
        }
    }
    ///<summary>
    /// Bitmap转HImage(24位3通道)
    /// </summary>
    public static void BitmapToHImageBpp24(Bitmap bmp, out HImage image)
    {
        try
        {
            image = new HImage();
            Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
            BitmapData srcBmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
            image.GenImageInterleaved(srcBmpData.Scan0, "rgb", bmp.Width, bmp.Height, 0, "byte", bmp.Width, bmp.Height, 0, 0, -1, 0);
            bmp.UnlockBits(srcBmpData);
        }
        catch (Exception ex)
        {
            image = null;
        }
    }

    ///<summary>
    /// Bitmap转HImage(32位4通道)
    /// </summary>
    public static void BitmapToHImageBpp32(Bitmap bmp, out HImage image, PixelFormat pixelFormat)
    {
        try
        {
            image = new HImage();
            Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
            BitmapData srcBmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, pixelFormat);
            image.GenImageInterleaved(srcBmpData.Scan0, "bgrx", bmp.Width, bmp.Height, 0, "byte", bmp.Width, bmp.Height, 0, 0, -1, 0);
            bmp.UnlockBits(srcBmpData);
        }
        catch (Exception ex)
        {
            image = null;
        }
    }
    /// <summary>
    /// Bitmap转HImage(8位单通道)
    /// </summary>
    public static void BitmapToHImageBpp8(Bitmap bmp,out HImage image)
    {
        try
        {
            image = new HImage();
            Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
            BitmapData srcBmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);
            image.GenImage1("byte", bmp.Width, bmp.Height, srcBmpData.Scan0);
            bmp.UnlockBits(srcBmpData);
        }
        catch (Exception ex)
        {
            image = null;
        }
    }
}

Models

ALL参数模型类

DrawingObjectModel

/// <summary>
/// 绘制对象(ROI)模型
/// </summary>
public class DrawingObjectModel
{
    /// <summary>
    /// 绘制对象(ROI)参数
    /// </summary>
    public HTuple[] RoiParams {  get; set; }

    /// <summary>
    /// 绘制对象(ROI)类型
    /// </summary>
    public RoiTypeModel RoiType { get; set; }

    /// <summary>
    /// 绘制对象(ROI)对象
    /// </summary>
    public HDrawingObject DrawObject { get; set; }

    /// <summary>
    /// 绘制对象(ROI)ID
    /// </summary>
    public HTuple DrawID { get; set; }

    /// <summary>
    /// 绘制对象(ROI)执行操作
    /// </summary>
    public RoiOperateModel Operate { get; set; }
    public DrawingObjectModel(RoiTypeModel roiType, HDrawingObject drawObj, HTuple drawID ,HTuple[] roiParams)
    {
        this.RoiParams = roiParams;
        this.RoiType = roiType;
        this.DrawObject = drawObj;
        this.DrawID = drawID;
        this.Operate = RoiOperateModel.None;
    }
    public DrawingObjectModel(RoiOperateModel Operate, HTuple drawID)
    {
        this.Operate = Operate;
        this.DrawID = drawID;
    }
}

DrawObjectParamModel

/// <summary>
/// ROI绘制形状参数模型:获取对应形状的参数值时使用
/// </summary>
public class DrawObjectParamModel
{
    /// <summary>
    /// 圆形参数
    /// </summary>
    public readonly static string[] CIRCLE = { "row", "column", "radius" };
    /// <summary>
    /// 椭圆参数
    /// </summary>
    public readonly static string[] ELLIPSE = { "row", "column", "phi", "radius1", "radius2" };
    /// <summary>
    /// 矩形1参数
    /// </summary>
    public readonly static string[] RECT1 = { "row1", "column1", "row2", "column2" };
    /// <summary>
    /// 矩形2参数
    /// </summary>
    public readonly static string[] RECT2 = { "row", "column", "phi", "length1", "length2" };
}

ImageModel

public class ImageModel: ModelsBase
{
    /// <summary>
    /// 图像ID
    /// </summary>
    public Int64 ID {  get; set; }
    /// <summary>
    /// 图像文件名(路径)
    /// </summary>
    public string FileName { get; set; }

    /// <summary>
    /// 图像目录
    /// </summary>
    public string Directory { get; set; }
    /// <summary>
    /// 原图
    /// </summary>
    public HImage SourceImage { get; set; }
    /// <summary>
    /// 灰度图
    /// </summary>
    public HImage GrayImage { get; set; }
}

ModelsBase

模型基类(空空如也,后面自己增加功能)

public abstract class ModelsBase{}

RoiOperateModel

枚举

/// <summary>
/// ROI操作模型
/// </summary>
public enum RoiOperateModel
{
    None = 0,
    添加 = 1,
    删除 = 2,
    修改 = 3,
    查询 = 4,
    重画 = 5
}

RoiTypeModel

枚举

 /// <summary>
 /// ROI类型枚举模型
 /// </summary>
 public enum RoiTypeModel
 {
     
     /// <summary>
     /// 矩形
     /// </summary>
     RECTANGLE1,
     /// <summary>
     /// 方向矩形
     /// </summary>
     RECTANGLE2,
     /// <summary>
     /// 圆
     /// </summary>
     CIRCLE,
     /// <summary>
     /// 椭圆
     /// </summary>
     ELLIPSE,
     /// <summary>
     /// 圆扇
     /// </summary>
     CIRCLE_SECTOR,
     /// <summary>
     /// 椭圆扇
     /// </summary>
     ELLIPSE_SECTOR,
     /// <summary>
     /// 线
     /// </summary>
     LINE,
     /// <summary>
     /// 轮廓
     /// </summary>
     XLD_CONTOUR,
     /// <summary>
     /// 文本
     /// </summary>
     TEXT,
     /// <summary>
     /// 没有返回,放最后面,否则转换时报错
     /// </summary>
     NONE,
 }

HalconModelSet_Ex

模板

TemplateBase

/// <summary>
/// 模板基类
/// </summary>
public abstract class TemplateBase
{
    private int modelCreatingMethod =0;             //模板创建方式:0默认缩放模板
    private int numLevels = 2;                      //金字塔层数
    private double angleStart = -180;               //起始角度
    private double angleExtent = 360;               //角度范围
    private double angleStep = 0;                   //角度步长
    private double scaleMin = 0.8;                  //最小缩放比
    private double scaleMax = 1.2;                  //最大缩放比
    private double scaleStep = 0.01;                //缩放步长
    private string optimization = "auto";           //模板优化
    private string metric = "use_polarity";         //匹配方法
    private int contrast = 30;                      //对比度
    private int minContrast = 10;                   //最小对比度

    /// <summary>
    /// 金字塔层数
    /// </summary>
    public int NumLevels { get => numLevels; set => numLevels = value; }

    /// <summary>
    /// 起始角度
    /// </summary>
    public double AngleStart { get => angleStart; set => angleStart = value; }

    /// <summary>
    /// 角度范围
    /// </summary>
    public double AngleExtent { get => angleExtent; set => angleExtent = value; }

    /// <summary>
    /// 角度步长
    /// </summary>
    public double AngleStep { get => angleStep; set => angleStep = value; }

    /// <summary>
    /// 最小缩放比
    /// </summary>
    public double ScaleMin { get => scaleMin; set => scaleMin = value; }

    /// <summary>
    /// 最大缩放比
    /// </summary>
    public double ScaleMax { get => scaleMax; set => scaleMax = value; }


    /// <summary>
    /// 缩放步长
    /// </summary>
    public double ScaleStep { get => scaleStep; set => scaleStep = value; }

    /// <summary>
    /// 模板优化
    /// </summary>
    public string Optimization { get => optimization; set => optimization = value; }

    /// <summary>
    /// 匹配方法
    /// </summary>
    public string Metric { get => metric; set => metric = value; }
    /// <summary>
    /// 对比度
    /// </summary>
    public int Contrast { get => contrast; set => contrast = value; }

    /// <summary>
    /// 最小对比度
    /// </summary>
    public int MinContrast { get => minContrast; set => minContrast = value; }
    /// <summary>
    /// 模板创建方式
    /// </summary>
    public int ModelCreatingMethod { get => modelCreatingMethod; set => modelCreatingMethod = value; }
}

CreateScaledShapeModel

有点参数重复自行删除

public class CreateScaledShapeModel:TemplateBase
{
    private int modelCreatingMethod = 0;             //模板创建方式:0默认缩放模板
    private int numLevels = 2;                      //金字塔层数
    private double angleStart = -180;               //起始角度
    private double angleExtent = 360;               //角度范围
    private double angleStep = 0;                   //角度步长
    private double scaleMin = 0.8;                  //最小缩放比
    private double scaleMax = 1.2;                  //最大缩放比
    private double scaleStep = 0.01;                //缩放步长
    private string optimization = "auto";           //模板优化
    private string metric = "use_polarity";         //匹配方法
    private int contrast = 30;                      //对比度
    private int minContrast = 10;                   //最小对比度

    /// <summary>
    /// 金字塔层数
    /// </summary>
    public int NumLevels { get => numLevels; set => numLevels = value; }
    /// <summary>
    /// 起始角度
    /// </summary>
    public double AngleStart { get => angleStart; set => angleStart = value; }
    /// <summary>
    /// 角度范围
    /// </summary>
    public double AngleExtent { get => angleExtent; set => angleExtent = value; }

    /// <summary>
    /// 角度步长
    /// </summary>
    public double AngleStep { get => angleStep; set => angleStep = value; }

    /// <summary>
    /// 最小缩放比
    /// </summary>
    public double ScaleMin { get => scaleMin; set => scaleMin = value; }

    /// <summary>
    /// 最大缩放比
    /// </summary>
    public double ScaleMax { get => scaleMax; set => scaleMax = value; }


    /// <summary>
    /// 缩放步长
    /// </summary>
    public double ScaleStep { get => scaleStep; set => scaleStep = value; }

    /// <summary>
    /// 模板优化
    /// </summary>
    public string Optimization { get => optimization; set => optimization = value; }

    /// <summary>
    /// 匹配方法
    /// </summary>
    public string Metric { get => metric; set => metric = value; }
    /// <summary>
    /// 对比度
    /// </summary>
    public int Contrast { get => contrast; set => contrast = value; }

    /// <summary>
    /// 最小对比度
    /// </summary>
    public int MinContrast { get => minContrast; set => minContrast = value; }
    /// <summary>
    /// 模板创建方式
    /// </summary>
    public int ModelCreatingMethod { get => modelCreatingMethod; set => modelCreatingMethod = value; }
}

FindScaledShapeModel

有点参数重复自行删除

/// <summary>
/// 匹配缩放模板参数:建议将所有匹配模板的参数都设置
/// </summary>
public class FindScaledShapeModel : TemplateBase
{
    private double angleStart = -180;
    private double angleExtent = 360;
    private double scaleMin = 0.8;              //最小缩放比
    private double scaleMax = 1.2;              //最大缩放比
    private double minScore = 5;
    private double maxOverlap = 0.5;
    private double greediness = 0.9;
    private int numMatches = 0;
    private int numLevels = 2;
    private string subPixel = "least_squares";

    /// <summary>
    /// 起始角度
    /// </summary>
    public double AngleStart { get => angleStart; set => angleStart = value; }
    /// <summary>
    /// 角度范围(大于起始角度,大于1)
    /// </summary>
    public double AngleExtent { get => angleExtent; set => angleExtent = value; }
    /// <summary>
    /// 最小缩放范围
    /// </summary>
    public double ScaleMin { get => scaleMin; set => scaleMin = value; }
    /// <summary>
    /// 最大缩放范围
    /// </summary>
    public double ScaleMax { get => scaleMax; set => scaleMax = value; }
    /// <summary>
    /// 最小分数
    /// </summary>
    public double MinScore { get => minScore; set => minScore = value; }
    /// <summary>
    /// 最大重叠率
    /// </summary>
    public double MaxOverlap { get => maxOverlap; set => maxOverlap = value; }
    /// <summary>
    /// 贪婪度(速度尺度)
    /// </summary>
    public double Greediness { get => greediness; set => greediness = value; }
    /// <summary>
    /// 匹配个数
    /// </summary>
    public int NumMatches { get => numMatches; set => numMatches = value; }
    /// <summary>
    /// 金字塔层数
    /// </summary>
    public int NumLevels { get => numLevels; set => numLevels = value; }
    /// <summary>
    /// 匹配精度
    /// </summary>
    public string SubPixel { get => subPixel; set => subPixel = value; }
}

代码上传完成

总结

1、代码本来像添加绘制多边形ROI的,刚开始是判断点击绘制按钮后,点击图像窗口,绘制点,右键结束。然后根据点生成区域,再生成绘制对象。最后跟添加其他绘制对象一样将该对象附加到窗体。实现了绘制,但是生成模板时出现bug,暂时没修补。
2、代码的不足之处是。基本处理绘制ROI功能外,都放置在了程序主界面,后面会分开,模块化。
3、许多功能如创建模板、匹配模板这些方法过于臃肿,后期需要拆解多把一些功能创建成方法。
4、待发现。 …

  • 18
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值