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