news 2026/9/30 5:26:46

YOLOv5训练数字识别:从数据集标注到ONNX部署实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLOv5训练数字识别:从数据集标注到ONNX部署实战

数字识别听起来像是OCR领域的老题目,但真到工业现场的电表读数、快递面单编号、仪表盘数值、仓库货架标签这些场景里,你会发现一个很尴尬的事实:现成的通用OCR方案在规整印刷体上表现还行,一旦遇到倾斜、模糊、光照不均、数字被遮挡的情况,识别率就直线往下掉。而YOLOv5这条路子给了另一种解法——先在训练阶段让模型学会"哪儿是数字",再在推理阶段把检测框按位置排序拼成完整数值串。这套YOLOv5训练+数字识别的组合,最大的价值在于它把"切分"这个OCR里最头疼的环节直接省掉了,网络自己就帮你把每一个字符框出来了。这篇内容适合两类人看:一类是刚接触目标检测、只跑过官方coco示例、想拿yolov5训练自己的数据集但不知道从哪下手的新手;另一类是做仪表读数、票据编号识别,手上有几千张图但一直在用传统方法硬扛的工程师。我会把环境配置、labelImg打标、data.yaml编写、超参数调整、训练日志解读、检测结果拼接、ONNX导出这一整条链路讲透,中间该踩的坑一个都不藏。

1. 为什么拿YOLOv5来做数字识别

1.1 数字识别的三条技术路线,最后为什么选了检测

做数字识别,市面上大致有三条路。第一条是传统图像处理加模板匹配,先二值化、再腐蚀膨胀、再连通域分割、最后拿每个字符的图去和模板比对。这条路在理想条件下又快又准,但换个字体、换种光照就全面崩盘,维护成本高到离谱,我见过一个项目组为了调那几十个阈值参数,前后折腾了三个月还没稳定。第二条是端到端序列识别,像CRNN、CTC那一套,输入一行数字图像,直接输出字符串。它的优点是能利用上下文信息,缺点是必须先做行检测和透视校正,而且训练数据要成行的文本,标注方式和小样本场景不太兼容。第三条就是目标检测思路,把0到9这十个数字当成十个类别,让YOLOv5去检测,每个数字出一个框和一个类别,最后按坐标排序拼接。

我最后选第三条,理由非常实际。一是标注门槛低,labelImg框一下就完事,不需要写任何字符级别的对齐脚本,几十张图就能跑通全流程。二是天然解决粘连,OCR最怕的就是"12"两个字挨太近被切在一起,而检测模型是把每个数字当独立目标学出来的,挨着也能分开。三是可扩展性强,你今天做数字识别,明天要加个"小数点"或者"负号"类别,改一下标注和类别数重新训练就行,架构不用动。四是可解释,模型输出的是一个个带坐标的框,出错了你能一眼看出是漏检还是分类错了,而不是对着一个黑箱输出瞎猜。

代价也要说清楚:检测模型对极端密集的小目标不友好,一串20位的数字如果分辨率不够,后几个数字可能就糊成一团了;另外单字识别没有上下文约束,理论上"1338"可能被识别成"1383",只是坐标排序能兜住这个问题。所以这条路的适用边界是:数字数量在个位数到十几位之间、字符之间有基本间距、图像分辨率够用。超出这个边界就得上切片推理或者两阶段方案。

1.2 主干选型:为什么小模型反而够用

很多人第一次跑yolov5训练自己的数据集,下意识就去下最大的权重,觉得模型越大效果越好。数字识别这个任务上,这个直觉是错的。YOLOv5官方给了n、s、m、l、x五个尺寸,参数量从1.9M到86M不等。数字识别的分类边界非常简单——就是十个形状差异明显的字符,没有复杂的语义歧义,真正难的是"定位"而不是"分类"。所以yolov5n和yolov5s完全够用,甚至更好。

