机器视觉项目里Halcon模板匹配估计是大家最常用的定位手段但很多刚入行的朋友会卡在下一步模板找到了然后呢如果只是拿坐标去告诉机械手抓取那确实够了可如果还要在指定位置测量边、量宽度仅仅靠匹配结果去硬套一个固定ROI是行不通的。今天这篇我直接给你一套能抄作业的组合思路用Halcon的形状模板做定位再通过对齐矩阵把参考位置的测量模板映射到实际图像上最后在测量矩形里取边缘并计算尺寸整个过程配完整代码。这套流程在工业现场非常常见比如五金件宽度检测、PCB焊盘尺寸测量、锂电极片边缘定位、连接器pin脚间距检查。核心就一句话先找到目标在哪、转了多少度然后把这个位姿变化应用到测量区域上才能量得准。下面我把每一步拆开讲顺便把我在实际项目里踩过的坑和调参习惯一起写出来。1. 整体思路与方案拆解1.1 为什么匹配之后还要做对齐很多初学者最容易误解的一点模板匹配只是告诉你目标在图像哪个位置、转了多少度并没有把你的测量区域带到目标身上。假设产品在生产线上会有±20°的偏转你在离线参考图像上画好了一个测量矩形直接把这个矩形的坐标搬到运行图里去量大概率量歪了因为被测边已经跟着产品一起转了。所以要引入对齐这一步。对齐的本质是建立参考坐标系到实际图像坐标系之间的一个刚性变换矩阵。这个变换只包含平移和旋转正好对应传送带上常见的产品位置漂移和姿态变化。有了这个矩阵参考图像里的任何点、线、测量矩形都能被精确投影到运行图上。这一步做完边缘测量才有意义。1.2 一次完整的流程长什么样我习惯把整个流程拆成五步离线阶段在参考图上框一个模板ROI创建Halcon形状模板并记录模板参考点的位置。离线阶段在参考图上布置测量矩形确定被测边的扫描方向记录这个矩形的位置和角度。在线阶段用find_shape_model在运行图上找到模板实例得到当前的行坐标、列坐标、角度和匹配分数。在线阶段用vector_angle_to_rigid把参考姿态和当前姿态之间的刚性变换矩阵算出来。在线阶段用这个矩阵把测量矩形的中心点和角度变换到运行图上最后执行measure_pairs或measure_pos取边缘。不要小看这五步里面每一步都有参数陷阱。很多测量结果不稳定问题往往不是出在最后的边缘测量算子而是前面的匹配和对齐阶段就已经埋下了误差。1.3 两种对齐路线的取舍拿到刚性变换矩阵后实际操作上通常有两种做法。第一种是变换整张图。用affine_trans_image把运行图按逆变换拉回到参考姿态之后所有后续处理都还在原来的参考坐标系里做代码最简单适合快速验证。但缺点是整图插值重采样会带来额外运算量和轻微模糊测量精度会被插值误差拖累现场大批量跑起来也很不划算。第二种是变换ROI。只把参考坐标系里的测量矩形中心、角度、模板轮廓等少量元素乘上变换矩阵搬到运行图对应位置再在原始图像上直接测量。这个方案不重采样图像数据量小、速度快测量精度也更好。我在正式项目里几乎都用第二种只有做可视化调试的时候会顺手把对齐后的模板轮廓affine_trans_contour_xld叠到原图上看看效果。2. 建模阶段把测量参考固定下来2.1 模板ROI怎么选才稳选模板ROI是门手艺活。先明确一个原则模板区域必须包含足够的、非对称的灰度梯度信息让Halcon在旋转和位移后也能唯一认出这个产品。我一般会选一个靠近被测边、特征又比较丰富的区域。比如要测某个金属片的宽度我就把模板框在金属片一端的边角上这个角上有两条互相垂直的边缘角度信息非常充分。最忌讳的是选一个圆、一条直线或者只有横向边缘的区域因为圆在角度方向上是退化的匹配时角度解算极不稳定直线则容易在同一个方向上来回滑动导致定位结果沿边缘方向漂移。另外ROI别太大。模板越大模型点数越多匹配速度越慢ROI太小又可能只覆盖了一小块没有辨识度的平面。我的经验是让ROI覆盖到目标区域附近一两个特征交叉点就够了实在不行再适当扩大。2.2 create_shape_model参数逐个拆建模算子create_shape_model的参数看着多但真正影响成败的主要是这几个NumLevels金字塔层级。我通常设4到6。层级太少搜索慢层级太多模板里细小边缘会被金字塔下采样抹掉匹配分反而上不去。如果模板里全是粗大边缘可以适当加高如果模板里有很细的刻痕特征就别超过5层。AngleStart和AngleExtent如果现场产品只会偏转±20°我会设-20到40也就是从-20度起始范围40度。角度范围越宽匹配越慢误匹配风险越高所以只给实际可能出现的角度余量不要随手给0到360。Optimization设auto就行Halcon会自动决定是否做模型点简化。对测量精度要求高的场景我会手动设point_reduction_low损失一点速度换稳定性。Metric默认use_polarity要求被测目标在背景上明暗关系稳定。如果现场光照可能反转比如暗物体变亮背景就要用ignore_global_polarity或ignore_local_polarity。后两者匹配时会忽略极性但区分度会下降模板更容易误匹配所以能不用就不用。Contrast和MinContrast设auto在多数情况下能用但遇到低对比度工件时我会先用inspect_shape_model看一眼模型边缘图再手动给一个Contrast阈值比如30到40只保留真正清晰稳定的边缘。2.3 用inspect_shape_model提前排雷不少朋友喜欢直接create_shape_model一把梭然后到在线阶段发现匹配分上不去才回过头怀疑模板建得不好。其实Halcon提供了一个非常直观的检查算子inspect_shape_model它能把金字塔各层上保留下来的模型边缘点画出来给你看。我的习惯是建模之前先跑一遍inspect_shape_model (ImageReduced, ModelImages, ModelRegions, 4, 30)然后把ModelRegions叠加在原始图像上看。如果保留下来的边缘点稀疏、破碎说明对比度阈值给高了如果边缘点铺得到处都是说明阈值给低了。这一步十秒钟就能排查掉大部分建模问题。另外get_shape_model_contours (ModelContours, ModelID, 1)拿到的是模型轮廓在线调试时把它对齐后叠到运行图上能一眼看出匹配位置到底准不准。2.4 参考点和参考测量矩形的确定模板建好后要明确一个参考点的概念。在离线参考图像上模板区域中心通常被当作参考位姿的锚点。我用area_center (ModelROI, Area, RefRow, RefCol)取ROI的中心作为RefRow和RefCol对应的参考角度记作0。后面vector_angle_to_rigid里面会用到这个点。测量矩形的位置需要在参考图上同时确定。用draw_rectangle2交互画一个矩形或者直接硬编码一个合理位置。矩形画的原则是扫描轴要垂直于被测边。比如被测边是水平方向的金属片上下边缘扫描方向就要竖直矩形长轴角度设为90度。这个矩形的长度要能覆盖整条被测边可能的范围宽度则是做灰度平均用的一般给5到15个像素太宽会把旁边的干扰特征也卷进来。3. 在线阶段定位、对齐、测量一条线3.1 find_shape_model结果如何用在线阶段第一步是find_shape_model。参数里我特别强调三个SubPixel必须设成least_squares这样才能拿到亚像素级别的位置和角度MinScore初始可以给0.5如果误匹配多再往上提Greediness在0.8到0.9左右现场干扰多就降到0.6。它的输出是RowFound、ColFound、AngleFound和Score。这里有个容易忽略的细节返回的Score不是用来判断找到了没有的唯一标准。即便分数很高位置也有可能在亚像素级别偏掉一点所以凡是做测量的项目我都会对连续几帧匹配结果做稳定性观察。如果行、列、角度三个数在小数点后两位还一直在跳说明模板本身或者边缘质量有问题先解决这个再谈测量。3.2 vector_angle_to_rigid为什么是关键vector_angle_to_rigid是整套对齐流程的核心它的作用是根据参考位姿和当前位姿生成一个刚性变换矩阵。调用方式vector_angle_to_rigid (RefRow, RefCol, 0, RowFound, ColFound, AngleFound, HomMat2D)它的原理很简单先把参考点平移到当前匹配点再绕匹配点旋转角度差合成一个刚体变换矩阵。得到HomMat2D之后参考坐标系里的任意点(r, c)都能通过affine_trans_pixel投影到运行图坐标系。这个矩阵只适合处理旋转和平移如果产品在视野里尺寸会变化比如离相机的距离不稳定就要换create_scaled_shape_model并额外处理缩放系数。大部分固定位姿的产线上刚性变换已经足够。3.3 把测量矩形搬到当前位置这里有个细节值得注意gen_measure_rectangle2创建的测量句柄一旦生成它的位置和角度就固定了。有人会问能不能复用句柄、只改参数。不行测量矩形的位置和角度改变时必须重新创建句柄所以我是在在线流程里动态生成新句柄的。实现思路分两步。第一步用affine_trans_pixel把参考测量矩形的中心点变换到运行图坐标affine_trans_pixel (HomMat2D, MeasRefRow, MeasRefCol, MeasRowRun, MeasColRun)第二步参考测量矩形的扫描角度加上当前匹配的角度MeasPhiRun : AngleFound MeasRefPhi这样测量矩形就跟着产品一起旋转了。再调用gen_measure_rectangle2生成新句柄传给measure_pairs。别忘了如果参考测量矩形在参考图上的角度是90度这里MeasRefPhi就取90度对应的弧度值。3.4 完整HDevelop代码示例我写一个简化但完整的例子。假设参考图里产品上有一条宽度需要测量的边模板参考点由area_center得到参考测量矩形中心是(270, 550)扫描角度是90度。* 离线建模 read_image (ImageRef, ref.bmp) gen_rectangle1 (ModelROI, 180, 320, 420, 700) reduce_domain (ImageRef, ModelROI, ImageReduced) * 检查模型边缘 inspect_shape_model (ImageReduced, ModelImages, ModelRegions, 4, 30) * 创建形状模板 create_shape_model (ImageReduced, 4, rad(-25), rad(50), auto, point_reduction_low, use_polarity, 30, 10, ModelID) get_shape_model_contours (ModelContours, ModelID, 1) * 参考点 area_center (ModelROI, Area, RefRow, RefCol) * 参考测量矩形扫描方向竖直 MeasRefRow : 270 MeasRefCol : 550 MeasRefPhi : rad(90) MeasLength1 : 80 MeasLength2 : 10 write_shape_model (ModelID, model.shm) * 在线检测 read_image (ImageRun, run.bmp) read_shape_model (model.shm, ModelID) find_shape_model (ImageRun, ModelID, rad(-25), rad(50), 0.5, 1, 0.5, least_squares, 0, 0.85, RowFound, ColFound, AngleFound, Score) if (Score 0.5) stop() endif * 计算刚性矩阵 vector_angle_to_rigid (RefRow, RefCol, 0, RowFound, ColFound, AngleFound, HomMat2D) * 变换测量矩形中心 affine_trans_pixel (HomMat2D, MeasRefRow, MeasRefCol, MeasRowRun, MeasColRun) MeasPhiRun : AngleFound MeasRefPhi * 生成运行时测量句柄 gen_measure_rectangle2 (MeasRowRun, MeasColRun, MeasPhiRun, MeasLength1, MeasLength2, Width, Height, nearest_neighbor, MeasureHandleRun) * 测量边对 measure_pairs (ImageRun, MeasureHandleRun, 1.5, 30, all, all, RowEdgeFirst, ColEdgeFirst, AmplitudeFirst, RowEdgeSecond, ColEdgeSecond, AmplitudeSecond, IntraDistance, InterDistance) * 对齐后的模板轮廓用于调试 affine_trans_contour_xld (ModelContours, ModelContoursAligned, HomMat2D) dev_display (ImageRun) dev_display (ModelContoursAligned)measure_pairs返回的IntraDistance就是边缘对之间的距离。如果测量对象是一个暗背景上的亮条带IntraDistance就是亮条带的宽度单位是像素。想要毫米值乘上标定比例系数即可。4. 边缘测量的细节与进阶4.1 measure_pairs与measure_pos怎么选measure_pairs是一对一对地找边缘适合量宽度、直径、间距因为它天然把两条边界关联起来了。measure_pos是单独找一个边缘适合只需要定位单条边界、然后做后续对齐、纠偏的场景。比如要找一个零件的左边边缘作为基准去补偿机器人取放位置就适合measure_pos。对同一个测量矩形measure_pairs和measure_pos用到的参数其实差不多区别在返回值。measure_pairs返回第一边缘和第二边缘的行列坐标及振幅还有两个关键的间距值IntraDistance是同一对边缘之间的间距InterDistance是相邻两对边缘的间距。测量宽度时我直接取IntraDistance。4.2 Sigma、Threshold、Transition、Select这些参数别乱调measure_pairs里前两个关键参数是Sigma和Threshold。Sigma是高斯平滑的尺度值越大抗噪声越强但边缘位置越容易偏移。对于干净的背光图像我一般给1到2对有纹理、有反光的现场图像会加到3左右但超过3就要警惕边缘偏置。Threshold是边缘振幅阈值只保留灰度变化足够剧烈的点。太小的阈值会把毛刺噪声当成边缘太大的阈值会丢掉弱边缘。实际操作中我会在图像上先观察剖面灰度图看看真实边缘的灰度落差大致是多少再设一个比这个落差低50%以上的阈值确保边缘能稳定触达。Transition决定检测的边缘极性。对于一条亮带扫描方向从暗区进入亮区时为positive对应第一条边缘从亮到暗则为negative。设all可以同时兼容两种情况但也会增加把干扰当边缘的概率。Select参数控制返回哪一组边缘对。测量单一目标时设first不够稳健因为测量矩形里一旦出现其他碎边就会误判我更常用all然后对返回的多个IntraDistance做筛选取接近期望值的那个。4.3 从像素到物理单位上面所有测量结果都是像素单位。要把像素变成毫米有两种常见做法。如果你用的是远心镜头畸变很小可以直接用一个标准件标定比例系数把一个已知宽度为KnownMM的工件放在视野里用同一套测量流程测出像素宽度MeasPix比例系数就是Scale : KnownMM / MeasPix。之后每次测到的像素宽度乘上这个系数即可。如果你的镜头有比较明显的透视畸变或者要求很高精度那就不能只用一个比例系数了。需要走Halcon的相机标定流程求出内参和外参再把像素坐标转换到世界平面。这是另一大块内容基础项目里用比例系数已经能覆盖大部分需求。4.4 更复杂的场景曲线边缘怎么办gen_measure_rectangle2适合测量直的边缘。如果被测边是弧线或者不规则曲线测量矩形放下去就很容易量偏。这种场景我有两个常用方案。一个是用edges_sub_pix先提取亚像素边缘轮廓再用fit_line_contour_xld或fit_circle_contour_xld拟合直线或圆最后测量拟合结果之间的距离。另一个是沿曲线轮廓打若干个小测量矩形分段测量再做统计。两种方案的精度都能做到不错但运算量会明显上去一般只在需要高灵活度的项目里用。5. 常见问题排查与调参心得5.1 匹配环节的坑匹配不到时大部分人第一反应是降低MinScore。这条路不一定对MinScore降太低会引入误匹配反而更麻烦。我习惯先按优先级排查检查角度范围是否覆盖现场实际角度。曾有客户现场治具会反放产品角度差180度但建模时只给了±20度当然匹配不到。检查Greediness。如果现场有遮挡、重叠或者对比度不稳定先把Greediness降到0.5试试。检查模板边缘质量。重新用inspect_shape_model看模型点是否稀疏。检查光照极性翻转。如果产品区域反光导致边缘明暗关系偶尔反转考虑换Metric或加偏振光。另外有一个容易踩的坑模板图像和运行图像大小不一致。如果相机分辨率在调试中途换过老模型直接加载会失效需要重新建模。5.2 测量数值不稳定的原因测量数值跳动最常见的原因不在测量算子而在前面的匹配角度不稳定。举个实际例子模板只覆盖了一条水平边角度自由度没被约束住find_shape_model返回的角度在小数点后两位浮动经过测量矩形旋转之后边缘位置就可能偏掉0.1到0.3像素。解决办法是重新选一个带有垂直和水平两个方向边缘的模板区域从根源上锁死角度。另一个常见问题是测量矩形宽度Length2太窄。Length2本质上是沿边缘方向的灰度平均窗口只有1个像素时边缘上的单个噪点会直接拉动测量结果。我一般会给到5像素以上实测稳定性会好很多。同时适当加大Sigma也能压住边缘方向上的高频抖动。最后如果测量的是带有反光的金属面边缘位置会随打光角度轻微漂移。这种物理层面的问题算法再调也很难完全消除只能通过固定光源位置、样品尽量放平来缓解。我的建议是调试时做一次静态重复性测试同一张图反复跑100次确认算法本身的方差再换多个不同位置的同款工件确认不同位姿下测量值的重复精度。5.3 性能优化的几个方向这套流程在单相机、单工位的场景下运算量通常不是瓶颈。但如果你要跑多相机或者高节拍设备可以从三个方面优化。第一限制角度搜索范围。线上如果确认产品最大偏转只有±15度就把AngleStart和AngleExtent收窄到实际值搜索时间能显著下降。第二适当提高NumLevels。匹配时先用低分辨率金字塔快速粗定位再往细层级精匹配层级高一级粗搜阶段速度就差好几倍。第三在find_shape_model里提高Greediness让搜索在金字塔高层就尽早剪枝。干扰少的场景设到0.8以上毫无压力。对于测量部分gen_measure_rectangle2每次重新创建句柄确实有开销但在单个测量场景下可以忽略。如果一帧图像里有几十个测量点可以考虑分组复用句柄或者直接改用edges_sub_pix一次性提取所有边缘再做几何测量。5.4 我的现场调试习惯最后分享一个我自己沉淀下来的调试顺序可以帮你少走弯路。拿到一个测量项目我不会直接写完整代码而是先做三件事第一步在参考图上仔细挑模板ROI和测量矩形位置各拍一张效果图存起来。第二步用inspect_shape_model确认模板边缘足够清晰。第三步用几张不同位置、不同角度的现场实际图像先跑一遍匹配只看RowFound、ColFound、AngleFound和Score是否稳定。这三步通过了再进入完整的测量流程。很多项目后期出问题翻回去看都是第一步没做扎实模板选了个角度不敏感的区域后面怎么调测量参数都于事无补。这套Halcon模板匹配对齐模板测量边的组合思路我几乎在每个定位测量类项目里都会用到。它不复杂但每一步都对最终结果有直接影响。你只要把建模时的麻烦做在前面在线运行时反而会很省心。下次遇到类似需求可以照着这个流程先搭一个最小闭环再逐步往里面加业务逻辑。
阅读完成 · 觉得有帮助?