在计算机视觉领域,目标检测一直是最基础也最具挑战性的任务之一。YOLOv7作为YOLO系列的最新成员,以其卓越的速度-精度平衡在工业界和学术界广受关注。但预训练模型在实际业务场景中往往表现不佳,这时就需要针对特定数据集进行微调(Fine Tuning)。
最近我在处理一个工业质检项目时,需要对电路板上的微小缺陷进行检测。经过对比测试,最终选择基于YOLOv7进行定制化训练。本文将完整记录从环境配置到模型部署的全流程,特别会分享在处理小目标检测时积累的实战经验。
相比前代版本,YOLOv7的主要优势体现在:
对于我们的电路板缺陷检测场景,YOLOv7的以下特性尤为关键:
我们的自定义数据集包含以下特点:
注意:工业场景中常见的数据问题包括类别不平衡(如"正常"样本远多于"缺陷"样本)、标注一致性差等。我们通过人工复查标注+数据增强策略缓解这些问题。
推荐使用以下环境配置:
bash复制# 基础环境
Python 3.8.10
CUDA 11.1
cuDNN 8.0.5
PyTorch 1.9.0
# 安装YOLOv7
git clone https://github.com/WongKinYiu/yolov7
cd yolov7
pip install -r requirements.txt
对于Windows用户,建议使用WSL2+Ubuntu环境以避免路径问题。我们团队实测发现,在Windows原生环境下训练时容易出现Dataloader的多进程错误。
YOLOv7要求YOLO格式的标注(class_id x_center y_center width_height)。转换脚本示例:
python复制import xml.etree.ElementTree as ET
import os
def convert_voc_to_yolo(xml_path, output_dir):
tree = ET.parse(xml_path)
root = tree.getroot()
with open(os.path.join(output_dir, xml_path.replace('.xml','.txt')), 'w') as f:
for obj in root.findall('object'):
cls = obj.find('name').text
bbox = obj.find('bndbox')
xmin = float(bbox.find('xmin').text)
ymin = float(bbox.find('ymin').text)
xmax = float(bbox.find('xmax').text)
ymax = float(bbox.find('ymax').text)
# 转换为YOLO格式
width = float(root.find('size/width').text)
height = float(root.find('size/height').text)
x_center = (xmin + xmax) / 2 / width
y_center = (ymin + ymax) / 2 / height
w = (xmax - xmin) / width
h = (ymax - ymin) / height
f.write(f"{class_dict[cls]} {x_center} {y_center} {w} {h}\n")
针对小目标检测,我们采用的特殊增强方法:
配置示例(data/hyp.scratch.custom.yaml):
yaml复制# 超参数配置
lr0: 0.01 # 初始学习率
lrf: 0.1 # 最终学习率 = lr0 * lrf
momentum: 0.937
weight_decay: 0.0005
# 增强参数
hsv_h: 0.015 # 色调增强幅度
hsv_s: 0.7 # 饱和度增强幅度
hsv_v: 0.4 # 明度增强幅度
degrees: 10.0 # 旋转角度范围
translate: 0.1 # 平移比例
scale: 0.5 # 缩放比例
shear: 0.0 # 剪切幅度
perspective: 0.0001 # 透视变换
flipud: 0.0 # 上下翻转概率
fliplr: 0.5 # 左右翻转概率
mosaic: 1.0 # mosaic概率
mixup: 0.1 # mixup概率
启动训练的命令行示例:
bash复制python train.py \
--weights yolov7.pt \
--cfg cfg/training/yolov7-custom.yaml \
--data data/circuit_defect.yaml \
--hyp data/hyp.scratch.custom.yaml \
--epochs 300 \
--batch-size 16 \
--img-size 640 \
--device 0,1 \
--workers 8 \
--name circuit_defect_v1
关键参数说明:
--img-size 640:输入图像尺寸(大尺寸有利于小目标检测)--batch-size 16:根据GPU显存调整(11GB显存可支持16)--device 0,1:使用多GPU训练--workers 8:Dataloader的进程数(建议设为CPU核心数的70%)我们发现以下学习率调整策略效果最佳:
实现方法(修改train.py):
python复制# 在optimizer初始化后添加
lf = lambda x: ((1 + math.cos(x * math.pi / epochs)) / 2) * (1 - lrf) + lrf # cosine
scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf)
# 预热处理
if epoch < 3:
xi = [0, 3] # x interp
for j, x in enumerate(optimizer.param_groups):
x['lr'] = np.interp(ni, xi, [0.1 if j == 2 else 0.0, x['initial_lr'] * lf(epoch)])
针对电路板微小缺陷的专项优化:
python复制python tools/anchors.py --data data/circuit_defect.yaml --img-size 640
yaml复制# 修改models/yolo.py
cls_pw: 1.5 # 原值为1.0
obj_pw: 1.0
fl_gamma: 0.0 # 设为0禁用Focal Loss
我们关注的核心指标:
| 指标名称 | 计算公式 | 目标值 |
|---|---|---|
| mAP@0.5 | 0.5IoU时的平均精度 | >0.85 |
| mAP@0.5:0.95 | 0.5到0.95IoU的平均精度 | >0.65 |
| 推理速度 | 在Jetson Xavier NX上的FPS | >30 |
| 模型大小 | 导出后的TensorRT引擎文件 | <50MB |
测试命令:
bash复制python test.py \
--weights runs/train/circuit_defect_v1/weights/best.pt \
--data data/circuit_defect.yaml \
--img-size 640 \
--batch-size 8 \
--task test \
--device 0
优化后的部署流程:
bash复制python export.py \
--weights best.pt \
--img-size 640 640 \
--batch-size 1 \
--device 0 \
--simplify \
--include onnx
bash复制trtexec --onnx=best.onnx \
--saveEngine=best.engine \
--fp16 \
--workspace=2048 \
--verbose
python复制import tensorrt as trt
with open("best.engine", "rb") as f:
runtime = trt.Runtime(trt.Logger(trt.Logger.WARNING))
engine = runtime.deserialize_cuda_engine(f.read())
我们在产线部署时发现的关键优化:
问题1:Loss震荡不收敛
问题2:验证集mAP低于训练集
问题3:小目标召回率低
我们在Jetson设备上遇到的性能问题及解决方法:
| 瓶颈环节 | 原始耗时(ms) | 优化手段 | 优化后耗时(ms) |
|---|---|---|---|
| 图像预处理 | 15.2 | 使用CUDA实现resize和normalize | 3.8 |
| 模型推理 | 28.5 | 启用FP16模式 | 12.1 |
| NMS后处理 | 6.7 | 使用CUDA实现NMS | 1.2 |
| 结果解析 | 4.3 | 预分配内存+批量处理 | 0.9 |
经过三周的迭代优化,我们的最终模型在测试集上达到:
这个项目给我的最大启示是:工业场景中的目标检测不能只追求学术指标,必须在精度、速度、稳定性之间找到最佳平衡点。后续我们计划引入主动学习机制,让模型能够从产线新数据中持续自我优化。