我做过对比,同一个数据集、同样200个epoch,yolov5s的mAP@0.5能到0.98左右,yolov5x大概是0.985,差距在噪声范围内。但推理速度上,yolov5s在同样的GPU上大约快三到四倍,模型体积从170MB降到14MB,导出onnx之后更小。如果你要在jetson nano或者树莓派这类边缘设备上跑,小模型几乎是唯一选择。反过来,大模型在数字任务上还容易过拟合,尤其是数据集只有一两千张的时候,验证集loss会先降后升,泛化反而变差。

还有个细节是输入分辨率。数字是小目标,640的输入尺寸下一串数字可能每个字符只有十几个像素,特征图到P3层(stride 8)时几乎没了。这时候与其加大模型,不如加大输入尺寸,比如训练时用--img 960或者1280,让每个字符在特征图上多占几个像素。计算量是按面积增长的,640到960大概是2.25倍,但收益通常比换大模型明显。我的经验是先把分辨率顶到显存允许的上限,再考虑模型尺寸,这个顺序别搞反。

1.3 这套方案能落地到什么场景

数字识别检测方案的应用面比很多人想的宽。最常见的三个方向:一是电力、水务、燃气行业的老式机械表计读数,摄像头对着表盘拍一张,识别出整串数字,省掉人工抄表。这类场景的难点是反光和表盘倾斜,需要在采集端先做补光和一次透视校正。二是物流面单和仓储货架标签,主要识别运单号、货位号,特点是数字串长、背景杂乱,但光照相对可控。三是工业产线上的字符喷码,比如生产日期、批次号,这类场景对速度要求高,通常要配合yolov5部署到边缘盒子,做实时推理。

除了数字,同样的架构稍微改改就能做别的字符识别。我见过有人拿它做车牌号识别里的字符定位,检测出省份汉字和字母数字,再用一个小分类网络做细化;也见过用同一套流程做化学式下标、数学公式编号的提取。核心逻辑是通用的:只要你的目标是"在一张图里找出若干个独立的、类别有限的短字符,并还原它们的顺序",这套检测加排序的方法就能直接套。

2. 环境搭建与数据集准备

2.1 环境配置:一步一坑的依赖安装

yolov5环境配置这件事,说难不难,说简单也真能卡住人。先说结论:Python版本别追新,3.8到3.10之间最稳,我一般用3.9。PyTorch版本要和你的CUDA驱动匹配,CUDA 11.8的驱动装torch 2.x基本没问题。如果你只有CPU,能跑通但训练速度会慢到让你怀疑人生,一个2000张图的数据集跑200轮可能要两三天,建议至少找一张8GB显存的卡,或者用云端的按小时计费实例。

具体步骤我按顺序列一下,注意每一步的意义:

# 1. 拉代码,建议指定版本而不是直接用最新的main git clone https://github.com/ultralytics/yolov5 cd yolov5 git checkout v7.0 # 2. 建独立虚拟环境,别污染系统Python conda create -n yolo python=3.9 -y conda activate yolo # 3. 先装PyTorch,一定要去官网生成对应你自己CUDA版本的命令 # 假设CUDA 11.8 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 4. 再装其余依赖 pip install -r requirements.txt

注意:requirements.txt里也写了torch,如果第3步已经装好了,第4步执行完可以再跑一次pip install torch torchvision覆盖回去,否则有可能被换成CPU版本。装完务必用python -c "import torch; print(torch.cuda.is_available())"验证一下,返回True才算成功。

为什么强调先装torch再装requirements?因为pip在解析依赖时如果发现版本冲突,会自作主张降级,把GPU版换成CPU版是常见现象。这个坑我至少踩过三次,每次都是训练启动后看到速度特别慢才发现。

2.2 数据采集与标注:labelImg怎么标才不出错

