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)
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)
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)
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)
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)
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)
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)
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)
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 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. 环境配置
- 安装Halcon 20.11或更高版本
- 将Halcon的
halcondotnet.dll添加到项目引用 - 修改项目文件中的Halcon路径为你的安装路径
2. 基本操作
- 打开图像:点击"打开图像"按钮
- 选择ROI类型:从工具栏选择ROI类型
- 绘制ROI:点击"绘制模式",然后在图像上点击拖动
- 编辑ROI:点击ROI的控制点进行拖拽修改
- 保存ROI:点击"保存ROI"导出为XML文件
- 加载ROI:点击"导入ROI"加载之前保存的ROI
3. 快捷键
Delete:删除当前ROITab:选择下一个ROIShift+Tab:选择上一个ROI- 鼠标滚轮:缩放图像
4. 支持的ROI类型
- 矩形(Rectangle1):轴对齐矩形
- 旋转矩形(Rectangle2):可旋转矩形
- 圆形(Circle):圆形区域
- 椭圆(Ellipse):椭圆形区域
- 多边形(Polygon):多边形区域
四、功能扩展建议
- 添加更多ROI类型:扇形、环形、不规则形状
- ROI组合操作:并集、交集、差集
- 图像预处理:亮度、对比度调整
- 测量工具:距离、角度、面积测量
- 批量处理:对多个图像应用相同ROI
- 模板匹配:基于ROI的模板匹配
- 导出为Halcon Region文件:
.reg格式
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/guygg88/article/details/161802728



