17  形状匹配

上一章(章节 16)把 NCC 捧上了工业定位主力的位置,也在结尾点破了它的边界:NCC 既不抗旋转,也不抗遮挡,更守不住非线性照明。产线上这三类常客频频越界——工件来料的角度随机、夹爪与料带盖住零件的一部分、镜面反光与油污把灰度映射扭成任意曲线。三种失效的根源相同:灰度匹配比较的是每一个像素的灰度值,而灰度恰恰是图像中最不稳定的东西,且像素块本身被钉死在一个朝向、一个尺寸上。出路是换一个比较对象——别比灰度,比形状:把模板抽象为一组边缘点(位置 + 梯度方向),匹配时在角度、尺度构成的位姿网格上搜索,只检查这些点处图像的梯度方向是否对得上。这就是形状匹配(shape-based matching),商用视觉库中抗干扰能力最强的定位算法。

本章用一套真实工业样图贯穿实验。轮廓匹配取自某定位方案的剪影样图(图 17.1):黑色零件铺在白底上,主角是一枚五边形楔块——竖直左边、近水平的上边、收向右侧的尖端、右下一道斜切角,平边与斜切让它强烈非旋转对称(rotational symmetry);陪衬的还有圆环垫圈、梳齿条等干扰件。五张样图里,这枚楔块以五花八门的角度散落(小节 17.2),正好考验旋转鲁棒性与抗干扰。尺度实验另取一组样图:同一堆零件(螺钉、旋钮、方形接插件)在三个物距下拍摄,零件随相机后退由近到远逐渐变小(小节 17.4)。

(a) 模板:五边形楔块(样图 1 中裁出,\(195\times 323\)
(b) 搜索图:样图 1,楔块 + 圆环 + 梳齿条干扰件
图 17.1: 本章轮廓匹配的真实样图。(a) 从样图 1 裁出的五边形楔块作模板,竖直边 + 斜切角令其非旋转对称;(b) \(1006\times 759\) 搜索图(原图 \(4024\times 3036\) 降采样 \(4\) 倍),楔块与圆环、梳齿条等干扰件同框。

17.1 基于梯度方向的相似度

形状匹配的模型不是像素块,而是一张边缘点清单:训练时对模板做边缘提取(章节 13 的滞后阈值流程),得到 \(n\) 个边缘点 \(p_i\),每个点附带该处的梯度方向(gradient direction) \(\theta_i\)。搜索时把这份清单按候选位姿(平移 + 旋转 + 尺度)变换到图像上,在每个点的落点处读出图像的梯度方向,与模型方向(随位姿同步旋转后)求差 \(\Delta\theta_i\),分数取方向一致性的平均:

\[ s=\frac{1}{n}\sum_{i=1}^{n}\cos\Delta\theta_i . \]

方向完全一致时 \(\cos\Delta\theta_i=1\)\(s=1\)(SDK 折算为百分制 100 分);方向随机时正负相消、贡献趋零。这一个式子里藏着形状匹配的三大武器。

对旋转、尺度天然可搜索,因为模型是一份带几何坐标的点清单,把它整体旋转 \(\phi\)、放缩 \(\sigma\) 即得一个新位姿的预测,逐位姿评分即可在连续的角度、尺度上定位——这正是灰度像素块做不到的:像素块一旋转就要重采样,且 NCC 公式里没有角度这个自由度。

对照明稳健,因为梯度方向不随灰度的增益与偏置改变:把图像换成 \(a f + b\)\(a>0\)),梯度向量变为 \(a\nabla f\)——长度变了,方向纹丝不动。更妙的是,即便灰度映射是非线性的,只要它在局部单调,等灰度线的走向就不变,方向仍然守恒。NCC 守得住线性,梯度方向连光滑的非线性都守得住。