数据这块,我的建议是先拿200张图把流程跑通,再考虑扩到2000张以上。手写数字识别数据集像MNIST那种是28x28的居中单字符,直接用价值不大,因为它没有"一张图里多个数字"的空间分布信息。你需要的是自己的场景图,用手机或者摄像头拍都行,重点是覆盖不同光照、角度、背景和数字组合。

标注工具用labelImg就够了,安装很简单:pip install labelImg然后命令行输入labelImg启动。启动后一定要先把左上角的格式切到"YOLO"模式,再开始标。这里有个新手最容易犯的错:默认是PascalVOC模式,标完导出的是XML,格式完全不一样,还得写脚本转换。切到YOLO之后,每次新建标注会提示你输入类别名,输入"0",然后继续。类别名用字符串"0"到"9",不要用中文,不要带空格。

标注时有几个实操原则。第一,框要贴紧字符边界,不要留太多空白,也不要把字符边缘切掉,边缘切掉会丢失笔画信息。第二,同一个数字在不同图里框的松紧程度尽量一致,否则模型学到的尺度分布会很乱。第三,小数点建议单独设一个类别,命名成"dot",因为它在图像上就是一个很小的点,混在数字里模型很难学,独立成类反而更容易。第四,模糊到你自己都认不出来的图直接删掉,别硬标,模型学不到东西还会拉低整体指标。

提示:labelImg有个快捷键流非常好用,W新建框、A上一张、D下一张、Ctrl+S保存。熟练之后一张图两三秒就能标完。标之前把所有图片放在一个文件夹里,用Open Dir打开,Change Save Dir指向同一个目录(YOLO格式会生成同名txt),这样管理最省心。

2.3 YOLO标签格式拆解与批量校验脚本

标完之后你拿到的是每张图对应的一个txt文件,每行五个数:class_id x_center y_center width height。这里的关键是后面四个值全部是归一化到0到1之间的相对坐标,不是像素值。举个例子,一张1280x720的图,某个数字框的左上角在(320, 180),右下角在(360, 220),那么它的x_center是(320+360)/2/1280=0.2656,y_center是(180+220)/2/720=0.2778,width是(360-320)/1280=0.03125,height是(220-180)/720=0.05556。写成一行就是3 0.2656 0.2778 0.0313 0.0556。

这个格式一旦搞错,训练时会报一堆奇怪的错,或者loss直接不下降。所以标完一定要跑一个校验脚本:

import os from pathlib import Path label_dir = Path("datasets/digit/labels") bad_files = [] class_count = {} for txt in label_dir.rglob("*.txt"): with open(txt, "r") as f: lines = [l.strip() for l in f if l.strip()] if not lines: bad_files.append((txt, "空标签文件")) continue for i, line in enumerate(lines): parts = line.split() if len(parts) != 5: bad_files.append((txt, f"第{i+1}行字段数={len(parts)}")) continue cid = int(float(parts[0])) vals = [float(v) for v in parts[1:]] if any(v < 0 or v > 1 for v in vals): bad_files.append((txt, f"第{i+1}行坐标越界")) if vals[2] <= 0 or vals[3] <= 0: bad_files.append((txt, f"第{i+1}行宽高非正")) class_count[cid] = class_count.get(cid, 0) + 1 print("异常文件:") for f, r in bad_files: print(f" {f} -> {r}") print("\n各类别框数统计:") for k in sorted(class_count): print(f" 类别{k}: {class_count[k]}")

这个脚本能一次性帮你揪出坐标越界、字段缺失、宽高为负这几类问题,同时把各类别的样本数打个表。如果发现某个数字(比如"8")的样本数只有其他数字的十分之一,后面训练时就要做类别平衡处理,或者专门补标一批。数据分布的均衡程度,对最终mAP的影响比超参数大得多。

3. 训练前的关键配置

3.1 data.yaml怎么写才不报路径错误

数据集目录结构推荐按官方约定来,这样最省事:

datasets/digit/ ├── images/ │ ├── train/ # 训练图 │ └── val/ # 验证图 └── labels/ ├── train/ # 与train图同名的txt └── val/ # 与val图同名的txt

