毕业设计选“基于深度学习的道路坑洼识别”这个方向的同学,最近明显多了起来。原因不难理解:既能用到Python、深度学习、图像识别这些当下最热的技术词汇,又有一个清晰可落地的应用场景,数据也相对容易获取。但真正动手做起来,不少人会卡在几个意想不到的地方——数据集不知道怎么搭、模型选型太贪心导致训练根本跑不动、分类还是定位没想清楚就直接开写代码。这篇文章把我自己做类似项目时的完整思路、选型理由和踩坑记录整理出来,希望能让准备做或正在做这个题目的同学少走几个弯路。
1. 这个题目的核心难点:它不是图像分类,而是目标检测
很多同学拿到这个题目,第一反应是“用CNN做分类嘛,把图片分成有坑洼和没坑洼两类”。这个方向只能拿到及格分。实际道路坑洼场景里,一张路面图中可能有多个坑洼,坑的位置、大小、形状都不一样,仅仅判断“图片里有没有坑”既不符合真实巡检需求,在算法难度上也略显单薄。认真做的话,题目本质是一个目标检测问题:不仅要识别是什么,还要给出目标在图像中的位置(边界框,Bounding Box),即“检测”。以通用目标检测任务基准COCO的评价体系衡量,偏小尺寸目标对该类任务的结果影响很大,而道路坑洼恰恰多数属于中小目标。想拿高分,理解清楚“分类 vs. 检测 vs. 分割”这三者的差异是第一步。
什么场景选什么任务,可以直接参考这个判断逻辑:
- 只需统计某一路段有无破损 → 图像分类(二分类或多分类)
- 不仅要知道有没有,还要知道坑在哪、大概多大 → 目标检测
- 需要精确计算坑洼面积、体积,甚至辅助后续维修的用料估算 → 实例分割(如Mask R-CNN)
- 需要区分坑洼、裂缝、修补带等不同病害类别 → 多类别检测
对于本科毕业设计而言,目标检测是性价比最高的选择。它比分类更有技术含量、比分割更容易实现,且公开预训练模型成熟、训练周期可控。如果你的导师要求更高,可以在检测模型基础上加一个分割头做扩展,后面细说。
2. 数据集建设:决定你项目成败的第一道关口
很多教程会把数据集作为一小节带过,实际上数据是这类项目里最耗时、最影响结果的环节。在做道路坑洼识别之前,建议先花时间把数据集方案选型定清楚,否则后面会反复推倒重来。
2.1 数据集来源的三种主流方案
方案一: Kaggle / GitHub 开源数据(省力,但要谨慎)。搜索“pothole detection dataset”能找到若干开源数据集,有的以图片为主,有的自带标注。公开数据质量参差不齐:有些只是单一的柏油路坑洼图片集,有的标注框非常粗糙(有的框把整个坑边都包进去,有的只框了坑心)。如果直接拿来做训练,后期验证效果会很难看。
方案二: 自己拍摄 + 手工标注(可控,但费时)。用手机在不同光照、角度、天气条件下拍路面,再用LabelImg或Label Studio标注。不少同学低估了这项工作的工作量——标注一张图,慢的时候要几分钟,300张图就能耗上一天。大多数人在这一步放弃。与手机拍摄相比,车辆行驶记录仪截帧是更高效也更贴近真实巡检场景的数据来源。
方案三: 公开数据 + 自采数据混合(推荐)。用开源数据做底座,自己补充拍摄一部分带有当地道路特征的数据,混合训练。既解决了数据量不足的问题,又避免了完全依赖开源数据的风格偏差。也是在毕业设计中展示思考深度的加分项。
2.2 类别体系设计:不要一上来就分“坑洼”和“裂缝”
很多同学最初把类别定为“pothole(坑洼)”和“crack(裂缝)”,认为类别越丰富显得项目越厉害。实际中,道路病害种类极其繁多,类别标注模糊时模型学习极易混乱,比如一个坑洼周边往往伴随着裂缝,边界划分不干净,标注时会发现同一张图的同一病害存在两种标签都适用的矛盾情况。作为毕业设计,二分类(背景 vs. 坑洼)或三分类(裂缝、坑洼、修补破碎)就足够了。关键是把目标框标准制定清楚,宁可框的范围略大,也不要把一个目标拆成两三个区域。我实际测试过,一个干净的二分类检测器,比一个标注混乱的三分类检测器的mAP高出近10个百分点。
2.3 数据规模的最低底线
如果使用预训练权重做迁移学习(强烈推荐),一份可用的最低标注数据量大致如下,低于这个规模屡试不爽的结论是效果明显不稳定:
| 数据规模 | 效果预期 | 建议 |
|---|---|---|
| < 300 张标注图 | 泛化能力差,基本只能适配训练集场景 | 不建议直接训练 |
| 300 ~ 800 张 | 能出结果,但需依赖预训练模型和强数据增强 | 入门下限 |
| 800 ~ 2000 张 | 效果比较稳,各类路况基本覆盖 | 推荐范围 |
| 2000 张以上 | 能较好应对真实道路变化 | 充裕 |
提示: 坑洼识别数据集里,**晴天、柏油路、近距离(1~3米)**的图片占绝大多数,模型容易对这些条件过拟合。自己扩展数据时优先补充阴天、雨天、逆光、阴影遮挡、水泥路、远距离拍摄这几类场景,一张顶五张。
3. 模型选型:为什么我建议用轻量型检测网络
选定任务和数据方案后,下一个问题就是选什么模型。这个环节容易踩两个极端:一是极其多见的“哪个模型在排行榜靠前就用哪个”,二是另一个方向上完全忽视算力约束——随便选一个几百兆参数量的模型,在普通笔记本上训练动辄几十个小时,中途显存不够崩掉,人也崩了。毕设选题时,可复现、可解释、可在自己机器上跑通,比模型的新奇程度重要得多。
3.1 主流目标检测模型横向对比
| 模型 | 类型 | 参数量/大小 | 推理速度 | 精度 | 部署难度 | 适合度 |
|---|---|---|---|---|---|---|
| Faster R-CNN | 两阶段 | 较大 | 慢 | 高 | 中 | 中等 |
| YOLOv5 / YOLOv8 | 单阶段 | 中/小 | 快 | 高 | 低 | 高 |
| SSD | 单阶段 | 中 | 快 | 中 | 低 | 中 |
| EfficientDet | 单阶段 | 中 | 中 | 中高 | 中 | 中 |
我最终选择YOLOv8,原因有三:
- 工程链路完整:数据标注格式、训练脚本、验证指标、模型导出一步到位,不需要自己造轮子,这对时间宝贵的毕业设计来说太重要了。
- 对中小目标相对友好:针对路面坑洼这类在图像中所占像素比例不大的目标,YOLOv8在保持速度的同时,对中/小目标的召回率明显优于前代版本。
- 部署方式灵活:可以把模型导出成ONNX或TensorRT格式,在CPU上跑推理也勉强可用,现场演示时不会出现“模型跑不动”的尴尬。
有经验的老师问起“为什么不选Faster R-CNN”,你完全可以说:“Faster R-CNN作为两阶段检测器精度上限确实更高,但考虑到道路巡检场景对实时性的要求,以及本课题实际算力资源有限,单阶段检测器在相近精度下推理速度提升数倍,更适合实际部署条件。”这句话一出口,答辩老师就知道你不是随便拿个模型套上去的。
3.2 预训练权重的正确用法
无论选哪个模型,都要使用在大型通用数据集上预训练过的权重作为初始参数,而不要随机初始化从头训练。在数据量有限的情况下,随机初始化训练不仅收敛慢,最终精度也可能差出一大截。这个在论文中要明确写出模型结构(Backbone通常是标准卷积网络,Neck通常为特征金字塔)和迁移学习策略,这部分内容属于答辩高频考点,需要仔细理解。比如卷基层和特征金字塔是什么意思,模型究竟学到了什么,这几段的阅读与准备值得花时间。
4. 核心实现细节:数据划分、训练策略与调参
讲完选型,进入动手环节。这一部分把从数据准备好到模型能正常收敛的流程完整走一遍。
4.1 数据划分的坑:不要随便随机划分
常规做法是把数据按8:1:1随机划分为训练、验证、测试。但路面数据有个特性:同一段路连续拍摄的图片高度相似,随机划分会导致几乎相同的图像同时出现在训练集和验证集中,验证指标虚高。正确的做法是按路段或按视频片段划分数据,确保同一路段的数据只出现在同一个集合里。这部分内容在答辦时可以展开叙述,比如“为保证模型泛化能力的评估有效性,数据划分阶段以路段为粒度进行划分,以避免帧间相似数据导致的评估偏差”,这比“我将数据按8:1:1划分”有说服力得多。
4.2 训练完整流程(以YOLOv8为例)
安装环境(建议Python 3.8-3.10 + PyTorch,CUDA按显卡驱动对应版本装)后,整流程如下:
# 1. 安装必要库 pip install ultralytics opencv-python # 2. 按指定目录结构放好数据和标签 # datasets/ # ├── images/train # ├── images/val # ├── labels/train # └── labels/val # 3. 编写数据配置文件 pothole.yaml # 内容示例: # train: datasets/images/train # val: datasets/images/val # nc: 1 # names: ['pothole'] # 4. 开始训练(关键参数说明见下方) yolo detect train data=pothole.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=8参数值参考:
- model=yolov8n.pt(n代表nano,最小型号):不用yolov8x,毕设数据量通常撑不起大模型的训练,且小模型在CPU上跑推理也能流畅演示。
- imgsz=640:输入分辨率,640是精度和速度比较均衡的常用值。如果你用OpenCV从视频中截帧,且坑洼普遍很小,可以尝试imgsz=960来提升小目标性能。
- batch=8:这是受显存约束设置的值。显存不够时优先减小batch,而不是减小imgsz。
- epochs=100:不用迷信更大epoch数;小数据集上不用早停法(patience),常设patience=30持续观察最佳模型输出。
4.3 训练结果怎么看:不只是看loss
训练完成后,ultralytics会输出一组指标。很多同学只看mAP数值,但真正要关注的是PR曲线和混淆矩阵:坑洼类别单独看Precision和Recall谁更差,是漏检问题更突出还是误报问题严重。我训练时遇到的一个典型情况是:用公开数据训练的模型,对自己的测试视频检测,Precision高但Recall低,说明大量小坑洼被漏掉。后处理时降低置信度阈值虽然能召回更多目标,但误报也随之上升,实际还是要回到数据层面补齐角度和距离的多样性再继续迭代。指标不对,先查数据和标注,永远不要先加模型复杂度。
5. 从模型到演示系统:如何把它包装成一个完整的项目
毕业设计一般要求有一个可演示的系统,不能只看命令行里的检测框。在这个环节多数同学会卡在“模型有了,不知道怎么放到界面里”。好在得益于深度学习工程化的成熟,这一步的难度已经降得很低。
5.1 模型导出与加速
训练完成后,把权重导出为ONNX格式:
yolo export model=best.pt format=onnx dynamic=True导出后可以用OpenCV的DNN模块或ONNX Runtime做CPU推理,也可以在GradCAM的辅助下输出热力图用于展示:
GradCAM可以在论文中作为“模型可视化分析”的素材,展示模型重点关注的是不是坑洼本身(而非背景纹理),这个细节很能拉高项目完整度和答辩印象分。
5.2 用Gradio快速搭一个Web演示界面
与其用Tkinter写一个丑丑的桌面程序,不如直接上Gradio——三行代码就可以把模型封装成交互页面:
import gradio as gr from ultralytics import YOLO model = YOLO("best.onnx") # 也可以加载pt文件 def detect(image): results = model(image) return results[0].plot() gr.Interface(fn=detect, inputs="image", outputs="image", title="道路坑洼检测Demo").launch()这个Demo支持图片上传、实时显示检测框和置信度,当场就能在浏览器里点着玩。想更进一步可以读rtsp拉流视频检测,把摄像头画面输进模型推理并在帧上绘制结果。这也是我在实际应用里感觉最出效果的一部分。
5.3 扩展成实例分割的可行性
如果你的数据集标注是分割掩码(很多开源数据集中是掩码格式),那可以一步到位直接使用YOLOv8-seg进行分割训练,输出每个坑洼的像素级轮廓。答辩时可以说“本课题在检测基础上进一步完成了分割模块,可辅助计算坑洼面积,为道路养护的维修用料估算提供参考”,但这需要额外投入标注工作,且模型体积变大。建议先完成检测再按时间余量决定是否加分割,核心主线永远是完整走通一个检测项目。
6. 实训避坑记录:四类高频问题的排查思路
6.1 Loss不下降或直接变NaN
掉进这个坑十有八九是数据问题,而不是模型问题。排查顺序建议按以下次序推进:
- 检查标注文件是否与图像文件名一一对应(绝对路径缺失是常见低级错误)
- 检查是否存在0尺寸的标注框,这种会直接传染给Loss计算
- 检查学习率是否过大(早停和优化器设置不当导致的震荡,有时候priority还要高于数据检查,实际操作中遇到过很多次)
- 用tb files检查DataLoader读入情况,确认不是路径错误导致的空batch
6.2 不收敛,loss像心跳信号一样忽高忽低
这类情况通常与batch size过小或学习率设置失当有关,用默认学习率时加大batch通常能缓解。另一种常见的表现形式是loss稳步下降但mAP纹丝不动——这时需要坦诚地面对:模型在训练集上表现有多好?如果训练集很好,验证集完全不行,直接原因通常是过拟合。应对过拟合优先采用提升数据增强强度、早停、以及sub-class数目(这里指把标注不清的难例删掉)这三板斧,而不是更换骨干网络。
6.3 对小坑洼基本免疫,一个都检测不到
这是最典型的道路坑洼识别痛点。原因有两层:其一,小目标在特征金字塔顶端被压缩得信息损失严重;其二,数据集中坑洼普遍距离近、面积占比大,模型默认“坑洼是大目标”。实操层面性价比最高的办法是:把训练图切成小块。例如用滑窗把1080P的大图切分成彼此重叠的小图,标注信息同时切分,相当于把每个目标“放大”了再做训练。这一招亲测有效,比换任何特征融合模块都直接。相比之下,数据增强中的mosaic、copy-paste更合适作为辅助方法配合使用。
6.4 推理时,对井盖、影子、绿色树叶疯狂误报
同样的坑洼特征在沥青路面区域和阴影边界区域容易被混淆,导致误检率偏高。这很大程度上是训练数据单一(几乎所有负样本都来自晴天柏油路)造成的。处理方案有三层:
- 第一层:在标注时把井盖、修补块、下水道口等容易引起混淆的目标单独分出区域,并在负样本集中扩充这类“干扰物”图片。严格来说类别也应该调整,但优先在数据层面修正。
- 第二层:训练中启用背景负样本采样,随机加入不含坑洼的路面图,强制模型学习背景差异。
- 第三层:简单后处理——同一视频帧的连续检测中,用置信度阈值和NMS阈值来抑制抖动的误报框。高级一点的可以用时序滤波,对连续多帧结果做投票,实时巡检效果能进一步改进。这段内容如果写进论文里,属于有工程价值的方法改进,值得展开。
6.5 环境配置崩溃
某个版本的CUDA和PyTorch不匹配、ultralytics更新后API不兼容,都能瞬间消耗掉一下午。针对这一点最有效的急救措施是:使用Anaconda为项目单独建立虚拟环境并锁定依赖版本,例如固定ultralytics==8.2.0,并在论文附录列出依赖清单。作为完赛(且心情平稳)的必要条件,把环境坑提前填平,是最省时间的投资。“我当时因为版本不一致,debug了两个下午,后来发现就是yolo导出的ONNX在OpenCV DNN模块中NMS参数名不同导致的”,这类经历如果你也遇到过,可在博客评论区抱团取暖。
7. 从“能跑通”到“有亮点”:哪些方向值得再深入一步
当主流程完全跑通之后,如果时间还有富余,以下三个方向可以给毕设增加一些真实有用的竞争力:
- 方向一:多病害多类别检测。从“坑洼检测”扩展到“坑洼、裂缝、修补不良”三种路面的同时检测。这是一个天然的多类别增量,对道路养护的实际场景有更高业务价值,答辩时也更好展开。
- 方向二:基于视频时序的巡检判定。将检测结果按跨帧追踪统计,比如目标在连续N帧中被检测到才判定为一个有效坑洼。可以在不增加训练复杂度的前提下显著降低误报,更贴近“坐在车里开着记录仪跑一遍就自动生成长度与坑洼清单”的真实任务。
- 方向三:部署到Jetson等边缘硬件。TensorRT加速后可跑实时推理,“端侧轻量化部署”是近两年高关注度的技术热点。有兴致可以试试把系统装进Jetson Nano或树莓派里,做一个真正能随车巡检的便携装置,这个完整度在毕设中往往属于亮点级别。
8. 写在最后的个人体会
作为一个完整走过这个坑的人,我最想说的是:深度学习的项目,最难的往往不是模型本身,而是从数据、环境、调试到工程化这一段冗长而有大量细节的基础工作。模型选得再花哨,数据一坨糊掉了,效果也是白搭;反过来,如果数据组织得当、模型选型克制、每一步都有验证,这个题目最后呈现出来的完整度和答辩状态通常不会差。最后再分享一个小技巧:训练脚本里记得加上固定随机种子(如seed=42),保证实验可复现,否则每次跑出来的指标都不一样,你会在写论文对比表格出处时抓狂——这就是过来人的血泪经验。