4. ROI管理与定位策略

在工业读码任务中,真正决定识别成败的往往不是解码算子本身,而是码区是否被稳定、准确地送入解码器。一张2000×1500像素的图像里,条码或字符可能只占几十像素宽,若把整幅图直接交给 find_bar_code 或 read_ocr,既慢又容易被背景纹理、反光、字符干扰。ROI(Region of Interest,感兴趣区域)管理要解决的就是:在什么位置、用什么方法、以多大范围把码区框出来,并保证这个框在连续生产中始终跟着码走。

一、三类定位手段的适用边界

HALCON 中码区定位通常有三条技术路线,它们不是互相替代,而是按“先粗后精、先稳后准”的顺序组合使用。

  • Blob 分析:基于灰度阈值、连通域和形态学,速度快,适合码区与背景对比明显、形状规整的场景,例如白底黑码的标签、印刷字符行。它给出的是区域位置和面积,不关心内部纹理。
  • 模板匹配(NCC):用 create_ncc_model 建立灰度模板,对光照变化有一定容忍度,适合码区外观稳定、允许轻微形变的场合,如固定型号的喷码字符块。
  • 形状匹配:用 create_shape_model 基于边缘梯度建立模型,对光照、对比度变化不敏感,支持旋转和缩放,是倾斜、部分遮挡、多码场景下最稳健的定位手段。

选择依据可以概括为:看对比度选 Blob,看纹理稳定性选 NCC,看几何鲁棒性选形状匹配。实际工程中,常见做法是用形状匹配定位产品上的基准特征(如定位孔、外形轮廓、Mark 点),再由该基准通过刚体变换推导出码区 ROI,而不是直接匹配码本身——因为码本身可能被遮挡或污损,而基准特征通常更可靠。

码区定位策略:基准定位 → ROI 推导 → 解码验证 采集图像 2000×1500 px 含定位孔 / 外形轮廓 码区可能倾斜 / 遮挡 形状匹配 create_shape_model 模板匹配 NCC create_ncc_model Blob 分析 threshold + connection 基准位姿 Row, Col, Angle Score ≥ 0.7 ROI 推导 affine_trans_region reduce_domain 多码:循环取位姿 遮挡:扩大搜索域 解码与验证 find_bar_code / read_ocr → 校验位、字符数、置信度 失败:调整阈值 / 搜索角 / 缩放范围 关键参数与经验取值(示例,需按实际标定调整) 形状匹配:AngleStart=-0.35 rad,AngleExtent=0.70 rad,MinScore=0.7,NumLevels=4,Greediness=0.9 NCC:NumLevels=3,MinScore=0.6;Blob:MinGray=80,MaxGray=200,Area 下限=200 px² ROI 外扩:码区包围盒四周各留 8~15 px,避免裁掉静区导致解码失败

二、ROI 的建立与传递

ROI 不是一次性画死的矩形,而应作为随产品位姿变化的参数化区域。标准做法是:在标定图像上,用 gen_rectangle1 或 gen_region_polygon 手工框出码区,记录它相对于基准特征(如定位孔中心)的偏移量 (dRow, dCol) 和相对角度 dPhi。生产运行时,先匹配基准得到当前位姿 (Row, Col, Phi),再用 vector_angle_to_rigid 构造刚体变换矩阵,通过 affine_trans_region 把标定 ROI 映射到当前图像,最后 reduce_domain 得到实际处理域。

这样做的价值在于:产品在视野内平移、旋转,ROI 自动跟随,解码算子始终只处理几十到几百像素的小区域,单帧耗时可以从整图搜索的数百毫秒降到十几毫秒量级。需要注意的是,ROI 应比码区包围盒四周各外扩 8~15 像素,因为条码两侧的静区(quiet zone)是解码的必要条件,裁得过紧会直接导致 find_bar_code 返回空。

三、多码、倾斜与部分遮挡的处理