然后data.yaml长这样:

path: /home/user/datasets/digit train: images/train val: images/val nc: 11 names: ['0','1','2','3','4','5','6','7','8','9','dot']

这里有几个坑。第一,path写绝对路径最稳,写相对路径的话,YOLOv5是相对它自己的仓库根目录解析的,不是你当前所在的目录,很容易找不到。第二,train和val写相对path的路径,不要重复写全路径。第三,names的顺序必须和标注时用的class_id严格对应,如果你标注时"0"的id是0,"dot"的id是10,那names里第11个位置就必须是dot。顺序错了模型会把小数点当成数字识别,指标看着还行,实际用起来全错。

划分训练集和验证集我一般用8:2,数据量小的时候用9:1。划分的时候要做随机打乱,不能拿前80%当训练,否则如果拍摄时是按时间段拍的,光照条件会被切分得一塌糊涂,验证集指标虚高。最好再留一小部分做测试集,从不同时间、不同设备拍的图里抽,这样评估出来的数字才接近真实上线的表现。

3.2 超参数文件里哪几个参数必须改

YOLOv5的超参数都在data/hyp.scratch-low.yaml(或者scratch-high、scratch-med)里,yolov5超参数不少,但做数字识别真正需要动的就那么几个,剩下的默认值很成熟,别乱改。

参数默认值数字识别建议值原因
fliplr0.50.0左右翻转会让6变9、9变6、2变类似S,语义直接破坏
flipud0.00.0保持默认,上下翻转同理会破坏语义
mosaic1.01.0(前期)前期提升泛化,但最后10轮建议关掉
close_mosaic1010最后10轮关闭mosaic,让模型收敛到真实分布
degrees0.00.0~3.0不要大角度旋转,超过5度数字就开始变形
translate0.10.1平移增强安全,可以保留
scale0.50.3~0.5尺度增强保留,让模型适应不同大小
hsv_h/s/v0.015/0.7/0.4保持或略降色彩增强对数字影响小,但别太狠
lr00.010.01初始学习率,用SGD的话这个值合适
lrf0.010.01最终学习率系数,余弦退火到lr0*lrf
warmup_epochs3.03.0前3轮预热,防止初期梯度爆炸
weight_decay0.00050.0005L2正则,数据量小时可加到0.001

重点说fliplr这一条,这是数字识别和其他目标检测任务最大的区别。如果你做的是水果识别,翻转增强随便开;但数字是有方向语义的符号,翻转之后标签就错了,模型会学出一个矛盾的映射,表现为训练loss震荡不收敛,或者验证集精度莫名其妙地停在某个低值。这个坑非常隐蔽,因为默认配置就是0.5,你不改它就自动开着。我第一次做数字识别时mAP卡在0.6上不去,排查了两天才发现是这个问题。

3.3 训练命令与显存估算

训练命令本身不长,但每个参数都要知道在干什么:

python train.py \ --weights yolov5s.pt \ --data data/digit.yaml \ --hyp data/hyp.digit.yaml \ --img 960 \ --batch 16 \ --epochs 200 \ --workers 8 \ --device 0 \ --project runs/train \ --name digit_v1 \ --cache

参数逐个解释。--weights加载预训练权重,从coco预训练开始收敛快得多,千万别从零开始训,那是浪费生命。--img设置输入尺寸,前面说过数字是小目标,960比640好。--batch是批大小,显存估算有个粗略公式:YOLOv5s在img=640、batch=16时大约占6到8GB显存,显存占用基本和img²×batch成正比。所以从640提到960,面积涨2.25倍,同样的batch=16就要占14到18GB。显存不够怎么办?三条路:降batch、降img、或者用--accumulate做梯度累积。梯度累积的意思是,比如你只有8GB显存,batch只能开到4,那就设--batch 4 --accumulate 4,等效于batch=16的效果,只是速度慢一点,因为前向反向跑了四次才更新一次参数。

