基于C# + Halcon的通用ROI绘制工具

C# + Halcon联合编程ROI绘制工具,支持矩形、旋转矩形、圆形、椭圆、多边形等多种ROI形状,具备绘制、编辑、保存、加载功能。


一、项目结构

复制代码
HalconROITool/
├── HalconROITool.csproj
├── Program.cs
├── MainForm.cs
├── MainForm.Designer.cs
├── HalconROI/
│   ├── ROIBase.cs
│   ├── ROIRectangle1.cs
│   ├── ROIRectangle2.cs
│   ├── ROICircle.cs
│   ├── ROIEllipse.cs
│   ├── ROIPolygon.cs
│   └── ROIUtility.cs
├── Controls/
│   ├── HalconWindowEx.cs
│   └── ROIPropertyPanel.cs
├── Models/
│   ├── ROIModel.cs
│   └── RoiConfig.cs
├── Resources/
└── bin/

二、核心代码实现

1. 主程序入口 (Program.cs)

csharp 复制代码
using System;
using System.Windows.Forms;

namespace HalconROITool
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            
            // 初始化Halcon
            try
            {
                HSystem.SetSystem("use_window_thread", "true");
            }
            catch
            {
                // 忽略错误
            }
            
            Application.Run(new MainForm());
        }
    }
}

2. 主窗体 (MainForm.cs)

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using System.Xml.Serialization;
using HalconDotNet;
using HalconROITool.Controls;
using HalconROITool.HalconROI;
using HalconROITool.Models;

namespace HalconROITool
{
    public partial class MainForm : Form
    {
        // 控件
        private HalconWindowEx halconWindow;
        private ToolStrip toolStrip;
        private StatusStrip statusStrip;
        private MenuStrip menuStrip;
        private Panel propertyPanel;
        
        // 状态
        private HObject currentImage = null;
        private string currentImagePath = "";
        private ROIPropertyPanel roiPropertyPanel;
        
        public MainForm()
        {
            InitializeComponent();
            InitializeControls();
        }
        
        private void InitializeComponent()
        {
            this.Text = "Halcon ROI绘制工具";
            this.Size = new Size(1200, 800);
            this.StartPosition = FormStartPosition.CenterScreen;
            this.Icon = Properties.Resources.Icon;
        }
        
        private void InitializeControls()
        {
            // 菜单栏
            InitializeMenu();
            
            // 工具栏
            InitializeToolbar();
            
            // 状态栏
            InitializeStatusbar();
            
            // Halcon窗口
            halconWindow = new HalconWindowEx
            {
                Dock = DockStyle.Fill
            };
            halconWindow.ROIChanged += HalconWindow_ROIChanged;
            halconWindow.StatusMessage += HalconWindow_StatusMessage;
            halconWindow.ImageDisplayed += HalconWindow_ImageDisplayed;
            
            // 属性面板
            InitializePropertyPanel();
            
            // 布局
            SplitContainer splitContainer = new SplitContainer
            {
                Dock = DockStyle.Fill,
                Orientation = Orientation.Vertical,
                SplitterDistance = this.Width - 300
            };
            
            splitContainer.Panel1.Controls.Add(halconWindow);
            splitContainer.Panel2.Controls.Add(propertyPanel);
            
            this.Controls.AddRange(new Control[] { 
                menuStrip, toolStrip, splitContainer, statusStrip 
            });
        }
        
        private void InitializeMenu()
        {
            menuStrip = new MenuStrip();
            
            // 文件菜单
            ToolStripMenuItem fileMenu = new ToolStripMenuItem("文件(&F)");
            fileMenu.DropDownItems.Add("打开图像(&O)...", null, MenuOpenImage_Click);
            fileMenu.DropDownItems.Add("关闭图像(&C)", null, MenuCloseImage_Click);
            fileMenu.DropDownItems.Add(new ToolStripSeparator());
            fileMenu.DropDownItems.Add("导入ROI(&I)...", null, MenuImportROI_Click);
            fileMenu.DropDownItems.Add("导出ROI(&E)...", null, MenuExportROI_Click);
            fileMenu.DropDownItems.Add(new ToolStripSeparator());
            fileMenu.DropDownItems.Add("退出(&X)", null, (s, e) => Application.Exit());
            
            // ROI菜单
            ToolStripMenuItem roiMenu = new ToolStripMenuItem("ROI(&R)");
            roiMenu.DropDownItems.Add("矩形(Rectangle1)", null, (s, e) => SetROIType(ROIBase.ROIType.Rectangle1));
            roiMenu.DropDownItems.Add("旋转矩形(Rectangle2)", null, (s, e) => SetROIType(ROIBase.ROIType.Rectangle2));
            roiMenu.DropDownItems.Add("圆形(Circle)", null, (s, e) => SetROIType(ROIBase.ROIType.Circle));
            roiMenu.DropDownItems.Add("椭圆(Ellipse)", null, (s, e) => SetROIType(ROIBase.ROIType.Ellipse));
            roiMenu.DropDownItems.Add("多边形(Polygon)", null, (s, e) => SetROIType(ROIBase.ROIType.Polygon));
            roiMenu.DropDownItems.Add(new ToolStripSeparator());
            roiMenu.DropDownItems.Add("删除当前ROI", null, (s, e) => halconWindow.DeleteActiveROI());
            roiMenu.DropDownItems.Add("删除所有ROI", null, (s, e) => halconWindow.DeleteAllROIs());
            roiMenu.DropDownItems.Add(new ToolStripSeparator());
            roiMenu.DropDownItems.Add("选择上一个ROI", null, (s, e) => halconWindow.SelectPreviousROI());
            roiMenu.DropDownItems.Add("选择下一个ROI", null, (s, e) => halconWindow.SelectNextROI());
            
            menuStrip.Items.AddRange(new ToolStripItem[] { fileMenu, roiMenu });
        }
        
