1. 先搞清楚这套光学字符识别例程到底在解决什么问题Halcon 的光学字符识别也就是大家嘴上说的 OCR在工业视觉里从来不是一个孤立的读字模块。它更像是整条检测链路里最末端的一环前面要有稳定的成像、准确的定位、干净的分割最后才轮到分类器去认字。我见过太多项目一开始就把希望寄托在换一个更强的分类器上结果图像里字符边缘都糊成一团换十次模型也没用。这套例程真正的价值是把从一张图到一串字符串的完整路径摊开给你看让你知道每一步该用什么算子、参数为什么这么给、哪一步出了问题该往哪里查。这篇文章面向三类人刚接触 Halcon 视觉算子、想拿 OCR 例程当入门模板的初学者手里已经有 OCR 项目在跑、但识别率卡在 95% 上不去的现场工程师还有做方案选型、需要判断这个活儿到底该用传统分割加分类器还是直接上深度学习文本识别的技术负责人。我会把例程背后的设计思路、关键算子的作用、参数的计算方式、以及那些文档里不会写但现场一定会踩的坑全部摊开讲清楚。你完全可以照着后面的流程把一套自己的字符识别方案从头搭起来。需要先明确一个认知Halcon 的 OCR 例程不是一个能直接用的成品,它是一组思路的集合。官方给的那几个示例覆盖的是不同难度层次的问题——从印刷体、高对比度、字符规整的理想场景到喷码、点阵、低对比度、字符粘连的恶劣场景用的完全是两套东西。你要是拿高对比度印刷体的参数去怼点阵喷码识别率能到 60% 都算你运气好。所以读例程的正确姿势是先判断自己的场景落在哪个难度区间再去对应那个例程里抄它的处理链路而不是把所有例程的算子拼在一起。1.1 从一次典型的误判说起OCR 在链路里的真实位置我印象比较深的一次是某五金件产线上的批次码识别。字符是激光打标在金属表面的人眼看着很清楚但相机拍出来一塌糊涂字符区域反光、背景有刀纹、字符本身只有 8 到 10 个像素高。当时第一版方案直接套了工业通用字符集分类器识别率大概 70%而且错得很规律——数字 8 认成 6数字 0 认成 8。这个错误模式本身就是线索不是分类器不认识字而是它在低分辨率下根本没法区分这些形状相近的字符图像信息量不够模型再强也变不出来。这个案例说明了一件事OCR 的识别率上限在图像采集完成的那一刻就已经被钉死了。后面所有的算法工作只是尽量逼近这个上限而不是突破它。所谓字符高度至少 20 像素这类经验值背后是分类器输入尺寸和笔画可分辨性的硬约束。你给分类器一个 10 像素高的字符它内部缩放一次、特征提取一次笔画结构早就被抹平了。所以在看例程的时候我对第一步的理解永远是它不只是教你调用do_ocr_multi_class_mlp而是示范了一套图像质量评估 → 预处理增强 → 分割 → 归一化 → 分类的标准链路。这条链路上任何一环崩了最终结果都会崩而且崩的方式各不相同。分割出错表现为漏字、多字、字符粘连归一化出错表现为整体识别率均匀偏低分类器训练不足表现为特定字符对反复混淆。学会从错误模式反推故障环节比记住算子名有用得多。1.2 三条技术路线传统分割分类、文本行检测、深度 OCRHalcon 在 OCR 这块其实给了三条不同年代、不同思路的路线读例程时必须先分清自己看的是哪一条否则会越看越乱。第一条是传统路线二值化或阈值分割把字符从背景里抠出来用形态学、连通域分析、形状筛选把字符区域整理干净再送进.occ分类器文件做分类。核心算子是threshold、connection、select_shape、sort_region加上do_ocr_multi_class_mlp或do_ocr_multi_class_cnn。这条路对图像质量要求高但只要图像够好速度快、资源占用低、结果可解释至今仍是产线上大批量字符检测的主力。第二条是文本行检测路线用create_text_finder建立文本模型find_text直接在灰度图上框出文本行和单个字符再交给分类器或直接输出结果。它的优势是不依赖严格的二值化阈值对背景不均、光照渐变的场景鲁棒性明显更好。这条路适合字符排列规整、有明确行结构的场景比如标签、包装盒、面板丝印。第三条是深度 OCR 路线create_deep_ocr建立深度模型apply_deep_ocr一次性完成检测加识别很多情况下不需要你手工分割字符。这条路在近几年的版本里才成熟对低对比度、字符变形、复杂背景的容忍度最高代价是推理速度慢、对硬件有要求、模型文件体积大而且可调参数少——出问题了你只能从数据端想办法不像传统路线能精确控制每一步。三条路线不是替代关系是适用边界不同的工具。我的判断顺序通常是先看图像质量能不能用传统方法稳定分割能就用传统路线因为它最好调、最好维护不能再看是不是光照或背景问题是的话试文本行检测都不行再上深度 OCR。反过来一上来就用深度模型往往会发现速度顶不住或者部署环境没有合适的加速卡。1.3 为什么不能直接套用现成分类器选型的取舍逻辑Halcon 自带一堆.occ分类器文件命名基本按字符集和应用场景划分比如覆盖数字加大小写字母的通用型、只覆盖数字的工业型、针对特定印刷规范的专用型。这些文件拿来就能用但能用和够用是两回事。现成分类器的训练样本来自它自己的字体库跟你的实际字体大概率对不上。印刷体差别不大还能凑合一旦碰上点阵喷码、激光打标、针式打印、手写体识别率会掉得很厉害。我一般的做法是先用现成分类器跑一遍把全部测试样本的识别结果导出来统计混淆矩阵。如果错误集中在少数几对字符上且整体识别率在 90% 以上那可以靠预处理和参数微调硬扛如果错误分散、识别率低于 85%基本可以判定字体不匹配必须自己做样本重训。重训的成本主要在样本采集和标注不在训练本身。一个字符集 36 类0-9 加 A-Z每类至少 30 到 50 个样本才算稳也就是一千多张小图人工标注大概要半天到一天。这笔投入在项目前期看起来很重但比后期在产线上天天救火划算得多。这里有个经验样本要覆盖你现场会遇到的所有劣化形态包括不同批次的打标深浅、不同角度的轻微倾斜、不同位置的镜头畸变、以及脏污遮挡。只采理想样本训出来的分类器上线第一天就会给你惊喜。2. 核心算子与预处理链路拆解这一节讲的是为什么这么写。例程里那些看起来随手加的emphasize、scale_image、gray_opening每一个都有明确的意图删掉任何一个都可能让识别率掉几个点。我按处理顺序把链路拆开讲你可以对照自己的图像判断哪一步需要保留、哪一步可以省。2.1 图像质量决定上限预处理链怎么搭预处理的唯一目标是让字符和背景的差异尽可能大同时不破坏字符的拓扑结构。注意后半句很多人只盯着对比度用各种锐化把边缘拉得特别硬结果细笔画断裂、圆孔闭合字符结构都变了分类器自然认不出来。一条比较通用的预处理链是这样的read_image (Image, ocr_input) rgb1_to_gray (Image, GrayImage) * 局部对比度增强窗口取字符笔画宽度的 2 到 3 倍比较合适 emphasize (GrayImage, Enhanced, 7, 7, 1) * 轻度平滑压掉打标颗粒噪声同时保住笔画 gauss_filter (Enhanced, Smoothed, 1.5) * 灰度拉伸把有效动态范围铺满 0-255 scale_image (Smoothed, Scaled, 2.0, -100)emphasize的参数是最容易被乱给的。它的签名是emphasize(Image, ImageEmphasize, MaskWidth, MaskHeight, Factor)前两个尺寸决定什么尺度算细节。如果字符笔画宽度是 3 像素掩膜尺寸给 7 左右刚好给 21 就会把整个字符当成细节去增强反而把背景噪声一起放大。Factor我一般从 1 开始试不超过 2超过之后噪声会明显抬头。gauss_filter的尺寸同理跟笔画宽度挂钩。笔画 3 像素sigma 给 1 到 1.5给 3 以上细笔画直接糊掉。这一步很多人觉得平滑会损失细节就跳过实测下来对激光打标、喷码这类有颗粒噪声的场景轻微高斯平滑带来的收益远大于损失。还有一种情况是背景光照不均比如弧形金属件中间亮两边暗。这时候全局阈值一定失效要用local_threshold或者先做背景估计再相减。例程里通常会给一个illuminate或者用大窗口mean_image估背景再sub_image的思路本质都是把低频光照分量扣掉保留高频的字符信息。这一步做完二值化的阈值就可以给得比较宽鲁棒性明显提升。注意预处理链不是越长越好。每加一步都会引入新的参数也就多一个可能调错的地方。我的原则是先只做灰度化加全局阈值跑一遍看漏检和误检分布再针对性加步骤。盲目堆五六步预处理最后出问题你根本不知道是哪一步导致的。2.2 字符分割的四种打法与适用边界分割是传统路线里最考验经验的部分。例程里能看到的基本是这四类打法我按从简单到复杂排一下。第一类全局阈值加连通域。最经典threshold拿到前景connection拆连通域select_shape按面积、高度、宽度筛掉噪声和粘在一起的块。适用条件是字符和背景灰度分布分离得很干净比如白底黑字、黑底白字的印刷标签。select_shape的阈值不要凭感觉给用region_features把候选区域的特征值全打出来看分布字符区域的高度通常集中在一个很窄的区间一眼就能看出该卡在哪里。第二类动态阈值。dyn_threshold配合一个mean_image做局部参考适合背景有渐变或者字符对比度不一致的情况。关键参数是Offset它决定比局部均值亮多少才算字符给小了噪声全进来给大了细笔画丢掉。取值一般从 5 到 15 之间试具体看图像噪声水平。第三类形态学辅助分割。针对字符粘连先用erosion_rectangle1或opening_rectangle1把粘连处腐蚀断再连通域再用dilation_rectangle1把字符补回来。结构元素的宽高要跟粘连方向匹配——横向粘连就用横向窄的结构元素去切。这个过程很像切年糕刀口位置不对就会把字切坏。第四类文本行检测。用create_text_finder加find_text让算法自己去找文本区域。这条路省掉了大量调参工作代价是对字符尺寸变化和极端倾斜的适应性一般需要给对min_contrast之类的参数。create_text_finder ([min_contrast,min_char_height], [25, 18], TextModel) find_text (Image, TextModel, TextResultID) get_text_object (Characters, TextResultID, character) get_text_result (TextResultID, class, Class)分割完成后一定要做排序。sort_region用character模式配合row或column把区域按阅读顺序排好否则分类器输出的字符串顺序是乱的后处理拼字符串时会出大问题。这个坑我踩过区域没排序输出的字符串字都对但顺序随机查了半天才定位到。2.3 分类器家族对比MLP、SVM、CNN 与深度 OCRHalcon 提供的分类器主要有几类选哪个要看你的约束条件。分类器类型典型算子速度抗噪能力训练成本适用场景MLP 多层感知机do_ocr_multi_class_mlp快中等低样本量要求适中印刷体、规整字体、大批量在线检测SVM 支持向量机do_ocr_multi_class_svm中等较好中等对样本质量敏感字符类别多、类间差异小的场景CNN 卷积网络do_ocr_multi_class_cnn中等偏慢好较高需要较多样本变形字符、低对比度、复杂背景深度 OCRapply_deep_ocr慢最好无需自训直接调用检测加识别一体、难例场景MLP 是例程里出现频率最高的原因是它训练快、样本需求不大、推理开销低。它的输入是归一化到固定尺寸的字符图像所以对字符的尺寸一致性有隐式要求——如果你的字符高度在 18 到 25 像素之间波动MLP 内部缩放后特征会有差异识别率会受影响。解决办法是在分割后统一做crop_domain加尺寸归一化把字符缩放到分类器训练时使用的尺寸附近。CNN 的优势在于它对形变和小幅位移不敏感卷积核本身就在做局部特征提取。代价是训练样本需求大而且推理速度明显慢于 MLP。产线节拍紧的时候这个差别可能是能不能用的分界线。我的经验是单张图字符数在 50 个以内、节拍要求 300ms 以上CNN 可以接受如果一张图几百个字符、节拍要求 100ms那就老老实实用 MLP 加好的预处理。深度 OCR 是另一条路它的调用方式非常简洁create_deep_ocr ([language,auto], DeepOcrHandle) apply_deep_ocr (Image, DeepOcrHandle, auto, auto, DeepOcrResult) get_deep_ocr_result (DeepOcrResult, words, Words) get_deep_ocr_result (DeepOcrResult, Words, word, WordText)它把检测和识别合成一步你不需要做二值化和分割。但要注意它的输出结构和传统算子完全不同拿到的是词级别的结果需要自己再做拆解和排序。另外它对模型文件版本有要求部署前一定要确认目标机器的运行环境里模型文件齐全否则会在初始化那一步直接报错。2.4 字符集与拒识设置Rej 与 NoRej 的坑.occ文件名里带NoRej和不带的区别是新手上手时最容易忽略的一个点。NoRej表示不拒识——分类器必须从字符集里挑一个最像的给出来哪怕输入根本不是字符。不带NoRej的版本允许输出无法识别。这个差别在实际项目里影响巨大。比如你只关心数字输入的图里混进了字母或者污点用NoRej版本污点会被强行认成某个数字产生误报用可拒识版本它会给你一个拒绝标记你可以据此判断这一帧无效。反过来如果你的场景是每一帧都必须有输出宁可错也不能空那NoRej更合适。字符集本身也要根据实际场景裁剪。全字符集 36 类数字加字母比纯数字 10 类的误识概率高得多因为类间距离更近。如果业务上只可能出现数字一定用纯数字分类器别图省事上全字符集。我见过一个项目字符集里有字母 O 和数字 0字体又是那种两者几乎一样的印刷体结果这两个字符的混淆率一直降不下来最后是把字符集裁掉 O 才解决的。拒识阈值这一块MLP 分类器支持设置拒识参数低于置信度阈值的结果会被标为无效。这个阈值的设定要用验证集来定不能拍脑袋。我的做法是把验证集所有样本的置信度打出来看正确识别和错误识别的置信度分布找一个能切掉大部分错误、又不会误杀太多正确结果的点。通常这个点在 0.6 到 0.8 之间具体看场景容忍度。3. 从零跑通一套工业 OCR完整实操流程前面讲的是是什么和为什么这一节讲怎么做。我按一个真实项目的推进顺序走一遍从光学参数反推到 ROI 定位到分类器训练到调参验证。中间会插入具体的参数计算你可以对照自己的场景替换数值。3.1 硬件与光学参数反推先算清楚再买设备这一步最容易被跳过也最不该跳过。字符识别的成像参数是可以反推出来的算清楚之后选相机和镜头就有依据了不会出现买完发现字符只有 8 个像素这种事故。反推的核心逻辑是先定字符在图像里需要多少像素高再反推系统分辨率再反推相机和视野。假设场景是视野宽度 80mm字符实际高度 3mm希望字符在图像里至少 30 像素高。那么系统需要的分辨率是 30 / 3 10 px/mm也就是每毫米对应 10 个像素。视野宽度 80mm对应图像宽度至少 800 像素。选一台 1600×1200 的相机约 200 万像素水平方向分辨率 1600 / 80 20 px/mm字符高度变成 3 × 20 60 像素。这个数值非常充裕甚至有点浪费——60 像素高的字符送进 MLP内部还要缩放反而引入了额外的重采样损失。更合理的配置是让字符高度落在 25 到 40 像素这个区间。按 30 像素算需要 10 px/mm按 1200×900 的相机算水平分辨率 1200 / 80 15 px/mm字符高 45 像素还是偏大。这时候可以减小视野或者降相机规格。提示字符高度不是越大越好。超过 50 像素之后识别率的提升非常有限但处理时间线性增加而且分类器内部的缩放会引入额外的插值误差。30 到 40 像素是个甜点区。除了分辨率还有两个参数必须一起考虑。对比度字符与背景的灰度差经验上要大于 40 个灰度级低于这个值二值化会非常难调只能靠局部阈值或深度方法。景深与畸变如果字符分布在不同的高度平面上要确保整个范围落在景深内否则边缘字符必然发虚。畸变则影响字符尺寸一致性广角镜头在视野边缘的字符会明显变形这对 MLP 这类对尺寸敏感的模型是硬伤。光源的选择同样关键。金属表面的激光打标用同轴光往往能把字符和背景拉开平面印刷用环形光或条形光做暗场可以让字符边缘产生阴影对比曲面工件要考虑光斑均匀性必要时用圆顶光。这一步的投入产出比极高光源选对了后面的算法工作量能减一半。3.2 ROI 定位与字符区域提取把搜索范围收窄整图直接做 OCR 是下策除了慢还会引入大量干扰。正确做法是先定位到字符所在的 ROI再在 ROI 内做分割。ROI 定位的方法取决于工件是否有固定的定位特征。有 Mark 点或者固定边缘的用形状匹配find_shape_model或者find_line找基准然后按相对偏移crop_domain或reduce_domain出 ROI。没有明显特征的可以用 blob 分析找字符团的包围盒比如先做一次粗分割把候选字符区域合并成一个大区域shape_trans成矩形再把这个矩形向外扩一点作为 ROI。* 定位基准 find_shape_model (Image, ModelID, -0.1, 0.1, 0.7, 1, 0.5, least_squares, 0, 0.9, Row, Column, Angle, Score) * 按基准做仿射变换把 ROI 摆正 vector_angle_to_rigid (Row, Column, Angle, RowRef, ColumnRef, 0, HomMat2D) affine_trans_region (RoiRegion, RoiAligned, HomMat2D, constant) reduce_domain (GrayImage, RoiAligned, RoiImage)摆正这一步很重要。字符整体倾斜 3 度人眼看着没感觉但送进分类器后字符的笔画方向变了对 MLP 这种不具旋转不变性的模型识别率会明显下降。用text_line_orientation可以直接估计文本行的倾斜角再用rotate_image转正比自己写角度检测省事。ROI 确定后把区域外的灰度统一置成背景值能显著减少后续分割的干扰。reduce_domain保留的是区域内的像素区域外在做threshold时不会参与这一招很实用。3.3 分类器训练样本制作与参数设置决定自训之后流程大概是采样本 → 分割出单字符 → 存成图片并标注类别 → 生成训练文件 → 调用训练算子 → 保存.occ文件 → 测试。样本采集要覆盖真实分布。我的做法是连续采 100 到 200 张现场图覆盖不同时间段考虑光源老化、不同批次考虑打标深浅、不同位置考虑镜头畸变。然后把分割链路跑一遍把分割出的字符区域全部导出来。这一步会顺带暴露分割的问题——如果某些字符分割不出来说明预处理或分割参数还需要调正好在训练前解决掉。导出样本时文件名带类别标签比如A_001.png、7_023.png。Halcon 的训练文件就是一个文本清单每行对应一个文件名和它的类别名。这一步用 OCR 助手做会快很多它提供可视化的样本管理、标注、训练和测试界面可以实时看到混淆矩阵比纯脚本调试效率高得多。训练参数里要重点关注几个字符归一化的目标宽高、是否使用颜色信息、隐藏层节点数、预处理方式。目标宽高要跟你实际字符的宽高比接近不要硬拉成正方形否则字符被拉伸变形特征就变了。隐藏层节点数从默认值开始试样本量大可以适当增加但加太多会过拟合表现为训练集识别率很高、验证集上不去。* 建立并训练 MLP 分类器参数名以本地版本算子参考为准 create_ocr_class_mlp (WidthChar, HeightChar, constant, default, CharSet, NumHidden, default, NumComponents, NumClasses, OCRHandle) trainf_ocr_class_mlp (OCRHandle, TrainingFile) write_ocr_class_mlp (OCRHandle, my_ocr.occ)训练完成后一定要在独立验证集上测不能用训练样本自测。验证集要包含训练时没见过的批次和位置。测试的时候不要只看总体识别率要看混淆矩阵——哪几对字符在互相混淆这个信息决定了你下一步该补样本还是改预处理。比如 8 和 B 混淆说明你的字体里这两个字符确实接近要么补更多这类样本要么在业务层做规则约束。3.4 调参与验证记录把每次改动都量化调参最怕的是凭感觉改、改完不知道有没有变好。我的习惯是建一个固定的验证集每次改动都跑一遍记录总体识别率、各字符的识别率、误识率、以及处理耗时。版本预处理分割方式分类器总体识别率单张耗时v1仅灰度化全局阈值通用 MLP88.2%42msv2加 emphasize 高斯全局阈值通用 MLP93.5%55msv3同上动态阈值通用 MLP94.1%68msv4同上动态阈值自训 MLP98.7%66ms这张表是我某次项目的真实记录能看出几个规律预处理带来的提升最大5 个点分割方式改进只有 0.6 个点自训分类器又贡献了 4.6 个点。这个排序说明什么说明瓶颈在图像质量和字体匹配不在分割算法。如果一开始就去死磕分割投入产出比会很低。耗时也要一起看。v3 比 v2 慢了 13ms收益只有 0.6 个点如果节拍紧张这一步完全可以砍掉。工程决策永远是在效果和成本之间找平衡点不是把每个指标都拉到最高。4. 常见问题排查实录与避坑清单这一节是我这些年攒下来的排查经验按症状分类。你可以把它当速查表用遇到问题先对号入座再往下深挖。4.1 识别率上不去的排查路径识别率低是最常见的抱怨但低的原因差别很大。我一般按这个顺序排查第一步看错误分布。把错识的样本全部导出来按字符分类统计。如果错误集中在少数几类是字体或样本问题如果错误均匀分散在所有字符上是图像质量或归一化问题如果错误跟位置相关比如只有图像边缘的字符错是光学畸变或景深问题。第二步看分割结果。把分割出的字符区域可视化出来肉眼检查有没有粘连、断裂、多切、漏切。这一步能解决一半以上的问题。粘连会导致两个字被当成一个分类器输出一个字符字符串长度就错了断裂会导致一个字被切成两块输出两个字符。这两种情况的错误模式很好认。第三步看归一化后的字符。把送进分类器之前的字符图打出来看是不是糊了、变形了、对比度不够了。这一步经常能发现预处理参数给过头的问题——比如高斯滤波 sigma 给到 3细笔画已经连成一片了。第四步才轮到怀疑分类器。换一个字符集更匹配的、或者自训一个对比看提升多少。下面这张表是我整理的常见症状与对策症状可能的根因排查手段对策特定字符对反复混淆字体中形状接近、样本不足看混淆矩阵补该字符样本或业务层加规则约束整体识别率均匀偏低归一化尺寸不匹配、预处理过度看归一化后的字符图调整目标宽高减小滤波强度漏字阈值过高、字符对比度不足看二值化结果降阈值、改局部阈值、加光源多字一字拆成多个字符断裂、笔画细看连通域形态学闭运算连接、提高分辨率字符串顺序错乱未做区域排序看区域输出顺序加sort_region边缘字符识别差镜头畸变、景深不足分区统计识别率缩小视野、换远心镜头换批次后识别率骤降打标工艺变化、光源老化跨批次对比测试补样本重训、光源做闭环监控4.2 现场踩过的坑文档里不会写的经验坑一忘了处理字符集里的形近字。有个项目的字符集里同时有数字 1、字母 I、字母 l字体又是那种三者几乎一样的设计。分类器再强也没法从图像上区分它们。最后的解决办法不是改算法是在业务层做规则约束——根据编码规则某个位置只可能是数字那就把这个位置的识别结果限制在数字类里。这种用业务先验压缩解空间的思路往往比调算法有效得多。坑二训练样本和实际输入的分辨率不一致。采样本的时候用的是调试相机上线换了一台分辨率更高的字符在图像里大了一倍。分类器内部会缩放但缩放带来的插值差异导致识别率掉了 6 个点。教训是样本采集必须用最终上线的相机和镜头或者至少保证字符像素高度一致。坑三忽略了对sort_region的排序基准。多行字符的场景按row排序会把不同行的字符混在一起。正确做法是先用partition_rectangle或者按行聚类把行分开行内再按列排序。这个坑我在两个项目里都踩过每次都是查输出字符串顺序不对才发现的。坑四光源衰减没做监控。一套系统上线跑了半年识别率从 99% 慢慢掉到 92%查了半天算法没找到原因最后发现是光源亮度衰减了 15%导致对比度下降。后来加了一个背景区域的灰度均值监控低于阈值就报警提示更换光源。这种缓慢劣化类的问题靠单次调试是发现不了的必须做在线监控。坑五过度依赖单一置信度阈值。一开始我把置信度低于 0.7 的结果全部判为无效结果发现大量正确识别也被拒了。原因是不同字符的置信度分布本身就不一样有些字符天生置信度就低。后来改成按字符分别设阈值或者用相对置信度第一候选和第二候选的差值来判断效果好了很多。注意所有阈值类参数都要用验证集的数据分布来定不能凭经验拍。把置信度、区域高度、对比度这些量的分布画出来看一眼比调十次参数都管用。4.3 版本迁移与长期维护的几点建议Halcon 版本升级时OCR 相关的算子签名和分类器文件格式偶尔会变。我在做版本迁移时的做法是先把旧版本的.occ文件在新版本里加载测试一遍确认兼容再把整条链路跑一遍验证集对比识别率有没有变化最后才动参数。不要一升级就顺手把参数也调了出了问题分不清是版本还是参数导致的。分类器文件要纳入版本管理。自训的.occ文件跟样本集是绑定的样本更新了模型就要重训所以两者要一起存档标注清楚训练日期、样本来源、验证集识别率。我见过有团队把模型文件随手放在工程目录里换人接手后完全不知道这个模型是什么时候、用什么数据训的只能重新来一遍。还有一点是性能。OCR 链路里的耗时大户通常是预处理和分割不是分类。如果节拍不够优先优化这两块能用reduce_domain收窄范围的就不要整图处理能用整数运算的就别用浮点能一次connection搞定的别反复做形态学。分类器这块MLP 已经很快了换成更轻的模型收益有限。最后提一下可解释性。产线上的操作人员不关心你用了什么模型他们只关心为什么这个件被判为不良。所以输出结果时除了字符串本身最好把每个字符的区域、置信度、以及判定依据一起存下来。出了问题能回溯这比识别率高 0.5 个点更有价值。我自己在实际项目里体会最深的一点是一套能长期稳定运行的 OCR 系统靠的不是某个特别强的算法而是每个环节都留了观测点出问题能快速定位到具体是哪一环。这个习惯比记住多少算子都重要。
阅读完成 · 觉得有帮助?