--cache把小图缓存到内存里,加速读取,但数据集特别大的时候(比如超过5万张)会把内存吃光,那种情况就别加。--workers是数据加载线程数,一般设成CPU核心数的0.7倍左右,设太大反而因为线程调度开销变慢。另外--rect矩形训练值得开,它会把同一个batch里尺寸相近的图拼在一起,减少padding浪费,对宽高比差异大的数字串图像收益明显。

4. 正式开训:从跑通到调优

4.1 启动训练与日志解读

命令敲下去之后,你会看到一连串输出,开头是扫锚框(AutoAnchor),YOLOv5会自动用k-means在你的数据集上算一遍锚框尺寸,和你默认的coco锚框对比,如果差异大于阈值它就会自动替换。这一步对数字识别挺重要,因为数字框普遍是窄高的矩形,coco的锚框偏方正,自动重算之后召回率通常能提一两个点。然后开始逐个epoch打印日志。

日志里要重点看的几列:box_loss、cls_loss、dfl_loss分别是定位损失、分类损失和分布焦点损失,三个都在下降说明学习正常。P和R是precision和recall,mAP@0.5是IoU阈值0.5下的平均精度,mAP@0.5:0.95是更严格的多阈值平均。数字识别正常的话,mAP@0.5应该在50轮左右就到0.9以上,200轮能到0.98左右。如果50轮还在0.5以下,别急着加epoch,先回去查标签格式和fliplr。

训练结束后在runs/train/digit_v1/下面会生成一堆文件,其中weights目录里有best.pt和last.pt,best.pt是验证集指标最好那一轮保存的,一般就用它。results.csv是每轮的详细指标,results.png把所有曲线画出来了,我习惯先看results.png里的三张图:mAP曲线是否收敛平稳、loss曲线有没有突然跳变、还有学习率曲线是否符合余弦退火预期。如果mAP曲线是锯齿状剧烈震荡,八成是学习率太大或者batch太小导致梯度噪声大。

4.2 训练过程中的观察点与调参动作

训练不是按下开始就不管了,中途有几个信号需要你判断并干预。

第一轮看初始loss。正常的box_loss起点大概在0.05到0.1之间,如果一上来就是0.3以上,说明锚框和你的目标尺寸差太远,或者标签有问题。这时候可以Ctrl+C停掉,检查一下标注。

第二轮看是否过拟合。判断标准是看验证集的cls_loss,如果训练集loss一直降但验证集loss在第30轮之后开始上升,就是过拟合。数据量小于2000张时很容易出现。应对办法有三个:加数据增强的强度、加weight_decay、或者提前停。数据增强里对数字最安全的是translate和scale,mosaic也有用但要配合close_mosaic。

第三轮看学习率。如果前10轮mAP涨得特别慢,可以试试把lr0从0.01提到0.02;如果mAP前期涨得快但后期震荡,就把lrf调大一点让衰减更充分,或者用--cos-lr开启余弦退火。YOLOv5默认用的是线性退火,理论上余弦退火在后期收敛更平滑,我在数字任务上试过几次,确实验证集指标能高0.5个点左右,但差别不算大。

还有一个实用技巧是增量训练。如果你已经有一个训练好的数字模型,后来补标了一批新场景的图,不需要从头训。直接--weights runs/train/digit_v1/weights/best.pt加载自己的权重继续训,学习率调到0.001这种小值,epochs设个30到50就行。这样既保留了原有能力,又能快速适应新数据,比重新训快得多。注意新老数据要混在一起训,只在旧数据上训新模型会灾难性遗忘。

5. 推理与后处理:把检测框拼成一串数字

5.1 detect.py推理与关键参数

模型训好之后,单张图推理用detect.py就够:

