工业踩坑实录(十七):从40分到高分:工业零件OCR,通用模型一上来就给我打脸
从40分到高分:工业零件OCR,通用模型一上来就给我打脸
工业零件上印一行字,你以为直接丢给OCR就能认。现实是,通用模型跑上去,准确率四十来分,跟瞎猜差不多。
2026-05-08 更新:发这篇文章之前收到一条私信提醒,说公众号「开麦视觉AI」有篇文章跟本系列的内容高度相似。我去看了下,第一性原理那篇、SOP状态机那两篇,核心框架、技术路线、案例细节都跟我的文章对得上,还标了原创。感谢提醒。
说明这些内容还是被认可的,有人愿意花时间读、花时间写,本身就是一种肯定。不过还是想友情提醒一下:借鉴可以,标原创不太合适。创作不易,互相尊重。
关于作者
我接触视觉整整10年。
工业视觉、烟草、煤矿等行业都有深度开发经验。从硬件选型、算法开发、模型训练,到上位机开发及部署,都在一线磨过。
之前是多家公司人工智能团队的技术负责人。现在自己创业了,还在继续做视觉落地这件事。
作者说
这个故事发生在学校。
当时有个比赛,任务是识别工业零件上的文字。零件上印着型号、批次号、生产日期、参数规格这些信息,要求算法自动识别出来,跟标准答案比对打分。
这活儿我一看就觉得简单。OCR嘛,成熟的很,开源框架一大堆,直接调接口跑不就完了。
第一次提交,得分四十来分。
四十来分是什么概念?一百个零件,六十个认错或者认不出来。你以为你在做OCR,实际上你在参加一个"比随机猜测强多少"的测试。
这个成绩把我打醒了。我花了一个多星期折腾,从通用OCR调参到自己搭一套检测+校正+识别的流水线,最后把分数提上去了。不是我算法多牛,是我终于理解了——工业零件上的OCR,跟文档OCR根本不是同一个问题。
回头看,这段经历对我后来的工作影响很大。后来做工业项目里但凡涉及OCR的场景,我第一反应都是"先定位再识别",再也不敢直接把整张图丢给OCR了。
这是第十七篇。
开场:工业零件上的文字,跟你想的不一样
先说说工业零件上的文字长什么样。
日常接触的OCR,识别的是文档、名片、路牌、车牌这些东西。背景干净、字体标准、排列规整、光照均匀。这种场景,任何一个成熟的OCR框架都能处理得很好。
工业零件不一样。
零件表面的文字来源有很多种:有的是钢印打上去的,字符凹下去或者凸起来;有的是激光打标,一束激光灼烧金属表面留下的痕迹;有的是喷码机喷上去的墨水字符;还有的是丝网印刷或者蚀刻。每种工艺产生的文字外观都不一样。
钢印字的边缘有金属挤压的痕迹,笔画粗细不均匀;激光打标的字颜色偏浅,对比度低,有些材质上几乎看不清;喷码的字容易模糊,还可能被油污覆盖。更头疼的是,零件表面本身的材质——金属反光、铸铁粗糙表面、塑料光滑表面——都会严重干扰识别。
而且文字不一定是水平的。很多零件形状不规则,文字跟着零件表面的弧度走,可能是倾斜的、弯曲的、甚至倒着的。
你拿一个通用OCR框架去识别这种图,它训练数据里根本没见过这种样本,能认出四十来分已经算它尽力了。
第一个坑:通用OCR为什么在工业零件上拉胯
比赛官方给的baseline方案是Tesseract。Tesseract基于LSTM,在通用场景下效果不错,但它是为文档OCR设计的,对工业字符的容忍度很低。
直接调接口,整张零件图丢进去,让它自己找文字区域再识别。
结果四十来分。
我开始分析原因。问题出在哪呢。
背景太复杂。OCR框架的文字检测模块是训练来识别"文档里的文字行"的。文档的文字行有什么特征——背景是白色或浅色的,文字是深色的,对比度高,排列整齐。但工业零件的照片里,背景是金属表面、油污、划痕、加工纹理,文字检测模块根本不知道该把哪个区域当成文字。
光照不均匀。金属表面的反光是OCR的噩梦。你拿手机拍一个金属零件,照片里总有一块特别亮的高光区域。高光区域里如果有文字,直接被白光淹没了。就算文字不在高光区域里,周围不均匀的光照也会让同一个字符的不同部分亮度差异很大,OCR模型提取到的特征是破碎的。
文字非标准。通用OCR模型的训练数据主要是印刷字体——宋体、黑体、Arial这些。钢印、激光打标、喷码这些工业字符的形态跟印刷字体差别很大。钢印字有挤压变形,笔画边缘不规则;激光打标字可能只有灰度变化没有明显的黑白边界;喷码字可能晕染扩散。OCR模型没见过这些样式,自然认不出来。
还有一个很容易忽略的问题:字符太小。很多零件上印的型号或批次号,字符高度只有十几个像素。在这种分辨率下,OCR模型根本提取不到足够的特征来做分类。
所以通用OCR在工业零件上拉胯,不是模型不行,是场景不匹配。你让一个只会做阅读理解的模型去做野外生存题,它当然做不好。
第二个坑:文字检测是OCR的灵魂,但普通检测框不够用
意识到通用OCR不行之后,我开始拆解问题。
OCR本质上两步:先检测文字在哪,再识别文字是什么。第一步不准,第二步再强也没用。所以我决定先把文字检测做好。
当时常用的文字检测方案有两种:EAST模型和四向分类器。EAST在处理长文本时效果不理想,经常截断;四向分类器更粗糙,不管文字实际角度是多少,都只能按0、90、180、270四个方向硬分。
我用了一个主流的文字检测模型,输出的是水平的矩形框。它能告诉你"图里这一块区域有文字",框出来给你。
这个方案听起来合理,但在工业零件上遇到了一个致命问题:文字经常是歪的。
零件表面的文字不一定水平排列。有的文字跟着零件的弧面走,有的是倾斜打印的,有的是零件本身摆放就不水平。水平矩形框能框住文字,但框进去的是一块倾斜的、包含大量背景的矩形区域。你把这个区域裁出来交给识别模型,识别模型面对的是一坨歪着的文字加上一圈无用的背景噪声。
识别模型怎么训练的?它训练数据里的文字全是水平的、正的。你突然塞给它一堆倾斜的文字,它当然认不出来。就像你让一个人去读一份倒着拿的报纸,他可能认得出几个字,但准确率肯定大打折扣。
而且水平框的框选精度也成问题。文字是歪的,框是正的,框的四个角必然有一个或两个角偏离文字区域,多框进去的是金属表面的纹理和反光,少框进去的是文字的边缘笔画。检测结果已经失真了,后面的识别再怎么努力都是在失真的输入上做文章。
所以我得换一种检测方式。不是检测"哪里有文字",而是检测"哪里有文字、文字有多长、文字朝哪个方向"。
我选择了YOLO OBB(Oriented Bounding Box)。YOLO OBB是YOLO目标检测的扩展,输出的检测框带有角度信息,通过四个角点加一个旋转角度来精确定位。不管文字朝哪个方向,检测框都能紧贴文字区域旋转。这是当时我觉得最合适的方案——速度快,精度高,还能直接拿到角度值用于后续校正。
第三个坑:旋转框检测+仿射变换,思路对但细节全是坑
我找到了旋转框检测的方案。普通的目标检测模型输出的是正矩形框,但旋转框检测模型输出的框带有角度信息,能够紧贴文字区域,不管文字朝哪个方向都能精确框选。
这个方案解决了文字定位的问题。但定位只是第一步,你还得把歪着的文字变成正的,才能让识别模型正常工作。
注意,这里有一个很容易忽略的中间步骤。你不能直接对整张原图做仿射变换——原图太大了,里面有零件、有背景、有各种干扰。正确的做法是:先用旋转框的坐标从原图上把文字区域抠出来,得到一张只包含文字的小子图。然后对这张子图做仿射变换拉正。这样变换的运算量小,而且不会把原图上其他区域的干扰带进来。
抠图→拉正,两步不能省,顺序也不能反。先抠再拉,子图里只有文字和少量周边背景,仿射变换的精度有保障;你要是对整张大图做变换,计算量大不说,坐标转换的累积误差也会更大。
拉正操作用的是仿射变换。原理很简单:你知道文字区域的旋转角度,你就能计算出一种几何变换,把这个歪着的子图映射成一个水平的子图。变换之后,文字就是正的了,识别模型就能正常识别。
思路很清晰,对吧。检测定位→拉正→识别,三步走。
但实际做的时候,每一步都有坑。
坑在旋转框的检测精度。旋转框的角度偏差哪怕只有两三度,变换之后的文字就会有一个轻微的倾斜。这个倾斜对人眼来说可能看不出来,但对识别模型来说足够造成干扰。识别模型是对水平文字训练的,你给它一个倾斜两三度的文字,字符之间的相对位置关系就变了,卷积特征就偏了,准确率就开始掉。
这个问题怎么解决?没有银弹,只能提高检测模型的角度回归精度。多加训练数据、调损失函数权重、用更好的backbone,这些常规操作都得试。另外还有一个取巧的办法:如果文字是水平排列的,你可以约束检测框的角度范围,只在-45度到45度之间回归,这样模型不用学360度的角度,精度自然更高。
坑在检测框的大小。旋转框要紧贴文字区域,不能多也不能少。多框了,拉正之后的图像里就混进了金属表面的纹理、划痕、反光,这些东西会干扰识别;少框了,文字的边缘笔画被切掉,识别模型看到的是残缺的字符。
这个问题的调试过程很折磨人。你得标注很多数据,标注的时候特别小心地让框贴合文字边界,不能松也不能紧。训练完之后还得一个个样本看检测结果,哪里框大了一点哪里框小了一点,来回调整。
坑在仿射变换本身的精度。仿射变换是线性变换,它只能处理旋转、平移、缩放和剪切这几种线性变形。如果零件表面是弯曲的,文字跟着弧面弯曲,仿射变换就拉不直了,拉出来还是弯的。碰到这种情况,可能需要用透视变换或者更复杂的非线性变换。
好在我当时做的比赛数据集里,文字基本都是平面的,最多就是倾斜,没有严重的弯曲。所以仿射变换够用了。
标注工具的坑。文字检测模型的训练需要大量标注数据。我用的是X-AnyLabeling,一个支持旋转框标注的开源工具。标注流程是用快捷键"O"创建旋转形状,然后用"zxcv"四个键微调角度——z大角度逆时针,x小角度逆时针,c小角度顺时针,v大角度顺时针。手动标完几百张之后,我把训练好的模型导出成ONNX格式加载到X-AnyLabeling里做自动标注,再手动修正检测结果。半自动的方式比纯手动快了三倍以上。
角度标准化的弯路。标注过程中我注意到,比赛数据的文本角度分布很不均匀,大部分集中在0度和90度附近。我一开始想了个"聪明"的办法:把所有角度离散化成8个分类(0、45、90、135、180、225、270、315度),写了个脚本批量转换标注文件里的角度值。训练完发现效果很差,因为这种硬分类丢掉了角度的连续性信息——35度的字被强制归到0度,和真正的0度字混在一起训练,模型学不到精细的角度区分。最后放弃了这个方案,直接用连续角度回归,效果反而更好。
第四个坑:识别模型对工业字体的适应问题
文字拉正了,可以交给识别模型了。这里我没有用Tesseract,而是选择了CRNN+CTC架构——CNN提取图像特征,RNN处理字符之间的序列关系,CTC解码输出最终文本。
选CRNN不选Tesseract的原因很具体:Tesseract基于LSTM,训练数据以文档场景为主,对工业字符的适应性差;CRNN的CNN部分可以提取更深层的图像特征,RNN部分能捕捉字符间的上下文关系,CTC解码不需要预先做字符分割。对于工业零件上长度不一、样式各异的文字串,CRNN的端到端能力更合适。
CRNN在标准印刷字体上的效果很好,但放在工业字符上,又得调。
钢印字符。钢印字的特点是笔画边缘有挤压变形。你看一个钢印的"A",它不是印刷体那种干净的三角形加横杠,而是三角形的两个边可能不太对称,横杠的两端可能有点歪,整个字符看起来有一种"被砸过"的感觉。CRNN的卷积核是按标准字体的边缘特征训练的,钢印的变形边缘会让特征提取产生偏差。
激光打标字符。激光打标的字,有时候对比度非常低。你肉眼能看出来这里有个字,但把它裁成小图丢给模型,模型可能觉得这只是一片灰蒙蒙的区域。这种情况下需要做图像增强——提高对比度、做二值化处理,让字符和背景的界限更清晰。
喷码字符。喷码的字最容易模糊,尤其是当喷嘴堵塞或者喷码距离不合适的时候。模糊的字符笔画会扩散、粘连,"3"和"8"分不清,"6"和"9"分不清,"1"和"7"的分不清。这种情况下识别模型的容错能力就很关键了。
我的做法是用工业场景的字符数据对CRNN做微调。好在工业字符的种类有限——主要是数字、大写英文字母和少量特殊符号,字符集不大。手工标注几百张零件图片上的文字,用这些数据微调模型,效果提升非常明显。
lmdb数据集制作的坑。CRNN训练需要lmdb格式的数据集,不是普通的图片文件夹。用官方的脚本把标注好的文本图像转成lmdb时,我踩了三个坑。
第一个坑,磁盘空间。脚本的默认配置是把lmdb数据库大小设为1TB。我本地磁盘没那么多空间,直接报错。查了半天才发现是map_size参数的问题,改成10G就行。看起来是个配置问题,但报错信息是乱码(Windows中文编码问题),根本看不出是磁盘空间不足。
第二个坑,字符过滤。比赛数据里的文本包含各种特殊符号,有些符号不在CRNN的字符集字典里。训练的时候遇到未知字符,loss直接变成NaN,整个训练就崩了。我写了个脚本把所有不在字典里的字符筛掉,重新生成干净的训练集。
第三个坑,epoch数。脚本里默认写了1亿轮。是的,你没看错,1亿。我当时没注意这个参数,训练启动后发现要跑到什么时候,一算发现按我当时的GPU速度得跑好几天。赶紧停掉改成一个合理的数字。看起来很低级的错误,但当你连续调了一天参数、脑子已经转不动的时候,这种问题就是容易忽略。
这里有一个经验值得分享:微调的时候不要只用正确样本,也要加一些困难样本。比如反光严重的、模糊的、油污覆盖的。模型只在干净样本上训练的话,到了现场遇到脏数据还是会崩。故意加一些噪声样本进去,模型的鲁棒性会好很多。
那条路到底是什么
把整个过程串起来,我最终用的方案是四步流水线。
第一步,旋转框检测。用一个旋转框目标检测模型,在整张零件图上找到所有文字区域的位置和角度。输出的是每个文字区域的旋转bbox和角度值。这一步解决的是"文字在哪、文字朝哪"的问题。
第二步,抠图。根据检测到的旋转bbox,从原图上裁出文字区域的小子图。这一步解决的是"把文字从复杂的零件背景中分离出来"的问题。原图上可能有反光、油污、加工纹理,全部留在背景里不要带进后续流程。
第三步,仿射变换校正。根据检测到的角度,对抠出来的子图做仿射变换,把倾斜的文字拉正。这一步解决的是"文字是歪的"这个问题。
第四步,CRNN识别。把拉正后的文字子图送进微调过的CRNN模型,输出识别结果。这一步解决的是"文字是什么"这个问题。
四步各管各的,每一步只解决一个具体问题,不指望一步到位。这个分工的思路看起来朴素,但效果比直接用一个端到端模型好得多。
为什么?因为工业场景太复杂了,你没法用一个模型同时学好"找到文字"、"拉正文字"和"识别文字"这三件事。每个任务的最佳优化方向不一样:检测任务需要关注位置的精确性和角度的准确性,校正任务需要关注几何变换的精度,识别任务需要关注字符特征的区分度。把这些任务拆开,每个模块专注做好一件事,整体效果反而更好。
当然,这个方案的代价也很明显。三步流水线意味着三个模型要维护、三个环节可能出错、三个地方要调参。任何一个环节出问题,最终结果都会受影响。工程复杂度比直接调一个OCR接口高了很多。
但你想拿高分,就得付这个代价。
一些细节上的教训
关于图像预处理。仿射变换之前做不做预处理,对最终结果影响很大。我试过在变换前做直方图均衡化和对比度增强,效果有好有坏。对比度增强能帮助反光严重的区域看清文字,但也会放大噪声。最后我的经验是:预处理要做,但要保守。轻度增强就够了,别把图像搞得面目全非。
关于抠图的像素扩展。旋转框裁剪文字区域的时候,如果框紧贴文字边缘,容易把字符的笔画切掉。我后来在每个方向多加了10个像素的边距,识别准确率立刻涨了一截。这个trick很简单,但很管用——多出来的10个像素是空白背景,不会干扰识别,但能保证字符的边缘笔画完整。
关于字符分割。有些零件上的文字是连续排列的,比如"ABC123"这样的型号。旋转框检测会把这一整串文字框成一个区域,CRNN能处理这种序列识别,但你得确保框选的起止位置准确。框多了一个字符、少了一个字符,识别结果可能就完全不一样了。
关于后处理纠错。识别结果出来之后,可以加一层简单的后处理逻辑。比如如果识别结果是"AB3D",但你知道这个零件的型号格式是"ABC"开头后面跟数字,那"D"很可能是个误识别的"0"。这种基于业务规则的纠错,有时候比提升模型精度更直接有效。
回头看:为什么一开始就错了
文章写到现在,回头复盘一下。
我一开始犯的最大错误,是把工业OCR当成了文档OCR。我默认了"OCR框架就是做文字识别的,丢进去一张有文字的图,它应该能认出来"。这个默认假设在文档场景下是对的,在工业场景下是错的。
文档OCR和工业OCR的区别,不在于模型架构的差异,在于输入数据的分布差异。文档里的文字是干净的、标准的、均匀排列的;工业零件上的文字是脏的、非标准的、随意摆放的。你拿在"干净数据"上训练出来的模型去处理"脏数据",效果差是必然的。
正确的做法是:先理解你的数据长什么样,再选择或者训练合适的模型。这个道理放在任何机器学习任务里都成立,但在工业场景里特别重要,因为工业数据的"脏"程度经常超出你的想象。
我的第二个错误是低估了工程细节的重要性。仿射变换的角度精度、检测框的大小控制、图像预处理的强度,这些看起来不起眼的细节,每一个都能让准确率波动好几个百分点。学术界的人可能觉得这些是"工程技巧"不值得写论文,但在实际项目里,这些细节决定了你的方案能不能用。
不是总结的总结
如果你正在做或者即将做工业零件的OCR识别,我把关键的坑点列一下。
别直接用通用OCR。工业零件上的文字跟文档文字是两个物种,通用模型在工业场景下的准确率可能低到让你怀疑人生。先用通用模型跑一版baseline,看看实际效果,你就知道问题的严重性了。
检测比识别更重要。文字区域找不准、角度测不对,后面的识别模型再强也白搭。把精力放在检测上,旋转框检测是工业OCR的标配,别用水平框凑合。
拉正这一步不能省。识别模型是对水平文字训练的,你给它歪的输入,它就给你歪的输出。仿射变换或者透视变换,根据实际文字的变形程度选择。
识别模型要微调。工业字符的样式(钢印、激光打标、喷码)跟标准印刷体差别大,用工业场景数据微调一个识别模型,投入产出比很高。
工程细节决定成败。角度精度、框选精度、预处理参数、后处理规则,这些细节加起来对准确率的影响可能比换一个更好的模型还大。
打包部署的坑。模型训练完了,我用pyinstaller把整个推理程序打包成exe。在开发机上测试没问题,发到另一台3090的机器上一跑——卡死。排查了半天,最后发现是pyinstaller打包的OpenCV版本和目标机器上的CUDA驱动不兼容。工业项目里,"训练环境能跑"和"部署环境能跑"之间永远隔着一条沟,别指望一次打包就能通。
最后说一句可能不太好听的话:工业OCR没有银弹。每一个现场都是不同的,每一个零件表面的文字都有自己的特点。你不可能做一个通用的工业OCR系统卖给所有客户,你只能针对具体场景做适配。这个适配的过程,就是工业视觉工程师的核心价值。
四十来分到高分,差的不是算法,是对场景的理解。
作者:头帕王子
系列专栏:工业视觉踩坑实录
如果觉得有用,点赞关注不迷路 👋
如果你也在做类似的工业视觉项目,希望这篇文章能帮你少走些弯路。有问题欢迎留言或加我好友讨论。
📎 相关专栏
- 工业视觉踩坑实录
