简介:这是一套基于YOLOv11的乡村道路障碍物检测系统设计资料,适合计算机视觉、人工智能方向的毕业设计和课程设计使用。项目以乡村道路上的行人、动物、车辆和堆放物等为检测对象,围绕数据集构建、模型训练调优、系统集成与测试展开,给出完整的工程设计流程。压缩包共23个文件,包含三个Python源代码文件,分别承担预测、验证和界面展示功能,并配有18张检测结果示意图、一份Markdown说明和一份Word文档,整体大小4.22MB,结构清晰便于查阅。目前已有29人学习下载,可用于快速复现目标检测项目。资料中的可运行代码、可视化结果和详细文档,既能帮助理解YOLOv11在复杂场景中的应用方法,也可作为课程设计或毕业设计的原型基础,方便在此基础上扩展自动报警、数据记录等辅助功能。
1. 乡村道路障碍物检测为什么比城市更麻烦
拿到「基于YOLOv11的乡村道路障碍物检测设计」这个标题时,我第一反应不是模型选哪个,而是数据长什么样。城市道路的目标检测样本太好找了——车辆、行人、红绿灯,公开数据集一抓一大把。但乡村道路是另一个世界:路上可能是一袋沙石、一棵倒伏的树枝、一台收割机、一头牛,甚至是一堆没有反光标识的水泥墩。这些东西在城市数据集里几乎没有,你用预训练权重直接去测,大概率全漏检。
更麻烦的是乡村道路的"背景噪声"。土路扬尘、树影、杂草、收割后留下的秸秆堆,在图像里和障碍物的纹理高度相似。YOLO系列的 backbone 提取的是纹理和形状特征,模型很容易把一堆杂草当成障碍物,或者把真正的沟坎当成路面忽略掉。所以这个设计的核心难点不在模型结构,而在如何让模型学会"乡村语境"下的障碍物边界。
这篇笔记适合谁?手里已经有乡村道路监控或农机视觉需求,想用 YOLOv11 搭一套检测方案的工程师;也适合正在做毕业设计或课程项目、需要把 YOLOv11 落地到具体场景的同学。我会按选型、数据、训练、参数、避坑到部署的顺序讲清楚,每一步都给可复现的命令和可改的参数。最后说一句:这个任务能不能做好,90% 取决于你数据怎么拍、怎么标,10% 才轮到调参。
2. YOLOv11 选型与结构:它比 v8 强在哪,强多少
2.1 为什么选 YOLOv11 而不是继续用 v8
我在做类似项目时,一开始用的 YOLOv8,后来换到 YOLOv11。最直接的感受是:小目标召回率和训练收敛速度都明显改善。乡村道路障碍物里有一类是"远处的小目标"——比如 100 米外路中间的一块石头,在 1080p 画面里只有 20×20 像素左右。v8 的检测头对这种目标经常漏,而 v11 的 C3k2 模块和更深的检测头对小目标的语义信息保留得更好。
模型选型上,我建议把重点放在yolo11m或yolo11l上,不要一上来就用yolo11x。乡村道路检测往往需要部署在边缘设备上,x版本推理速度太慢,精度提升却有限。我给一个简单粗暴的选型表,按你的硬件条件来:
| 设备类型 | 推荐模型 | 推理速度预期(FPS, 640×640) | 内存占用 |
|---|---|---|---|
| 树莓派 4B / 低端盒子 | yolo11n | 8-12 | 约 1GB |
| Jetson Nano / 中端盒子 | yolo11s | 10-15 | 约 1.5GB |
| Jetson Orin / 桌面GPU | yolo11m | 25-35 | 约 3GB |
| 服务器 / 高性能GPU | yolo11l/x | 40-60 | 约 6GB+ |
2.2 C3k2 和 C2PSA 到底改了什么
这一节写给想搞懂原理的人,不想看可以直接跳到 4 章。YOLOv11 最大的结构变化是把 v8 的 C2f 模块改成了 C3k2,并加入了 C2PSA 注意力模块。C3k2 在保持轻量化的同时,通过多分支的梯度流设计,让网络在深层仍能有效传递梯度,训练时不容易梯度消失,收敛更快。C2PSA 是借鉴 Transformer 的注意力机制,但用卷积实现,成本更低,它能增强模型对"远距离上下文"的感知。
这个结构对乡村道路的意义在哪?乡村场景里障碍物往往和周围环境颜色接近,比如土黄色的石头躺在土黄色路面上。C2PSA 能让模型结合更大范围的感受野,判断"这里有一块区域和它周围的纹理不一致",从而降低漏检。调参时你会发现,v11 对数据量的要求比 v8 略高,但同样的数据量下,训练 100 个 epoch 的 mAP50 往往比 v8 高 2-4 个点。
2.3 环境搭建与最小验证命令
我一般用 conda 建环境,CUDA 版本和 PyTorch 的匹配关系必须提前确认。下面这套命令在 Ubuntu 20.04 / CUDA 11.8 环境下验证过:
# 创建虚拟环境,Python 用 3.9 或 3.10 conda create -n yolo11 python=3.10 -y conda activate yolo11 # 安装 PyTorch,注意 cu118 对应 CUDA 11.8 pip install torch==2.1.0 torchvision==0.16.0 --index-url https://download.pytorch.org/whl/cu118 # 安装 ultralytics 包,YOLOv11 需要 8.3.0 及以上版本 pip install ultralytics # 下载 yolov11m 预训练权重并跑一次推理验证环境 yolo predict model=yolo11m.pt source=https://ultralytics.com/images/bus.jpg跑通上面的命令后,你应该能在runs/detect/predict里看到标注了目标的输出图。如果报 CUDA 不可用,先执行python -c "import torch; print(torch.cuda.is_available())",返回False就说明是 PyTorch 和显卡驱动不匹配,重装对应 cu 版本的 torch 即可。这一步把环境问题挡在训练之前,后面所有坑都更好排查。
3. 乡村道路数据集的标注与增强:决定成败的环节
3.1 采集和标注:别只拍"完美角度"
这个项目的训练数据最忌讳从公开数据集里硬搬——城市车辆、行人数据对乡村障碍物检测几乎没用。你需要自己拍或者从现场监控里截取真实乡村道路画面。采集时注意三点:第一,覆盖不同时段,清晨逆光、中午强光、傍晚阴影、夜间车灯照明下的障碍物外观完全不同;第二,覆盖不同路况,水泥路、柏油路、碎石路、纯土路;第三,覆盖不同距离,把障碍物出现在画面中 1/4 高度、1/2 高度、3/4 高度的样本都留足。
标注工具用 LabelImg 或 X-AnyLabeling 都行,输出 YOLO 格式的 txt 文件,每行是class_id x_center y_center width height,坐标是归一化到 0-1 的。我常用的类别定义如下,具体按你的场景增减:
0: stone_block # 石块、混凝土块 1: tree_branch # 倒伏树枝 2: agricultural_machine # 农机、拖拉机、收割机 3: animal # 牛、羊等牲畜 4: pile # 沙石堆、秸秆堆、杂物堆 5: construction # 路障、锥桶、水泥墩3.2 标注边界的几个隐藏规则
标注时最常翻车的地方是"要不要把被遮挡的部分框进来"。我的经验是:如果障碍物被遮挡超过 30%,不标;如果只被草叶遮挡边缘,标注时把框尽量贴合可见部分,不要试图脑补完整轮廓。因为 YOLO 的损失函数是基于框的 IoU 计算的,你脑补出来的虚边会导致回归目标不一致,训练时模型会很困惑。
另外,单类别的框尽量用矩形紧贴目标,不要留大块空白背景。很多同学习惯把框稍微扩大一些"留点余量",这在障碍物检测里很致命——框内背景过多,模型会把背景特征学进目标特征里,推理时很容易误检。我一般建议框和目标的贴合度至少 95%,宁可略小不可略大。
3.3 数据增强:模拟乡村恶劣条件
训练乡村道路模型,光靠原始图像是不够的。夜间、雨天、大雾、镜头沾泥,这些情况必须用增强模拟出来。ultralytics 自带的增强参数在 yaml 里配置,但默认值更偏向通用场景,建议手动改大几个关键项:
# dataset.yaml 同级新建 hyp.yaml,训练时用 --hyp hyp.yaml 加载 # 关键增强参数 hsv_h: 0.02 # 色调扰动,乡村道路颜色单调,稍微增强抵抗色差 hsv_s: 0.8 # 饱和度扰动调大,模拟不同光照饱和度变化 hsv_v: 0.6 # 明度扰动调大,模拟阴影和曝光差异 flipud: 0.1 # 上下翻转概率,对俯视视角图像有帮助 fliplr: 0.5 # 左右翻转,常规操作 mosaic: 1.0 # Mosaic 增强开到最大,小目标受益明显 mixup: 0.2 # MixUp 增强,增加模型对遮挡和混乱场景的鲁棒性 # 以下两项关键:模拟雨雾和镜头脏污 # ultralytics 8.3+ 支持自定义增强,下面这行是 albumentations 的配置方式 # 如果不想折腾 albumentations,可以用 OpenCV 脚本离线生成一批雨雾增强图如果你不想改 yaml,更简单的做法是用 python 脚本离线做增强,生成一个更大的数据集。我常用的是模拟雨滴划线、高斯模糊模拟雾气、随机亮度对比度抖动。离线增强的好处是你可以肉眼检查生成图,确认增强没有把障碍物变得不可辨认。增强的目的是增加多样性,不是把图像毁了——如果人眼都看不出原目标,模型也学不到。
3.4 标注质量检查:训练前必做这一步
训练前花 10 分钟检查标注,能省 3 小时调参。我一般写一个脚本统计标注框的尺寸和位置分布,看看是否有异常:框宽度或高度为 0、坐标超出图像范围、类别 id 超出类数量。另外还要看每个类别的样本均衡情况。乡村数据集很容易出现"石块 800 个、农机 50 个"的极端不均衡,此时需要针对性补充少样本类别,或者在线增强时对少样本类别提高采样权重。
检查脚本很短,但每次都能筛出问题:
import os from collections import Counter label_dir = "datasets/train/labels" class_counter = Counter() total_boxes = 0 invalid_files = [] for fname in os.listdir(label_dir): path = os.path.join(label_dir, fname) with open(path) as f: lines = f.readlines() for line in lines: parts = line.strip().split() if len(parts) != 5: invalid_files.append(fname) continue cls = int(parts[0]) w = float(parts[3]) h = float(parts[4]) if w <= 0 or h <= 0 or w > 1 or h > 1: invalid_files.append(fname) continue class_counter[cls] += 1 total_boxes += 1 print(f"总标注框数: {total_boxes}") print(f"类别分布: {class_counter}") if invalid_files: print(f"异常文件: {invalid_files[:10]}") else: print("标注格式检查通过")这个脚本输出的类别分布,就是你要不要调 loss 权重的依据。如果某个类别的框数不到总数的 5%,我建议先补数据再训练,否则模型大概率对这个类别无感。
4. 训练与验证:参数怎么设、mAP 怎么读
4.1 划分数据集并组织目录
标注完成后,把图片和标签按 YOLO 标准目录放好:
datasets/ ├── images/ │ ├── train/ # 训练图片 │ └── val/ # 验证图片 └── labels/ ├── train/ # 训练标签 └── val/ # 验证标签划分比例我习惯用 9:1,乡村道路场景样本往往只有几百到一两千张,留太多验证集会压缩训练量。但验证集至少要 60 张以上,不然 mAP 波动太大。每张图片必须和它对应的 txt 标签文件名完全一致,比如IMG_0234.jpg对应IMG_0234.txt,否则训练时 ultralytics 会跳过这张图,而你毫无察觉。
4.2 训练命令与核心参数
有了数据集和 yaml 配置后,训练命令如下。这里我用yolo11m作为起点,如果你的数据量小于 500 张,建议换yolo11s,否则容易过拟合:
# 训练命令,开启 AMP 混合精度,mosaic 在前 20 个 epoch 后关闭 yolo train data=datasets/rural_road.yaml model=yolo11m.pt epochs=150 imgsz=640 batch=16 workers=8 device=0 project=runs name=exp_rural hyp=hyp.yaml amp=True close_mosaic=20参数含义逐个说:data指向你的数据 yaml,里面写清train、val路径和nc、names;model可以是预训练权重,也可以只写yolo11m.yaml从零训练——但强烈建议用预训练权重微调,乡村障碍物虽然场景特殊,但基础纹理特征和 COCO 数据集是相通的;epochs在 120-200 之间,乡村数据量少,大于 200 基本注定过拟合;imgsz用 640,不要贪图大分辨率,除非你部署时也保证同样分辨率;close_mosaic在末尾关闭 mosaic,让模型适应真实分布,这个参数能有效防止 mosaic 引起的检测框抖动。
4.3 学习率与 batch size 的耦合关系
batch大小直接决定初始学习率。YOLOv11 的默认学习率lr0=0.01是在 batch 16 下调好的。如果你把 batch 改成 8,建议同步降低学习率到lr0=0.005;改成 32,则提升到0.02。否则,batch 小了学习率太大,loss 会震荡;batch 大了学习率太小,收敛极慢。我见过最典型的翻车:一个同学在 8G 显存上强行使用batch=32,结果CUDA out of memory,然后他把imgsz降到 416,模型倒是跑起来了,但小目标检测能力肉眼可见地下降。我的建议:要么老老实实batch=16配 640 分辨率,要么减少数据加载器线程数workers=4来省内存。
4.4 从训练曲线判断模型状态
训练结束后,打开runs/exp_rural/下的results.csv,重点关注metrics/mAP50(B)和metrics/mAP50-95(B)两条曲线。乡村障碍物检测的评价指标,我一般只看 mAP50,因为障碍物检测对框的精确度要求不像目标跟踪那么苛刻,只要能把障碍物框出来且 IoU > 0.5 就算有效检测。mAP50 达到 0.85 以上基本够用,0.9 以上算优秀。
如果训练曲线显示 mAP50 在后期还缓慢上升,说明还没收敛,可以加大 epochs 继续训练;如果 mAP50 上升但 val loss 开始回升,那已经过拟合了,需要返回去加数据增强或减小模型。另一种情况:mAP50-95 和 mAP50 差距过大(比如 0.9 vs 0.45),说明框的位置准确度差,模型对目标定位不稳,这时优先检查标注框是否紧贴目标,而不是盲目的调 IoU 阈值。
5. 避坑:乡村场景下 YOLOv11 训练与推理的 8 个高频问题
5.1 模型把所有石头都测出来了,但把树桩也当成石头
现象:验证集 mAP 很好看,但实际跑视频时,路边所有深色圆形物体都被标成stone_block,树桩、轮胎、甚至牛的背影都不放过。
原因:数据集里"石头"这一类别的背景太单一,可能你只拍了石头在路面上的照片,没有拍长满青苔的石块、嵌在土里的石块、半埋在草里的石块。模型学到的不是"石头"这个本质,而是"深色圆形+周围是路面"这个组合特征。
解决:把错检样本收集起来,用yolo predict跑一遍输出检测框,再把误检的图人工标注成background类别单独训一轮负样本。很多同学不知道 YOLO 可以增加一个 background 类,虽然它会增加类别数,但实际效果立竿见影——模型有了"这玩意不是障碍物"的样本,误检率直接降一半。另外,收集数据时专门拍一批"干扰物"照片,包括树桩、水管、轮胎、铁桶,全部标为对应类别或 background,这比调置信度阈值靠谱得多。
5.2 loss 一直在降,但 mAP 纹丝不动
现象:训练时train/box_loss从 0.08 降到 0.03,但验证集 mAP 卡在 0.6 上不去。
原因:这个现象在乡村场景里最常见的原因是数据分布太窄,验证集和训练集是同一条路上前后拍的照片,模型靠记忆过拟合,没有学到泛化特征。另一个原因是你用了mosaic=1.0且没有close_mosaic,验证时目标被拼接切割的分布和训练时不一样。
解决:先查数据集是不是存在"同源"问题——如果训练集和验证集来自同一段视频的连续帧,必须按时间段重新切分,不能用随机划分。然后打开close_mosaic=10,在最后 10 个 epoch 关闭增强,让模型回归真实分布。如果还是不行,把mosaic降到 0.5,mixup降到 0.1,宁可增强弱一点,不要强到失真。
5.3 夜间图像的检测结果惨不忍睹
现象:白天 mAP 0.88,到了夜间只有 0.4,大量漏检。
原因:训练集里夜间样本太少,或者夜间样本只有车灯直射的场景,没有路灯、月光、完全没有光源的真实夜间场景。增强里的hsv_v只能调亮度,模拟不了红外补光下的灰度纹理,更模拟不了车灯强光导致的过曝。
解决:核心是补数据,不是调参。我一般会从监控视频里按小时采样,把 18 点到 6 点的帧都抽出来,挑出清晰度尚可的加入训练集。如果实在没有夜间数据,可以退而求其次用红外或热成像相机,但那样要重训模型。博主在这里说句实话,没有夜间真实数据的夜间检测就是玄学,任何数据增强都救不了。
5.4 用 TensorRT 部署后精度不如原生模型
现象:同一张图片,PyTorch 推理 mAP 0.87,导出 TensorRT FP16 后 mAP 掉到 0.80。
原因:FP16 精度损失在小目标上尤其明显,因为小目标的特征值本身就很小,FP16 的舍入误差会把细微纹理抹掉。另外,你导出时imgsz要与你训练时完全一致,如果训练是 640,导出却用 1280,模型会重新缩放 Anchor,导致精度跳变。
解决:第一步先检查导出时的imgsz设置;第二步尝试 TensorRT 的 INT8 量化前先跑 FP16,确认 FP16 精度可接受后再考虑 INT8;第三步如果 FP16 损失仍然明显,可以考虑只对检测头使用 FP16、backbone 保持 FP32,或者干脆部署时用yolo11s的 FP16 而不是yolo11m的 FP16,因为小模型的舍入误差对相对值影响更小。
5.5 推理时同一障碍物在相邻视频帧间框体抖动严重
现象:视频里障碍物框一会儿 200×300,一会儿 180×280,前后帧框大小跳变,看起来很不稳定。
原因:NMS 阈值设置过低,或者训练时没有充分学习障碍物的尺度变化。另一个常见原因是推理时用了过高的iou=0.7但conf=0.25,导致同一个目标上保留了多个重叠框,帧间的框选择随机切换。
解决:后处理里提高iou到 0.7 以上,让重叠框合并更彻底;同时把conf适当提高到 0.3,滤掉低置信度的噪声。如果还抖,可以对输出框做一阶低通滤波,把当前帧的框和上一帧的框做加权平均,权重可取 0.7 对 0.3。这个技巧不改变模型,但能显著提升视频观看体验。
5.6 训练时显存不足,但数据集明明不大
现象:batch=16, imgsz=640直接 OOM,机器是 16G 显存。
原因:注意你的workers数,数据加载时多个 worker 会预加载图像到内存,但显存占用主要来自计算图。还有一个隐蔽原因是你的 dataloader 里没有设置pin_memory=False,在内存不足的机器上,pin memory 会拖垮内存带宽。
解决:workers=4、batch=8、imgsz=640,通常能跑动。如果还 OOM,把amp=True打开——混合精度能省一半显存。我见过有人在 16G 显存上跑yolo11x还开了amp=False,这不是调参问题,这是硬件选型问题。
5.7 训练结果不错,但导出 ONNX 后报错
现象:yolo export model=best.pt format=onnx时报Anchor indexing相关错误。
原因:大多是因为你的imgsz与模型训练尺寸不一致,导致 grid 尺寸不匹配。
解决:导出时显式指定尺寸:yolo export model=best.pt format=onnx imgsz=640。如果还报错,检查 ultralytics 版本是否过低,升级到最新版即可。
5.8 训练到一半中断,如何续训
现象:训练到 87 epoch 时断电,重启后想继续。
解决:ultralytics 默认每 epoch 都保存last.pt,直接用它续训:
yolo train data=dataset.yaml model=runs/exp_rural/weights/last.pt epochs=150 resume=True注意续训时要保持和原训练相同的参数,尤其close_mosaic和hyp,否则学习率调度会错乱。如果原训练给了close_mosaic=20,续训时还是要给这个参数,计算机会从上次的 epoch 位置继续关闭策略。
6. 部署落地:把模型装进边缘设备的关键技巧
6.1 模型裁剪和蒸馏是最后一步棋
如果训练出的yolo11m在目标设备上跑不到实时帧率,我的习惯是先做通道剪枝,而不是直接换yolo11s重训。剪枝工具用torch_pruning或 ultralytics 自带的剪枝接口,剪掉一定比例的通道后用训练数据微调 30-50 epoch。实际效果是参数量减少约 40%,mAP 只降 1-2 个点,比直接用yolo11s从头训练好得多——因为你已经在乡村数据上训出了yolo11m的知识,剪枝只是把冗余通道去掉,保留了核心特征提取能力。
剪枝的步骤如下:先训练一个yolo11m,然后基于验证集计算每个通道的贡献度,剪掉贡献小的通道,再用原训练集微调。微调时学习率降到原训练最后阶段的一半,比如原最后lr=0.0001,微调用0.00005,训练 30 epoch 就够,多了会破坏已收敛的权重。
6.2 导出到 TensorRT 的完整流程
边缘设备上最常见的部署方案是 TensorRT。导出前先确认训练时的imgsz,然后执行转换:
yolo export model=best.pt format=engine device=0 imgsz=640 half=True workspace=4half=True使用 FP16,workspace=4给 TensorRT 引擎分配 4GB 显存。转换完成后,用生成的best.engine做推理验证。我强烈建议在正式部署前用一段 10 分钟真实乡村道路视频跑一遍,统计每帧推理耗时,并根据丢帧情况决定是否启用 TensorRT 的 PVA 或 DLA 核心(如果有的话)。
TensorRT 引擎对输入图像的预处理要求很严格,你必须保证推理时送入的图像大小、归一化方式和训练时完全一致。ultralytics 的推理接口会自动处理这些,但如果你是自己写 C++ 或 Python 推理代码,务必检查letterbox填充逻辑。填充颜色是灰色(114, 114, 114),不是黑色,很多自己写的部署代码在这里翻车。
6.3 降低误报的后处理开关:ROI 与置信度策略
实际部署中,我不建议直接采用conf=0.25的默认阈值。乡村道路场景误报成本高——如果系统把路边的草判成障碍物然后触发刹车,那这系统没法用。我的习惯是分场景设阈值:白天conf=0.35,夜间conf=0.45,因为夜间误报更多。另外,如果相机视角固定,可以画一个 ROI 区域,只检测路面范围内的障碍物。YOLO 本身不做 ROI,但你在后处理代码里加一个多边形判断即可,框中心点落在 ROI 内才输出。
import cv2 import numpy as np # 假设 points 是四点 ROI 多边形 roi_points = np.array([[100, 400], [540, 300], [700, 300], [1180, 400]], dtype=np.int32) def in_roi(box_center, roi_points): result = cv2.pointPolygonTest(roi_points, (box_center[0], box_center[1]), False) return result >= 0 # 在推理后处理中对每个框中心做判断 # box_center = (int(x1+x2)/2, int(y1+y2)/2) # if in_roi(box_center, roi_points): # final_boxes.append(box)这段逻辑很简单,但能过滤掉大量路边的树木和房屋引起的误检。ROI 参数需要根据你实际的相机安装位置和俯仰角来标定,我通常是在部署现场取一帧画面,用画图工具标出路面四边形的四个顶点。
6.4 帧间跟踪和触发策略
最后一层优化是帧间确认。单帧检测到障碍物不立刻报警,而是连续 N 帧都检测到同一位置才触发,这个 N 根据车速和帧率调整。比如车速 60km/h、帧率 15fps,那么一帧车辆前进约 1.1 米,N 取 3 帧可以容忍短暂遮挡又不会错过紧急情况。实现上可以用简单的 IoU 匹配:当前帧框和上一帧框的 IoU 大于 0.5 就认为是同一目标,计数加一;否则重置。
我用这种方法把误报率从每分钟 3 次降到了每小时 1 次以下。这里的关键是不要用跟踪算法而用 IoU 匹配——乡村道路场景目标少,IoU 匹配足够,也不担心 ID Switch 问题。
6.5 一个容易忽视的验证习惯
所有的优化做完后,我习惯把被模型漏检的帧保存下来,每周看一眼。这个"漏检垃圾桶"目录能直观反映模型在现场的真实表现。有一次我发现漏检的都是在夜间强逆光下靠近镜头的障碍物,原因是训练数据里没有这种近景大尺度样本,后来补了一批距离 2-3 米的障碍物照片,重新微调一轮,漏检率立刻下降。这个习惯我保持了很久,也建议你坚持下去——数据集是活的,模型不是训完就能一劳永逸的,乡村道路每个季节的路况都不一样,秋季的秸秆和春季的洪水冲积物外观完全不同,定期补数据微调,才是这个项目长期稳定运行的关键。希望帮到你。
本文还有配套的精品资源,点击获取