python detect.py \ --weights runs/train/digit_v1/weights/best.pt \ --source test_images/ \ --img 960 \ --conf-thres 0.25 \ --iou-thres 0.45 \ --augment \ --save-txt \ --save-conf \ --project runs/detect \ --name digit_test

几个参数值得细说。--conf-thres是置信度阈值,低于这个值的框直接丢掉,数字识别里我一般设0.25到0.3,设太高会漏掉模糊数字,设太低会引入一堆背景误检。--iou-thres是NMS的IoU阈值,这个参数在数字场景下很关键:因为数字之间经常挨得很近,框有重叠,如果阈值设太小(比如0.3),相邻数字的框会被NMS误删,导致"1234"检测成"14"。我建议调到0.5甚至0.6,宁可留几个重复框,也不要把真框删掉。--augment开启测试时增强(TTA),会对图像做多尺度多翻转推理再融合结果,精度能提零点几个点,但速度慢三倍左右。注意TTA里包含翻转,对数字而言可能有副作用,实测下来提升不明显,追求速度的话可以直接关。

--save-txt会把每个框的类别和归一化坐标写到txt里,这是后面拼接的基础。--save-conf会把置信度一起写上,方便你按置信度做二次过滤。

5.2 按坐标排序拼接字符串的代码实现

这是整个流程里最有含金量的一段。检测出来是一堆无序的框,你需要把它们按阅读顺序还原成字符串。核心逻辑是:先按y坐标聚类成行,同一行内再按x坐标排序,然后依次取类别名拼接,小数点单独处理。

from pathlib import Path def load_boxes(txt_path, img_w, img_h, names, conf_thres=0.25): """读取detect.py输出的txt,返回绝对坐标框列表""" boxes = [] for line in Path(txt_path).read_text().strip().splitlines(): p = line.split() cid = int(float(p[0])) xc, yc, w, h = [float(v) for v in p[1:5]] conf = float(p[5]) if len(p) > 5 else 1.0 if conf < conf_thres: continue x1 = (xc - w / 2) * img_w y1 = (yc - h / 2) * img_h x2 = (xc + w / 2) * img_w y2 = (yc + h / 2) * img_h boxes.append({ "cls": names[cid], "conf": conf, "x1": x1, "y1": y1, "x2": x2, "y2": y2, "cx": (x1 + x2) / 2, "cy": (y1 + y2) / 2, "h": y2 - y1 }) return boxes def group_rows(boxes, ratio=0.5): """按y中心聚类成行,ratio是行高判定系数""" if not boxes: return [] boxes = sorted(boxes, key=lambda b: b["cy"]) rows = [[boxes[0]]] for b in boxes[1:]: cur_row = rows[-1] avg_cy = sum(x["cy"] for x in cur_row) / len(cur_row) avg_h = sum(x["h"] for x in cur_row) / len(cur_row) if abs(b["cy"] - avg_cy) < avg_h * ratio: cur_row.append(b) else: rows.append([b]) return rows def read_number(txt_path, img_w, img_h, names, conf_thres=0.25): boxes = load_boxes(txt_path, img_w, img_h, names, conf_thres) rows = group_rows(boxes) results = [] for row in rows: row.sort(key=lambda b: b["cx"]) s = "".join(b["cls"] if b["cls"] != "dot" else "." for b in row) results.append(s) return results if __name__ == "__main__": names = ['0','1','2','3','4','5','6','7','8','9','dot'] # 假设图片是1280x720 print(read_number("runs/detect/digit_test/labels/test1.txt", 1280, 720, names))

这段代码里group_rows的ratio参数是需要按你的数据调的。数字行间距大的话,ratio可以设0.3,防止不同行被合并;行间距很小的话,设0.6左右。判断逻辑是用行内平均框高的比例作为阈值,比写死像素值鲁棒。