\(a<0\)(明暗反转)时梯度方向翻转 \(180^\circ\)\(\cos\Delta\theta_i=-1\)——分数反被罚。是按 \(\cos\) 计(要求极性 polarity 一致)还是按 \(\lvert\cos\rvert\) 计(忽略极性),由 SDK polarity 参数控制:曲面反光、背光切换会让局部极性翻转,此时才放开为 2。

天然容忍遮挡,因为分数是逐点贡献的平均:被盖住的模型点落在遮挡物上,那里的梯度方向与模型无关,贡献在 \([-1,1]\) 内随机、均值近零——缺失的点只是稀释分数,丝毫不破坏其余可见点的贡献。零件可见七成,分数就大约剩七成。对照 NCC:遮挡像素带着巨大的灰度残差全额计入平方和,少量遮挡就足以把相关性拖到不可辨认。一个是”少数服从多数”,一个是”一票否决”,鲁棒性的差距由此而来。

17.2 旋转与抗干扰实验

SciSvScaleShapeMatch 从样图 1 的楔块建模(角度范围 \(\pm 180^\circ\)、步长 \(1^\circ\)、尺度固定 1.0),以 minScore = 40 在五张样图上全图搜索。五张图里散落着六枚楔块(样图 3 同框两枚),六枚全部命中,角度从 \(-152^\circ\) 跨到 \(+102^\circ\),整整 \(254^\circ\) 的旋转范围:

表 17.1: 轮廓匹配实测结果(模板来自样图 1,minScore=40,subpixel=0)
样图 实例 位置 (px) 解出角度 分数
1 楔块(模板源,直立) \((318, 237)\) \(0^\circ\) 99.8
2 楔块(大角度旋转) \((455, 497)\) \(-152^\circ\) 99.8
3 楔块 A \((325, 196)\) \(102^\circ\) 99.6
3 楔块 B \((323, 570)\) \(89^\circ\) 93.7
4 楔块(近直立) \((702, 252)\) \(1^\circ\) 99.8
5 楔块(旋转) \((310, 209)\) \(-68^\circ\) 95.5

位置为 \(1006\times 759\) 降采样图中的像素坐标。SDK 返回角度为数学正方向(逆时针为正),图像坐标系(\(y\) 向下)渲染时取负。剪影边缘对比度极高,五张图的命中分数均在 93 分以上,旋转再大也不掉分——这正是”比方向不比灰度”的直接体现。

最值得玩味的有两点。其一,干扰件无一误检:每张图都设了 matchCount=3 的名额,但圆环垫圈、梳齿条这些”形状不对”的零件没有一个够到 minScore=40——梯度方向清单认的是楔块那套独特的边段组合,垫圈的圆弧、梳齿的方齿都对不上。其二,回画的模型轮廓精准贴合图 17.2 中每枚楔块上的灰色细线,是模型边缘按解出位姿(含旋转角)回画的结果,紧紧裹住零件的真实边界,连样图 2 那枚倒转 \(152^\circ\) 的也分毫不差。

