前言

模板匹配(Shape-Based Matching)是 Halcon 最经典的功能,也是产线上用最多的定位算法。不管是引导机器人抓取,还是做有无检测,底层都是模板匹配。

但网上大部分教程都停在 HDevelop 里跑通了就完事。真正的落地场景是:C# 上位机里,点击"注册模板"→ 点击"开始匹配"→ 串口/TCP 往外吐坐标

本文提供一个可以直接拿去改的工程框架。


工程结构

HikvisionHalconDemo/
├── Program.cs              ← 入口
├── CameraCapture.cs        ← 相机采集(复用上篇文章代码)
├── TemplateMatcher.cs      ← 模板管理核心
├── PatternMatchingDemo.cs  ← 匹配执行
├── CoordinateTransformer.cs← 像素→物理坐标转换
└── ResultDisplay.cs        ← 结果显示(HWindowControl)

1. 模板注册(TemplateMatcher.cs)

模板匹配的第一步是用一张标准件的图片创建模板:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
using System;
using System.IO;
using HalconDotNet;

namespace HikvisionHalconDemo
{
    public class TemplateMatcher : IDisposable
    {
        private HObject _modelRegion;
        private HTuple _modelId;
        private string _modelPath;
        private bool _isTrained;

        public bool IsTrained => _isTrained;

        public TemplateMatcher(string modelDir = "models")
        {
            _modelPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, modelDir);
            Directory.CreateDirectory(_modelPath);
        }

        /// <summary>
        /// 创建模板:框选 ROI → 训练模板 → 保存到文件
        /// </summary>
        public bool CreateTemplate(HObject image, HObject roiRegion, string templateName)
        {
            try
            {
                HObject templateImage;
                // 1. ROI 裁切
                HOperatorSet.ReduceDomain(image, roiRegion, out templateImage);
                // 2. 创建形状模板
                HOperatorSet.CreateScaledShapeModel(templateImage,
                    "auto",      // NumLevels
                    -3.15,       // AngleStart(-180°)
                    6.29,        // AngleExtent(360°)
                    "auto",      // AngleStep
                    0.8,         // ScaleMin
                    1.2,         // ScaleMax
                    "auto",      // ScaleStep
                    "none",      // Optimization
                    "use_polarity",  // Metric
                    30,          // Contrast
                    10,          // MinContrast
                    out _modelId);

                // 3. 保存到文件
                string savePath = Path.Combine(_modelPath, $"{templateName}.shm");
                HOperatorSet.WriteShapeModel(_modelId, savePath);

                _isTrained = true;
                Console.WriteLine($"模板 '{templateName}' 创建成功,已保存至 {savePath}");
                return true;
            }
            catch (HalconException ex)
            {
                Console.WriteLine($"模板创建失败: {ex.Message}");
                return false;
            }
        }

        /// <summary>
        /// 加载已保存的模板
        /// </summary>
        public bool LoadTemplate(string templateName)
        {
            string loadPath = Path.Combine(_modelPath, $"{templateName}.shm");
            if (!File.Exists(loadPath))
            {
                Console.WriteLine($"模板文件不存在: {loadPath}");
                return false;
            }

            try
            {
                HOperatorSet.ReadShapeModel(loadPath, out _modelId);
                _isTrained = true;
                Console.WriteLine($"模板 '{templateName}' 加载成功");
                return true;
            }
            catch (HalconException ex)
            {
                Console.WriteLine($"模板加载失败: {ex.Message}");
                return false;
            }
        }

        public void Dispose()
        {
            if (_modelId != null)
            {
                HOperatorSet.ClearShapeModel(_modelId);
                _modelId.Dispose();
            }
        }
    }
}

选型参数说明

1
2
3
4
5
6
7
// 关键参数解读:
CreateScaledShapeModel(image,
    "auto",        // 金字塔层数:auto 让 Halcon 自己算
    -3.15, 6.29,   // 角度范围:-180° ~ 180°(弧度)
    0.8, 1.2,      // 缩放范围:80% ~ 120%
    "use_polarity",// 极性:物体与背景对比度不变时选这个
    30, 10);       // Contrast=30 忽略低对比度细节,MinContrast=10 抗噪

