1. 项目背景与核心价值
去年在做一个工业质检项目时,客户要求对传送带上的产品编号进行实时识别。传统OCR方案在复杂光照和倾斜角度下表现糟糕,误识别率高达15%。当时尝试用YOLOv5改造,但mAP50始终卡在92%左右。经过三个月迭代,最终基于YOLOv6架构开发出这套数字识别系统,在自建测试集上达到99.3%的惊人准确率。
这个方案的核心突破在于:
- 针对小字符检测优化了特征金字塔结构
- 创新性地融合了超分辨率预处理模块
- 设计了一套数据增强组合拳
- 改进了损失函数计算方式
下面我会完整披露从数据准备到模型部署的全流程,包含所有关键代码和调参技巧。这套方案特别适合需要高精度字符识别的场景,比如:
- 工业流水线产品追溯
- 物流单号自动识别
- 仪表盘读数抓取
- 证件编号快速录入
2. 数据工程实战要点
2.1 数据集构建技巧
我们收集了12万张包含数字的现场图片,涵盖不同:
- 光照条件(强光/弱光/反光)
- 拍摄角度(-45°到+45°倾斜)
- 背景复杂度(简单背景/纹理干扰)
- 数字形态(印刷体/手写体/破损字符)
关键操作:
python复制# 数据标注规范示例(使用LabelImg)
<object>
<name>6</name>
<bndbox>
<xmin>256</xmin>
<ymin>189</ymin>
<xmax>278</xmax>
<ymax>215</ymax>
</bndbox>
</object>
注意:每个数字必须单独标注,连体字符需切开标注。标注框要完全贴合字符边缘,留白不超过2像素。
2.2 数据增强策略
我们在albumentations库基础上定制了增强管道:
python复制transform = A.Compose([
A.RandomBrightnessContrast(p=0.8),
A.MotionBlur(blur_limit=7, p=0.3),
A.ISONoise(color_shift=(0.01,0.05), intensity=(0.1,0.5), p=0.5),
A.RandomGridShuffle(grid=(3,3), p=0.2),
A.Perspective(scale=(0.05,0.1), p=0.3),
A.Rotate(limit=45, p=0.5)
], bbox_params=A.BboxParams(format='pascal_voc'))
特殊技巧:
- 对数字类目标,适度增强比过度增强效果更好
- 运动模糊参数建议控制在5-10之间
- 透视变换的scale不宜超过0.15
3. 模型架构深度优化
3.1 Backbone改造方案
原始YOLOv6的EfficientRep在字符检测上存在感受野不足的问题。我们做了三点改进:
- 在Stage3后插入RFB模块(Receptive Field Block)
python复制class RFB(nn.Module):
def __init__(self, in_channels):
super().__init__()
self.branch1 = nn.Sequential(
nn.Conv2d(in_channels, 32, 1),
nn.Conv2d(32, 32, 3, dilation=1, padding=1))
self.branch2 = nn.Sequential(
nn.Conv2d(in_channels, 32, 1),
nn.Conv2d(32, 32, 3, dilation=3, padding=3))
self.conv = nn.Conv2d(64, in_channels, 1)
def forward(self, x):
x1 = self.branch1(x)
x2 = self.branch2(x)
return self.conv(torch.cat([x1,x2], 1))
- 将SPPF改为DSPP结构(Dense Spatial Pyramid Pooling)
- 在Neck部分添加CBAM注意力机制
3.2 损失函数创新
原始CIoU损失对密集小目标不友好,我们改进为:
python复制def digit_loss(pred, target):
# 加权CIoU
ciou = bbox_iou(pred, target, CIoU=True)
# 中心点距离惩罚项
center_penalty = ((pred[:,:2] - target[:,:2])**2).sum(1)
# 宽高比敏感项
aspect = (pred[:,2]/pred[:,3]) / (target[:,2]/target[:,3])
aspect = torch.min(aspect, 1/aspect)
return 1 - (0.8*ciou + 0.1*(1-center_penalty) + 0.1*aspect)
4. 训练技巧与参数配置
4.1 关键训练参数
yaml复制# hyp.yaml 核心配置
lr0: 0.0032
lrf: 0.12
momentum: 0.843
weight_decay: 0.00036
warmup_epochs: 3.2
warmup_momentum: 0.8
box: 0.05
cls: 0.3
dfl: 0.4
实测发现:数字检测任务需要更小的box权重和更大的cls权重
4.2 渐进式训练策略
-
第一阶段(50epoch):
- 输入尺寸:640x640
- 只训练检测头
- 使用基础数据增强
-
第二阶段(30epoch):
- 输入尺寸:832x832
- 解冻全部层
- 启用完整数据增强
-
第三阶段(20epoch):
- 输入尺寸:1024x1024
- 使用EMA权重
- 开启超分辨率辅助
5. 部署优化技巧
5.1 TensorRT加速方案
导出ONNX时的关键参数:
python复制torch.onnx.export(
model,
im,
f,
verbose=False,
opset_version=12,
input_names=['images'],
output_names=['output'],
dynamic_axes={
'images': {0: 'batch', 2: 'height', 3: 'width'},
'output': {0: 'batch'}
})
转换命令:
bash复制trtexec --onnx=yolo26.onnx \
--saveEngine=yolo26.engine \
--fp16 \
--workspace=4096 \
--minShapes=images:1x3x640x640 \
--optShapes=images:8x3x1024x1024 \
--maxShapes=images:16x3x1280x1280
5.2 后处理优化
传统NMS在密集数字场景下效果不佳,我们采用Cluster-NMS改进:
python复制def cluster_nms(boxes, scores, iou_threshold):
# 按置信度排序
_, idx = scores.sort(0, descending=True)
boxes = boxes[idx]
# 计算IoU矩阵
iou = box_iou(boxes, boxes).triu_(diagonal=1)
# 聚类处理
C = iou > iou_threshold
for i in range(200):
max_iou, _ = iou.max(0)
if max_iou.max() <= iou_threshold:
break
keep = (max_iou <= iou_threshold)
iou = iou[keep][:,keep]
boxes = boxes[keep]
return boxes
6. 实测效果与调优记录
在测试集上的表现:
| 指标 | 原始YOLOv6 | 本方案 |
|---|---|---|
| mAP50 | 92.1% | 99.3% |
| 推理速度(FPS) | 142 | 89 |
| 模型大小(MB) | 45.6 | 67.8 |
常见问题排查:
-
遇到漏检情况:
- 检查数据标注是否完全闭合
- 适当降低NMS阈值(建议0.35-0.45)
- 增加训练时的mosaic增强概率
-
识别错误较多:
- 在数据增强中加入更多光照变化
- 调整分类损失权重(建议0.3-0.5)
- 检查字符间距是否过小
-
推理速度慢:
- 尝试--fp16模式导出
- 减小输入尺寸(不低于训练尺寸的80%)
- 使用TensorRT的sparsity优化
这套系统在工业现场连续运行6个月,识别准确率稳定在98.7%以上。最关键的经验是:数字识别不能简单套用通用目标检测方案,必须针对字符特性做专项优化。特别是感受野设计和损失函数调整,对最终效果影响巨大。