街景字符识别是计算机视觉领域的经典问题,这次天池竞赛的数据集来自真实场景的门牌号图片,包含训练集3万张、验证集1万张,测试集8万张。与MNIST等标准数据集不同,这些图片存在光照不均、角度倾斜、字符粘连等现实场景的挑战。最初我尝试用传统CNN方法,但发现对多字符检测效果不佳,最终选择YOLOv5方案有三大优势:
这里特别说明选择YOLOv5x版本的原因:虽然模型更大,但在测试中发现其对小字符的识别准确率比YOLOv5s高出7-9个百分点。实际训练时可以用以下代码快速加载预训练模型:
python复制model = torch.hub.load('ultralytics/yolov5', 'yolov5x', pretrained=True)
官方提供的JSON标注需要转换为YOLO格式,这个环节我踩过两个坑:一是坐标归一化处理错误导致训练发散,二是多字符样本处理不当。这里分享经过验证的可靠处理方法:
python复制import json
import cv2
from pathlib import Path
def convert_json_to_yolo(json_path, img_dir, output_dir):
with open(json_path) as f:
data = json.load(f)
for img_name, anno in data.items():
img_path = str(Path(img_dir)/img_name)
img = cv2.imread(img_path)
h, w = img.shape[:2]
txt_path = str(Path(output_dir)/f"{Path(img_name).stem}.txt")
with open(txt_path, 'w') as f_txt:
for i in range(len(anno['label'])):
# 计算归一化坐标 (center_x, center_y, width, height)
x_center = (anno['left'][i] + anno['width'][i]/2) / w
y_center = (anno['top'][i] + anno['height'][i]/2) / h
box_w = anno['width'][i] / w
box_h = anno['height'][i] / h
# YOLO格式:class_id center_x center_y width height
line = f"{anno['label'][i]} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}\n"
f_txt.write(line)
按照8:1:1的比例划分训练集、验证集和测试集。关键是要确保字符类别分布均衡,我用了这个检查脚本:
python复制from collections import defaultdict
def check_class_distribution(label_dir):
class_count = defaultdict(int)
for label_file in Path(label_dir).glob('*.txt'):
with open(label_file) as f:
for line in f:
class_id = int(line.strip().split()[0])
class_count[class_id] += 1
print("字符类别分布统计:")
for cls, count in sorted(class_count.items()):
print(f"数字{cls}: {count}个样本")
使用YOLOv5x模型训练时,经过多次实验验证的最佳参数组合如下:
| 参数名 | 推荐值 | 作用说明 |
|---|---|---|
| --img-size | 640 | 输入图像尺寸 |
| --batch-size | 16 | 根据GPU显存调整 |
| --epochs | 300 | 充足训练轮次 |
| --hyp | data/hyps/hyp.scratch.yaml | 超参数配置文件路径 |
| --rect | 启用 | 矩形训练提升效率 |
| --multi-scale | 启用 | 多尺度增强鲁棒性 |
特别提醒两个易错点:
--rect参数启动训练的命令示例:
bash复制python train.py --img 640 --batch 16 --epochs 300 --data coco.yaml --weights yolov5x.pt --hyp hyp.scratch.yaml --rect --multi-scale
修改data/hyps/hyp.scratch.yaml中的增强参数:
yaml复制hsv_h: 0.015 # 色相增强幅度
hsv_s: 0.7 # 饱和度增强幅度
hsv_v: 0.4 # 明度增强幅度
degrees: 10 # 旋转角度范围
translate: 0.1 # 平移比例
scale: 0.9 # 缩放幅度
shear: 2 # 剪切幅度
原生的NMS在处理密集字符时效果不佳,我改进了utils/general.py中的non_max_suppression函数:
python复制def non_max_suppression(prediction, conf_thres=0.25, iou_thres=0.45, classes=None):
# 修改iou计算方式为DIoU
iou = bbox_iou(prediction[:, :4], prediction[:, :4], DIoU=True)
# 添加字符间距约束
min_char_gap = 2 # 最小字符间距(像素)
for i, det in enumerate(prediction):
if len(det) > 1:
centers = det[:, :2] + det[:, 2:4]/2
dists = torch.cdist(centers, centers)
mask = dists < min_char_gap
iou[mask] = 1 # 强制合并过近的检测框
# 标准NMS流程
x = prediction[iou < iou_thres]
return x
需要重写结果输出逻辑以符合比赛要求:
python复制# 在detect()函数中添加以下代码
results = []
for *xyxy, conf, cls in det:
xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist()
results.append({
'x': xywh[0],
'cls': int(cls),
'conf': float(conf)
})
# 按x坐标排序后拼接字符
results.sort(key=lambda x: x['x'])
final_code = ''.join([str(r['cls']) for r in results])
bash复制python detect.py --weights runs/train/exp/weights/best.pt --source data/test --img-size 640 --conf-thres 0.3 --save-txt --save-conf
运行后会生成包含file_name和file_code两列的CSV文件,直接提交即可。我在测试集A上使用这套方案获得了0.934的准确率,关键是要确保预测时的图像尺寸与训练时一致。如果遇到字符漏检的情况,可以适当降低conf-thres参数到0.2-0.25范围。