⚠️ 角度范围越大、缩放范围越大,匹配速度越慢。如果你的工件是固定角度放置,把范围缩小到 ±5°(-0.087, 0.174),速度能快 5-10 倍。


2. 匹配执行(PatternMatchingDemo.cs)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
using System;
using System.Collections.Generic;
using HalconDotNet;

namespace HikvisionHalconDemo
{
    public class MatchResult
    {
        public double Row { get; set; }
        public double Column { get; set; }
        public double Angle { get; set; }
        public double Scale { get; set; }
        public double Score { get; set; }
    }

    public class PatternMatchingDemo
    {
        private HTuple _modelId;

        public PatternMatchingDemo(HTuple modelId)
        {
            _modelId = modelId;
        }

        /// <summary>
        /// 执行模板匹配,返回所有结果
        /// </summary>
        public List<MatchResult> FindMatches(HObject image,
            double minScore = 0.5,
            int numMatches = 0)
        {
            var results = new List<MatchResult>();

            try
            {
                HTuple hv_Row, hv_Column, hv_Angle, hv_Scale, hv_Score;

                HOperatorSet.FindScaledShapeModel(
                    image,
                    _modelId,
                    -3.15, 6.29,   // 搜索角度范围
                    0.8, 1.2,      // 搜索缩放范围
                    minScore,      // 最低得分
                    numMatches,    // 匹配数量(0=找出全部)
                    0.5,           // MaxOverlap
                    "least_squares",  // 亚像素精度
                    0, 0.8,        // 金字塔层范围
                    "true",        // 贪婪度
                    out hv_Row, out hv_Column, out hv_Angle,
                    out hv_Scale, out hv_Score);

                for (int i = 0; i < hv_Score.Length; i++)
                {
                    results.Add(new MatchResult
                    {
                        Row = hv_Row[i].D,
                        Column = hv_Column[i].D,
                        Angle = hv_Angle[i].D,
                        Scale = hv_Scale[i].D,
                        Score = hv_Score[i].D
                    });
                }
            }
            catch (HalconException ex)
            {
                Console.WriteLine($"匹配异常: {ex.Message}");
            }

            return results;
        }

        /// <summary>
        /// 在 UI 上显示匹配结果
        /// </summary>
        public void DisplayMatches(HWindow window, HObject image, List<MatchResult> results)
        {
            HOperatorSet.DispObj(image, window);
            HOperatorSet.SetColor(window, "green");
            HOperatorSet.SetLineWidth(window, 2);

            foreach (var r in results)
            {
                // 显示十字线
                HOperatorSet.DispCross(window, r.Row, r.Column, 60, 0);

                // 显示 ROI 矩形
                HObject cross;
                HOperatorSet.GenCrossContourXld(out cross, r.Row, r.Column, 60, 0);
                HOperatorSet.DispObj(cross, window);
                cross.Dispose();

                // 显示得分
                string msg = $"Score: {r.Score:F2}  ({r.Row:F1}, {r.Column:F1})";
                HOperatorSet.DispText(window, msg, "image", r.Row - 30, r.Column + 10,
                    "green", "box", "false");
            }
        }
    }
}

3. 像素坐标转物理坐标(CoordinateTransformer.cs)

模板匹配输出的是像素坐标,给机器人用需要转成物理坐标(mm)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using HalconDotNet;

namespace HikvisionHalconDemo
{
    public class CoordinateTransformer
    {
        private HTuple _homMat2D;  // 仿射变换矩阵

        /// <summary>
        /// 九点标定:计算像素→物理的仿射变换
        /// </summary>
        /// <param name="pixelPoints">九点在图像中的行列坐标</param>
        /// <param name="worldPoints">九点在机器人坐标系中的 XY 坐标</param>
        public void Calibrate(Tuple<double, double>[] pixelPoints,
                              Tuple<double, double>[] worldPoints)
        {
            if (pixelPoints.Length != worldPoints.Length || pixelPoints.Length < 3)
                throw new ArgumentException("需要至少3对对应点");

            HTuple px = new HTuple(), py = new HTuple();
            HTuple wx = new HTuple(), wy = new HTuple();

            foreach (var p in pixelPoints) { px.Append(p.Item1); py.Append(p.Item2); }
            foreach (var w in worldPoints) { wx.Append(w.Item1); wy.Append(w.Item2); }

            // Halcon 仿射变换:最小二乘拟合
            HOperatorSet.VectorToHomMat2d(px, py, wx, wy, out _homMat2D);
        }

