
1. 为什么你需要认真对待 union_collinear_contours_xld做机器视觉Halcon开发的朋友肯定遇到过这个场景用threshold和edges_sub_pix提取边缘后得到了一堆断断续续的XLD轮廓明明是一条完整的直线边缘却被分成十几段间距还不一样。这时候你要是直接拿这些碎片去拟合直线或者做测量结果往往不稳定——角度飘、距离跳同一个工件放五次五次测出来的数都不一样。问题出在哪不是你阈值选得不好也不是光源不行而是你缺了一步关键处理轮廓合并。Halcon里处理这个需求的算子有好几个比如union_adjacent_contours_xld、union_cocircular_contours_xld但如果你要合并的是共线的、彼此分离的直线段那么核心算子是union_collinear_contours_xld。这个算子的作用一句话概括把满足共线条件、间距在一定范围内、重叠度符合要求的XLD轮廓段合并成一条完整的XLD轮廓。它适合做直线类边缘的预处理比如工件边缘检测、标定板直线提取、尺寸测量、定位基准边拟合。今天这篇就围绕这个算子展开把原理、参数、实操、坑一次性讲透。2. 先搞懂这个算子到底在做什么2.1 XLD轮廓的本质和断开原因XLDeXtended Line Description是Halcon里亚像素精度的轮廓描述方式比像素级Region精度高得多常用于精密测量和定位。它的本质是由一系列带坐标的点行、列坐标连接而成的折线或曲线点与点之间可以认为是直线段连接。那为什么提取出来的边缘会断开我归纳了三个最常见原因工件表面存在细微的划痕、脏污导致局部梯度值低于阈值边缘响应断裂。光照不均匀比如弧形光源打出来的光在边缘上形成强弱变化某个区域对比度不够边缘就断了。edges_sub_pix或threshold提取时阈值设置偏高一些灰度过渡平缓的区域就被过滤掉了。不管原因是什么结果都相同你手里是一条条离散的XLD段无法直接用于拟合或测量。2.2 共线合并的核心思路union_collinear_contours_xld做事情的核心逻辑是把离散的XLD段按“是否位于同一条直线上”这个标准进行分组然后把同组内满足条件的段合并成一条更长的轮廓。它内部会做几件事对每条输入轮廓做直线拟合内部使用最小二乘拟合直线得到直线的方向角度。比较相邻或相近轮廓之间的角度差判断它们是否近似平行共线。计算轮廓首尾端点到对方直线的距离判断是否满足距离阈值。检查它们之间的间隔、重叠情况决定是否合并以及如何连接。这个算子是专门针对直线轮廓设计的不是通用的任意形状合并。如果你要合并的是圆弧那请用union_cocircular_contours_xld如果你要合并的是首尾相邻但方向连续的轮廓那用union_adjacent_contours_xld更合适。选错算子效果会非常差。2.3 算子签名和参数概览在HDevelop里调用格式如下union_collinear_contours_xld(Contours : UnionContours : MaxDist, MaxAngGap, MaxOverlap, MaxDistToLine, MaxGap, MinLength : )参数列表整理如下参数类型含义Contours输入待合并的XLD轮廓数组UnionContours输出合并后的XLD结果MaxDist输入相邻轮廓间的最大参考距离MaxAngGap输入轮廓间最大角度偏差弧度MaxOverlap输入允许的最大重叠比例MaxDistToLine输入轮廓点到拟合直线的最大距离MaxGap输入允许合并的最大间隔MinLength输入合并后轮廓的最小长度小于此值则丢弃这里每个参数背后都有讲究接下来逐个拆解。3. 核心参数逐一拆解每个参数对应什么效果3.1 MaxDistToLine判断共线的核心公差MaxDistToLine的作用是判断一条轮廓上的点到另一条轮廓拟合出来的直线的最大垂直距离如果距离超过这个值就认为这两条轮廓不共线。换句话说这个参数控制了“共线”的严格程度。取值越小要求越严格只有质量很好的直线段才会被合并取值越大一些稍有弯曲或偏移的轮廓也会被合并进来。这里要注意MaxDistToLine的单位是像素一般按照图像分辨率来设置。如果是高精度测量场景比如像素当量0.01mm/pixel那么MaxDistToLine建议取1~2个像素即可再大就会把轻微弯曲的边缘也强行合并成直线拟合精度反而下降。如果是粗糙的定位场景可以适当放宽到3~5个像素。我的建议是先用小值跑一遍观察合并结果再逐步增大直到断开的部分被连接起来同时不会误并弯曲轮廓为止。3.2 MaxAngGap角度偏差的容忍上限MaxAngGap的单位是弧度控制的是两条轮廓拟合成直线之后方向角之间的最大差值。如果两条轮廓的角度差小于MaxAngGap就认为它们是近似平行的可以做共线合并。这里有个容易踩的坑Halcon返回的角度范围是-π/2到π/2也就是0到90度之间实际上角度范围是-pi/2到pi/2所以在比较两条直线的角度差时要注意角度环绕的问题。比如一条线角度是89度另一条是-89度实际只差了2度但由于角度表示方式的原因直接相减会得到178度。Halcon内部处理这个算子时已经考虑了角度归一化但我们自己在调参时心里要有数不要看到手册里MaxAngGap默认值0.02就觉得很小0.02弧度大约等于1.15度实际能容忍的角度偏差已经不小了。通常做直线边缘合并时MaxAngGap取0.02到0.1弧度就够用。取太大会把明显不平行、但夹角很小的两条边合并在一起对后续测量产生严重干扰。3.3 MaxDist和MaxGap距离相关参数这两个参数经常被搞混简单区分一下MaxDist用于判断两条轮廓之间的距离远近决定它们是不是“候选的相邻轮廓”。MaxGap决定两条轮廓之间的缝隙到底允许多大如果缝隙超过MaxGap就不合并。可以这么理解MaxDist是粗筛先把距离太远的轮廓排除掉缩小计算范围MaxGap是精判真正决定哪些缝隙可以被跨越。MaxGap的取值要看实际图像中断缝的大小。比如你提取的边缘断了几十像素那MaxGap至少要设到断缝平均宽度的1.5到2倍但不要设太大否则会把本应是两条独立直线的轮廓也连成一整条造成边缘特征丢失。MaxDist如果不确定怎么设可以先设成和MaxGap相同的值再根据效果单独调整。通常MaxDist略大于MaxGap是可以接受的但不要相差太多否则算法会在无关轮廓上浪费计算时间。3.4 MaxOverlap控制重叠情况MaxOverlap表示两条轮廓允许重叠的最大比例取值范围是0到1。如果两条轮廓在直线方向上的投影有重叠部分且重叠长度占较短轮廓长度的比例超过MaxOverlap就不会合并这两条轮廓。实际场景里为什么会出现重叠一种常见情况是提取边缘时同一物理边缘被提取出了两条平行的XLD一条是边缘外侧一条是边缘内侧比如边缘有两条响应的情况。这时候重叠度很高MaxOverlap设为0.5左右就能阻止合并避免把每个真实物理边缘都合并掉。如果你希望尽量合并所有共线片段MaxOverlap可以设小一点比如0.1或0.2如果你担心误并就设0.5以上。3.5 MinLength过滤碎片MinLength是一个后处理过滤条件合并完成后长度小于MinLength像素的轮廓会被丢弃。这个参数主要用来清理噪声产生的小线段。但要注意MinLength是在合并之后才起作用的不是合并之前。也就是说即使一条轮廓本身很短只要它和其他轮廓合并后总长度超过了MinLength它就不会被丢弃。所以这个参数通常和前面的合并参数搭配使用用来保证输出轮廓的最低质量。3.6 参数之间的联动关系这几个参数不是完全独立的它们共同决定哪些轮廓被合并、怎样连接。我用一个简化的场景来说明参数联动假设有两条XLD轮廓A和BA长50像素B长80像素它们在同一条直线上A的尾端和B的首端之间有一道30像素的缝隙。现在设置MaxDist100MaxGap40MaxAngGap0.05MaxDistToLine2MaxOverlap0.2。算法处理流程大致如下计算A和B的拟合直线角度假设差值小于0.05弧度角度条件满足。计算A尾端到B拟合直线的距离假设小于2像素共线条件满足。计算A和B之间的距离假设小于100像素进入候选集合。判断A尾端和B首端之间的缝隙30小于MaxGap40间隙条件满足。检查重叠情况A和B在直线方向上的投影无重叠重叠比例为0小于0.2满足。合并A和B生成一条长约160像素的轮廓考虑缝隙连接。合并后长度大于MinLength保留输出。上述任何一个条件不满足A和B就不会合并。理解了这个联动逻辑你就能根据实际图像精确调参。4. 实操案例从断线到完整直线边缘4.1 准备测试图像和读取图片我这里用一个模拟场景做演示拍摄一个矩形工件的边缘边缘由于表面污渍和光照问题提取后断成了好几段。* 读取图像 read_image (Image, workpiece.png) * 转灰度 rgb1_to_gray (Image, GrayImage)如果你手头没有合适的测试图也可以用Halcon自带的图像或者自己用draw_region画几个直线条纹来模拟边缘。4.2 亚像素边缘提取* 使用edges_sub_pix提取亚像素边缘 edges_sub_pix (GrayImage, Edges, canny, 1.5, 20, 40)这里有个细节edges_sub_pix的高低阈值选择很关键。低阈值越低提取到的边缘细节越多包含的噪声也越多高阈值越高越能过滤掉弱边缘但也更容易导致轮廓断开。实际工作中我会先打开灰度直方图观察边缘和背景的灰度分布再设定高阈值大约为峰值处灰度的1.5到2倍低阈值取高阈值的一半左右。提取出的Edges里包含大量轮廓其中就包括目标的边缘碎片和各类噪点。4.3 筛选目标方向的轮廓直接对所有轮廓做共线合并不现实因为图像里有各个方向的边缘强行合并会把垂直方向的边缘也拉进来。所以我在合并之前会做一步方向筛选只保留感兴趣方向比如接近水平方向的轮廓。* 遍历所有轮廓筛选角度在(-10度, 10度)附近的轮廓 select_contours_xld (Edges, SelectedContours, direction, -0.17, 0.17, -0.5, 0.5)这里select_contours_xld的direction属性需要传入最小和最大角度弧度还有最小和最大长度-0.5和0.5表示不限制长度。角度范围你可以根据自己的目标边缘方向来调整。4.4 使用union_collinear_contours_xld合并* 合并共线轮廓 union_collinear_contours_xld (SelectedContours, UnionContours, 50, 0.05, 0.2, 2.0, 40, 30)参数设置说明MaxDist50相邻轮廓之间的距离小于50像素才参与合并计算够用且不会太慢。MaxAngGap0.05约2.86度以内的角度偏差都算共线。MaxOverlap0.2允许少量重叠防止把重复提取的平行边误并。MaxDistToLine2.0轮廓点到拟合直线的最大距离为2像素保证合并质量。MaxGap40断缝不超过40像素都可以连接。MinLength30合并后长度小于30像素的轮廓直接丢掉。跑完之后用count_obj查看输出轮廓数量再用拟合直线看一下效果。4.5 拟合直线并输出测量结果合并完成后可以做一个直线拟合看看结果稳不稳定* 拟合每条合并后轮廓的直线 fit_line_contour_xld (UnionContours, tukey, -1, 0, 5, 2, RowBegin, ColBegin, RowEnd, ColEnd, Nr, Nc, Dist) * 计算直线角度 angle_line (RowBegin, ColBegin, RowEnd, ColEnd, Angle)用tukey作为拟合算法的好处是对离群点有较强的鲁棒性。如果合并后的轮廓仍有少量噪点比如轻微的毛刺tukey拟合比最小二乘更稳定。实际测试中合并前拟合和合并后拟合角度波动能从±0.3度缩小到±0.05度这个提升对精密测量来说非常可观。5. 实战经验那些文档里不会写的细节5.1 合并前先做轮廓筛选效果翻倍很多初学者拿到轮廓就直接union_collinear_contours_xld一把梭结果发现合并出来乱七八糟。原因很简单这个算子会把所有满足共线条件的轮廓都合并不管你是需要的那条边还是无关的干扰边缘。我的习惯是在调用union_collinear_contours_xld之前先做一次轮廓清洗。具体清洗手段包括按长度过滤用select_contours_xld先去掉太短的噪声轮廓一般保留长度大于10到20像素的轮廓。按方向过滤只保留与你目标边缘方向相近的轮廓。按灰度一致性过滤可以取每条轮廓附近区域的灰度均值排除那些位置虽然共线但灰度特征不一致的背景干扰。做完这步之后再执行共线合并效果会稳定很多。5.2 union_collinear_contours_xld对弯曲边缘无效这个算子内部拟合的是直线如果你输入的轮廓本身是弯曲的比如圆弧的一部分拟合出来的直线会偏离真实形状导致合并结果质量严重下降。所以在使用之前建议先确认轮廓形状。如果图像中既存在直线边缘又存在圆弧边缘建议先按轮廓的凸性、弯曲程度做分类再分别处理。* 按轮廓形状分成直线类和曲线类 select_contours_xld (Edges, LineContours, contour_length, 20, 1000, -0.5, 0.5)select_contours_xld有contour_length属性选择但不直接支持弯曲度筛选。更精确的方式是用get_contour_global_attrib_xld获取轮廓的全局属性或者直接对每条轮廓用fit_line_contour_xld拟合后看拟合残差残差大的说明不是直线。5.3 合并顺序对结果有影响吗union_collinear_contours_xld内部对轮廓的处理顺序是按照轮廓在输入数组中的顺序逐个判断合并。这意味着如果输入顺序不同合并结果可能有细微差别尤其是当存在多条轮廓同时满足合并条件时先合并哪两条会影响后续的合并决策。针对这种情况建议输入轮廓前先按位置排序比如按轮廓起点的行坐标或列坐标排序让算法按顺序扫描时更符合直觉。使用sort_contours_xld可以做到sort_contours_xld (SelectedContours, SortedContours, upper_left, true, column)5.4 合并后轮廓的方向问题Halcon的XLD轮廓是有方向属性的一般是边缘过渡方向暗到亮或亮到暗决定的。union_collinear_contours_xld在合并时会自动处理方向不一致的轮廓确保合并后的轮廓方向一致。但这里有坑如果你的轮廓方向不一致且你后续要用这些轮廓做边缘对比如测量两条平行边之间的距离那么合并后轮廓的方向信息可能不是你想要的那个方向。我建议合并完成后用get_contour_attrib_xld检查一下每段轮廓的方向属性必要时用reverse_contours_xld做翻转。5.5 处理速度大图上的性能问题如果你处理的图像很大比如5000万像素的屏检图像XLD轮廓数量可能上千条。union_collinear_contours_xld需要对轮廓两两比较计算量随轮廓数量呈指数级增长。优化思路有几个先用select_contours_xld过滤掉明显不需要的轮廓减少输入数量。把图像分成区域块每个块内独立执行合并再跨块合并。适当提高MaxDist让算法更快排除远处轮廓。实际操作中我遇到过8000多条轮廓直接调用union_collinear_contours_xld耗时超过5秒的情况过滤后只剩200多条耗时降到100毫秒以内。差距非常大。5.6 与其他union系列算子的协作实际项目中往往不是单一算子解决问题的。以下是我常用的组合方式第一步union_adjacent_contours_xld把首尾相接的轮廓先连接起来减少碎片数量。第二步union_collinear_contours_xld处理共线但不相接的轮廓。第三步select_contours_xld按长度筛掉噪声轮廓。第四步fit_line_contour_xld拟合直线输出测量结果。这个组合几乎覆盖了直线类边缘从提取到拟合的全部场景。如果你处理的是矩形工件的四条边先做共线合并再做相邻连接效果比单一算子好很多。6. 常见问题与排查技巧实录6.1 合并后出现阶梯状轮廓现象合并后的轮廓看起来像阶梯一段一段偏折。排查思路检查MaxAngGap是否设得过大。过大的角度容忍度会让方向不一致的轮廓被强行合并形成折线。检查MaxDistToLine是否设得过大。如果允许轮廓点到拟合直线距离较大合并后会引入偏移点拟合时产生偏折。检查输入轮廓是否存在锯齿。亚像素轮廓本身的锯齿会干扰拟合直线的精度。解决办法适当减小MaxAngGap和MaxDistToLine或者在合并前对原始轮廓做smooth_contours_xld平滑处理。smooth_contours_xld (Contours, SmoothContours, 3)6.2 该合并的轮廓没有被合并现象明明两条轮廓在同一条直线上间隙也不大但就是不合并。排查方向按优先级排列检查MaxGap是否小于实际缝隙宽度这是最常见的原因。检查MaxDist是否小于轮廓实际距离如果MaxDist太小两条轮廓根本不会进入候选。检查MaxAngGap是否过小如果两条轮廓角度差略大比如因为边缘粗糙导致拟合角度波动可能就超过容忍度了。检查MaxOverlap设置如果轮廓有重叠且重叠比例超过了MaxOverlap就会拒绝合并。检查MinLength如果合并后长度小于MinLength结果会被丢弃看起来就像没合并。建议调试方法是逐步放宽参数每次只改一个观察结果变化。能用可视化工具查看中间结果最好——Halcon的XLD显示在调试时非常重要每步都显示出来参数影响一目了然。6.3 合并后把两条平行边并成了一条现象工件有两道平行边缘比如台阶的上下两个面本应是两条独立的直线结果被合并成一条。原因分析如果两条平行边距离很近比如几个像素MaxDistToLine可能同时覆盖两条边导致算法把它们当成同一条直线的离群点。MaxAngGap对平行边的角度差不起作用因为它们本来就平行角度差为0。罪魁祸首往往是MaxDistToLine设置过大或者两条边之间没有足够的间隔。解决方法缩小MaxDistToLine让算法对“共线”的要求更严格。利用MaxOverlap阻止重叠合并。如果两条平行边在投影方向上有重叠设置MaxOverlap为较小值比如0.1可以有效阻止误并。最稳妥的方式在合并前的轮廓筛选阶段根据边缘极性暗到亮还是亮到暗把轮廓分类再分别合并。6.4 合并结果长度不一致测量跳动同一张图多次运行合并出的轮廓长度有时是300像素有时是280像素导致后续测量不稳定。原因通常是边缘提取阈值不稳定导致每次提取出的断点位置不同进而影响合并长度。解决方案固定阈值。不要用固定阈值提取后再合并而是根据图像灰度自动计算阈值。合并后不要直接用轮廓端点的坐标作为测量基准而是用拟合直线的参数如方向角、直线方程作为测量依据这样对端点位置不敏感。多次提取做统计平均。6.5 常见问题速查表现象可能原因解决方案合并后折线状MaxAngGap过大 / MaxDistToLine过大减小参数或先平滑轮廓有缝隙未合并MaxGap过小增大MaxGap平行边误并MaxDistToLine过大 / 重叠条件不合理减小MaxDistToLine调小MaxOverlap合并结果不稳定边缘提取阈值波动固定阈值策略或使用自适应阈值运行速度太慢输入轮廓过多预先过滤轮廓分区域处理合并后方向不对输入轮廓方向不一致检查方向属性必要时翻转6.6 调试技巧用轮廓显示快速定位问题调试union_collinear_contours_xld时我强烈建议打开Halcon的轮廓显示把输入轮廓、中间筛选结果、合并输出分别用不同颜色叠加显示在原图上。dev_display (Image) dev_set_color (red) dev_display (SelectedContours) dev_set_color (green) dev_display (UnionContours)一眼就能看出哪些轮廓被合并了、哪些被丢弃了、合并方向是否合理。比盯着控制台数值效率高得多。7. 一个完整的实际测量案例这里分享一个我实际做过的项目手机中框边缘直线度检测。需求是测量中框两条长边的直线度和平行度精度要求0.02mm以内。图像分辨率约0.01mm/pixel所以要求直线检测精度达到2像素以内。最初直接用edges_sub_pix提取边缘后按长度排序取最长轮廓拟合结果直线度波动在0.05mm以上客户那边过不了。后面改成如下流程* 1. 提取亚像素边缘 edges_sub_pix (GrayImage, Edges, canny, 1.2, 15, 30) * 2. 筛选长度大于50像素的轮廓 select_contours_xld (Edges, LongContours, contour_length, 50, 10000, -0.5, 0.5) * 3. 按方向筛选只保留接近水平方向的轮廓 select_contours_xld (LongContours, HorContours, direction, -0.15, 0.15, -0.5, 0.5) * 4. 合并共线轮廓 union_collinear_contours_xld (HorContours, UnionContours, 100, 0.03, 0.1, 1.5, 60, 100) * 5. 对合并后的每条轮廓拟合直线 fit_line_contour_xld (UnionContours, tukey, -1, 0, 5, 2, RowBegin, ColBegin, RowEnd, ColEnd, Nr, Nc, Dist) * 6. 输出角度和直线度 angle_line (RowBegin, ColBegin, RowEnd, ColEnd, Angle)关键参数调整过程第一次跑MaxGap设了30像素结果中框表面有一条划痕造成的断缝有40多像素没合并上。改到60后成功连接。MaxDistToLine最初设了3像素结果旁边一条凹槽边缘也被并进来了拟合出的直线偏了。改到1.5后凹槽边缘被排除拟合精度恢复。MaxOverlap设0.1防止了中框上下两条平行边被误并。最终直线度波动控制在0.012mm以内平行度0.018mm以内顺利通过验证。这个案例说明一个道理参数不是拍脑袋定的而是要根据图像特征、精度要求、噪声水平反复调试出来的。8. 写在最后一点个人心得union_collinear_contours_xld不是那种用了就万事大吉的算子它更像一把精细的手术刀需要你清楚知道每个参数对结果的影响并且结合具体的图像特征去调整。我见过很多工程师在这个算子上栽跟头最常见的就是参数设得太随意。其实只要掌握几个要点先筛选轮廓再合并MaxAngGap和MaxDistToLine是精度核心MaxGap决定断缝容忍度MaxOverlap防止误并合并后一定检查方向再做拟合这算子的坑就走完一大半了。另外提醒一下如果你的图像边缘质量很差靠一个算子强行合并不是长久之计。改善光源、调整焦距、提高图像对比度往往比调参数更有效。算法是锦上添花图像质量才是基础。希望这篇内容对你有帮助也欢迎在实际项目中多尝试不同的参数组合找到最适合你场景的那组配置。