(a) 样图 1:\(0^\circ\)
(b) 样图 2:\(-152^\circ\)
(c) 样图 3:两枚(\(102^\circ\)/\(89^\circ\)
(d) 样图 4:\(1^\circ\)
(e) 样图 5:\(-68^\circ\)
(f) 模板(参照)
图 17.2: 六枚楔块全部命中:黑色十字标记解出的中心,灰色细线为模型边缘按解出位姿的回画。圆环、梳齿条等干扰件无一误检。

把灰度 NCC 拉来做同一件事,立刻露怯。用直立的楔块模板(不带任何旋转搜索)去算相关:在样图 1 的直立实例处 NCC = 1.000(模板即取自此处),但在样图 2 那枚旋转实例的中心处(位置由形状匹配给出)NCC 骤降到 0.585——远低于常用的 0.80 判定线。NCC 的卷积窗口被钉死在一个朝向上,工件一转就对不齐,分数崩塌;形状匹配把角度作为显式搜索维度,转到哪儿找到哪儿。这是 NCC 与形状匹配最根本的分野:前者只认一个姿态,后者搜遍整个位姿空间

17.3 遮挡实验

真实样图里零件彼此分得很开,没有现成的遮挡场景,于是在样图 1 的楔块上叠加一根合成的中灰遮挡条(模拟料带或夹爪横跨工件),其余像素保持真实。遮挡条盖住了零件 36.0% 的面积(可见比例 0.64)。重新搜索,形状匹配仍稳稳命中:位姿锁定在 \((318, 237)\)\(0^\circ\),与无遮挡时分毫不差,分数从 99.8 降到 76.5——衰减比 0.77,与可见比例 0.64 同一量级。小节 17.1 的”分数 ≈ 可见比例”在商用实现里大体成立;比 0.64 略高,是因为遮挡条恰好横跨零件中段,而楔块的方向特征更密集地分布在尖端与斜切角,那些部位仍露在外。

图 17.3: 遮挡实验:在真实楔块上叠加中灰遮挡条(盖住 36% 面积),形状匹配仍以 76.5 分命中,位姿与无遮挡时一致。浅色细线为模型轮廓的回画——它径直跨过遮挡条延伸到不可见区域,算法”知道”被盖住的部分在哪里。

由此得到 minScore 的工程读法:它就是你愿意容忍的最大遮挡比例。完好楔块得 95–100 分,minScore 设 40 即默许约六成轮廓缺失;设 70 则要求零件基本完整。但抗遮挡能力不光由 minScore 决定:金字塔粗筛参数 partialThreshold 要求粗层有足够比例的模型可见,遮挡预算大时必须同步放开(本例用 0.4)——否则遮挡候选在粗层就被淘汰,根本到不了精评分。

17.4 尺度匹配

定位还有第四个自由度。相机到工件的物距一变——换型号、换托盘高度、镜头重新调焦——零件在图像里的尺度(scale)就变,固定大小的模板立刻对不上轮廓。解法与角度如出一辙:把尺度也作为显式搜索维度,训练时按 startScale/scaleExtent/scaleStep 在角度 × 尺度的网格上预生成姿态,搜索时逐格评分。

尺度样图正是为此而设:同一组零件在三个物距下拍摄,零件由近到远逐渐缩小。取最近那张(样图 1)里的方形接插件(矩形体 + 下凸方舌,深色轮廓、非旋转对称)建模,在 \([0.55, 1.20]\) 的尺度范围(步长 0.05)内搜索三张图:

表 17.2: 尺度匹配实测:接插件随物距收缩,尺度被逐一恢复
尺度样图 解出尺度 解出角度 分数
1(最近,模板源) 1.000 \(0^\circ\) 97.8
2(中) 0.850 \(-2^\circ\) 88.0
3(最远) 0.650 \(-2^\circ\) 74.4

三张图分别解出尺度 1.000、0.850、0.650——零件随相机后退缩到原来的 85%、65%,而尺度搜索把每一档都还原了出来(图 17.4)。分数随尺度下降(97.8→88.0→74.4)是因为零件越小、有效边缘点越少、真实照片的柔和边缘越发吃力,但 74.4 分仍远高于 minScore=25,定位稳固。回画的模型轮廓按解出尺度缩放后,严丝合缝地套住每张图里大小不一的接插件。

(a) 样图 1:尺度 1.000
(b) 样图 2:尺度 0.850
(c) 样图 3:尺度 0.650
图 17.4: 尺度匹配:同一接插件在三个物距下被定位,解出尺度 1.000 / 0.850 / 0.650。黑色细线为模型轮廓按解出尺度缩放后的回画,准确套住大小各异的零件。

尺度匹配补的是姿态的第四个自由度,不替代标定章节 5):物距变了,像素—毫米的换算关系也变了,要把尺寸量准仍须重新标定。尺度匹配解决的只是”物距漂移后还能不能找到、并知道缩放了多少”。