        /// <summary>
        /// 保存标定结果
        /// </summary>
        public void SaveCalibration(string path)
        {
            HOperatorSet.WriteTuple(_homMat2D, path);
        }

        public void LoadCalibration(string path)
        {
            HOperatorSet.ReadTuple(path, out _homMat2D);
        }

        /// <summary>
        /// 像素坐标 → 物理坐标
        /// </summary>
        public (double X, double Y) PixelToWorld(double row, double col)
        {
            if (_homMat2D == null)
                throw new InvalidOperationException("请先执行标定");

            HTuple qx, qy;
            HOperatorSet.AffineTransPoint2d(_homMat2D, row, col, out qx, out qy);
            return (qx.D, qy.D);
        }
    }
}

九点标定怎么做

运动步骤:
1. 机器人走到位置1(记录机器人坐标 X1, Y1)
2. 拍一张图,找出特征在图像中的位置(Row1, Col1)
3. 重复步骤1-2,走 9 个位置形成 3×3 网格
4. 9 对对应点喂给 VectorToHomMat2d → 完成标定

精度建议:
- 标定板范围覆盖整个视野的 80% 以上
- 标定位置不要共线(至少 3 个不共线的点)
- 标定点的越多(9 点或 16 点),畸变矫正越准

4. 完整流程串联

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
using System;
using System.Threading;
using HalconDotNet;

class Program
{
    static void Main(string[] args)
    {
        // 1. 连接相机
        using (var camera = new CameraCapture())
        {
            if (!camera.Connect("CAMERA_001"))
            {
                Console.WriteLine("相机连接失败");
                return;
            }

            // 2. 加载模板
            using (var matcher = new TemplateMatcher())
            {
                if (!matcher.LoadTemplate("product_a"))
                {
                    Console.WriteLine("请先注册模板!");
                    return;
                }

                // 3. 初始化匹配引擎
                var matching = new PatternMatchingDemo(null /*实际传modelId*/);
                var transformer = new CoordinateTransformer();
                transformer.LoadCalibration("calib.tup");

                // 4. 连续匹配
                camera.StartCapture();
                camera.FrameCaptured += (img) =>
                {
                    var results = matching.FindMatches(img, minScore: 0.6);

                    foreach (var r in results)
                    {
                        var phys = transformer.PixelToWorld(r.Row, r.Column);
                        Console.WriteLine($"匹配: ({phys.X:F3}, {phys.Y:F3})mm 角度: {r.Angle:F2}° 得分: {r.Score:F2}");
                        // 通过串口/TCP 发送坐标给机器人
                    }
                };

                Console.WriteLine("按回车停止...");
                Console.ReadLine();
            }
        }
    }
}

5. 性能调优

场景建议效果
固定角度放置角度范围缩到 ±5°匹配速度快 5-10 倍
物体大小固定缩放范围设 1.0-1.0匹配速度快 3-5 倍
多目标同时找NumMatches=0会找出所有匹配
只找第一个NumMatches=1 + Greediness=0.9最快出结果
低对比度环境MinContrast 降到 5提高检出率,噪声也会多
旋转对称物体角度范围 0-360°但会显著变慢

速度参考(i5-8500T,130万像素)

配置耗时
全角度 360°,全缩放 0.8-1.280-120ms
限制角度 ±5°,固定缩放8-15ms
限制角度 ±5°,固定缩放,NumMatches=13-5ms

总结

模板匹配在 C# 里的完整落地链路:

拍照 → ROI框选 → CreateScaledShapeModel → 保存 .shm
                                     ↓
拍照 → FindScaledShapeModel → 像素坐标
                                     ↓
九点标定 → VectorToHomMat2d → 仿射变换
                                     ↓
物理坐标 → 串口/TCP → 机器人抓取

代码从文件保存/加载、亚像素精度、标定转换到结果显示都涵盖在内,直接拿去改项目名就能用。

发布日期:2026-07-02
标签:#机器视觉 #Halcon #C# #模板匹配 #.NET