资讯动态

Halcon MLP金属激光刻号OCR识别与训练调优实践

发布时间:2026/10/25 3:37:39 来源:尧图企业网站定制
金属件表面那串激光打出来的号码人眼扫一眼就完了交给相机和算法却常常要磨很久。金属表面激光刻号这件事真正的难点从来不在识别两个字上而在它前面那一长串环节反光压不住、刻痕深浅不一、字符边缘带熔渣、换一批材料对比度全变。这篇内容就围绕在 Halcon 里用 MLP 分类器把刻号认成字符串这条链路展开把成像、分割、样本、训练、后处理、现场排查一段一段拆开讲。凡是做过金属件追溯、零部件打码、工序流转登记这类活的人看标题大概就知道我说的是什么痛点刚接触 Halcon OCR 的朋友也能顺着流程把一套能跑的方案搭起来。1. 为什么金属激光刻号这件事拍照容易识别难1.1 激光刻号在图像里到底长什么样先把刻号这个东西在像素层面描述清楚后面所有参数才有依据。激光在金属表面打标本质是局部高温烧蚀形成几十微米深的凹坑凹坑底部是粗糙的熔融重凝组织边缘常有翻边和毛刺。这种结构在图像上呈现三种典型形态低角度暗场照明下凹坑边缘产生强烈的散射亮线字符看起来像用亮笔勾了个轮廓同轴光照明下凹坑底部粗糙面反射弱字符整体发暗、背景发亮混合光下则是有明有暗、对比度极不均匀。麻烦在于这三者会随材料、粗糙度、氧化层厚度同时变化。同一台设备、同一套参数打在不锈钢上清楚换到铸铝件上字符就糊成一片。更头疼的是刻号往往打在非平面区域——轴类件的圆柱面、法兰的倒角边、冲压件的过渡圆角字符沿曲面分布宽高比随视角线性变化靠左的字符被压扁靠右的被拉长。所以别指望一套预处理走天下。真正稳定的做法是把成像先做扎实固定工作距离、固定入射角、加偏振片压镜面反射、必要时用多帧不同曝光的图做合成让刻痕的对比度在整幅图里尽量一致。这一层没做好后面分类器再训练也是白搭因为你喂给它的样本本身就是噪声。1.2 直接拿现成OCR字体文件为什么会翻车Halcon 自带一批训练好的 OCR 分类器比如工业字符集那一类很多人第一反应是直接 read_ocr_class_mlp 读进来do_ocr_multi_class_mlp 一调能出结果就收工。做印刷体、喷码、标签的场景这么干确实爽放到金属刻号上往往识别率掉到六成以下。原因不复杂。现成分类器的训练样本是标准印刷字体笔画干净、边缘锐利、灰度分布集中激光刻号的字符笔画是凹槽边缘是散射亮边加阴影同一个字母在不同工件上的灰度直方图能差出一大截。更别说点阵式打标的字符是由一个个离散小坑组成的中间有断点跟连续笔画完全是两种形态。分类器没见过这种分布只能靠猜。还有一个隐蔽的坑极性。Halcon 的 OCR 分类器默认按暗字符、亮背景训练。如果你的打光方案让字符发亮直接送进去识别率会莫名其妙地低而且低得没有规律。转成暗字符再送或者训练时就统一极性两种做法都行关键是训练和推理必须一致。1.3 MLP在这个链路里解决的是哪一段问题把整条链路摆开看成像 → 校正 → 字符区域提取 → 单字符分割 → 归一化 → 分类 → 字符串后处理。MLP 只负责分类这一段输入是一个已经切好的、尺寸归一化的单字符小图输出是它属于哪个字符类别以及置信度。这个定位很重要因为它意味着两件事。第一分割质量直接决定识别上限字符框切歪了、粘连了、缺笔画了MLP 再强也救不回来第二MLP 的抗干扰能力来自样本覆盖度而不是网络本身有多深。一个 600 输入、60 隐藏、36 输出的三层网络在字符样本足够多样的前提下识别率能干到 99% 以上样本单一的话网络再大也是死记硬背。我倾向把 MLP 当成最后一道裁量来用前面用几何规则和形态学把能确定的事情先确定掉只把真正需要判断的模糊字符交给它。这样既省算力出问题时也容易定位是哪一段崩的。2. 把图变成能喂给分类器的东西前段链路设计2.1 打光与取图的几个硬要求打光方案基本决定了项目成败这不是夸张。金属刻号常用的三种打法低角度环形暗场、同轴光、条形光斜照。低角度环形暗场是首选入射角压到 10 到 20 度光打在刻痕边缘的翻边上产生强散射字符轮廓像被描了边。缺点是同一幅图里字符亮度差异大需要靠后续算法拉平。同轴光适合镜面件刻痕底部粗糙面反射弱、背景镜面反射强字符呈暗色对比度反而稳但对平面度要求高稍微有点弧度就一块亮一块暗。条形光斜照适合圆柱面沿母线方向打光能把整排字符压在一个亮度带上。几个容易被忽略的硬要求工作距离和角度必须机械固定别指望人手摆放。哪怕偏差 5 毫米暗场照明的亮度梯度就会让阈值参数失效。曝光时间宁短勿长产线上工件在动运动模糊会让字符边缘糊掉而边缘恰恰是暗场照明下信息最集中的地方。加偏振片。金属表面镜面反射是偏振保持的刻痕散射光偏振态被破坏用起偏加检偏能把背景反射压掉一大截字符信噪比提升明显。ROI 先框死。如果工件有定位特征先做形状匹配找基准把图像仿射到标准位后面所有阈值和区域参数才敢写死。2.2 校正、增强与字符区域的稳定提取拿到图之后第一步不是二值化是校正。相机镜头畸变用标定参数做径向校正工件位姿用形状匹配做仿射对齐两步做完字符区域就落在固定 ROI 里后面的处理量能砍掉七成。增强环节我用得最多的是gray_bothat也就是黑帽变换。原理是闭运算先填掉图像里的小暗坑再减去原图剩下的正好是那些比周围暗的小结构——激光刻痕恰好符合这个特征。它对背景亮度梯度不敏感比直方图均衡化稳定得多。dev_update_off () read_image (Image, mark_sample_01) get_image_size (Image, Width, Height) rgb1_to_gray (Image, Gray) * 黑帽变换突出比邻域暗的细小结构刻痕 gray_bothat (Gray, GrayBot, 11, 11) * 二值化黑帽结果中刻痕呈高亮 threshold (GrayBot, RawRegion, 28, 255) * 连通域拆分 connection (RawRegion, ConnRegions) * 按高度筛不用面积筛避免把 1、I 这类窄字符滤掉 select_shape (ConnRegions, CharCand, height, and, 20, 90) select_shape (CharCand, CharCand, width, and, 6, 80) select_shape (CharCand, CharCand, area, and, 40, 3000) * 合并后再拆防止单个字符被拆成两段笔画 union1 (CharCand, CharUnion) closing_rectangle1 (CharUnion, CharClosed, 3, 2) connection (CharClosed, CharRegions) * 按行、从左到右排序 sort_region (CharRegions, SortedChar, character, true, row)这段代码里有两个细节值得说。一是筛选条件用 height 而不是 area因为窄字符的面积可能只有宽字符的三分之一用面积卡下限会把 1 筛掉二是先 union1 再 closing 再 connection刻号常出现笔画断裂先合并能避免一个字符被切成两块。结构元素尺寸 11×11 是根据字符笔画宽度估计的实践上取字符平均宽度的 0.8 到 1.2 倍之间效果最好太小去不掉背景纹理太大字符会被糊在一起。2.3 单字符归一化这一步做错后面全白干字符切出来了下一个问题是怎么送进分类器。MLP 的输入层是固定维度的所以每个字符都得缩放到统一尺寸这是硬约束。归一化有两个方案各有取舍。方案一直接拉伸到固定宽高比如统一缩放到 20×30。优点是简单、速度快、分类器内部实现像create_ocr_class_mlp就是这么干的缺点是破坏了宽高比1 被拉宽、W 被压扁不同字符的形变程度不一样。样本量足够大时这个问题会被吸收掉因为每类字符都是同样的形变方式。方案二按高度等比缩放 居中补边保持字符原始宽高比多余区域用背景灰度填充。保真度高但同一类字符的填充量随宽度变化反而引入新的类内差异。我一般先用方案一出问题再考虑方案二。但不管选哪个有三件事必须做第一极性统一。字符是亮底暗字还是暗底亮字整个样本库和推理链路必须一致。如果不一致用complement或invert_image翻转过来。第二字符框要贴紧笔画但不能切边。外接矩形smallest_rectangle1比最小外接矩形稳但遇到倾斜字符会带入大量背景。可以用shape_trans转成rectangle1之后再crop_domain保证边界整齐。第三灰度范围归一化交给分类器自己处理。Halcon 的 MLP 支持normalization预处理会自动把输入特征映射到固定区间这个参数是存在分类器内部的推理时会自动套用同样的变换。所以千万别在外部再手工拉伸一遍双重归一化会让训练和推理的分布对不上这是新手最常见的翻车点之一。3. 样本库怎么攒决定识别率上限的苦活3.1 每类多少张、怎么分布才够用字符类别数先定下来。刻号一般包含 0 到 9 十个数字加上 26 个大写字母再加工位符、分隔符总数在 36 到 40 之间。每个类别 80 到 150 张样本是个比较舒服的起点类别少于 30 张时置信度分布会很难看经常出现真字符置信度 0.6、错误字符置信度 0.7的情况。比数量更重要的是分布。样本要覆盖实际会遇到的成像变化具体说就是这几项都要有变化维度建议覆盖范围不覆盖的后果亮度至少 3 档曝光换批次材料后整体识别率下降对比度从清晰到接近不可辨认弱刻痕字符被系统性误判旋转角度±5 度字符轻微倾斜即失效尺度基准的 0.9 到 1.1 倍工件摆放距离波动导致失败粘连程度含少量粘连样本现场粘连字符频繁报错磨损/污渍含油污、轻微划伤样本脏件直接拒识很多项目失败的根因就在这——样本全是从同一批完美工件上采的验收时漂亮上线三个月后材料换了供应商识别率断崖式下跌。3.2 用OCR助手和脚本两条路批量产样本样本制作有两条路我两条都会用。第一条是用 Halcon 自带的 OCR 训练助手。它的价值在于可视化你能一边标注一边看字符的归一化效果发现分割不对的样本当场剔除。流程是把所有样本图读进去算法自动切出候选字符人工输入每个字符的真实值最后导出 trf 训练文件。这个方式的效率在最初几百张样本时是最高的。第二条是脚本化生成。先把确定能自动分割的样本跑一遍批量流程把每个字符区域和类别按文件名规则存盘比如A_0001.png、7_0032.png。类别从文件名前缀解析这样样本库天然带标签后续扩样本只是往文件夹里扔文件。* 批量切片把每张整图切成单字符小图并保存 list_files (sample_imgs, [files,follow_links], ImageFiles) tuple_regexp_select (ImageFiles, [\\.(png|bmp|tif)$,ignore_case], ImageFiles) CharIdx : 0 for I : 0 to |ImageFiles| - 1 by 1 read_image (Img, ImageFiles[I]) rgb1_to_gray (Img, GrayImg) gray_bothat (GrayImg, GrayBot, 11, 11) threshold (GrayBot, RawRegion, 28, 255) connection (RawRegion, ConnRegions) select_shape (ConnRegions, CharCand, height, and, 20, 90) select_shape (CharCand, CharCand, width, and, 6, 80) count_obj (CharCand, N) for K : 1 to N by 1 select_obj (CharCand, OneChar, K) reduce_domain (GrayImg, OneChar, CharImg) crop_domain (CharImg, CharCrop) CharIdx : CharIdx 1 write_image (CharCrop, png, 0, samples_raw/unknown_ CharIdx$04d) endfor endfor切完之后人工过一遍把文件名里的unknown_改成真实字符值。这一步没法省除非你能拿到刻号生成规则去反推。样本扩增环节可以做但要有节制。旋转 ±3 度、缩放 0.92 到 1.08、亮度偏移 ±10 灰度级、轻微高斯模糊这几样是安全的。别做大幅度的仿射扭曲和对比度反转那样造出来的样本跟真实分布偏离太远反而干扰训练。3.3 训练集与验证集划分与样本管理样本攒到一定程度一定要分训练集和验证集。比例 8:2 或者 7:3 都行关键是验证集要按工件批次划分不能按字符随机划分。按字符随机分的话同一个字符在训练集和验证集里可能来自同一张图验证结果虚高上线就现原形。具体做法把采集的原始整图编号按 8:2 分两个文件夹训练集只从文件夹 A 切验证集只从文件夹 B 切。这样验证集里的字符跟训练集在成像条件上是独立的测出来的识别率才有参考价值。样本库要当资产管。每次现场换材料、换光源、换镜头都补一批新样本进去版本号递增。我吃过这个亏——早期没做版本管理后面想回退到识别率最高那版分类器发现样本早被覆盖了只能重采。4. MLP结构与训练参数算清楚再动手4.1 输入层、隐藏层、输出层节点数怎么定输入层节点数 归一化宽 × 归一化高。取 20×30 就是 600 个输入。这个尺寸不算大优点是训练快、泛化好缺点是笔画细节丢失对 8 和 B、6 和 G 这种形近字符可能不够分。如果现场这两组经常混把尺寸提到 24×36864 输入会有改善。再大就没必要了20×30 已经能保住主要笔画结构。隐藏层节点数是最容易拍脑袋的参数。有个经验公式可以参考隐藏层 ≈ sqrt(输入节点 × 输出节点)按 600 输入、36 输出算得到 sqrt(21600) ≈ 147。这只是上限参考实际用 40 到 80 就够原因是字符分类的特征维度没那么高隐藏层太大只会记住训练样本的噪声。隐藏层节点训练时间训练集准确率验证集准确率结论20快97.2%96.5%略欠拟合50中99.6%98.9%推荐起点80中99.9%99.1%可用200慢100%97.8%已过拟合输出层节点数 类别数这个没什么可调的。类别顺序要固定下来跟字符映射表一一对应后面解析结果全靠这张表。4.2 学习率、动量、迭代次数的实操取值这里有个很多人不知道的事Halcon 的 MLP 训练用的是自适应步长算法不需要你手工设学习率和动量。train_class_mlp和trainf_ocr_class_mlp的签名里压根没有这两个参数只有三个MaxIterations最大迭代次数WeightTolerance权重变化容差小于这个值认为收敛ErrorTolerance误差容差所以别在参数表里到处找学习率找不到的。实测取值参考* 通用分类器路线 train_class_mlp (MLPHandle, 300, 0.0001, 0.001, Error, ErrorLog) * OCR 分类器路线 trainf_ocr_class_mlp (OCRHandle, mark_train.trf, 300, 0.0001, 0.001, Error, ErrorLog)MaxIterations给 200 到 500 之间给太少没收敛给太多浪费时间且容易过拟合。WeightTolerance0.0001 左右比较合适ErrorTolerance0.001 到 0.01具体看ErrorLog的回落曲线。训练完Error值一般能落到 0.01 以下如果停在 0.1 以上说明样本类别严重不均衡或者特征里有冲突得回头查样本。4.3 训练过程监控与过拟合判断训练完必须看ErrorLog。它是每次迭代的误差记录正常形态是前 30 次快速下降之后缓慢收敛。如果曲线来回震荡通常是对比的类别有冲突样本——同一个字符的样本里既有清晰的也有极度模糊的网络不知道该往哪边靠。如果曲线一直平着不动检查特征值是不是全 0常见于区域提取失败crop 出来是空白图。过拟合的判断靠验证集。训练集 100%、验证集 96%这种差距可以接受训练集 100%、验证集 88%就是背题了。处理办法不是减网络是加样本多样性——把验证集里错的那些样本挑出来看它们属于哪种成像条件然后针对性补 50 到 100 张同类样本重新训练。另外一个实用技巧训练前把样本顺序打乱。add_sample_class_mlp是按添加顺序存的如果样本库是按类别存的文件夹顺序读进来的训练初期会长时间只看到前几类收敛路径会跑偏。5. 识别、置信度与字符串后处理5.1 置信度门限与Top-N的使用方式识别调do_ocr_multi_class_mlp返回的Class是字符串元组Confidence是对应的置信度元组。read_ocr_class_mlp (mark_mlp.omc, OCRHandle) do_ocr_multi_class_mlp (SortedChar, Gray, OCRHandle, Class, Confidence) * 拼接成完整字符串 CodeStr : MinConf : 1.0 for I : 0 to |Class| - 1 by 1 CodeStr : CodeStr Class[I] if (Confidence[I] MinConf) MinConf : Confidence[I] endif endfor置信度怎么用这里有个经验绝对置信度不如信度差好用。所谓信度差就是第一候选和第二候选的置信度之差。一个字符如果 top1 是 0.62、top2 是 0.58虽然 top1 过了 0.6 的门限但两个候选咬得这么紧基本可以判定是模棱两可应该走拒识重拍。反过来 top1 0.55、top2 0.15差值 0.4反而是可靠的高置信判断。用通用分类器路线classify_class_mlp时可以直接要 Top-N* Num 2 表示返回前两个候选 classify_class_mlp (MLPHandle, FeatureVector, 2, ClassIdx, ConfTop) * ConfTop 里是前两名的置信度做差判断 ConfGap : ConfTop[0] - ConfTop[1] if (ConfGap 0.25) * 置信度不足标记为可疑触发重拍或人工复核 endif门限值不是拍出来的是拿验证集跑一遍统计正确样本和错误样本的置信度分布找一个分界点。一般正确样本的置信度集中在 0.9 以上错误样本散在 0.4 到 0.7 之间中间空出一段。门限就设在那段空白里。5.2 易混字符纠错与业务规则校验金属刻号里最容易混的几组字符按出现频率排易混组合混淆原因纠错手段0 / O形状几乎相同若编码规则是纯数字直接强制转 01 / I / l窄笔画字符差异极小用字符宽度辅助判断5 / S上半部曲线相似检查顶部横笔是否水平延伸8 / B右侧两个封闭环看上下环大小是否一致2 / Z转折角度相似看左上角是否有小回转6 / G左侧闭合加右侧横笔看右下横笔长度能靠业务规则解决的绝对不要交给分类器。刻号如果遵循固定格式比如两位年份 两位月份 四位流水号 一位校验位那就把格式写成正则或者位置判断在每个位置上限定允许的字符集。第一位只可能是数字那这个位置的分类结果直接在数字候选里取最大值某一位只可能是字母同理。这一招能把形近字符的错误率砍掉七成以上。更狠一点的做法是加校验位。如果编码里有校验位比如模 10 或模 36 校验识别完先算一遍校验不通过就整串判为可疑直接重拍。这比逐字符纠结置信度有效得多。5.3 多分类器投票和拒识策略对识别率要求极高的场景可以上多分类器组合。Halcon 里 MLP、KNN、SVM 都有对应的分类器接口同一批字符样本分别训练三个推理时三个都跑多数票胜出。* 三分类器投票的骨架 classify_class_mlp (MLPHandle, FeatureVector, 1, ClassMlp, ConfMlp) classify_class_knn (KNNHandle, FeatureVector, 1, ClassKnn, ConfKnn) * SVM 走 classify_class_svm * 三个结果取众数全不一致时判拒识这么做代价是推理时间翻两三倍一般只在低速工位用。高速产线上更实际的做法是分级拒识所有字符置信度都高于高门限 → 直接放行有字符落在中间区间 → 允许重拍一次取两次结果中置信度高的重拍后仍在中间区间 → 报可疑转人工任何字符低于低门限 → 立即停机报警这套策略的价值在于把系统性错判转成可控的拒识。产线最怕的不是偶尔报一次可疑而是悄悄把 8 认成 B 写进数据库三个月后追溯发现全是错的。6. 现场排查实录与稳定性优化6.1 常见问题速查表现场遇到的毛病基本逃不出这十几种我整理成表方便对照。现象大概率的真实原因处理方向整体识别率 60% 左右且无规律字符极性与训练样本相反统一极性后重训特定字符系统性认错该类样本太少或分布单一补该类样本至 100 张以上换批次材料后识别率骤降表面反射率变化对比度整体偏移补新批次样本或加自适应对比度拉伸字符被切成两半笔画断裂闭运算没兜住加大闭运算结构元素或先 union 再切相邻字符粘连字符间距小于笔画宽度改用投影法或等宽分区切分边缘字符识别错ROI 边界裁掉了部分笔画扩大 ROI或按整行定位后统一切置信度普遍偏低归一化尺寸与训练不一致检查缩放尺寸和插值方式训练误差降不下去样本类别冲突或特征全零查样本图是否空白剔除矛盾样本白天正常夜间报错环境光干扰加遮光罩改用脉冲光源同步触发长时间运行后变慢句柄未释放、图像累积循环内 clear_obj分类器只加载一次6.2 节拍与内存的优化手段整套流程跑一遍如果每次都从头二值化全图速度会很难看。几个能立刻见效的优化第一ROI 收缩。找到工件基准后把字符区域裁到一个固定的小窗口里后续所有算子只在这个窗口上跑。图像面积小一个数量级耗时直接降到零头。第二分类器只加载一次。read_ocr_class_mlp放在循环外句柄全程复用。有人写成每次识别都 read 一遍那是把分类器的反序列化开销吃满速度慢得没道理。第三能不用 MLP 的地方统统不用。如果字符是等宽等距排列的点阵打标常见直接用partition_rectangle按固定宽度切比连通域分析快十倍而且不会出现粘连问题。代价是需要精确定位这就要靠前段的形状匹配兜底。第四多线程处理。Halcon 的不少算子支持多核set_system里可以配置线程数二值化、形态学这类像素级运算并行收益明显。但注意别把线程数开到满超线程有时候反而拖慢。第五可疑字符才重拍。全图重拍的成本是一次曝光加一次传输比重新处理一整幅图便宜得多。把重拍限制在置信度不达标的样本上平均节拍基本不受影响。6.3 我在这个项目里踩过的几个坑最后一个坑值得单独拎出来说因为它隐蔽性极强训练样本用write_image存成了 png读回来的时候灰度量化和原来不完全一致。png 是无损的但保存时如果做了类型转换比如从 real 转 byte读数会掉精度。表现就是训练时验证集 99%现场跑 92%。后来统一改成存 bmp 或者直接用 trf 文件把区域和图像一起存问题消失。第二个坑是归一化尺寸的插值方式。zoom_image_size默认是最近邻字符边缘会出现锯齿同一类字符的锯齿形态随缩放比例变化等于给每张样本加了随机噪声。改成bilinear之后验证集准确率提升了一个多点。这个参数在很多例程里被忽略但换到小尺寸字符上影响很实在。第三个坑跟极性有关。现场为了压反光把光源角度调了一下结果字符从暗变亮识别率瞬间崩盘。当时排查了半天以为是分类器坏了最后发现是极性反了。以后凡是调整光路第一件事就是重新看一眼字符的明暗关系。第四个坑是分类器的类别索引和字符映射表错位。训练时字符顺序是按文件夹列表读的识别时映射表是手工敲的两边差了一位导致所有 A 都被输出成 B。这种错误在置信度上看不出来因为每一类都是 0.99。后来改成训练和推理共用同一份从 trf 文件读出来的CharacterNames杜绝人工手打。还有一条经验别指望一次训练用到老。金属刻号这东西受材料批次影响太大我的做法是每次换批次就先跑 50 个工件做验证识别率低于 97% 就补样本重训把分类器当成耗材来管理反而最省心。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