多码场景:若一幅图内有多个相同码,形状匹配的 find_shape_model 会返回多个实例,每个实例带独立的 Row、Col、Angle 和 Score。此时不要只取第一个,而应遍历全部实例,对每个位姿分别做 ROI 变换和解码,并用 tuple_sort 按位置排序输出,保证结果顺序稳定。若多个码内容不同,则应为每种码建立独立模板,或先用 Blob 分割出候选区域再逐一分类。

倾斜场景:倾斜会同时带来旋转和透视形变。纯旋转可由形状匹配的 Angle 参数覆盖,搜索范围建议设为 ±20°(约 ±0.35 rad);若存在明显透视(如相机斜装),则需用 find_shape_model 配合 vector_to_proj_hom_mat2d 做投影变换,或改用 find_planar_uncalib_deformable_model 处理平面形变。倾斜还会让条码的条空宽度在图像上不均匀,因此解码前可考虑用 affine_trans_image 把 ROI 反向旋转回水平,再送入解码器,成功率通常更高。

部分遮挡:遮挡分为“基准被遮”和“码区被遮”两类。基准被遮时,形状匹配的 Score 会下降,此时应降低 MinScore 到 0.5~0.6 并允许部分匹配,或改用多个冗余基准(如两个定位孔,任一可用即可)。码区被遮时,若遮挡比例不大,条码解码器本身有一定纠错能力;若遮挡严重,则应扩大 ROI 搜索范围,尝试多个候选位姿,取解码成功且校验通过的那个结果。

定位策略的核心思想是“用稳定的特征去定位不稳定的码”。基准特征越简单、越对称、越少受光照影响,整个读码流程就越稳。不要试图让形状匹配直接去匹配条码本身——条码的纹理在光照变化下梯度极不稳定,匹配分数会剧烈波动。

四、一个可复用的定位流程示例

* 1. 建立基准形状模型(离线一次)
create_shape_model (ImageReduced, 4, rad(-20), rad(40), 'auto', 'auto', \
                    'use_polarity', 30, 10, ModelID)

* 2. 在线:匹配基准,取位姿
find_shape_model (Image, ModelID, rad(-20), rad(40), 0.7, 1, 0.5, \
                  'least_squares', 0, 0.9, Row, Col, Angle, Score)

* 3. 由标定偏移构造刚体变换,映射 ROI
vector_angle_to_rigid (RefRow, RefCol, RefPhi, Row, Col, Angle, HomMat2D)
affine_trans_region (RefROI, ROICurrent, HomMat2D, 'nearest_neighbor')

* 4. 在 ROI 内解码
reduce_domain (Image, ROICurrent, ImageROI)
find_bar_code (ImageROI, CodeRegion, 'Code128', BarCodeHandle, DecodedStr)

* 5. 校验:字符数、校验位、Score 阈值
if (|DecodedStr| == 0 or Score < 0.7)
    * 触发重定位:放宽 MinScore、扩大角度范围、切换备用基准
endif

这段流程的关键判断点在最后一步:解码失败不等于定位失败。应先检查 ROI 是否真的覆盖了码区(可用 dev_display 叠加显示),再判断是定位偏了还是图像质量不够。若 ROI 正确但解码失败,问题通常在光照、焦距或码本身质量;若 ROI 偏移,则回到匹配参数和基准选择上排查。把这两类原因分开,调试效率会明显提高。

常见误区:把 ROI 设得过大以求“保险”。ROI 越大,背景中与码相似的纹理越容易被误检,同时解码耗时线性上升。正确做法是让 ROI 刚好覆盖码区加静区,靠定位精度而不是靠范围冗余来保证稳定性。

总结来说,ROI 管理的本质是把“找码”和“读码”解耦:用形状匹配、模板匹配或 Blob 分析中最适合当前成像条件的那一种去稳定地找到基准,再由基准推导出码区,最后在受控的小区域内解码并校验。多码靠遍历实例,倾斜靠角度搜索与反向校正,遮挡靠冗余基准与多候选验证。把这套策略固化下来,读码率才能从“偶尔成功”变成“连续稳定”。