        private void InitializeToolbar()
        {
            toolStrip = new ToolStrip();
            
            // 打开图像
            ToolStripButton btnOpen = new ToolStripButton("打开图像");
            btnOpen.Click += MenuOpenImage_Click;
            
            // 保存ROI
            ToolStripButton btnSave = new ToolStripButton("保存ROI");
            btnSave.Click += MenuExportROI_Click;
            
            // 分隔符
            toolStrip.Items.Add(new ToolStripSeparator());
            
            // ROI类型选择
            ToolStripLabel lblROI = new ToolStripLabel("ROI类型:");
            toolStrip.Items.Add(lblROI);
            
            ToolStripComboBox cmbROIType = new ToolStripComboBox();
            cmbROIType.Items.AddRange(new string[] { 
                "矩形", "旋转矩形", "圆形", "椭圆", "多边形" 
            });
            cmbROIType.SelectedIndex = 0;
            cmbROIType.SelectedIndexChanged += (s, e) => 
            {
                ROIBase.ROIType type = (ROIBase.ROIType)cmbROIType.SelectedIndex;
                SetROIType(type);
            };
            
            // 绘制模式开关
            ToolStripButton btnDrawMode = new ToolStripButton("绘制模式");
            btnDrawMode.CheckOnClick = true;
            btnDrawMode.CheckedChanged += (s, e) => 
            {
                halconWindow.IsDrawingMode = btnDrawMode.Checked;
                UpdateStatus($"绘制模式: {(btnDrawMode.Checked ? "开" : "关")}");
            };
            
            toolStrip.Items.AddRange(new ToolStripItem[] { 
                btnOpen, btnSave, 
                new ToolStripSeparator(),
                lblROI, cmbROIType, btnDrawMode
            });
        }
        
        private void InitializeStatusbar()
        {
            statusStrip = new StatusStrip();
            
            ToolStripStatusLabel lblStatus = new ToolStripStatusLabel
            {
                Text = "就绪",
                Spring = true
            };
            
            ToolStripStatusLabel lblImageInfo = new ToolStripStatusLabel
            {
                Text = "无图像",
                BorderSides = ToolStripStatusLabelBorderSides.Left
            };
            
            ToolStripStatusLabel lblROIInfo = new ToolStripStatusLabel
            {
                Text = "ROI: 0",
                BorderSides = ToolStripStatusLabelBorderSides.Left
            };
            
            statusStrip.Items.AddRange(new ToolStripItem[] { 
                lblStatus, lblImageInfo, lblROIInfo 
            });
        }
        
        private void InitializePropertyPanel()
        {
            propertyPanel = new Panel
            {
                Dock = DockStyle.Fill,
                BackColor = SystemColors.Control
            };
            
            TabControl tabControl = new TabControl
            {
                Dock = DockStyle.Fill
            };
            
            // ROI属性标签页
            TabPage tabROI = new TabPage("ROI属性");
            roiPropertyPanel = new ROIPropertyPanel();
            roiPropertyPanel.Dock = DockStyle.Fill;
            roiPropertyPanel.PropertyChanged += RoiPropertyPanel_PropertyChanged;
            tabROI.Controls.Add(roiPropertyPanel);
            
            tabControl.TabPages.Add(tabROI);
            propertyPanel.Controls.Add(tabControl);
        }
        
        #region 事件处理
        
        private void HalconWindow_ROIChanged(object sender, ROIBase roi)
        {
            UpdateROIInfo();
            roiPropertyPanel.SetROI(roi);
        }
        
        private void HalconWindow_StatusMessage(object sender, string message)
        {
            UpdateStatus(message);
        }
        
        private void HalconWindow_ImageDisplayed(object sender, HObject image)
        {
            UpdateImageInfo();
        }
        
        private void RoiPropertyPanel_PropertyChanged(object sender, ROIBase roi)
        {
            halconWindow.ReDraw();
        }
        
        private void MenuOpenImage_Click(object sender, EventArgs e)
        {
            using (OpenFileDialog dialog = new OpenFileDialog())
            {
                dialog.Filter = "图像文件|*.bmp;*.jpg;*.jpeg;*.png;*.tiff;*.tif|所有文件|*.*";
                dialog.Title = "打开图像文件";
                
                if (dialog.ShowDialog() == DialogResult.OK)
                {
                    LoadImage(dialog.FileName);
                }
            }
        }
        
        private void MenuCloseImage_Click(object sender, EventArgs e)
        {
            currentImage?.Dispose();
            currentImage = null;
            currentImagePath = "";
            
            halconWindow.DisplayImage(null);
            UpdateImageInfo();
        }
        
        private void MenuImportROI_Click(object sender, EventArgs e)
        {
            using (OpenFileDialog dialog = new OpenFileDialog())
            {
                dialog.Filter = "ROI文件|*.roi;*.xml;*.json|所有文件|*.*";
                dialog.Title = "导入ROI";
                
                if (dialog.ShowDialog() == DialogResult.OK)
                {
                    ImportROIs(dialog.FileName);
                }
            }
        }
        
        private void MenuExportROI_Click(object sender, EventArgs e)
        {
            using (SaveFileDialog dialog = new SaveFileDialog())
            {
                dialog.Filter = "ROI文件|*.roi|XML文件|*.xml|JSON文件|*.json";
                dialog.FileName = "ROIs_" + DateTime.Now.ToString("yyyyMMdd_HHmmss");
                dialog.Title = "导出ROI";
                
                if (dialog.ShowDialog() == DialogResult.OK)
                {
                    ExportROIs(dialog.FileName);
                }
            }
        }
        