17.5 模板设计

样图里的零件并非随便选一个都能当模板,这套真实数据恰好把三条经验摆在明处。

第一,模板必须打破旋转对称。设想用那枚圆环垫圈建模:圆的轮廓转任意角度仍是同一个圆,模型里绝大多数边缘点对一切角度都”方向一致”,分数照样很高,但报出的角度纯属随机——这是典型的旋转对称陷阱。本章选五边形楔块正因为它的竖直边与斜切角贡献了大量方向唯一的边缘点,角度才能被牢牢锁定(六枚实例角度残差都很小)。教训写成一句话:分数只担保”轮廓对上了”,不担保”姿态是唯一解”——建模前检查模板是否(近似)旋转对称应是固定动作。

第二,特征要独一无二。样图里的梳齿条是一排重复方齿,若拿它建模,场景中任何一段等距方波边缘都可能冒领高分;楔块的边段组合在这套零件里别无分号,干扰件才会全数落选。模板要选场景中独一无二的结构组合,阈值则须高于任何干扰物可能挣到的最高分。

第三,真实照片的边缘是软的。尺度样图是金属件的实拍灰度图,边缘只有几个像素宽、还带反光与噪声。直接对其做盒式降采样会把梯度幅值压到对比度阈值以下,导致连零件自身的图都匹配不上;必须在全分辨率上匹配,并把金字塔层数压到 2 层(自动分层会在高层把软边缘抹没,粗筛即失败)。剪影样图对比度极高,这些顾虑全然不存在——这本身就说明,模板与样图的”边缘质量”直接决定了参数怎么配。

17.6 SciVision 实现

先说一个名字的坑:SDK 里的 SciSvOCV 不是轮廓匹配——OCV 是 optical character verification,做字符缺陷校验。轮廓匹配与尺度轮廓匹配统一由 SCIMV::SciSvScaleShapeMatch 承担:scaleExtent=0 即纯轮廓匹配,给非零范围即尺度匹配,一个类覆盖两种用法。训练与搜索:

SCIMV::SciSvScaleShapeMatch sm;
SciROI mask;                          // 必须 UNDEF:GenRect1 矩形会报 120001037
SciMatchModel model;
sm.CreateScaleImageModel(tmplImg, mask, /*pyramidLevel*/ 2,   // 软边缘限 2 层,剪影可用自动 -1
    /*startAngle*/ -180, /*angleExtent*/ 360, /*angleStep*/ 1,
    /*startScale*/ 1.0f, /*scaleExtent*/ 0.0f, /*scaleStep*/ 0.05f,
    /*lowerContrast*/ -1, /*upperContrast*/ -1,        // 边缘滞后阈值,-1 自动
    /*filterCoefficient*/ 3.0f, /*filterLength*/ -1,   // 训练侧平滑
    /*minContrast*/ -1, /*optimization*/ -1,
    /*polarity*/ 0, /*preGeneration*/ 1, &model);      // preGeneration 必须为 1

SciPointArray centers; SciVarArray angles, scales, scores;
sm.FindScaleImageModel(sceneImg, searchROI, model,
    -180, 360, 1.0f, 0.0f,
    /*minScore*/ 40, /*matchCount*/ 3, /*overlapRatio*/ 40,
    /*subpixel*/ 0, /*endLevel*/ 0, /*clutter*/ 0.0f,
    /*coarseScore*/ 35, /*partialThreshold*/ 0.5f, /*spreadWinSize*/ 2,
    &centers, &angles, &scales, &scores);

训练侧的角度、尺度两组三参数同义;lowerContrast/upperContrast 是模板边缘提取的滞后双阈值,决定哪些点进入模型清单;polarity小节 17.1 的极性开关。搜索侧 minScore/matchCount/overlapRatio 控制阈值、实例数与去重;GetScaleTemplateContours 可取出模型边缘轮廓,本章叠加图正是用它按解出位姿回画的。

