Halcon 机器视觉
大约 10 分钟约 3081 字
Halcon 机器视觉
简介
Halcon 是德国 MVTec 公司开发的工业机器视觉软件库,提供丰富的图像处理算法。通过 C# 调用 Halcon,可以在 WPF 应用中实现目标检测、测量定位、二维码识别等视觉功能,广泛应用于质量检测、装配验证、缺陷检测等场景。
特点
环境配置
安装与引用
# Halcon 安装后,引用以下 DLL
# halcondotnet.dll — 核心 .NET 封装
# HalconDotNet.dll — 完整 .NET 接口<!-- WPF 项目引用 -->
<ItemGroup>
<Reference Include="halcondotnet">
<HintPath>C:\Program Files\MVTec\HALCON-22.11\bin\dotnet48\halcondotnet.dll</HintPath>
</Reference>
</ItemGroup>
<!-- 使用 HSmartWindowControl -->
<!-- xmlns:halcon="clr-namespace:HalconDotNet;assembly=halcondotnet" -->基本图像操作
图像采集与显示
/// <summary>
/// Halcon 图像采集与显示
/// </summary>
public class HalconService : IDisposable
{
private HObject? _currentImage;
private HTuple _windowHandle = -1;
// 加载图像文件
public void LoadImage(string filePath)
{
HOperatorSet.ReadImage(out _currentImage, filePath);
}
// 在窗口中显示图像
public void DisplayImage(HSmartWindowControlWPF viewer)
{
if (_currentImage == null) return;
_windowHandle = viewer.HalconWindow;
HOperatorSet.DispObj(_currentImage, _windowHandle);
HOperatorSet.SetColor(_windowHandle, "green");
HOperatorSet.SetLineWidth(_windowHandle, 2);
}
// 获取图像尺寸
public (int Width, int Height) GetImageSize()
{
if (_currentImage == null) return (0, 0);
HOperatorSet.GetImageSize(_currentImage, out HTuple width, out HTuple height);
return ((int)width, (int)height);
}
public void Dispose()
{
_currentImage?.Dispose();
}
}常用视觉算法
1. 边缘检测
/// <summary>
/// 边缘检测 — 提取物体轮廓
/// </summary>
public class EdgeDetection
{
// Canny 边缘检测
public HObject DetectEdges(HObject image)
{
HOperatorSet.Rgb1ToGray(image, out HObject grayImage);
// Canny 边缘检测
HOperatorSet.EdgesSubPix(grayImage, out HObject edges,
"canny", 1.5, 20, 40);
grayImage.Dispose();
return edges;
}
// Sobel 边缘检测
public HObject SobelEdge(HObject image)
{
HOperatorSet.Rgb1ToGray(image, out HObject grayImage);
HOperatorSet.SobelAmp(grayImage, out HObject edgeAmplitude, "sum_abs", 3);
// 阈值分割
HOperatorSet.Threshold(edgeAmplitude, out HObject region, 30, 255);
grayImage.Dispose();
edgeAmplitude.Dispose();
return region;
}
}2. 模板匹配
/// <summary>
/// 模板匹配 — 在图像中查找目标
/// </summary>
public class TemplateMatching
{
private HTuple _modelID = -1;
// 创建模板
public void CreateTemplate(HObject templateImage, int row1, int col1, int row2, int col2)
{
// 裁剪模板区域
HOperatorSet.GenRectangle1(out HObject templateRegion, row1, col1, row2, col2);
HOperatorSet.ReduceDomain(templateImage, templateRegion, out HObject templateImageReduced);
// 创建形状模板
HOperatorSet.CreateShapeModel(
templateImageReduced,
"auto", // NumLevels
new HTuple(-0.39).TupleRad(), // AngleStart
new HTuple(0.79).TupleRad(), // AngleExtent
"auto", // AngleStep
"auto", // Optimization
"use_polarity", // Metric
"auto", // Contrast
"auto", // MinContrast
out _modelID);
templateRegion.Dispose();
templateImageReduced.Dispose();
}
// 查找模板
public (double Row, double Col, double Angle) FindTemplate(HObject searchImage)
{
HOperatorSet.FindShapeModel(
searchImage,
_modelID,
new HTuple(-0.39).TupleRad(),
new HTuple(0.79).TupleRad(),
0.7, // MinScore
1, // NumMatches
0.5, // MaxOverlap
"least_squares", // SubPixel
0, // NumLevels
0.75, // Greediness
out HTuple row,
out HTuple column,
out HTuple angle,
out HTuple score);
if (row.Length > 0)
{
return (row[0].D, column[0].D, angle[0].D);
}
return (0, 0, 0);
}
public void Dispose()
{
if (_modelID != -1)
{
HOperatorSet.ClearShapeModel(_modelID);
}
}
}3. 二维码识别
/// <summary>
/// 条码/二维码识别
/// </summary>
public class BarcodeReader
{
public string[] ReadBarcodes(HObject image)
{
// 创建条码模型
HOperatorSet.CreateBarCodeModel(out HTuple barCodeHandle);
// 查找条码
HOperatorSet.FindBarCode(
image,
out HObject codeRegions,
barCodeHandle,
"auto", // CodeType: auto, EAN-13, QR Code 等
out HTuple decodedDataStrings);
// 清理
HOperatorSet.ClearBarCodeModel(barCodeHandle);
codeRegions.Dispose();
var results = new string[decodedDataStrings.Length];
for (int i = 0; i < decodedDataStrings.Length; i++)
{
results[i] = decodedDataStrings[i].S;
}
return results;
}
// 二维码识别
public string[] ReadQRCodes(HObject image)
{
HOperatorSet.CreateDataCode2dModel(
"QR Code", "default_parameters", "standard_recognition",
out HTuple modelHandle);
HOperatorSet.FindDataCode2d(
image, out HObject codeRegions, modelHandle,
out HTuple resultHandles, out HTuple decodedStrings);
HOperatorSet.ClearDataCode2dModel(modelHandle);
var results = new string[decodedStrings.Length];
for (int i = 0; i < decodedStrings.Length; i++)
{
results[i] = decodedStrings[i].S;
}
return results;
}
}4. 尺寸测量
/// <summary>
/// 尺寸测量 — 卡尺工具
/// </summary>
public class MeasurementTool
{
// 测量两点间距离
public double MeasureDistance(HObject image, double startRow, double startCol, double endRow, double endCol)
{
// 创建测量矩形
HOperatorSet.GenMeasureRectangle2(
(startRow + endRow) / 2, (startCol + endCol) / 2, // 中心
new HTuple(Math.Atan2(endRow - startRow, endCol - startCol)), // 角度
Math.Sqrt(Math.Pow(endRow - startRow, 2) + Math.Pow(endCol - startCol, 2)) / 2, // 宽
10, // 高
image.GetImageSize()[0], image.GetImageSize()[1],
"nearest_neighbor",
out HTuple measureHandle);
// 执行测量
HOperatorSet.MeasurePos(
image, measureHandle,
1.0, // Sigma
30, // Threshold
"all", // Transition
"all", // Select
out HTuple rowEdge, out HTuple colEdge,
out HTuple amplitude, out HTuple distance);
HOperatorSet.CloseMeasure(measureHandle);
return distance.Length > 0 ? distance[0].D : 0;
}
// 测量宽度(两个平行边缘)
public double MeasureWidth(HObject image, double row, double col, double angle, int width)
{
HOperatorSet.GenMeasureRectangle2(row, col, angle, width, 10,
1920, 1080, "nearest_neighbor", out HTuple handle);
HOperatorSet.MeasurePos(image, handle, 1.0, 30, "positive", "all",
out HTuple posRows, out HTuple posCols, out HTuple amp, out HTuple dist);
HOperatorSet.CloseMeasure(handle);
return posRows.Length >= 2 ? Math.Abs(posRows[1].D - posRows[0].D) : 0;
}
}WPF 集成
MVVM 视觉检测
/// <summary>
/// 视觉检测 ViewModel
/// </summary>
public class VisionInspectionViewModel : ObservableObject
{
private readonly HalconService _halconService;
private string _resultText = "等待检测...";
public string ResultText
{
get => _resultText;
set => SetProperty(ref _resultText, value);
}
private bool _isPassed;
public bool IsPassed
{
get => _isPassed;
set => SetProperty(ref _isPassed, value);
}
public ICommand CaptureCommand { get; }
public ICommand InspectCommand { get; }
public HSmartWindowControlWPF? Viewer { get; set; }
public VisionInspectionViewModel()
{
_halconService = new HalconService();
CaptureCommand = new RelayCommand(Capture);
InspectCommand = new RelayCommand(Inspect);
}
private void Capture()
{
_halconService.LoadImage("sample.png");
if (Viewer != null)
{
_halconService.DisplayImage(Viewer);
}
}
private void Inspect()
{
try
{
// 执行检测逻辑
var result = RunInspection();
IsPassed = result.Passed;
ResultText = result.Passed
? $"检测通过 — 尺寸:{result.Measurement:F2}mm"
: $"检测失败 — {result.ErrorMessage}";
}
catch (Exception ex)
{
ResultText = $"检测异常:{ex.Message}";
IsPassed = false;
}
}
private InspectionResult RunInspection()
{
// 调用视觉算法
return new InspectionResult(true, 25.35, null);
}
}
public record InspectionResult(bool Passed, double Measurement, string? ErrorMessage);Blob 分析与区域分割
灰度阈值分割
/// <summary>
/// Blob 分析 — 通过阈值分割提取目标区域
/// </summary>
public class BlobAnalysis
{
// 基本阈值分割
public HObject ThresholdSegmentation(HObject image)
{
HOperatorSet.Rgb1ToGray(image, out HObject grayImage);
// 全局阈值
HOperatorSet.Threshold(grayImage, out HObject region, 128, 255);
// 连通域分析
HOperatorSet.Connection(region, out HObject connectedRegions);
// 按面积筛选
HOperatorSet.SelectShape(connectedRegions, out HObject selectedRegions,
"area", "and", 500, 100000);
grayImage.Dispose();
region.Dispose();
connectedRegions.Dispose();
return selectedRegions;
}
// 自动阈值(Otsu)
public HObject AutoThreshold(HObject image)
{
HOperatorSet.Rgb1ToGray(image, out HObject grayImage);
// 使用直方图自动确定阈值
HOperatorSet.BinaryThreshold(grayImage, out HObject region,
"max_separability", "dark", out HTuple usedThreshold);
Console.WriteLine($"自动阈值: {usedThreshold}");
grayImage.Dispose();
return region;
}
// 动态阈值(局部阈值)
public HObject DynamicThreshold(HObject image)
{
HOperatorSet.Rgb1ToGray(image, out HObject grayImage);
// 先做均值滤波
HOperatorSet.MeanImage(grayImage, out HObject meanImage, 31, 31);
// 将原图与均值图做差
HOperatorSet.DynThreshold(grayImage, meanImage, out HObject region,
5, "light");
grayImage.Dispose();
meanImage.Dispose();
return region;
}
// 区域特征提取
public void ExtractRegionFeatures(HObject region)
{
// 面积
HOperatorSet.AreaCenter(region, out HTuple area, out HTuple row, out HTuple column);
// 紧凑度
HOperatorSet.Compactness(region, out HTuple compactness);
// 外接矩形
HOperatorSet.SmallestRectangle1(region,
out HTuple row1, out HTuple col1, out HTuple row2, out HTuple col2);
// 方向
HOperatorSet.OrientationRegion(region, out HTuple phi);
for (int i = 0; i < area.Length; i++)
{
Console.WriteLine($"区域 {i}: 面积={area[i].I}, " +
$"中心=({row[i].D:F1}, {column[i].D:F1}), " +
$"方向={phi[i].D * 180 / Math.PI:F1}°");
}
}
}1D 测量与卡尺
精密尺寸测量
/// <summary>
/// 1D 测量 — 精密边缘定位和尺寸测量
/// </summary>
public class PrecisionMeasurement
{
// 测量圆的直径
public double MeasureCircleDiameter(HObject image, double centerX, double centerY)
{
// 创建测量弧
HOperatorSet.GenMeasureArc(centerY, centerX, 100, 0, Math.PI * 2,
10, 1920, 1080, "nearest_neighbor", out HTuple measureHandle);
// 执行测量
HOperatorSet.MeasurePos(image, measureHandle, 1.0, 30,
"all", "all", out HTuple rowEdge, out HTuple colEdge,
out HTuple amplitude, out HTuple distance);
HOperatorSet.CloseMeasure(measureHandle);
// 计算直径
if (rowEdge.Length >= 2)
{
double dx = colEdge[0].D - colEdge[1].D;
double dy = rowEdge[0].D - rowEdge[1].D;
return Math.Sqrt(dx * dx + dy * dy);
}
return 0;
}
// 测量平行边缘间距(宽度)
public double MeasureWidth(HObject image, double row, double col,
double angle, int measureLength)
{
HOperatorSet.GenMeasureRectangle2(row, col, angle,
measureLength, 10, 1920, 1080,
"nearest_neighbor", out HTuple measureHandle);
// 测量正负边缘对
HOperatorSet.MeasurePos(image, measureHandle, 1.0, 20,
"all", "all", out HTuple rowEdge, out HTuple colEdge,
out HTuple amplitude, out HTuple distance);
HOperatorSet.CloseMeasure(measureHandle);
// distance 就是两个边缘之间的距离
return distance.Length > 0 ? distance[0].D : 0;
}
// 批量测量 — 测量多个位置
public List<double> BatchMeasure(HObject image, List<(double Row, double Col, double Angle)> positions)
{
var results = new List<double>();
foreach (var pos in positions)
{
var width = MeasureWidth(image, pos.Row, pos.Col, pos.Angle, 50);
results.Add(width);
}
return results;
}
}缺陷检测实战
表面缺陷检测流程
/// <summary>
/// 表面缺陷检测 — 工业质检常见场景
/// </summary>
public class DefectDetection
{
// 频域滤波缺陷检测
public HObject FrequencyDomainDefect(HObject image)
{
// 转灰度
HOperatorSet.Rgb1ToGray(image, out HObject grayImage);
// 傅里叶变换
HOperatorSet.FftImage(grayImage, out HObject fftImage);
// 创建频域滤波器(带通滤波,去除低频背景和高频噪声)
HOperatorSet.GenBandpass(out HObject filter, 0.1, 0.3);
// 频域相乘
HOperatorSet.MultImage(fftImage, filter, out HObject filtered, 1, 0);
// 逆傅里叶变换
HOperatorSet.FftImageInv(filtered, out HObject resultImage);
// 阈值提取缺陷
HOperatorSet.Threshold(resultImage, out HObject defects, 30, 255);
HOperatorSet.Connection(defects, out HObject connectedDefects);
HOperatorSet.SelectShape(connectedDefects, out HObject realDefects,
"area", "and", 50, 50000);
// 清理中间变量
grayImage.Dispose();
fftImage.Dispose();
filter.Dispose();
filtered.Dispose();
resultImage.Dispose();
defects.Dispose();
connectedDefects.Dispose();
return realDefects;
}
// 模板差分缺陷检测
public HObject TemplateDiffDefect(HObject image, HObject templateImage)
{
// 转灰度
HOperatorSet.Rgb1ToGray(image, out HObject grayCurrent);
HOperatorSet.Rgb1ToGray(templateImage, out HObject grayTemplate);
// 对齐(如果需要,使用模板匹配获取偏移量)
// ...
// 图像相减
HOperatorSet.SubImage(grayCurrent, grayTemplate, out HObject diffImage, 1, 128);
// 绝对值
HOperatorSet.AbsImage(diffImage, out HObject absDiff);
// 阈值提取缺陷
HOperatorSet.Threshold(absDiff, out HObject defectRegion, 200, 255);
HOperatorSet.Connection(defectRegion, out HObject connectedDefects);
// 清理
grayCurrent.Dispose();
grayTemplate.Dispose();
diffImage.Dispose();
absDiff.Dispose();
defectRegion.Dispose();
return connectedDefects;
}
}多线程图像处理
异步采集与显示
/// <summary>
/// 多线程图像处理 — 避免阻塞 UI 线程
/// </summary>
public class AsyncImageProcessor : IDisposable
{
private CancellationTokenSource _cts;
private Task _processingTask;
private readonly BlockingCollection<HObject> _imageQueue = new(10);
// 启动异步处理
public void StartProcessing(Action<HObject, List<InspectionResult>> onResultReceived)
{
_cts = new CancellationTokenSource();
_processingTask = Task.Run(async () =>
{
while (!_cts.Token.IsCancellationRequested)
{
try
{
if (_imageQueue.TryTake(out var image, 100))
{
var results = ProcessImage(image);
// 在 UI 线程上回调
onResultReceived?.Invoke(image, results);
image.Dispose();
}
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
Console.WriteLine($"处理异常: {ex.Message}");
}
}
}, _cts.Token);
}
// 添加待处理图像
public void EnqueueImage(HObject image)
{
if (!_imageQueue.TryAdd(image))
{
image?.Dispose(); // 队列满时丢弃
}
}
private List<InspectionResult> ProcessImage(HObject image)
{
var results = new List<InspectionResult>();
// 执行视觉检测算法
// ...
return results;
}
public void Dispose()
{
_cts?.Cancel();
_processingTask?.Wait(TimeSpan.FromSeconds(5));
_imageQueue.Dispose();
}
}
public record InspectionResult(string Type, double Value, bool Passed);优点
缺点
总结
Halcon 是工业机器视觉的标准工具。通过 HalconDotNet 在 WPF 中集成视觉功能。推荐流程:HDevelop 中调通算法 → 导出 C# 代码 → 集成到 WPF 应用。核心掌握模板匹配、边缘检测、二维码识别和尺寸测量四大算法。
关键知识点
- 先分清主题属于界面层、ViewModel 层、线程模型还是设备接入层。
- WPF 文章真正的价值在于把 UI、数据、命令、线程和资源关系讲清楚。
- 上位机场景里,稳定性和异常恢复常常比界面花哨更重要。
- 设备接入类主题通常同时涉及协议、线程、实时刷新和异常恢复。
项目落地视角
- 优先明确 DataContext、绑定路径、命令源和线程切换位置。
- 涉及设备时,补齐超时、重连、日志、告警和资源释放策略。
- 复杂控件最好提供最小可运行页面,便于后续复用和排障。
- 明确采集周期、重连策略、数据缓存和状态同步方式。
常见误区
- 把大量逻辑堆进 code-behind,导致页面难测难维护。
- 在后台线程直接操作 UI,或忽略 Dispatcher 切换。
- 只验证正常路径,不验证设备掉线、权限缺失和资源泄漏。
- 只验证通信成功,不验证断线、抖动和异常包。
进阶路线
- 继续向 MVVM 工具链、控件可复用性、性能优化和视觉树诊断深入。
- 把主题放回真实设备流程,思考启动、连接、采集、显示、告警和恢复。
- 沉淀成控件库、通信层和诊断工具,提高整套客户端的复用度。
- 继续补齐设备模拟、离线回放、现场诊断和配置中心能力。
适用场景
- 当你准备把《Halcon 机器视觉》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合桌面业务系统、监控看板、工控上位机和设备交互界面。
- 当系统需要同时处理复杂 UI、后台任务和硬件通信时,这类主题尤为关键。
落地建议
- 优先明确 View、ViewModel、服务层和设备层边界,避免代码隐藏过重。
- 涉及实时刷新时,提前设计 UI 线程切换、节流和资源释放。
- 对硬件通信、日志、告警和异常恢复建立标准流程。
排错清单
- 先检查 DataContext、绑定路径、INotifyPropertyChanged 和命令状态是否正常。
- 排查 Dispatcher 调用、死锁、后台线程直接操作 UI 的问题。
- 出现内存增长时,优先检查事件订阅、图像资源和窗口生命周期。
复盘问题
- 如果把《Halcon 机器视觉》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《Halcon 机器视觉》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《Halcon 机器视觉》最大的收益和代价分别是什么?
