18 特征匹配与颜色匹配
前两章的匹配方法有一个共同点:它们都是稠密的——灰度匹配(章节 16)逐像素比对整块模板,形状匹配(章节 17)逐点比对整条轮廓,姿态(角度、尺度)必须作为显式维度逐档搜索,范围越大代价越高。本章介绍两种”另辟蹊径”的匹配。特征匹配(feature matching)把图像压缩成几十个关键点(keypoint),比对的不再是整片像素而是稀疏的点集——当零件可能以任意角度、甚至不同尺度出现时,它的代价几乎不随姿态范围增长。颜色匹配(color matching)则干脆放弃几何:“这是红盖还是黄盖”的分选问题里,重要的不是零件在哪、转了多少度,而是它是什么颜色——用颜色统计量做分类,比任何几何匹配都直接。
特征匹配部分用一组真实工业样图:Smart3”特征匹配”定位方案的样片——一枚五臂转子/链轮铸件,暗场照明下拍成 \(1000\times 800\) 的 8 位灰度图。同一颗物理零件在四幅图中以不同的位置与角度出现,这正是特征匹配最典型的用武之地。我们把其中一幅(rotor_001,零件居中靠上)裁出零件方块当模板(图 18.1 (a)),在另外三幅(rotor_002/003/004)里搜索它。零件表面满布铸造喷砂纹理、布满通孔、卡槽与臂尖,角点极其丰富;但它又近乎五重旋转对称——这给匹配埋了一处真实的坑,下文详述。颜色匹配部分则保持合成数据:本套”特征匹配”样图是单通道灰度,无法驱动颜色匹配;为完整呈现 SciSvColorMatch 的用法与色度行为,颜色实验沿用红/绿/蓝/黄瓶盖分选的合成场景,并在 小节 18.4 说明。
rotor_001 中零件的亮度重心为中心裁出的 \(480\times 480\) 方块——喷砂纹理、通孔、卡槽与五条臂尖提供数百个角点;(b) 一幅 \(1000\times 800\) 搜索图:同一零件在暗场上以不同位置、不同旋转出现,背景近黑、无内部结构。
18.1 特征点与描述子
特征匹配的第一步是从图像中挑出少数”有个性”的点。什么样的点有个性?平坦区域内的一个像素,挪到哪里邻域都长一样,无从辨认;边缘上的点沿边缘滑动时邻域几乎不变,只能定一个方向;只有角点(corner)——灰度沿两个独立方向都剧烈变化的点——其邻域在平移下处处不同,能被唯一地”认出来”。这与 章节 25 里角点作为轮廓显著点的直觉一脉相承:检测器在图像上寻找局部”角点性”最强、且强度超过阈值的位置,作为关键点。
光有位置还不够,还要能在另一幅图里认出同一个点。为此给每个关键点的邻域计算一个描述子(descriptor)——把邻域的灰度或梯度分布编码成一个特征向量,作为这个点的”指纹”。两幅图中描述子最接近的点对,就是候选的对应(correspondence)。要让旋转后的同一个角点仍有相近的指纹,描述子在计算前先估计邻域的主方向,把邻域旋到统一朝向再编码——这就是旋转不变性(rotation invariance)的来源:角度不靠穷举搜索,而是被描述子”消化”掉了。
这正是稀疏匹配在代价上翻盘的关键。稠密模板匹配必须遍历位姿空间:位置 \(\times\) 角度 \(\times\) 尺度的网格,每个格点做一次全模板比对,角度范围扩大一倍,代价就翻一倍。特征匹配则把流程拆成三步:检测关键点(每幅图只做一次,与姿态无关)、比对几十个描述子、最后从候选对应中解出位姿——位姿空间从头到尾没有被枚举过。候选对应里难免混入认错的外点(outlier),所以解位姿用的是 RANSAC:反复随机抽取最小点组拟合一个刚体(或相似)变换,统计有多少对应在该变换下吻合——即内点(inlier)——内点最多的变换胜出,位姿由全体内点最小二乘精化。
RANSAC 我们在 章节 14 拟合直线时已经用过:在被外点污染的数据中,“随机抽最小样本、投票选共识”比最小二乘鲁棒得多。那里的外点是误入的边缘点,这里的外点是认错的描述子对应——配方完全相同。
一笔代价账:本章样图若用灰度匹配覆盖 \(360^\circ\),按 \(1^\circ\) 步长要预备 360 个模板姿态逐一扫描;特征匹配只需检测一次关键点(本例每幅搜索图约 410 个),再在 \(335\times{\sim}410\) 量级的描述子对里找对应——角度范围再大,这笔账也不变。
当然,天下没有免费的午餐:特征匹配要求模板有足够多可检测的角点。光滑无纹理的零件(圆盘、素色板)提不出几个关键点,模型根本建不起来——本章这枚转子铸件恰好相反,喷砂表面与孔、槽、臂尖处处是角,是特征匹配的”友好”对象。但它带来另一类真实的麻烦:近五重旋转对称。几何上自相似的零件,描述子在不同臂上彼此撞脸,容易把位姿解到一个”错位”的共识上——这正是下一节要直面的工程坑。
18.2 特征匹配实验
模板取自 rotor_001:先对零件做亮度阈值求重心(\((533.3,492.6)\)),以重心为中心裁出 \(480\times 480\) 方块(图 18.1 (a))。用 STABLE 检测器在模板上训练,得到 335 个模型特征点(图 18.2):它们密布于喷砂表面、通孔与卡槽边缘、臂尖等角点性最强处。同一模型在三幅 \(1000\times 800\) 搜索图上各检出约 410 个场景关键点(420 / 414 / 406)。
真实样图没有标注真值位姿,我们用两条互相独立的证据来校验匹配质量。其一是已知答案的自匹配:把模型放回它自己的源图 rotor_001 里搜,理应回到模板原点。实测定位于 \((533.00,493.00)\)、角度 \(0.000^\circ\)、尺度 \(1.0000\)——对模板原点 \((533,493)\) 的误差为 0.00 px,这把整条”裁剪—建模—搜索”的链路校了零。其二是与强度重心的交叉核对:转子近似中心对称,其亮度重心是一个与特征匹配完全无关的稳定物理参照,匹配解出的中心若与之吻合,即互证可靠。
三幅图的匹配结果(图 18.3)干净利落,全部在最严格的匹配阈值 60 上一次命中:
| 搜索图 | 解出中心 | 角度 | 尺度 | 强度重心 | 中心误差 |
|---|---|---|---|---|---|
rotor_002 |
\((558.72,458.24)\) | \(-44.97^\circ\) | \(0.9996\) | \((558.6,457.8)\) | 0.43 px |
rotor_003 |
\((701.58,239.17)\) | \(-149.99^\circ\) | \(0.9995\) | \((701.1,239.4)\) | 0.51 px |
rotor_004 |
\((308.59,558.20)\) | \(-44.98^\circ\) | \(1.0000\) | \((308.6,557.8)\) | 0.37 px |
中心误差全部在 0.5 px 量级,尺度无一例外落在 \(1.000\) 的 \(0.05\%\) 之内——这与”同一相机、同一工作距离、零件未缩放”的物理事实严丝合缝,是结果合理性的有力旁证。注意我们根本没告诉匹配器角度范围:\(-45^\circ\) 到 \(-150^\circ\) 的旋转被描述子与 RANSAC 自然吸收。还有一处微妙却重要:解出的角度(\(-45^\circ\)、\(-150^\circ\))并非五重对称步距 \(72^\circ\) 的整数倍——这说明匹配并未被旋转对称”将就”到某个等价解上,而是靠铸件独一无二的喷砂纹理锚定了真实的绝对朝向。
这次实验暴露了一个教科书几乎不写、产线上却足以让方案翻车的工程现实:描述子的选择。最初用 STABLE 检测器 + STABLE 描述子(手册的”稳健”默认搭配),自匹配完美、rotor_003 也定位到了 3.6 px,但 rotor_002 与 rotor_004 双双严重错位——中心偏出 183 px 与 52 px、尺度漂到 \(1.06\) 与 \(0.78\),且必须把匹配阈值一路放宽到 10 才勉强出结果。病根正是上一节埋下的近五重对称:STABLE 描述子在自相似的臂间分辨力不足,RANSAC 把共识投到了一个旋转错位的解上。改用 FAST 描述子后,三幅图全部在严格阈值 60 上命中、中心误差降到 0.5 px、尺度回到 \(1.00\)——同一套检测器、同一组关键点,仅换描述子,结果天差地别。教训是:在几何自相似的零件上,描述子的分辨力比检测器的多寡更要命;“默认参数”未必为你的零件而设,对竞品参数做一次实测扫描往往比读手册更快找到答案。
还有一处 SDK 语义值得记下:FindFeatureImageModel 即使把 matchCount 设为 2,一次调用也只返回 1 个实例——RANSAC 选出内点最多的那一个变换,次优实例的对应在它眼里只是一堆外点。本章每幅图恰好只有一颗零件,故每图调用一次即可;但若一幅图里有多颗同款零件,必须在应用层做”找到一个 → 把它从图上抹除 → 再找”的迭代。
最后值得点明的是稀疏匹配鲁棒性的来源:图 18.3 里映射后的模型点精确贴合每个零件,是因为位姿由点群的几何一致性决定,而非某个单点之准。检测器只认”角点性”,个别候选对应连错臂、连到背景亮斑都在所难免;能把它们一一排除的,是 335 个模型点必须同时各就各位的硬约束。稀疏匹配的鲁棒性不在单点之准,而在群点之合。
18.3 颜色的表示与匹配
换一个场景:传送带上流过红、绿、蓝、黄四种瓶盖,任务是分选。这里几何完全不重要——盖子都是圆的——重要的只有颜色。最朴素的做法是把区域的平均颜色当作一个三维向量 \((R,G,B)\),与各参考色算欧氏距离,距离最近者胜。
这个做法埋着一颗雷:RGB 把亮度与色度耦合在一起。同一个红盖,灯光暗三成,\((R,G,B)\) 三个分量近乎等比例缩小——颜色向量沿着指向原点的射线滑动,与参考色的欧氏距离随之增大,哪怕人眼看它”还是那个红”。产线上灯管老化、电压波动、工件离灯远近不一,亮度从来不是常数;用原始 RGB 距离做分选,等于把分类阈值押在照明的稳定性上。
解决思路是把亮度从颜色里除掉,只留下”色彩的比例”——这就是色度(chromaticity)。最简单的色度坐标是归一化 RGB:
\[ r = \frac{R}{R+G+B}, \qquad g = \frac{G}{R+G+B}, \]
(\(b\) 由 \(r+g+b=1\) 给出)。三个分量同乘一个增益,分子分母同步缩放,\((r,g)\) 纹丝不动——线性的亮度变化被精确消去,与 NCC 靠归一化消去照明增益(章节 16)是同一套思想在颜色空间的翻版。HSV 空间的色调 H、饱和度 S 与明度 V 的分离(章节 7 的彩色二值化已经用过)是另一种实现,工程效果相通。
几何图像:RGB 空间中”同色不同亮”的点排在一条过原点的射线上;色度归一化把每条射线投影到 \(R+G+B=\text{常数}\) 的平面上的一个点。分类在这个平面上做,亮度维度被整个折叠掉了。
颜色匹配器的使用方式是一类一模型:为每种参考色训练一个颜色模型,对待测区域用每个模型各打一个分,得分最高者即为预测类别。它的内部颜色表示是否做了色度化处理,手册未必明说——但我们可以用实验把它问出来。
18.4 颜色匹配实验
本节用合成数据:前面特征匹配所用的 Smart3 样图是单通道灰度,无法承载颜色信息;为完整演示 SciSvColorMatch 的用法并探究其内部颜色表示,我们构造一组色块分选场景。参考样本是四个瓶盖色块(红/绿/蓝/黄,每像素每通道叠加 \(\pm 10\) 的均匀噪声,图 18.4)。测试集是一张 \(3\times 4\) 网格(图 18.5):四列对应四种颜色,三行把盖子的亮度分别乘以 \(0.8\times\)、\(1.0\times\)、\(1.2\times\)——模拟照度与反射率 \(\pm 20\%\) 的波动。
分类结果干净利落:12/12 全部正确,真类得分落在 81.7~92.9 之间,对最强错误类的平均余量约 40 分——不是险胜,是碾压。
但本实验真正的看点不是正确率,而是亮度扫描。按行统计真类平均得分:
| 亮度 | \(0.8\times\) | \(1.0\times\) | \(1.2\times\) |
|---|---|---|---|
| 真类平均得分 | 91.0 | 89.9 | 84.1 |
亮度上下波动 \(20\%\),得分曲线几乎是平的——最大落差仅 5.8 分。如果匹配器内部用的是原始 RGB 距离,\(\pm 20\%\) 的亮度会把颜色向量推离参考色相当远,得分理应大幅滑落;平坦的曲线指向另一个结论:该 SDK 的颜色匹配行为接近色度空间,而非原始 RGB 距离。\(1.2\times\) 行那一点轻微下降也有合理解释:黄色盖的 R 通道参考值为 210,乘 \(1.2\) 后达 252、已贴近 255 的上限,叠加 \(\pm 10\) 噪声后被钳位截断——饱和让色度真的偏了,扣分扣得有理。
还有一处方法论上的细节值得展开。我们同时跑了两个手工基线:圆盘均值的原始 RGB 距离分类与色度距离分类——结果都是 12/12。这说明四色分选任务本身太容易了:红绿蓝黄的色相相距甚远,\(\pm 20\%\) 的亮度还不足以让原始 RGB 翻车,正确率这个指标对”SDK 是否色度化”毫无判别力。能区分两种假说的,是得分曲线的形状:原始 RGB 假说预言得分随亮度明显滑落,色度假说预言曲线平坦——实测站在了后者一边。设计实验时要让相互竞争的解释给出不同的预言,否则跑出再漂亮的数字也只是自我安慰。
18.5 SciVision 实现
特征匹配由 SCIMV::SciSvFeatureMatch 承担,训练与搜索两步:
SCIMV::SciSvFeatureMatch fm;
SciROI mask; // 默认构造 = UNDEF,表示整图(必须如此!)
SciMatchModel model;
fm.CreateFeatureImageModel(tmplImg, mask,
SCI_FEATUREMATCH_DETECTOR_STABLE, // detectorMethod: 0=STABLE 1=FAST 2=EDGEPOINT
SCI_FEATUREMATCH_DESCRIPTOR_FAST, // descriptorMethod: 本零件实测 FAST 远胜 STABLE
/*sampleStep*/ 1, // 采样步距:1/2/4,越大点越稀、越快
/*scaleRange*/ 0, // 尺度档位 0..3,0 = 不扩展尺度
/*detectorThreshold*/ 30, // 角点响应阈值 [1,255],越高点越少
&model);
SciFeaturePointArray pts;
fm.GetFeatureModelPoints(model, &pts); // 本例 335 个模型特征点
SciPointArray centers; SciVarArray angles, scales;
fm.FindFeatureImageModel(sceneImg, roi, model,
SCI_FEATUREMATCH_MATCHER_STABLE,
/*matcherThreshold*/ 60, // 匹配质量下限 [1,100],本例严格档 60 即全部命中
/*matchCount*/ 1, // 注意:给多大也只返回 1 个实例
¢ers, &angles, &scales);训练侧最致命的不是阈值多寡,而是 descriptorMethod 的选择:本章近旋转对称的转子上,STABLE 描述子分辨不开相似的臂、把位姿解到错位共识,FAST 描述子才稳(见 小节 18.2)。detectorThreshold 与 sampleStep 控制点的密度与速度;scaleRange 在零件可能远近变化(如相机高度不固定)时才需要打开。搜索侧的 matcherThreshold 是 小节 18.2 讨论的内点率门槛——描述子选对后,本例三幅图在最严格的 60 档即全部命中。返回的 angles 是数学正方向(逆时针为正),在 y 轴向下的图像坐标里绘制位姿需取负换算——与灰度匹配(章节 16)同一个约定坑。
颜色匹配由 SCIMV::SciSvColorMatch 承担:
SCIMV::SciSvColorMatch cm;
SciROI cmask; // 同样必须 UNDEF
SciMatchModel cmodel;
cm.CreateColorImageModel(refImg, cmask,
/*pyramidLevel*/ -1, // -1 = 自动选金字塔层数
SCI_COLOR_SENSITIVITY_HIGH, // 颜色灵敏度 0..2
&cmodel);
SciPointArray ctr; SciVarArray ang, score;
cm.FindColorImageModel(gridImg, cellROI, cmodel,
/*minScore*/ 1, /*matchCount*/ 1, /*overlapRatio*/ 50,
/*startAngle*/ 0, /*angleExtent*/ 10, /*angleStep*/ 1, // extent 须 ≥10
&ctr, &ang, &score);分类时对每个待测 ROI 用四个模型各调用一次 FindColorImageModel,取得分最高者;某模型在异色区域返回”无匹配”(122407002)属正常现象,按 0 分处理即可。
五个实测踩过的坑,如实记录:
- 描述子选择影响成败。同一检测器、同一组关键点,STABLE 描述子在近旋转对称的转子上把
rotor_002/004误定位(中心偏 183 px / 52 px、尺度漂到 \(1.06\) / \(0.78\)),FAST 描述子则三幅图全部在严格阈值 60 上命中、误差 \(\le 0.5\) px。几何自相似的零件务必把两种描述子都实测一遍。 - mask 必须传 UNDEF。两个
Create*Model的mask参数都必须传默认构造的 SciROI表示整图;传一个GenRect1全图矩形,特征匹配报”特征点不足”(122406005),颜色匹配则训练”成功”但后续匹配失败(122407002)。 FindFeatureImageModel单实例语义。matchCount给多大都只返回 1 个实例;多实例必须在应用层做”找到 → 抹除 → 再找”的迭代。GetFeatureImagePoints反而要矩形 ROI。与 Create 恰好相反,给它 UNDEF ROI 会报 122406101,必须传GenRect1(0,0)-(W,H)的全图矩形(右下角排他)。同族 API 的 ROI 语义不要想当然。FindColorImageModel的angleExtent不能为 0。色块明明没有旋转,angleExtent=0却会间歇性返回 122407002;统一传 \(\ge 10\) 后稳定。间歇性失败比稳定失败更难排查,遇到时先怀疑边界参数。
完整可运行工程在 code/feature_color_matching/。
工业案例:端子颜色分选的灯光波动
某连接器厂按护套颜色分选端子,初版方案在 RGB 空间设固定阈值盒分类。运行数月后误判率逐月攀升,排查发现是车间荧光灯管老化造成的色温与照度双重漂移:RGB 阈值盒是按验收当天的灯光标定的,灯一变,颜色向量整体平移出盒。整改分两步:特征上改用色度坐标(归一化 RGB)做分类,把亮度维度先行隔离;同时在视野角落固定一块白色参考块,每班次用它的实测 RGB 重算白平衡(white balance)增益,校正剩余的色温漂移。改造后误判率回落趋稳。教训有二:颜色任务必须把”亮度通道”从特征里隔离出去;并且要给系统留一个随时间可校的”白”——照明永远在变,能变的环境就要有能跟着变的基准。
18.6 小结
- 特征匹配 = 关键点 + 描述子 + 几何一致性:检测角点性强的稀疏关键点,用旋转归一化的描述子建立候选对应,RANSAC 在外点中投票解出位姿——位姿空间不被枚举,大角度、尺度变化的代价远低于稠密匹配。本章用真实转子样图:335 个模型点对约 410 个场景点,同一零件在三幅图中以不同位姿出现,中心误差全部 \(\le 0.5\) px、尺度恒为 \(1.000\)(自匹配误差 0.00 px 作链路校零)。
- 稀疏匹配的鲁棒性在群点之合,不在单点之准:个别关键点会连错臂、连到背景亮斑,靠 335 个模型点必须同时各就各位的几何一致性才把它们排除在位姿之外;代价是模板必须角点丰富,光滑无纹理的零件不适用。
- 工程现实:描述子选择是几何自相似零件的胜负手。近旋转对称的转子上 STABLE 描述子误定位(偏 50~180 px),FAST 描述子才把三幅图全部稳在严格阈值 60;此外
FindFeatureImageModel一次只返回一个实例,多实例要”找-擦-再找”。 - 颜色分类要用色度,不要用原始 RGB:归一化 \(r=R/(R+G+B)\) 类的色度坐标把亮度从颜色中除掉,照明波动下分类才稳;实测 SDK 颜色匹配在 \(\pm 20\%\) 亮度扫描下真类得分仅落差 5.8 分,行为接近色度空间。
- 设计有判别力的实验:四色分选任务连原始 RGB 基线都能拿满分,正确率无法区分假说;能区分的是得分曲线随亮度的形状——让竞争假说给出不同预言,数据才说得上话。
稀疏特征匹配的三大支柱各有奠基文献:角点检测的 Harris–Stephens 算子 (Harris 和 Stephens 1988)、把检测与描述推向尺度不变的 Lowe 的 SIFT (Lowe 2004),以及以二进制描述子大幅提速的 Rublee 等人的 ORB (Rublee 等 2011)。特征点、描述子与基于颜色的识别在 Steger 等人的著作中有更系统的论述 (Steger, Ulrich, 和 Wiedemann 2018)。