本章实测踩过的坑如实记录。其一,mask 必须传默认构造的 UNDEF ROI:传 GenRect1 全模板矩形直接报错 120001037——与特征匹配、颜色匹配同一家族脾气。其二,preGeneration 必须为 1(完整预生成模型),否则后续匹配不可用。其三,subpixel 只能用 0:设 1(插值精化)直接崩溃 0xC0000005;设 2(最小二乘精化)会”高分漂移”——分数很高却带着几十度的角度错误,更具欺骗性。其四,软边缘的真实照片要压金字塔层数并全分辨率匹配:本章尺度样图若按惯例盒式降采样并用自动分层(pyramidLevel=-1),连接插件自身的图都报”匹配失败”(122411002);改为全分辨率、pyramidLevel=2 后,三张图的尺度齐刷刷解出。另注意 coarseScore 必须 \(\le\) minScore,否则粗层就把合格候选筛没了。完整工程在 code/shape_matching/

工业案例:油污工件的定位切换

某机加工车间对带切削液油污的工件做抓取定位,最初用灰度匹配:油膜让表面灰度随光照角度起伏,分数在一天内波动可达 30 分,阈值难以整定。切换到形状匹配后立刻稳住——油污改变的是灰度分布,工件的轮廓纹丝不动,梯度方向相似度天然免疫。但第一版直接框了整个工件,内部加工纹理全被收进边缘点清单:建模匹配均慢,纹理被油污局部抹掉时分数抖动。第二版只保留外轮廓与两个关键定位孔,速度与稳定性双双达标。教训有二:形状匹配的稳健来自”只比对可靠的边缘”,把不可靠细节塞进模型等于自废武功;模板设计与算法选型同等重要。

17.7 小结

  • 形状匹配把模板抽象为边缘点 + 梯度方向的清单,分数 \(s=\frac1n\sum\cos\Delta\theta_i\) 是方向一致性的平均:模型是带几何坐标的点清单,故能在角度、尺度上显式搜索;梯度方向不随增益/偏置乃至局部单调非线性改变;缺失点只稀释分数不破坏其余——可搜索、照明稳健、容忍遮挡三性同源于此。
  • 旋转鲁棒:一枚五边形楔块模板在五张真实样图上找到全部六枚实例,角度跨 \(-152^\circ\sim+102^\circ\)、分数均 \(\ge 93.7\),圆环与梳齿条干扰件无一误检;同一旋转实例处灰度 NCC 仅 0.585(< 0.80),印证 NCC 不抗旋转。
  • 遮挡下分数 ≈ 可见比例:合成遮挡条盖住真实楔块 36% 面积,分数 99.8→76.5(衰减比 0.77),位姿仍精确;minScore 即可容忍的最大遮挡比例,partialThreshold 等粗筛参数须随遮挡预算放开。
  • 尺度作为第四维由 scaleExtent 打开,真实接插件随物距收缩,三张图依次解出尺度 1.000 / 0.850 / 0.650;尺度匹配补姿态,不替代标定。
  • SDK 侧记住几件事:mask 传 UNDEF、preGeneration=1、subpixel 只能 0、coarseScore \(\le\) minScore;软边缘的实拍图要全分辨率匹配并把金字塔压到 2 层,否则连零件自身的图都匹配失败。

形状匹配解决了”零件在哪儿”的最难形态;当待找的不是刚性轮廓而是颜色块或纹理特征时,还有特征匹配与颜色匹配可选(章节 18)。基于边缘的形状匹配另有两条经典线索:Borgefors 的分层倒角匹配以距离变换度量轮廓相似度 (Borgefors 1988),Belongie 等人的形状上下文则用分布直方图描述点集形状并求解对应 (Belongie, Malik, 和 Puzicha 2002)。形状匹配的原理与工程细节在 Steger 等人的著作中有最系统的论述 (Steger, Ulrich, 和 Wiedemann 2018)