        #endregion
        
        #region 核心功能
        
        private void LoadImage(string filePath)
        {
            try
            {
                currentImage?.Dispose();
                HOperatorSet.ReadImage(out currentImage, filePath);
                currentImagePath = filePath;
                halconWindow.DisplayImage(currentImage);
                UpdateImageInfo();
                UpdateStatus($"已加载图像: {Path.GetFileName(filePath)}");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"加载图像失败: {ex.Message}", "错误", 
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        
        private void SetROIType(ROIBase.ROIType type)
        {
            halconWindow.CurrentROIType = type;
            string[] typeNames = { "矩形", "旋转矩形", "圆形", "椭圆", "多边形" };
            UpdateStatus($"当前ROI类型: {typeNames[(int)type]}");
        }
        
        private void ImportROIs(string filePath)
        {
            try
            {
                string ext = Path.GetExtension(filePath).ToLower();
                
                if (ext == ".xml")
                {
                    XmlSerializer serializer = new XmlSerializer(typeof(List<ROIModel>));
                    using (FileStream stream = new FileStream(filePath, FileMode.Open))
                    {
                        List<ROIModel> models = (List<ROIModel>)serializer.Deserialize(stream);
                        halconWindow.DeleteAllROIs();
                        
                        foreach (var model in models)
                        {
                            ROIBase roi = CreateROIFromModel(model);
                            if (roi != null)
                            {
                                halconWindow.AddROI(roi);
                            }
                        }
                    }
                    UpdateStatus($"已从 {Path.GetFileName(filePath)} 导入{model.Count}个ROI");
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"导入ROI失败: {ex.Message}", "错误", 
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        
        private void ExportROIs(string filePath)
        {
            try
            {
                string ext = Path.GetExtension(filePath).ToLower();
                
                if (ext == ".xml")
                {
                    List<ROIModel> models = new List<ROIModel>();
                    foreach (var roi in halconWindow.ROIs)
                    {
                        var model = new ROIModel
                        {
                            Type = roi.Type.ToString(),
                            Name = roi.Name,
                            Parameters = roi.GetParameters().ToDArr()
                        };
                        models.Add(model);
                    }
                    
                    XmlSerializer serializer = new XmlSerializer(typeof(List<ROIModel>));
                    using (FileStream stream = new FileStream(filePath, FileMode.Create))
                    {
                        serializer.Serialize(stream, models);
                    }
                    UpdateStatus($"{models.Count}个ROI已保存到 {Path.GetFileName(filePath)}");
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"导出ROI失败: {ex.Message}", "错误", 
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        
        private ROIBase CreateROIFromModel(ROIModel model)
        {
            switch (model.Type)
            {
                case "Rectangle1":
                    var rect1 = new ROIRectangle1();
                    rect1.SetParameters(new HTuple(model.Parameters));
                    return rect1;
                    
                case "Rectangle2":
                    var rect2 = new ROIRectangle2();
                    rect2.SetParameters(new HTuple(model.Parameters));
                    return rect2;
                    
                case "Circle":
                    var circle = new ROICircle();
                    circle.SetParameters(new HTuple(model.Parameters));
                    return circle;
                    
                case "Ellipse":
                    var ellipse = new ROIEllipse();
                    ellipse.SetParameters(new HTuple(model.Parameters));
                    return ellipse;
                    
                case "Polygon":
                    var polygon = new ROIPolygon();
                    polygon.SetParameters(new HTuple(model.Parameters));
                    return polygon;
                    
                default:
                    return null;
            }
        }
        
        private void UpdateImageInfo()
        {
            if (currentImage == null)
            {
                SetStatusText(1, "无图像");
                return;
            }
            
            try
            {
                HOperatorSet.GetImageSize(currentImage, out HTuple width, out HTuple height);
                HOperatorSet.CountChannels(currentImage, out HTuple channels);
                SetStatusText(1, $"{width.I}x{height.I} ({channels.I}通道)");
            }
            catch
            {
                SetStatusText(1, "图像信息获取失败");
            }
        }
        
        private void UpdateROIInfo()
        {
            int count = halconWindow.GetROICount();
            SetStatusText(2, $"ROI: {count}");
        }
        
        private void UpdateStatus(string message)
        {
            SetStatusText(0, message);
        }
        
        private void SetStatusText(int index, string text)
        {
            if (statusStrip.InvokeRequired)
            {
                statusStrip.Invoke(new Action<int, string>(SetStatusText), index, text);
                return;
            }
            
            if (index >= 0 && index < statusStrip.Items.Count)
            {
                statusStrip.Items[index].Text = text;
            }
        }
        
        #endregion
        
        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            base.OnFormClosing(e);
            currentImage?.Dispose();
        }
    }
}

3. Halcon窗口扩展控件 (HalconWindowEx.cs)

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using HalconDotNet;
using HalconROITool.HalconROI;

namespace HalconROITool.Controls
{
    public class HalconWindowEx : HWindowControl
    {
        public event EventHandler<ROIBase> ROIChanged;
        public event EventHandler<string> StatusMessage;
        
        public bool IsDrawingMode { get; set; }
        public ROIBase.ROIType CurrentROIType { get; set; } = ROIBase.ROIType.Rectangle1;
        public ROIBase ActiveROI { get; private set; }
        public List<ROIBase> ROIs { get; private set; } = new List<ROIBase>();
        
        private bool _isDragging = false;
        private int _dragHandleIndex = -1;
        private PointF _lastMousePos = PointF.Empty;
        private HObject _currentImage = null;
        private HTuple _imageWidth, _imageHeight;
        
        public HalconWindowEx()
        {
            this.HMouseDown += HalconWindow_HMouseDown;
            this.HMouseMove += HalconWindow_HMouseMove;
            this.HMouseUp += HalconWindow_HMouseUp;
            this.HMouseWheel += HalconWindow_HMouseWheel;
        }
        
        public void DisplayImage(HObject image)
        {
            try
            {
                _currentImage = image;
                HOperatorSet.ClearWindow(this.HalconWindow);
                
                if (image != null)
                {
                    HOperatorSet.GetImageSize(image, out _imageWidth, out _imageHeight);
                    HOperatorSet.SetPart(this.HalconWindow, 0, 0, _imageHeight - 1, _imageWidth - 1);
                    HOperatorSet.DispObj(image, this.HalconWindow);
                }
                
                ReDraw();
                StatusMessage?.Invoke(this, "图像已显示");
            }
            catch (Exception ex)
            {
                StatusMessage?.Invoke(this, $"显示图像失败: {ex.Message}");
            }
        }
        
        public void AddROI(ROIBase roi)
        {
            ROIs.Add(roi);
            ActiveROI = roi;
            ReDraw();
            ROIChanged?.Invoke(this, roi);
        }
        
        public void DeleteActiveROI()
        {
            if (ActiveROI != null)
            {
                ROIs.Remove(ActiveROI);
                ActiveROI = null;
                ReDraw();
                ROIChanged?.Invoke(this, null);
            }
        }
        
        public void DeleteAllROIs()
        {
            ROIs.Clear();
            ActiveROI = null;
            ReDraw();
            ROIChanged?.Invoke(this, null);
        }
        
        public void SelectNextROI()
        {
            if (ROIs.Count == 0) return;
            
            int currentIndex = ActiveROI != null ? ROIs.IndexOf(ActiveROI) : -1;
            int nextIndex = (currentIndex + 1) % ROIs.Count;
            ActiveROI = ROIs[nextIndex];
            ReDraw();
            ROIChanged?.Invoke(this, ActiveROI);
        }
        
        public void SelectPreviousROI()
        {
            if (ROIs.Count == 0) return;
            
            int currentIndex = ActiveROI != null ? ROIs.IndexOf(ActiveROI) : -1;
            int prevIndex = (currentIndex - 1 + ROIs.Count) % ROIs.Count;
            ActiveROI = ROIs[prevIndex];
            ReDraw();
            ROIChanged?.Invoke(this, ActiveROI);
        }
        
        public int GetROICount()
        {
            return ROIs.Count;
        }
        
        public HRegion GetAllRegions()
        {
            HRegion unionRegion = new HRegion();
            unionRegion.GenEmptyRegion();
            
            foreach (var roi in ROIs)
            {
                try
                {
                    HRegion region = roi.GetRegion();
                    unionRegion = unionRegion.Union2(region);
                }
                catch { }
            }
            
            return unionRegion;
        }
        
        public void ReDraw()
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new Action(ReDraw));
                return;
            }
            
            try
            {
                HOperatorSet.ClearWindow(this.HalconWindow);
                
                if (_currentImage != null)
                {
                    HOperatorSet.DispObj(_currentImage, this.HalconWindow);
                }
                
                foreach (var roi in ROIs)
                {
                    bool isActive = (roi == ActiveROI);
                    roi.Draw(this.HalconWindow, 
                        isActive ? "red" : "green", 
                        isActive ? 3 : 2);
                    
                    if (isActive)
                    {
                        roi.DrawHandles(this.HalconWindow, "yellow", 7);
                    }
                }
            }
            catch (Exception ex)
            {
                StatusMessage?.Invoke(this, $"重绘失败: {ex.Message}");
            }
        }
        
        private void HalconWindow_HMouseDown(object sender, HMouseEventArgs e)
        {
            if (!IsDrawingMode) return;
            
            try
            {
                double imageRow = e.Y;
                double imageCol = e.X;
                
                // 检查是否点击了控制点
                if (ActiveROI != null)
                {
                    int handleIndex = ActiveROI.GetHandleAtPosition(imageRow, imageCol, 10);
                    if (handleIndex >= 0)
                    {
                        _isDragging = true;
                        _dragHandleIndex = handleIndex;
                        _lastMousePos = new PointF(e.X, e.Y);
                        return;
                    }
                }
                
                // 开始绘制新的ROI
                ROIBase newROI = CreateROI(CurrentROIType, imageRow, imageCol);
                if (newROI != null)
                {
                    AddROI(newROI);
                    _isDragging = true;
                    _dragHandleIndex = 0;
                    _lastMousePos = new PointF(e.X, e.Y);
                }
            }
            catch (Exception ex)
            {
                StatusMessage?.Invoke(this, $"鼠标按下错误: {ex.Message}");
            }
        }
        
        private void HalconWindow_HMouseMove(object sender, HMouseEventArgs e)
        {
            if (!IsDrawingMode) return;
            
            try
            {
                double imageRow = e.Y;
                double imageCol = e.X;
                StatusMessage?.Invoke(this, $"坐标: ({imageRow:F1}, {imageCol:F1})");
                
                if (_isDragging && ActiveROI != null)
                {
                    double deltaRow = e.Y - _lastMousePos.Y;
                    double deltaCol = e.X - _lastMousePos.X;
                    
                    ActiveROI.MoveHandle(_dragHandleIndex, deltaRow, deltaCol);
                    ReDraw();
                    ROIChanged?.Invoke(this, ActiveROI);
                    _lastMousePos = new PointF(e.X, e.Y);
                }
            }
            catch (Exception ex)
            {
                StatusMessage?.Invoke(this, $"鼠标移动错误: {ex.Message}");
            }
        }
        
        private void HalconWindow_HMouseUp(object sender, HMouseEventArgs e)
        {
            _isDragging = false;
            _dragHandleIndex = -1;
        }
        
        private void HalconWindow_HMouseWheel(object sender, HMouseEventArgs e)
        {
            try
            {
                HTuple row1, col1, row2, col2;
                HOperatorSet.GetPart(this.HalconWindow, out row1, out col1, out row2, out col2);
                
                double centerRow = (row1.D + row2.D) / 2;
                double centerCol = (col1.D + col2.D) / 2;
                
                double zoomFactor = (e.Delta > 0) ? 0.8 : 1.2;
                
                double newRow1 = centerRow - (centerRow - row1.D) * zoomFactor;
                double newCol1 = centerCol - (centerCol - col1.D) * zoomFactor;
                double newRow2 = centerRow + (row2.D - centerRow) * zoomFactor;
                double newCol2 = centerCol + (col2.D - centerCol) * zoomFactor;
                
                HOperatorSet.SetPart(this.HalconWindow, newRow1, newCol1, newRow2, newCol2);
                ReDraw();
            }
            catch (Exception ex)
            {
                StatusMessage?.Invoke(this, $"鼠标滚轮错误: {ex.Message}");
            }
        }
        
        private ROIBase CreateROI(ROIBase.ROIType type, double row, double col)
        {
            switch (type)
            {
                case ROIBase.ROIType.Rectangle1:
                    return new ROIRectangle1(row, col, row + 50, col + 50);
                    
                case ROIBase.ROIType.Rectangle2:
                    return new ROIRectangle2(row, col, 0, 50, 25);
                    
                case ROIBase.ROIType.Circle:
                    return new ROICircle(row, col, 30);
                    
                case ROIBase.ROIType.Ellipse:
                    return new ROIEllipse(row, col, 0, 40, 20);
                    
                case ROIBase.ROIType.Polygon:
                    return new ROIPolygon(row, col);
                    
                default:
                    return null;
            }
        }
    }
}

4. ROI基类 (ROIBase.cs)

csharp 复制代码
using System;
using HalconDotNet;

namespace HalconROITool.HalconROI
{
    public abstract class ROIBase
    {
        public enum ROIType
        {
            Rectangle1,
            Rectangle2,
            Circle,
            Ellipse,
            Polygon
        }
        
        public ROIType Type { get; protected set; }
        public string Name { get; set; } = "ROI";
        public bool IsActive { get; set; }
        public bool IsVisible { get; set; } = true;
        
        public abstract void Draw(HWindow window, string color, int lineWidth);
        public abstract void DrawHandles(HWindow window, string color, int size);
        public abstract int GetHandleAtPosition(double row, double col, double tolerance);
        public abstract void MoveHandle(int handleIndex, double deltaRow, double deltaCol);
        public abstract HRegion GetRegion();
        public abstract HTuple GetParameters();
        public abstract void SetParameters(HTuple parameters);
        public abstract ROIBase Clone();
        
        protected double Distance(double row1, double col1, double row2, double col2)
        {
            double dRow = row2 - row1;
            double dCol = col2 - col1;
            return Math.Sqrt(dRow * dRow + dCol * dCol);
        }
        
        protected void DrawCross(HWindow window, double row, double col, double size, string color)
        {
            try
            {
                HOperatorSet.SetColor(window, color);
                HOperatorSet.DispLine(window, row - size, col, row + size, col);
                HOperatorSet.DispLine(window, row, col - size, row, col + size);
            }
            catch { }
        }
    }
}

5. 矩形ROI (ROIRectangle1.cs)

csharp 复制代码
using System;
using HalconDotNet;

namespace HalconROITool.HalconROI
{
    public class ROIRectangle1 : ROIBase
    {
        private double _row1, _col1, _row2, _col2;
        
        public ROIRectangle1()
        {
            Type = ROIType.Rectangle1;
            Name = "矩形";
        }
        
        public ROIRectangle1(double row1, double col1, double row2, double col2)
        {
            Type = ROIType.Rectangle1;
            Name = "矩形";
            _row1 = row1;
            _col1 = col1;
            _row2 = row2;
            _col2 = col2;
        }
        
        public override void Draw(HWindow window, string color, int lineWidth)
        {
            if (!IsVisible) return;
            
            try
            {
                HOperatorSet.SetColor(window, color);
                HOperatorSet.SetLineWidth(window, lineWidth);
                HOperatorSet.DispRectangle1(window, _row1, _col1, _row2, _col2);
            }
            catch { }
        }
        
        public override void DrawHandles(HWindow window, string color, int size)
        {
            if (!IsVisible) return;
            
            DrawCross(window, _row1, _col1, size, color);
            DrawCross(window, _row1, _col2, size, color);
            DrawCross(window, _row2, _col2, size, color);
            DrawCross(window, _row2, _col1, size, color);
        }
        
        public override int GetHandleAtPosition(double row, double col, double tolerance)
        {
            if (Distance(row, col, _row1, _col1) <= tolerance) return 0;
            if (Distance(row, col, _row1, _col2) <= tolerance) return 1;
            if (Distance(row, col, _row2, _col2) <= tolerance) return 2;
            if (Distance(row, col, _row2, _col1) <= tolerance) return 3;
            return -1;
        }
        
        public override void MoveHandle(int handleIndex, double deltaRow, double deltaCol)
        {
            switch (handleIndex)
            {
                case 0: _row1 += deltaRow; _col1 += deltaCol; break;
                case 1: _row1 += deltaRow; _col2 += deltaCol; break;
                case 2: _row2 += deltaRow; _col2 += deltaCol; break;
                case 3: _row2 += deltaRow; _col1 += deltaCol; break;
            }
        }
        
        public override HRegion GetRegion()
        {
            HRegion region = new HRegion();
            region.GenRectangle1(_row1, _col1, _row2, _col2);
            return region;
        }
        
        public override HTuple GetParameters()
        {
            return new HTuple(_row1, _col1, _row2, _col2);
        }
        
        public override void SetParameters(HTuple parameters)
        {
            if (parameters.Length >= 4)
            {
                _row1 = parameters[0].D;
                _col1 = parameters[1].D;
                _row2 = parameters[2].D;
                _col2 = parameters[3].D;
            }
        }
        
        public override ROIBase Clone()
        {
            return new ROIRectangle1(_row1, _col1, _row2, _col2)
            {
                Name = this.Name,
                IsActive = this.IsActive,
                IsVisible = this.IsVisible
            };
        }
    }
}

6. 圆形ROI (ROICircle.cs)

csharp 复制代码
using System;
using HalconDotNet;

namespace HalconROITool.HalconROI
{
    public class ROICircle : ROIBase
    {
        private double _row, _col, _radius;
        
        public ROICircle()
        {
            Type = ROIType.Circle;
            Name = "圆形";
        }
        
        public ROICircle(double row, double col, double radius)
        {
            Type = ROIType.Circle;
            Name = "圆形";
            _row = row;
            _col = col;
            _radius = radius;
        }
        
        public override void Draw(HWindow window, string color, int lineWidth)
        {
            if (!IsVisible) return;
            
            try
            {
                HOperatorSet.SetColor(window, color);
                HOperatorSet.SetLineWidth(window, lineWidth);
                HOperatorSet.DispCircle(window, _row, _col, _radius);
            }
            catch { }
        }
        
        public override void DrawHandles(HWindow window, string color, int size)
        {
            if (!IsVisible) return;
            
            DrawCross(window, _row, _col, size, color);
            DrawCross(window, _row, _col + _radius, size, color);
        }
        
        public override int GetHandleAtPosition(double row, double col, double tolerance)
        {
            if (Distance(row, col, _row, _col) <= tolerance) return 0;
            if (Distance(row, col, _row, _col + _radius) <= tolerance) return 1;
            return -1;
        }
        
        public override void MoveHandle(int handleIndex, double deltaRow, double deltaCol)
        {
            switch (handleIndex)
            {
                case 0: _row += deltaRow; _col += deltaCol; break;
                case 1: 
                    double newRadius = Distance(_row, _col, _row + deltaRow, _col + deltaCol);
                    _radius = Math.Max(1, newRadius);
                    break;
            }
        }
        
        public override HRegion GetRegion()
        {
            HRegion region = new HRegion();
            region.GenCircle(_row, _col, _radius);
            return region;
        }
        
        public override HTuple GetParameters()
        {
            return new HTuple(_row, _col, _radius);
        }
        
        public override void SetParameters(HTuple parameters)
        {
            if (parameters.Length >= 3)
            {
                _row = parameters[0].D;
                _col = parameters[1].D;
                _radius = parameters[2].D;
            }
        }
        
        public override ROIBase Clone()
        {
            return new ROICircle(_row, _col, _radius)
            {
                Name = this.Name,
                IsActive = this.IsActive,
                IsVisible = this.IsVisible
            };
        }
    }
}

7. ROI属性面板 (ROIPropertyPanel.cs)

csharp 复制代码
using System;
using System.Windows.Forms;
using HalconDotNet;
using HalconROITool.HalconROI;

namespace HalconROITool.Controls
{
    public class ROIPropertyPanel : UserControl
    {
        public event EventHandler<ROIBase> PropertyChanged;
        
        private ROIBase _currentROI;
        private PropertyGrid propertyGrid;
        private Button btnDelete;
        private Button btnClone;
        private Button btnApply;
        
        public ROIPropertyPanel()
        {
            InitializeComponent();
        }
        
        private void InitializeComponent()
        {
            this.Size = new System.Drawing.Size(280, 600);
            
            // 属性网格
            propertyGrid = new PropertyGrid
            {
                Dock = DockStyle.Fill,
                PropertySort = PropertySort.Categorized,
                HelpVisible = false
            };
            propertyGrid.PropertyValueChanged += PropertyGrid_PropertyValueChanged;
            
            // 按钮面板
            Panel buttonPanel = new Panel
            {
                Dock = DockStyle.Bottom,
                Height = 40
            };
            
            btnDelete = new Button
            {
                Text = "删除",
                Location = new Point(10, 8),
                Size = new Size(60, 25)
            };
            btnDelete.Click += BtnDelete_Click;
            
            btnClone = new Button
            {
                Text = "克隆",
                Location = new Point(80, 8),
                Size = new Size(60, 25)
            };
            btnClone.Click += BtnClone_Click;
            
            btnApply = new Button
            {
                Text = "应用",
                Location = new Point(150, 8),
                Size = new Size(60, 25)
            };
            btnApply.Click += BtnApply_Click;
            
            buttonPanel.Controls.AddRange(new Control[] { btnDelete, btnClone, btnApply });
            
            this.Controls.Add(propertyGrid);
            this.Controls.Add(buttonPanel);
        }
        
        public void SetROI(ROIBase roi)
        {
            _currentROI = roi;
            propertyGrid.SelectedObject = roi != null ? new ROIPropertyWrapper(roi) : null;
            propertyGrid.Refresh();
        }
        
        private void PropertyGrid_PropertyValueChanged(object s, PropertyValueChangedEventArgs e)
        {
            if (_currentROI != null)
            {
                PropertyChanged?.Invoke(this, _currentROI);
            }
        }
        
        private void BtnDelete_Click(object sender, EventArgs e)
        {
            if (_currentROI != null)
            {
                _currentROI = null;
                propertyGrid.SelectedObject = null;
                PropertyChanged?.Invoke(this, null);
            }
        }
        
        private void BtnClone_Click(object sender, EventArgs e)
        {
            if (_currentROI != null)
            {
                ROIBase clonedROI = _currentROI.Clone();
                clonedROI.Name = _currentROI.Name + "_Copy";
                PropertyChanged?.Invoke(this, clonedROI);
            }
        }
        
        private void BtnApply_Click(object sender, EventArgs e)
        {
            if (_currentROI != null)
            {
                PropertyChanged?.Invoke(this, _currentROI);
            }
        }
        
        // ROI属性包装器
        private class ROIPropertyWrapper : System.ComponentModel.ICustomTypeDescriptor
        {
            private ROIBase _roi;
            
            public ROIPropertyWrapper(ROIBase roi)
            {
                _roi = roi;
            }
            
            public string Name
            {
                get { return _roi.Name; }
                set { _roi.Name = value; }
            }
            
            public bool IsVisible
            {
                get { return _roi.IsVisible; }
                set { _roi.IsVisible = value; }
            }
            
            // 实现ICustomTypeDescriptor接口
            public System.ComponentModel.AttributeCollection GetAttributes() => System.ComponentModel.TypeDescriptor.GetAttributes(_roi);
            public string GetClassName() => System.ComponentModel.TypeDescriptor.GetClassName(_roi);
            public string GetComponentName() => System.ComponentModel.TypeDescriptor.GetComponentName(_roi);
            public System.ComponentModel.TypeConverter GetConverter() => System.ComponentModel.TypeDescriptor.GetConverter(_roi);
            public System.ComponentModel.EventDescriptor GetDefaultEvent() => System.ComponentModel.TypeDescriptor.GetDefaultEvent(_roi);
            public System.ComponentModel.PropertyDescriptor GetDefaultProperty() => System.ComponentModel.TypeDescriptor.GetDefaultProperty(_roi);
            public object GetEditor(Type editorBaseType) => System.ComponentModel.TypeDescriptor.GetEditor(_roi, editorBaseType);
            public System.ComponentModel.EventDescriptorCollection GetEvents() => System.ComponentModel.TypeDescriptor.GetEvents(_roi);
            public System.ComponentModel.EventDescriptorCollection GetEvents(Attribute[] attributes) => System.ComponentModel.TypeDescriptor.GetEvents(_roi, attributes);
            public System.ComponentModel.PropertyDescriptorCollection GetProperties() => GetProperties(null);
            public System.ComponentModel.PropertyDescriptorCollection GetProperties(Attribute[] attributes)
            {
                var properties = new System.ComponentModel.PropertyDescriptorCollection(null);
                
                // 添加通用属性
                properties.Add(new ROIPropertyDescriptor("Name", typeof(string), "基本信息"));
                properties.Add(new ROIPropertyDescriptor("IsVisible", typeof(bool), "基本信息"));
                
                // 根据ROI类型添加特定属性
                if (_roi is ROIRectangle1 rect1)
                {
                    properties.Add(new ROIPropertyDescriptor("Row1", typeof(double), "位置"));
                    properties.Add(new ROIPropertyDescriptor("Column1", typeof(double), "位置"));
                    properties.Add(new ROIPropertyDescriptor("Row2", typeof(double), "位置"));
                    properties.Add(new ROIPropertyDescriptor("Column2", typeof(double), "位置"));
                }
                else if (_roi is ROICircle circle)
                {
                    properties.Add(new ROIPropertyDescriptor("CenterRow", typeof(double), "位置"));
                    properties.Add(new ROIPropertyDescriptor("CenterColumn", typeof(double), "位置"));
                    properties.Add(new ROIPropertyDescriptor("Radius", typeof(double), "尺寸"));
                }
                // 其他ROI类型的属性...
                
                return properties;
            }
            public object GetPropertyOwner(System.ComponentModel.PropertyDescriptor pd) => _roi;
        }
        
        private class ROIPropertyDescriptor : System.ComponentModel.PropertyDescriptor
        {
            private Type _propertyType;
            private string _category;
            
            public ROIPropertyDescriptor(string name, Type propertyType, string category)
                : base(name, null)
            {
                _propertyType = propertyType;
                _category = category;
            }
            
            public override Type ComponentType => typeof(ROIBase);
            public override bool IsReadOnly => false;
            public override Type PropertyType => _propertyType;
            public override string Category => _category;
            
            public override object GetValue(object component)
            {
                var roi = component as ROIBase;
                if (roi == null) return null;
                
                // 根据属性名返回值
                switch (Name)
                {
                    case "Name": return roi.Name;
                    case "IsVisible": return roi.IsVisible;
                    case "Row1": return (roi as ROIRectangle1)?._row1;
                    case "Column1": return (roi as ROIRectangle1)?._col1;
                    case "Row2": return (roi as ROIRectangle1)?._row2;
                    case "Column2": return (roi as ROIRectangle1)?._col2;
                    case "CenterRow": return (roi as ROICircle)?._row;
                    case "CenterColumn": return (roi as ROICircle)?._col;
                    case "Radius": return (roi as ROICircle)?._radius;
                    default: return null;
                }
            }
            
            public override void SetValue(object component, object value)
            {
                var roi = component as ROIBase;
                if (roi == null) return;
                
                // 根据属性名设置值
                switch (Name)
                {
                    case "Name": roi.Name = value.ToString(); break;
                    case "IsVisible": roi.IsVisible = (bool)value; break;
                    case "Row1": (roi as ROIRectangle1)._row1 = (double)value; break;
                    case "Column1": (roi as ROIRectangle1)._col1 = (double)value; break;
                    case "Row2": (roi as ROIRectangle1)._row2 = (double)value; break;
                    case "Column2": (roi as ROIRectangle1)._col2 = (double)value; break;
                    case "CenterRow": (roi as ROICircle)._row = (double)value; break;
                    case "CenterColumn": (roi as ROICircle)._col = (double)value; break;
                    case "Radius": (roi as ROICircle)._radius = (double)value; break;
                }
            }
            
            public override bool CanResetValue(object component) => false;
            public override void ResetValue(object component) { }
            public override bool ShouldSerializeValue(object component) => true;
        }
    }
}

8. 数据模型 (ROIModel.cs)

csharp 复制代码
using System;
using System.Xml.Serialization;

namespace HalconROITool.Models
{
    [Serializable]
    [XmlRoot("ROI")]
    public class ROIModel
    {
        [XmlAttribute("Type")]
        public string Type { get; set; }
        
        [XmlAttribute("Name")]
        public string Name { get; set; }
        
        [XmlArray("Parameters")]
        [XmlArrayItem("Parameter")]
        public double[] Parameters { get; set; }
        
        [XmlAttribute("IsVisible")]
        public bool IsVisible { get; set; } = true;
    }
}

9. 项目文件 (HalconROITool.csproj)

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{YOUR-PROJECT-GUID}</ProjectGuid>
    <OutputType>WinExe</OutputType>
    <RootNamespace>HalconROITool</RootNamespace>
    <AssemblyName>HalconROITool</AssemblyName>
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.Data" />
    <Reference Include="System.Deployment" />
    <Reference Include="System.Drawing" />
    <Reference Include="System.Net.Http" />
    <Reference Include="System.Windows.Forms" />
    <Reference Include="System.Xml" />
    <!-- Halcon引用 -->
    <Reference Include="halcondotnet">
      <HintPath>C:\Program Files\MVTec\HALCON-20.11-Progress\bin\dotnet35\halcondotnet.dll</HintPath>
    </Reference>
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Program.cs" />
    <Compile Include="MainForm.cs" />
    <Compile Include="MainForm.Designer.cs" />
    <Compile Include="HalconROI\ROIBase.cs" />
    <Compile Include="HalconROI\ROIRectangle1.cs" />
    <Compile Include="HalconROI\ROICircle.cs" />
    <Compile Include="HalconROI\ROIRectangle2.cs" />
    <Compile Include="HalconROI\ROIEllipse.cs" />
    <Compile Include="HalconROI\ROIPolygon.cs" />
    <Compile Include="Controls\HalconWindowEx.cs" />
    <Compile Include="Controls\ROIPropertyPanel.cs" />
    <Compile Include="Models\ROIModel.cs" />
  </ItemGroup>
  <ItemGroup>
    <None Include="App.config" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

参考代码 c#与halcon联合编程通用绘制roi www.youwenfan.com/contentcsv/111927.html

三、使用说明

1. 环境配置

  1. 安装Halcon 20.11或更高版本
  2. 将Halcon的halcondotnet.dll添加到项目引用
  3. 修改项目文件中的Halcon路径为你的安装路径

2. 基本操作

  1. 打开图像:点击"打开图像"按钮
  2. 选择ROI类型:从工具栏选择ROI类型
  3. 绘制ROI:点击"绘制模式",然后在图像上点击拖动
  4. 编辑ROI:点击ROI的控制点进行拖拽修改
  5. 保存ROI:点击"保存ROI"导出为XML文件
  6. 加载ROI:点击"导入ROI"加载之前保存的ROI

3. 快捷键

  • Delete:删除当前ROI
  • Tab:选择下一个ROI
  • Shift+Tab:选择上一个ROI
  • 鼠标滚轮:缩放图像

4. 支持的ROI类型

  • 矩形(Rectangle1):轴对齐矩形
  • 旋转矩形(Rectangle2):可旋转矩形
  • 圆形(Circle):圆形区域
  • 椭圆(Ellipse):椭圆形区域
  • 多边形(Polygon):多边形区域

四、功能扩展建议

  1. 添加更多ROI类型:扇形、环形、不规则形状
  2. ROI组合操作:并集、交集、差集
  3. 图像预处理:亮度、对比度调整
  4. 测量工具:距离、角度、面积测量
  5. 批量处理:对多个图像应用相同ROI
  6. 模板匹配:基于ROI的模板匹配
  7. 导出为Halcon Region文件.reg格式
相关推荐
yugi9878381 小时前
基于 RFID 的智能公交刷卡系统
stm32·嵌入式硬件
点灯小铭2 小时前
基于单片机的雨量检测智能汽车雨刮器模拟系统设计与实现
单片机·嵌入式硬件·汽车·毕业设计·课程设计·期末大作业
双河子思2 小时前
《代码整洁之道》——读书笔记(持续更新)
开发语言·c++·c#
诙_2 小时前
unity——C#
unity·c#·游戏引擎
三佛科技-134163842122 小时前
腕式血压计方案开发设计,腕式血压计MCU控制芯片选择
单片机·嵌入式硬件·物联网·智能家居
cici158743 小时前
C# LAS 点云读取与处理工具
stm32·单片机·c#
OnlyEasyCode3 小时前
C# 发送QQ邮箱验证码or其他
开发语言·c#
listhi5204 小时前
基于 LabVIEW 和 51 单片机的温度检测系统
单片机·mongodb·labview