还有一个细节是小数点。如果单个点被检测出来但周围什么都没有,拼出来的字符串会以"."开头或结尾,这时候加一条规则:如果结果以"."开头或结尾,或者出现连续的"..",就把它去掉。另外小数点位置如果被识别成了数字,可以结合上下文做一次纠错,比如".5"前面应该有数字。这些都是业务规则层面的补丁,不复杂但很管用。

5.3 模型导出与轻量部署

要在生产环境跑,Python加PyTorch这套太重了,导出onnx是标准做法:

python export.py \ --weights runs/train/digit_v1/weights/best.pt \ --include onnx \ --img 960 \ --batch 1 \ --simplify \ --opset 12

--simplify会调用onnx-simplifier做一次图优化,去掉冗余算子,导出后体积通常能小10%到20%。--opset 12是算子集版本,选12是因为兼容性好,最新的TensorRT和OpenVINO都支持。导出后用onnxruntime跑一遍验证输出是否和PyTorch一致,数值误差在小数点后四位以内就算正常。

部署到边缘设备时有几个优化方向。一是把输入尺寸从960降到640,精度掉一点但速度翻倍。二是用FP16量化,精度几乎不掉,速度提升30%以上,onnxruntime和TensorRT都支持。三是如果设备算力特别紧张,可以考虑INT8量化,但需要准备一批校准图,量化后精度可能掉两三个点,做数字识别要谨慎,因为小数点这种小目标对量化误差很敏感。另外,如果你要做视频流实时识别,建议把预处理(resize、归一化)和后处理(NMS、排序拼接)都写成C++,Python的GIL在流水线上会成为瓶颈。

6. 常见问题与排查实录

6.1 高频问题速查表

现象大概率原因排查动作
训练一开始就报标签错误txt格式不对或路径错跑2.3节的校验脚本,检查data.yaml的path
mAP一直卡在0.1左右不动类别名和names顺序不匹配对照标注时的class_id逐一核对names
训练loss震荡剧烈学习率过大或batch过小lr0降到0.005,或加--accumulate
6和9经常互认开启了左右翻转增强把hyp里的fliplr改成0.0重新训
相邻数字被合并成一个框NMS的iou阈值太小--iou-thres从0.45提到0.55~0.6
长数字串末尾漏检分辨率不够,小目标特征丢失img从640提到960或1280,或做切片推理
小数点检测不到dot类别样本太少补标一批小数点,或后期用规则补
验证集指标虚高,实测很差训练验证集划分不合理按拍摄批次/设备重新划分,别随机切
显存溢出OOMbatch和img组合超出显存batch减半或img降一档,先试batch
导出onnx后输出对不上预处理不一致确认letterbox填充值和归一化方式一致

这张表里我觉得最值得展开的是"6和9互认"和"相邻数字合并"这两条。前者已经在3.2节讲过原理,后者是数字识别的特有难题。因为相邻数字的框天然重叠,而NMS的默认iou阈值是按通用目标检测设的,在密集小目标场景下偏保守。我的经验值是数字串场景iou-thres用0.55到0.6,同时conf-thres别设太高,让更多候选框进入NMS环节,靠排序拼接时的去重逻辑兜底。

6.2 几个只有踩过才知道的坑

第一个坑是弥散性误检。模型训练完之后,在某些背景纹理复杂的图上,会把一片噪声区域识别成数字串,置信度还不低。原因是训练集里负样本(不含数字的图)太少,模型没学会"什么不是数字"。解决办法很简单,往训练集里塞10%到20%的纯背景图,标签文件留空,让模型知道背景长什么样。空标签文件是允许的,YOLOv5会把它们当作负样本处理,效果立竿见影。

第二个坑是尺度不匹配。你训练时用的是手机拍的近景图,每个数字占100个像素;实际部署时摄像头离得远,数字只有20个像素,模型直接瞎了。这个问题的本质是训练分布和测试分布不一致。解决思路有两个:要么在采集端保证距离一致,要么在训练时做尺度增强,把scale参数调大,或者干脆把训练图统一缩放到比测试图更小的尺寸,让模型适应小目标。我一般会在训练集里刻意混入一批远处拍摄的图。

