第一次接触YOLOv9时,我也被这个目标检测领域的新星惊艳到了。相比前代版本,YOLOv9在保持实时性的同时,精度提升明显。但真正用起来才发现,从数据准备到最终模型验证,每个环节都有不少需要注意的细节。这次我就把自己在车牌识别项目中的完整实践过程分享出来,包括那些踩过的坑和验证有效的解决方案。
整个流程可以划分为四个关键阶段:数据标注与格式转换、数据集划分、模型训练调优、模型验证分析。每个阶段环环相扣,前一步的输出就是下一步的输入。比如数据标注的质量直接影响模型效果,而训练时的参数配置又决定了验证阶段的性能表现。下面我就按照实际开发顺序,一步步带你走通这个闭环。
工欲善其事,必先利其器。我试过多种标注工具,最终选择了LabelImg这款开源工具。安装很简单:
bash复制pip install labelimg
labelimg
但在实际标注时,有几个经验值得分享:
标注完成后会生成XML文件,包含每个目标的类别和位置信息。这时候的目录结构是这样的:
code复制raw_data/
├── images/
│ ├── img1.jpg
│ └── img2.jpg
└── labels/
├── img1.xml
└── img2.xml
YOLOv9需要的是特定格式的TXT标注文件。我优化过的转换脚本如下:
python复制import xml.etree.ElementTree as ET
import os
def convert_annotation(xml_path, classes):
tree = ET.parse(xml_path)
root = tree.getroot()
size = root.find('size')
width = int(size.find('width').text)
height = int(size.find('height').text)
txt_lines = []
for obj in root.iter('object'):
cls = obj.find('name').text
if cls not in classes:
continue
xmlbox = obj.find('bndbox')
x1 = float(xmlbox.find('xmin').text)
y1 = float(xmlbox.find('ymin').text)
x2 = float(xmlbox.find('xmax').text)
y2 = float(xmlbox.find('ymax').text)
# 转换为YOLO格式:中心点坐标和宽高,均归一化
x_center = ((x1 + x2) / 2) / width
y_center = ((y1 + y2) / 2) / height
w = (x2 - x1) / width
h = (y2 - y1) / height
txt_lines.append(f"{classes.index(cls)} {x_center} {y_center} {w} {h}")
return txt_lines
这个脚本处理了两个关键点:
新手常犯的错误是把所有数据都作为训练集。我建议采用6:2:2的比例划分:
实现代码的核心逻辑:
python复制import random
from sklearn.model_selection import train_test_split
all_files = os.listdir(annotations_dir)
random.shuffle(all_files)
# 先分训练集和临时集
train_files, temp_files = train_test_split(all_files, test_size=0.4)
# 再分验证集和测试集
val_files, test_files = train_test_split(temp_files, test_size=0.5)
YOLOv9自带的增强策略已经很丰富,但在data.yaml中可以进一步配置:
yaml复制train: ../dataset/images/train
val: ../dataset/images/val
nc: 36 # 类别数
names: ['num_0', 'num_1', ..., 'charB_A', ...] # 与标注时一致
# 增强参数
augmentations:
hsv_h: 0.015 # 色调变化幅度
hsv_s: 0.7 # 饱和度变化幅度
hsv_v: 0.4 # 明度变化幅度
degrees: 10 # 旋转角度范围
translate: 0.1 # 平移比例
scale: 0.5 # 缩放幅度
对于特殊场景(如倾斜车牌),可以增加自定义增强:
克隆官方仓库后,重点修改train.py中的参数:
python复制# 基础配置
parser.add_argument('--weights', type=str, default='yolov9-c.pt', help='初始权重路径')
parser.add_argument('--cfg', type=str, default='models/detect/yolov9-c.yaml', help='模型配置文件')
parser.add_argument('--data', type=str, default='data/data.yaml', help='数据集配置文件')
parser.add_argument('--epochs', type=int, default=300) # 车牌识别通常200-300轮足够
# 硬件相关
parser.add_argument('--batch-size', type=int, default=16, help='根据显存调整')
parser.add_argument('--workers', type=int, default=4, help='数据加载线程数')
# 学习率策略
parser.add_argument('--lr0', type=float, default=0.01, help='初始学习率')
parser.add_argument('--lrf', type=float, default=0.1, help='最终学习率 = lr0 * lrf')
训练过程中遇到过几个典型问题:
CUDA内存不足:
损失震荡不收敛:
python复制# 在train.py中调整优化器
optimizer = torch.optim.SGD(model.parameters(), lr=hyp['lr0'], momentum=0.9, nesterov=True)
# 或改用AdamW
optimizer = torch.optim.AdamW(model.parameters(), lr=hyp['lr0'], weight_decay=0.05)
类别不平衡:
yaml复制# 在data.yaml中添加类别权重
class_weights: [1.0, 1.2, ..., 0.8] # 样本少的类别权重调高
val.py的关键参数配置:
python复制parser.add_argument('--weights', type=str, default='runs/train/exp/weights/best.pt', help='模型路径')
parser.add_argument('--data', type=str, default='data/data.yaml', help='数据集配置')
parser.add_argument('--batch-size', type=int, default=8, help='验证时可以比训练时大')
parser.add_argument('--conf-thres', type=float, default=0.4, help='置信度阈值')
parser.add_argument('--iou-thres', type=float, default=0.5, help='NMS IoU阈值')
验证输出会包含这些关键指标:
| 指标 | 理想范围 | 说明 |
|---|---|---|
| mAP@0.5 | >0.8 | IoU阈值为0.5时的平均精度 |
| mAP@0.5:0.95 | >0.6 | 多IoU阈值下的平均精度 |
| Precision | >0.85 | 检出目标中正确的比例 |
| Recall | >0.8 | 实际目标被检出的比例 |
如果发现指标不理想,可以这样排查:
YOLOv9内置的可视化功能很实用:
bash复制python val.py --task study --data data.yaml --weights best.pt
这会生成:
我在车牌项目中就通过混淆矩阵发现数字"8"和"B"容易混淆,通过增加这两个字符的困难样本,准确率提升了12%。