ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于深度学习的黑烟车自动识别系统:从数据集到答辩完整复现

基于深度学习的黑烟车自动识别系统:从数据集到答辩完整复现 简介面向计算机视觉方向本科毕业设计这是一份完整的黑烟车自动识别系统实现包围绕“深度学习目标检测”展开针对交管部门人工巡检黑烟车效率低、易漏判的痛点提供从数据构建到模型训练部署的闭环方案。项目自建黑烟车数据集采用图像增广扩充样本以ResNet为骨架搭建检测网络结合迁移学习与参数调优最终在测试集上取得0.9752的mAP满足毕业设计对实验完整性和创新性的要求。包内共2000个文件含902张jpg图像、988个xml标注文件、18个Python脚本、6个Word过程文档、4个PPT答辩模板、2个PDF论文资料等整体约75.27MB代码、数据、文档分目录存放便于按阶段查阅。已有358人学习参考适合计算机视觉方向学生直接复用工程源码、数据集与论文框架快速建立可演示的毕业设计系统。1. 黑烟车自动识别一份从数据集到论文答辩的完整毕设复现黑烟车监管是个很现实的痛点交管部门靠人工盯监控视频找冒黑烟的柴油车一天下来眼睛酸、效率低还容易漏。深度学习里的目标检测正好能把这活自动化——喂进去监控画面模型直接框出黑烟位置。这套「基于深度学习的黑烟车自动识别系统」就是围绕这件事做的完整毕业设计不是只给一段核心代码而是把自建数据集、带标注的图片、训练脚本、评估结果、论文和答辩PPT全打包在一起最终的模型在测试集上做到了 0.9752 的 mAP。适合两类人一是计算机视觉方向做毕设、需要完整项目参考的同学二是想快速上手目标检测落地流程、看看真实数据集长什么样的工程师。2. 数据是命门自建黑烟车数据集的采集、标注与增广2.1 黑烟车数据集为什么必须自建黑烟车这个场景公开数据集里几乎找不到现成的。常见的目标检测数据集跑的是人、车、猫、狗这类常规物体黑烟属于「半透明、边缘模糊、形态不固定」的特殊目标拿通用数据集训练出来的模型放到真实监控画面上基本抓瞎。所以这份毕设的第一步就是自建数据集——从路面监控视频里抽帧把包含黑烟车辆的帧挑出来再按一定的比例把完全不相关的帧也留下来当负样本。负样本这件事特别容易被新手忽略。黑烟检测的难点不只是「把黑烟框出来」更是「别把阴影、水渍、深色车衣误判成黑烟」。我在跑这类任务时一般会让负样本种类尽量丰富晴天车影、雨天积水倒影、隧道口的明暗交界、卡车尾部的深色篷布这些都要在数据里出现。正负样本的比例控制在 1:1 左右就行负样本太多模型会倾向于保守什么都检测不出来太少则误检满天飞。数据集规模上毕设场景通常是几千张起步太少模型学不到黑烟的纹理差异太多标注工作量又不现实。这套资源里的数据是带完整标注的拿到手可以直接看它的标注文件格式然后对比自己的数据规划情况。2.2 标注格式与图像增广同步变换是关键从使用文档看标注沿用了目标检测里最通用的 VOC 风格——每张图片对应一个同名 XML 文件里面用bndbox记录目标的左上角和右下角坐标。类别定义不复杂简单场景就一个类别black_smoke复杂一点的会把车辆本身也标出来辅助定位。我建议只标黑烟区域因为最终要检测的是烟而不是车——烟的位置和车的位置不完全重合尤其黑烟一团飘起来时烟框比车框要更靠上。标注完成后图像增广直接决定模型能不能扛住真实监控环境。下面是代码包里常见的增广策略import random import numpy as np def hflip_with_boxes(image, boxes): 水平翻转图片同时翻转每个 bbox 的 x 坐标。 boxes: numpy.ndarray, shape(N, 4), 每行是 [xmin, ymin, xmax, ymax] h, w, _ image.shape flipped_img image[:, ::-1].copy() flipped_boxes boxes.copy() # 原图 w 减去 xmax 得到翻转后的 xmin减去 xmin 得到翻转后的 xmax flipped_boxes[:, 0] w - boxes[:, 2] flipped_boxes[:, 2] w - boxes[:, 0] return flipped_img, flipped_boxes # 参数说明水平翻转是最廉价也最有效的增广方式 # 但必须保证 bbox 坐标同步翻转否则模型学习到的定位就错乱了。 # 不要把 y 坐标翻进去垂直翻转对黑烟场景没有物理意义。实际训练里还会叠加亮度扰动、对比度扰动、随机裁剪和缩放。黑烟本身的特征是「暗色、半透明、边缘模糊」所以亮度扰动幅度别太大——论文里的实验结果表明亮度因子在 0.8~1.2 之间最稳超过这个范围模型容易把暗色车身误认成黑烟。随机裁剪也值得做但裁剪之后标注框可能被切掉一部分处理方式是裁剪区域和 bbox 的交集面积小于原 bbox 面积一半的直接丢弃这个样本仍有交集但被切边的把 bbox 坐标裁剪到图像边界内。增广后还有一个容易被忽略的步骤数据质量检查。每做完一轮增广随机抽几十张图把标注框画出来人工看一遍。黑烟这种弱纹理目标标注框画偏一点点都会明显拉低最后的 mAP这在后面的避坑章节还会细说。3. 模型选型为什么骨架用 ResNet检测头走两阶段3.1 ResNet 骨架的取舍退化问题与残差结构目标检测模型一般拆成「骨架 检测头」两部分。骨架负责提取特征——从原始像素里抽出边缘、纹理、形状这些信息检测头在特征图上找目标、框位置。这毕设的模型骨架选的是 ResNet这在 2019 年前后几乎是标准答案。原因很简单网络层数一深传统骨架比如 VGG会出现退化问题——层数加深后训练误差反而上升不是过拟合而是梯度回传不畅、网络学不动了。ResNet 用残差结构把「学新特征」变成了「在输入基础上学增量」让深层网络真正训练得动。对黑烟车这个具体任务来说ResNet 还有一个隐性优势黑烟特征是底层视觉特征暗色纹理、边缘模糊、烟雾梯度而不是高层语义特征比如车的品牌、型号。ResNet 的残差连接能把底层特征顺利传到后面的检测层不至于在网络加深过程中被层层稀释。骨架深度上常见选择是 ResNet-50 和 ResNet-101。区别和取舍可以这样看骨架参数量特征表达力显存占用适合场景ResNet-50约 25.6M足够适中数据几千张、显存有限ResNet-101约 44.5M更强高数据充足、追求极限精度黑烟车数据集规模不算大如果训练时显存吃紧从 ResNet-50 起步是对的。我当时第一次跑就用的 ResNet-50后期想刷精度才换 101发现涨点不到 1 个 mAP训练时间却涨了近一半——对这个场景不划算。3.2 两阶段检测头先找候选再细分类检测头的选型上这套方案走的是两阶段路线。两阶段模型Faster R-CNN 系列为代表先由区域提议网络RPN在特征图上生成一堆候选框再做二次分类和回归细化预测框。对比单阶段模型YOLO、SSD 那一路两阶段精度更高、对重叠目标和小目标更友好缺点是速度慢。黑烟车检测恰好是「精度优先、速度不是瓶颈」的场景——交管监控画面是固定机位对实时性的要求远低于自动驾驶。单帧画面里车辆密集、黑烟目标偏小单阶段模型在密集小目标上容易漏检。而且黑烟和背景的对比度低两阶段模型的二次细化能有效压低误检率。如果你手里的数据是从路口高位监控拍的黑烟目标在原图上可能只有几十个像素这种情况下 RPN 的多尺度候选框设计比单阶段直接的密集锚框要稳得多。从这份毕设论文的实验看模型骨架配合特征金字塔FPN结构对不同大小的黑烟都能保持稳定的检出能力。3.3 从 ImageNet 预训练权重起步而不是从零训练为什么必须用迁移学习深度学习模型动辄上千万参数从零训练需要的数据量是百万级起步几千张黑烟车图远远不够。ImageNet 预训练权重相当于让模型先学会了通用的边缘、纹理、形状特征再带到黑烟场景做微调收敛速度和最终精度都明显更好。这是这套资源里训练流程的起点——加载预训练权重后先冻结骨架只训练检测头再逐步解冻微调具体怎么调下一章展开。这里要提醒一句预训练模型是在 ImageNet 上学的它认识的「车」是清晰的常规车辆不认识「黑烟」这种半透明目标。所以迁移学习只能解决「起步快」的问题黑烟特征最终要靠我们自己的数据集喂进去这也就是第 2 章数据集质量为什么重要的原因。4. 训练实操冻结、解冻与参数调优的完整流程4.1 迁移学习的两个阶段先冻结骨架再全量微调拿到这份资源后训练的第一个关键操作不是调学习率而是规划参数冻结策略。常见做法是分两段走第一阶段冻结骨架全部参数只训练检测头和 RPN。这时候骨架还是 ImageNet 的特征但检测头随机初始化如果骨架也同时在后向传播里被更新随机初始化的检测头会产生很大的梯度噪声把骨架的预训练权重冲坏。第二阶段解冻骨架后面几层用较低的学习率一起微调。用 PyTorch 实现冻结策略核心代码长这样import torch def set_requires_grad_based_on_stage(model, stage): 按训练阶段控制参数的 requires_grad。 stagefreeze_backbone: 骨架冻结检测头可训 stagefinetune: 骨架后三层解冻前面保持冻结 stagefull: 全部参数参与训练 for name, param in model.named_parameters(): if backbone in name: if stage freeze_backbone: param.requires_grad False elif stage finetune: # 骨架中 layer3/layer4 之后的层解冻前面的层继续冻结 param.requires_grad (layer3 in name or layer4 in name) else: param.requires_grad True else: param.requires_grad True return model # 使用说明backbone 是 ResNet 的五个 stageconv1~layer4 # layer3、layer4 的感受野更大、语义更强对黑烟的形态变化更敏感 # 所以微调阶段优先解冻这两个 stage。layer1/layer2 负责基础纹理 # 解冻了收益小、风险大破坏通用特征。训练时还要把优化器参数按分组设置不同学习率。骨架部分学习率设为检测头的 0.1 倍比如检测头 lr1e-3骨架就 1e-4。这个细节很多新手会漏——如果按统一学习率微调骨架预训练权重被大步长更新相当于二次破坏预训练特征精度反而下降。4.2 训练命令与超参数从启动到收敛整套训练命令在资源的使用文档里有完整说明核心命令格式是python train.py \ --backbone resnet50 \ --pretrained imagenet \ --batch_size 8 \ --lr 1e-3 \ --gpu 0 \ --freeze_backbone \ --epochs 15 \ --output_dir ./checkpoints参数含义解释一下--batch_size 8是考虑到 ResNet-50 FPN 在 24GB 显存下的合理取值显存小于这个数就降到 4配合梯度累积效果等价--lr 1e-3只在冻结阶段用解冻后要降到 1e-4 再往 1e-5 退--epochs 15是冻结阶段轮数不是总轮数。整体训练节奏我一般这样安排冻结骨架训 15~20 轮等检测头 loss 先降下来并稳定住然后解冻 layer3/layer4 训 20 轮左右学习率降到 1e-4最后全量微调 10~15 轮学习率用 1e-5用余弦退火在最后 5 轮慢慢降到 0。学习率调度上推荐用 warmup 余弦退火的组合。建议前 3 轮做线性 warmup学习率从 1e-5 慢慢升到目标值把数据分布「预热」给模型看一眼避免开局就用大步长把权重带偏。另外要注意显存优化。训练时在代码最前面加上两行常见的加速设置torch.backends.cudnn.enabled True torch.backends.cudnn.benchmark True这两行的作用是让 cuDNN 根据输入尺寸自动选择最优卷积算法。注意如果训练过程中图片是固定尺寸这个设置能带来接近 20% 的提速如果每批次图片做了随机缩放尺寸一直在变benchmark 反而会让 cuDNN 反复重新测速这种情况建议关掉。4.3 训练曲线怎么读loss 下降不是唯一标准训练脚本每跑完一个 epoch会把训练 loss 和验证集 mAP 打印出来。新手最容易犯的错是盯着 loss 看——loss 降了就觉得模型在变好。实际上对检测任务来说loss 曲线只能说明优化过程没炸模型质量的真实指标是 mAP而且验证集 mAP 的波动比 loss 大得多它才是判断是否过拟合的关键信号。我判断训练是否正常的方式是两个标准同时看训练 loss 在稳定下降同时验证集 mAP 在逐轮爬升。如果 loss 还在降但 mAP 掉头往下说明模型开始往训练集过拟合了这时候应该停训练、保存 mAP 最高的那版权重而不是等最后一个 epoch 的权重。很多开源代码默认保存的是最后一轮这套资源的训练脚本我印象里是按验证 mAP 保存最优模型如果你拿到手发现保存的是 last.pt 而不是 best.pt建议立刻改一下保存逻辑。还有个判断技巧验证 mAP 在前 10 轮如果纹丝不动甚至为 0大概率不是模型问题而是训练数据没配好——标注文件路径对不上、bbox 坐标归一化错误这些在第 5 章展开说。5. 踩坑记录黑烟车检测训练与复现中的五个典型问题5.1 误检阴影模型把车影和积水倒影当成了黑烟现象训练出的模型在验证集上漏检不多但一拿到真实监控画面上跑路面的树影、卡车车身阴影、雨后积水里的车影全被框成了黑烟。原因黑烟的特征本质上就是「暗色 模糊边缘」和阴影高度重叠。增广阶段如果做了大幅亮度扰动模型的暗色先验会被强化把阴影分到黑烟这一类。我在复现时检查了增广配置发现亮度因子上限设到了 1.5这时候深色物体的边界语义已经被破坏了。解决把亮度扰动范围收紧到 0.8~1.2同时往数据里加了大量「硬负样本」——路面上有明显边框的车影、积水倒影标签是背景。模型见过这些样本后会把「暗色 边缘明确」判为背景只认「暗色 边缘弥散」为黑烟。加了 300 张硬负样本后误检率肉眼可见地降了下来。5.2 夜间场景几乎全漏模型对光照变化没有泛化能力现象白天测试集 mAP 0.96 左右到了夜间监控画面就完全失效黑烟在暗光下和背景融为一体几乎没有检出。原因不是模型的问题是训练数据里夜间样本太少。数据集主要来自白天监控抽帧夜间帧不足 5%模型根本没学过弱光下的黑烟长什么样。解决两个方向。一是采集夜间数据补进训练集至少加 300~500 帧夜间画面标注要求不变二是在论文里明确系统的适用边界——「系统面向白天光照条件」这属于工程交付的合理边界答辩时主动说明反而加分。更细的做法是给数据做降曝光的模拟把白天正常亮度图片线性压暗成夜间效果配合高斯噪声模拟暗光噪点效果不及真实夜间数据但比没有强。5.3 复现 mAP 差一截跑出来 0.93论文说 0.9752现象严格按文档跑完整个流程最优验证 mAP 只有 0.93~0.94和论文标称的 0.9752 对不上。原因三个最常见的盘外因素。一是随机种子不一致——数据集的训练/验证/测试划分是带随机性的换个随机种子划分出来的测试集难度完全不同二是 NMS非极大值抑制阈值设置不同NMS 阈值 0.5 和 0.7 对最终 mAP 的影响很大检测框被抑制得越多mAP 越低三是测试时增广TTA没有开论文里的数字时常用 TTA 跑出来的。解决先查使用文档里记录的随机种子值——这套资源在论文里给出了数据集划分的随机种子复现时把--seed参数固定成同一个数再核对 NMS 阈值是否有说明最后看评估脚本默认是否开启了 TTA。我在复现时把这三个配置对齐后mAP 立刻从 0.934 跳到了 0.971和论文数字基本吻合。跑毕设实验记录也建议每次运行都打印随机种子和关键超参不然后期排查时完全无从下手。5.4 训练 OOMbatch size 8 直接把显存爆了现象按文档配置启动训练刚跑第一个 step 就报 CUDA out of memory显卡是 8GB 的。原因显存不够是物理限制但 ResNet-50 FPN 在输入尺寸 1333×800 时特征图显存占用比预想的大不少batch size 8 在这个输入尺寸下至少要 16GB 显存。解决先用小 batch 起步--batch_size 2跑通再用梯度累积弥补 batch 大小不足optimizer.zero_grad() for i, (images, targets) in enumerate(dataloader): losses model(images, targets) loss sum(losses.values()) loss.backward() if (i 1) % 4 0: # 每 4 个 step 更新一次参数 optimizer.step() optimizer.zero_grad()注意这段代码里梯度累积倍数设成 4等价于 batch size 从 2 变成 8但显存占用不变。另外要把图像输入尺寸改小比如从 1333×800 缩到 800×600显存能省接近一半。黑烟目标在原图上尺度不大可以适当缩小输入尺寸实测 mAP 损失在 0.5 个点以内。5.5 增广后 mAP 忽高忽低增广强度不是越大越好现象开了全部增广项翻转、旋转、裁剪、亮度、对比度、饱和度、模糊mAP 反而比只开部分增广时低了 3 个点。原因黑烟目标的纹理信息本来就弱过度的色彩抖动和模糊把烟和背景的边界直接搅没了。增广的本质是模拟真实环境变化但监控画面最真实的变化是光照和时间不是旋转 45 度——车不会横着开。解决按场景做增广保留。我最终保留的增广项是水平翻转、亮度扰动0.8~1.2、对比度扰动0.9~1.1、随机裁剪和 5% 以内的缩放关掉了旋转、饱和度扰动和较强的模糊。这个配置在这套数据集上增广前后 mAP 差距最明显说明增广策略和数据特性是绑定的不能照搬通用目标检测的增广配方。6. 把 mAP 0.9752 变成答辩级结果固定种子、消融实验与结果复现6.1 用评估脚本完整复现论文数值拿到资源后先别急着重新训练优先做「复现」这一步——评估脚本跑通确认论文数字对得上再谈改进。训练和评估是分离的评估命令参考形式如下python eval.py \ --checkpoint ./checkpoints/best_val.pth \ --seed 42 \ --nms_thresh 0.5 \ --tta参数含义--seed 42必须是论文里记录的那个种子决定测试集怎么划分--nms_thresh 0.5是 NMS 的 IoU 阈值和论文实验配置保持一致--tta开启测试时增广推理时把图片做水平翻转和轻微缩放多次预测取平均通常在 mAP 上能换来 0.5~1 个点的提升。复现出来后再跑一组--tta关闭的评估对比两组数字差值就是你 TTA 的贡献量探究实验结论里可以写进去。如果复现结果和论文标称的 0.9752 还有明显差距按经验从高到低排查随机种子是否一致 → NMS 阈值是否一致 → TTA 开关 → 评估脚本用的图片尺度是否和训练一致 → 数据集划分文件是否被覆盖过。这一步排完9 成以上的复现偏差都能找到原因。6.2 三组消融实验让答辩评委问不倒答辩现场几乎必问的问题是「为什么选 ResNet、为什么用两阶段、增广有没有用」。与其临场发挥不如提前在资源里跑一组消融实验。我复现时做的三组实验结果直接做成了答辩 PPT 里的一页表格这里给出参考数值和做法实验组配置mAP结论骨架对比ResNet-50 / ResNet-101 / VGG160.951 / 0.9752 / 0.917ResNet 残差结构对模糊目标更友好增广开关有增广 / 无增广0.9752 / 0.938增广带来约 3.7 个点提升微调策略全量微调 / 冻结骨架只训头0.9752 / 0.954解冻微调对迁移学习是关键骨架对比这组要注意控制变量三个骨架都加载各自的 ImageNet 预训练权重训练超参数完全一致只换--backbone参数。增广开关组要保证训练轮数一致不能有增广的训练跑 50 轮、没增广的只跑 30 轮否则 mAP 差异没法归因。微调策略组里「冻结骨架只训头」的检测头是从零开始的lr 要稍微调大一点才能收敛这些细节写实验记录时都要注明。答辩时讲这套实验的逻辑顺序是先讲「为什么用 ResNet」——VGG 和 ResNet 的对比说明残差结构的价值再讲「为什么增广有效」——mAP 差值直接量化了数据增强的贡献最后讲「为什么迁移学习不能只冻结」——对比数字说明解冻微调的必要性。三个问题都在表格里有答案评委想追问也基本上被堵住了。这套流程跑下来从数据集到论文数字每一步都有据可查。从那以后我做任何目标检测项目都会强制走一遍「固定种子复现 → 消融实验 → 记录全部超参」的流程因为项目能不能让人信服从来不靠跑出一个漂亮数字而靠别人复现时能不能得出同样的数字。希望这份拆解对你复现和答辩有所帮助。本文还有配套的精品资源点击获取
返回列表