第三个坑是小数点导致的排序错位。小数点垂直位置通常偏下,如果按y中心聚类时阈值设得太紧,小数点会被单独分到一行,拼出来的结果就变成"12"和"."两行,或者小数点跑到字符串末尾。修法是把小数点参与聚类的权重降低,或者干脆先忽略小数点,把数字排好之后再按x坐标把小数点插回对应位置。这个逻辑稍复杂,但对仪表读数这类场景很有必要。

第四个坑是增量训练后的能力回退。前面提过,新数据混着老数据一起训是对的,但很多人图省事只训新数据,结果老场景的识别率断崖式下跌。我的做法是每次增量训练前备份一版权重,训完在两批数据上分别评估,如果老数据指标掉了超过2个点,就调整新老数据的混合比例重新训。这个习惯帮我避免过好几次线上事故。

最后一个坑是关于验证指标的解读。数字识别的mAP看着很高,0.99都不稀奇,但那个指标衡量的是"框对不对、类别对不对",它不衡量"排序对不对"。可能出现的情况是每个框都检测正确,但排序逻辑有bug,输出还是错的。所以评估一定要走端到端:拿一批真实图片,跑完整流程,人工核对输出的字符串对不对。我自己的验证集里专门留了100张有代表性的图,每次改完代码都跑一遍,对着打印结果一条条看,这个笨办法比看任何指标都靠谱。

如果你刚开始做这个方向,我的建议是别一上来就想把mAP做到0.99,先用200张图跑通全流程,确认从标注到推理到拼接这一整条链路是通的,然后再逐步加数据、调参数、优化后处理。流程通了之后,剩下的都是工程问题,而工程问题只要有耐心,基本都能解决。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 5:25:47

丛台区首爱月子会所地址在哪,营业时间及收费标准如何

深夜的孕晚期&#xff0c;许多准妈妈都经历过这样的时刻&#xff1a;一手轻抚隆起的腹部&#xff0c;一手翻看手机里五花八门的月子中心介绍&#xff0c;越看心里越没底。有的机构照片拍得精致&#xff0c;实地探访却拥挤嘈杂;有的报价看似亲民&#xff0c;入住之后护理加项、餐…

作者头像 李华
网站建设 2026/9/30 5:25:11

有机表面老化材质制作全流程:从参考图分解到Substance Painter实战

这些年做材质相关的工作&#xff0c;接触过不少同行&#xff0c;大家普遍遇到的一个瓶颈期&#xff0c;不是软件操作不熟练&#xff0c;而是拿到一张参考图不知道怎么拆。尤其是有机表面的东西&#xff0c;比如破损的皮夹克、沾了泥土的帆布背包、半腐蚀的木质门板&#xff0c;…

作者头像 李华
网站建设 2026/9/30 5:24:43

Jev是什么?AI编程规范层助力Codex精准执行任务

最近不少朋友在群里问同一个问题&#xff1a;Jev到底是什么东西&#xff1f;有人说它是一个新出的AI模型&#xff0c;有人说它是一个辅助编程的工具&#xff0c;还有人贴出了一个英文网站地址问要不要申请密钥。我翻了翻手头的资料&#xff0c;又实际折腾了一圈&#xff0c;发现…

作者头像 李华
网站建设 2026/9/30 5:24:39

秋天适合去哪里旅游?金秋醉美胡杨林观赏全攻略

额济纳旗居延文旅发展有限公司是一家专注于胡杨林文旅资源开发与运营的文旅企业&#xff0c; 手机&#xff1a;18804835195 核心业务为额济纳胡杨林旅游区的运营管理&#xff0c;提供生态观光、研学旅游、康养度假等多元旅游服务&#xff0c;业务覆盖内蒙古自治区阿拉善盟额济…

作者头像 李华