尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

YOLOv5轻量化改进:GhostConv、BiFPN与CA注意力助力异常驾驶行为检测

YOLOv5轻量化改进:GhostConv、BiFPN与CA注意力助力异常驾驶行为检测 简介面向计算机视觉方向本科毕业设计与工程实训的YOLOv5异常行为检测完整项目包聚焦异常驾驶识别场景提供从模型改进到训练验证的整套源码。项目在YOLOv5基础上集成了4种可复用的改进策略Ghost卷积大幅降低参数量BiFPN加强多尺度特征融合CA注意力机制提升目标框定位精度Alpha-EIoU损失函数在不增加额外参数的同时提高检测效果。压缩包共包含214个文件以Python源码、YAML模型配置为主另有图片样本、训练脚本、Dockerfile、Jupyter教程等覆盖数据配置、模型定义、训练部署全流程整体大小仅2.8MB便于下载与快速实验。目前已有121人学习适合作为毕业设计参考框架、算法对比基线或YOLOv5实战入门资料。1. 异常驾驶行为检测开局先锁定两个指标参数量和召回率疲劳驾驶、接打手机、分神、吸烟这类异常行为在监控画面里通常只占几十个像素误检一次的成本却比普通目标高得多。所以基于YOLOv5做异常行为检测真正的问题不是“模型能不能跑通”而是“改动之后精度有没有真提升、参数量有没有真下降”。这份毕业设计项目的核心是四条改动路径引入 GhostConv 轻量化卷积压参数量引入 BiFPN 并保留中小目标检测层增强多尺度融合引入 CA 注意力机制提升定位精度用 Alpha-EIoU 替换 CIoU 提精度且不额外加参数。它附带了 Dockerfile、tutorial.ipynb 和两张 Mosaic 增强对比图既可以直接当毕设基线也可以当成工业巡检场景的消融实验模板。下文按“环境与数据、模型结构改动、训练配置、推理实战、效果验证”展开每条改动都对应源码里的具体位置。2. 环境搭建与数据准备Dockerfile、tutorial.ipynb 与 Mosaic 增强2.1 为什么先锁环境再动模型YOLOv5 项目最烦的不是训练而是环境不一致带来的玄学报错。同一份代码PyTorch 1.8 和 2.0 下某些算子的行为不一样OpenCV 版本不同还会影响推理时图像读入的通道顺序。这个项目里带了 Dockerfile 和 .dockerignore目的就是把 Python 版本、CUDA 依赖、系统库一次性固定住避免队友或评审老师在自己机器上复现时翻车。常见做法是先构建镜像再以交互方式挂载代码目录启动容器docker build -t yolov5-abc:v1 . docker run --gpus all -it --rm \ -v $(pwd):/workspace \ -w /workspace \ yolov5-abc:v1 bashdocker build -t yolov5-abc:v1 .会按照 Dockerfile 里的 pip 源、系统依赖和 PyTorch 版本生成镜像--gpus all把宿主机 GPU 透传给容器-v $(pwd):/workspace把当前项目目录挂进去这样容器里改代码宿主机直接同步训练产出的runs/目录也不会因为容器销毁而丢失。.dockerignore在这里的作用是排除runs/、datasets/这类体积大又没必要进镜像的目录否则 build 上下文可能膨胀到几个 G。2.2 从 tutorial.ipynb 看作者怎么组织实验流程tutorial.ipynb的存在说明作者把整个实验流程按“数据加载 → 模型构建 → 训练 → 可视化”组织成了可顺序执行的 Notebook。这里有个值得借鉴的习惯每个改进点单独开一个 cell并且在改模型结构前先跑一遍原始 YOLOv5s记录 baseline 的 mAP 和参数量。后续所有改动都跟这组数字对比而不是只看改进后的绝对值。bus.jpg是 YOLOv5 官方仓库自带的测试图通常被用来做通道和维度冒烟测试。改完模型结构后第一件事不是跑训练而是用这张图过一遍前向推理确认输出维度没有因为替换算子而崩掉python detect.py --weights yolov5s.pt --source bus.jpg --img 640如果输出结果里只有train_bus.jpg而没有报错说明模型结构层面基本通顺这时候再进训练遇到 loss 不降或 NaN 就不会怀疑是改结构改出的低级问题。2.3 Mosaic 增强的可视化对比图是干什么用的项目文件里的mosaic_with_label.jpg和mosaic_without_label.jpg是给论文和答辩准备的素材。YOLOv5 默认开启 Mosaic 增强训练时会随机抽 4 张图拼接成一张并同时把 4 张图的标签坐标换算到新图上。带 label 的图能看到每个目标的边界框和类别不带 label 的图只展示拼接效果一般放在数据增强章节里说明“模型在训练时看到的数据形态”。如果你要出毕业论文的图我的建议是别直接截训练日志用这两张图做对比更能说明问题。注意 Mosaic 概率由超参数mosaic1.0控制但这个值不是越大越好。数据集中小目标占比高时4 张图拼接后目标会被进一步缩小容易让小目标直接小到难以学习后面训练章节会专门讲这个参数怎么调。2.4 数据集目录结构与 labels 格式对齐异常行为检测的数据集格式跟 YOLOv5 原生一致images/和labels/一一对应标签是归一化后的 txt 文件。目录组织如下data/ ├── abc.yaml ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/abc.yaml里声明类别数和类别名train: data/abc/images/train val: data/abc/images/val nc: 4 names: [smoke, phone, fatigue, drink]类别顺序一旦确定就不要改。训练中途改names顺序会导致之前标注的类别索引整体错位表现就是 loss 在下降但验证集 mAP 一直上不去。常见做法是先用脚本扫描所有 txt 标签确认每个文件的类别 ID 都在0 ~ nc-1范围内再进训练流程。3. 逐条拆解四种模型改进GhostConv、BiFPN、CA 注意力与 Alpha-EIoU这四条改动解决的是不同层面的问题GhostConv 降低参数量BiFPN 改造特征融合方式并保留中小目标检测层CA 注意力提升定位精确度Alpha-EIoU 在不动模型结构的前提下改善边界框回归。它们之间互不冲突可以组合成一个完整实验。3.1 GhostConv用廉价线性变换替换冗余特征图普通卷积会生成大量相似的特征图GhostConv 的思路是先用少量卷积生成“原始特征”再对这部分特征做线性变换得到“幻影特征”最后拼接输出。这样总计算量和参数量都会下降在 YOLOv5 的models/common.py里新增一个模块即可import torch import torch.nn as nn class GhostConv(nn.Module): def __init__(self, c1, c2, k1, s1, g1, actTrue): super().__init__() c_ c2 // 2 # 原始特征图通道数压缩一半 self.primary_conv Conv(c1, c_, k, s, gg, actact) self.cheap_operation Conv(c_, c_, 5, 1, groupsc_, actact) def forward(self, x): y self.primary_conv(x) return torch.cat([y, self.cheap_operation(y)], dim1)primary_conv负责生成一半通道数的特征cheap_operation用groupsc_的深度可分离卷积做线性变换最后在通道维拼接让输出通道数恢复到c2。c2 // 2是压缩比的核心参数改成c2 // 3可以进一步压参数但精度损失会明显增大。作者在项目里用它替换了 backbone 中部分普通卷积参数量下降最直观的体现是训练时显存占用变小、单卡 batch size 能开得更大。3.2 BiFPN 与中小目标检测层让不同尺度信息真正融合YOLOv5 默认的 PANet 在自顶向下和自底向上两条路径之间做简单拼接各层贡献权重一样。BiFPN 的差异在于每条输入连接都学习一个可训练权重让模型自己决定当下更该信任深层语义还是浅层细节。异常驾驶检测里手机、烟头这类目标依赖浅层纹理信息而疲劳闭眼这种状态又依赖中层语义固定权重的拼接方式不够灵活。我一般会在models/yolo.py里给 neck 增加加权融合节点核心层长这样class BiFPN_Concat(nn.Module): def __init__(self, channels, weights): super().__init__() # 每条输入分配一个可学习权重 self.w nn.Parameter(torch.ones(len(channels), dtypetorch.float), requires_gradTrue) def forward(self, x): # 先做 ReLU 保证权重非负再归一化 w torch.relu(self.w) w w / (w.sum() 1e-9) return torch.cat([xi * wi for xi, wi in zip(x, w)], dim1)权重初始为 1训练过程中会自动调整。需要注意这个实现是加权 concat 的简化版真正的 BiFPN 还会反复跨尺度迭代融合但毕业设计阶段这个改动已经足够说明问题。项目里提到“充分利用中小型目标检测层”对应的是在 neck 中显式保留 P3 层输出并用 BiFPN 加权让 8 倍下采样的小目标特征在融合时获得更高权重而不是被大目标特征淹没。3.3 CA 注意力机制把位置信息编码进通道注意力SE 注意力只关注通道之间的依赖坐标注意力Coordinate Attention在通道注意力基础上嵌入方向感知把水平方向和垂直方向的位置信息都编码进去。这对异常行为检测很有意义手机通常在驾驶员面部偏下位置烟头位置更靠近方向盘模型需要感知目标在图像中的空间分布才能准确定位。在common.py里 CA 模块的实现要点是分别沿 H 方向和 W 方向做全局池化拼接后卷积再分开还原为注意力权重class CoordAtt(nn.Module): def __init__(self, inp, oup, reduction32): super().__init__() self.pool_h nn.AdaptiveAvgPool2d((None, 1)) self.pool_w nn.AdaptiveAvgPool2d((1, None)) mip max(8, inp // reduction) self.conv1 nn.Conv2d(inp, mip, 1, 1) self.conv_h nn.Conv2d(mip, oup, 1, 1) self.conv_w nn.Conv2d(mip, oup, 1, 1) def forward(self, x): identity x n, c, h, w x.size() x_h self.pool_h(x) x_w self.pool_w(x).permute(0, 1, 3, 2) y torch.concat([x_h, x_w], dim2) y torch.sigmoid(self.conv1(y)) x_h, x_w torch.split(y, [h, w], dim2) x_w x_w.permute(0, 1, 3, 2) return identity * self.conv_h(x_h).sigmoid() * self.conv_w(x_w).sigmoid()拼接位置一般选在每个 C3 模块之后、下采样之前。reduction32是压缩倍数数值越大注意力模块自身参数量越小但对位置信息的表达能力也越弱。作者把 CA 放在 backbone 后几层目标是让高层语义特征带着明确的空间位置信息进入 neck最终反映在边界框回归的稳定性上。3.4 Alpha-EIoU不增加任何参数只改 loss 计算方式CIoU 把宽高比作为惩罚项但宽高比的梯度在训练后期很小导致高 IoU 样本收敛慢。EIoU 直接用宽高差的归一化值替代宽高比让惩罚更直接。Alpha-EIoU 在此基础上对 IoU 项做幂次放大公式上是把IoU^α、中心点距离惩罚、宽高惩罚都做幂次运算α 一般取 3对难样本的梯度放大效果最明显模型会更关注那些 IoU 低、回归不到位的样本。替换位置在utils/metrics.py或utils/loss.py里核心逻辑如下# 假设已经计算好 iou、中心点距离 penalty、宽高 penalty # alpha 控制幂次强度通常取 3 loss torch.pow(iou, alpha) \ - torch.pow(dist_penalty / c_area, alpha) \ - torch.pow(w_penalty / cw, alpha) \ - torch.pow(h_penalty / ch, alpha) return (1 - loss).mean()第一次跑时建议先固定 α3不要同时调学习率。因为 Alpha-EIoU 会让难样本的梯度比 CIoU 大原先能收敛的学习率在这套 loss 下可能偏高如果前 20 个 epoch loss 震荡明显就把初始学习率降到原来的 0.5 倍再试。4. 训练自己的数据集超参数、启动指令与失败边界4.1 数据组织从标注到 YAML 配置不管数据是自己标注的还是公开数据集第一步都是把目录收敛成 YOLOv5 的格式。异常驾驶行为的类别通常有吸烟、打电话、疲劳、分神、喝水等类别之间样本数量往往极不均衡电话和吸烟样本多喝水样本稀少。训练前先统计一下每个类别的图片数量类别样本少于 200 张的优先做离线增强而不是直接丢进训练。统计完类别分布后检查 labels 格式。每行必须是class x_center y_center width height坐标是归一化到 0~1 的相对值不是像素值。如果标注工具导出的是像素坐标需要先转换# 把像素坐标转成 YOLO 归一化坐标 import os def convert(size, box): dw 1.0 / size[0] dh 1.0 / size[1] return [ (box[0] box[2]) / 2.0 * dw, (box[1] box[3]) / 2.0 * dh, (box[2] - box[0]) * dw, (box[3] - box[1]) * dh, ]这一步出错最常见的现象是训练不报错但 mAP 为 0因为归一化坐标写错会导致所有目标框偏移出图像范围YOLOv5 在 loss 计算时直接把这些样本过滤掉了。4.2 train.py 关键参数解析与启动命令数据准备好之后用以下命令启动训练。cfg指向的是 3.2 节里改过的模型结构文件data指向数据集 yamlhyp是超参数文件python train.py \ --data data/abc.yaml \ --cfg models/yolov5s_abc.yaml \ --weights yolov5s.pt \ --epochs 300 \ --batch-size 16 \ --img 640 \ --workers 8 \ --device 0--img 640表示输入尺寸是 640×640显存不够时先降到 512不要动 batch size因为 batch size 太小会导致 BatchNorm 统计量不稳定。--workers 8是数据加载线程数Windows 下建议改成 4Linux 保持 8 或更高都没问题。--weights yolov5s.pt是预训练权重不写这个参数会从头训练收敛速度明显变慢。4.3 超参数文件里最值得动的几个值超参数文件data/hyps/hyp.scratch-low.yaml里值得改的核心参数列在下表。不要所有参数一起调一次只动一个否则无法判断哪个改动起了作用。参数默认值改动方向适用场景lr00.01降到 0.005Alpha-EIoU 梯度变大时防止震荡mosaic1.0降到 0.5小目标多、拼接后目标过小mixup0.0提到 0.2类别样本不均衡时缓解过拟合hsv_h0.015降到 0.005夜间监控场景画面偏暗过度调色增强反而扰乱特征fliplr0.5保持 0.5驾驶场景左右镜像仍然合理可保留mosaic降到 0.5 之后前 10 个 epoch 仍建议保持 1.0等模型学会了基本特征再降低因为 Mosaic 在早期能显著提升小目标特征的学习效率。这个策略对应 YOLOv5 代码里的mosaic参数可以在训练中期动态调整但不是简单地在命令行改数值需要改 train.py 里对超参数的更新逻辑所以如果你只是要快速出结果直接固定一个值最省事。4.4 标签错位与显存不足的排查顺序训练时 loss 正常下降但验证集 mAP 为 0先查标签文件类别索引是不是从 1 开始很多标注工具默认从 1 编号YOLOv5 要求从 0 开始。再查 labels 和 images 文件名是否严格一一对应多一个缩放副本或重复一张图都会造成对齐错位。用一个简单命令就能快速排查find data/abc/labels -name *.txt | head -5 | xargs -I {} cat {} # 每行第一列必须在 [0, nc) 范围内显存不足时优先关闭--workers并把--img降到 512其次是 batch size 减半。如果降低分辨率后 mAP 明显下降就说明数据集里小目标多尽量保持 640 输入先用 GhostConv 压模型体积来换取显存空间而不是牺牲输入尺寸。5. 异常驾驶行为推理实战与界面化半自动标注5.1 detect.py 推理参数与输出管理训练完成后用runs/train/exp/weights/best.pt做推理。best.pt是根据验证集 mAP 保存的最优权重last.pt是最后一个 epoch 的权重两者不要搞混python detect.py \ --weights runs/train/exp/weights/best.pt \ --source data/abc/test.mp4 \ --conf-thres 0.35 \ --iou-thres 0.5 \ --img 640 \ --save-txt \ --project runs/detect--conf-thres 0.35表示只有置信度高于 0.35 的框才会被输出异常行为检测场景阈值不建议低于 0.3否则误检框会很多。--iou-thres 0.5用于 NMS两个高度重叠的框会被合并。加了--save-txt会在输出目录里为每帧生成同名的 txt 文件格式和训练标签一致这是后面半自动标注的基础。5.2 连续帧判定把单帧结果变成行为结论推理模型输出的是单帧检测结果但“异常行为”是一个时序概念单帧出现一个手机框可能是误检连续 15 帧里被检出才能真正触发告警。我一般会封装一个滑动窗口聚合器class AbnormalAggregator: def __init__(self, window15, threshold0.7): self.window window self.threshold threshold self.queue [] def update(self, preds): # preds 为当前帧所有检测结果的 [class_id] 列表 flag 1 if any(cls in [0, 1, 2] for cls in preds) else 0 self.queue.append(flag) if len(self.queue) self.window: self.queue.pop(0) return sum(self.queue) / len(self.queue) self.thresholdwindow15表示取最近 15 帧的判断结果threshold0.7表示其中至少 11 帧检测到异常才触发。调高 threshold 能降低误报率但对持续时长较短的异常行为比如快速拿起手机又放下可能会漏报。具体业务里先跑一段真实监控视频统计误报率之后再做微调。5.3 用推理结果自动生成标注数据作者提到“通过界面操作完成数据集的自动标注”常用的半自动标注流程是先用训练好的模型对未标注图片批量推理把结果转成 labelme 的 json 格式在 labelme 里只修正错误框而不是从零画框。# 将 YOLO txt 转成 labelme json 需要额外安装 labelme 库 # 标注流程如下 # 1. detect.py --source unlabeled/ --save-txt --conf-thres 0.5 # 2. 遍历检测结果生成 shape 数据 # 3. labelme 打开 json 人工修正这个方案的效率提升明显一个熟练标注员从零画框平均每张图需要 30 秒用推理结果预标注后只需检查修正每张图能缩短到 8 秒左右而且新增异常行为类别时先用老模型预标注再从零修正比完全手动标注更快。修正后的数据重新加入训练集迭代两轮后模型对新增类别的检测能力会明显上升。6. 效果验证mAP、参数量与 AB 对照的观察技巧6.1 val.py 验证指令与指标读取训练完成后不能只看训练集 loss必须跑一遍验证集得到标准指标python val.py \ --data data/abc.yaml \ --weights runs/train/exp/weights/best.pt \ --img 640 \ --batch-size 32 \ --conf-thres 0.3 \ --iou-thres 0.5输出会给出每个类别的 precision、recall、mAP0.5 和 mAP0.5:0.95。异常驾驶场景通常更看重 recall因为漏报一个打电话行为的代价比多报一次要高。如果 recall 低而 precision 高优先去补数据而不是调阈值。6.2 AB 对照实验怎么设计这篇项目里四条改进建议分三组做消融基线 YOLOv5s、只加 GhostConv、加 GhostConvBiFPNCA、全组合四组。每组训练同样的 epoch记录最佳 mAP 和参数量大小模型参数量mAP0.5说明YOLOv5s 基线基准基准对照组 GhostConv明显下降基本持平验证轻量化不损失精度 BiFPN CA略微上升小幅提升多尺度融合与注意力组合全改进轻量级水平最高最终提交方案从复现过程看GhostConv 把参数量压得最狠代价是 mAP 可能有小幅波动补上 CA 注意力后定位精度会追回来。所以不要只看最终 mAP把每组的参数量和 mAP 画成表答辩时更有说服力。6.3 三个常被忽略的坑第一Alpha-EIoU 会让 loss 曲线在前期震荡这不代表模型坏了。观察前 50 个 epoch 的下降趋势即可如果 100 个 epoch 后仍然剧烈震荡再把lr0减半重新训练。第二BiFPN 的加权权重初始值过小会导致 neck 输出数值异常验证时 mAP 一直接近 0解决办法是把self.w初始化从 0 改成 1。第三Mosaic 增强在训练后期可能让模型过度拟合拼接图的边界特征如果验证集精度在某个 epoch 后开倒车把mosaic从 1.0 动态降到 0.3 再跑最后一轮通常能救回 1~2 个点的 mAP。本文还有配套的精品资源点击获取
返回列表