简介:基于Faster-RCNN网络模型的车辆、行人及交通信号目标检测算法,是一份面向计算机视觉初学者与目标检测开发者的完整实战资源。资源涵盖完整Python源码、配套数据集、项目报告及详细注释,可直接用于交通场景下多类别目标的检测训练与推理演示。压缩包共89个文件,以30个py源码和36个pyc编译文件为核心代码,辅以jpg测试图像、txt说明文本、md文档、pdf项目报告及json类别映射文件,整体大小仅3.61MB,下载与部署非常轻量。代码严格按功能模块划分,包含backbone特征提取网络(支持ResNet50、MobileNetV2、VGG)、network_files中的Faster R-CNN与RPN核心模块、train_utils训练验证工具以及自定义数据集读取脚本,并提供了train_res50_fpn.py、predict.py、validation.py等一键运行入口,注释细致,适合对照学习。目前已有226人学习浏览,若在Ubuntu/CentOS系统配合GPU环境,使用Pytorch1.6及以上版本即可顺利运行,是入门Faster-RCNN并动手实践目标检测的不错参考。
1. 把 Faster-RCNN 跑通车辆行人交通信号检测:这份源码包到底值不值得下
目标检测这个方向,YOLO 系列现在确实火,但在交通场景里,Faster-RCNN 依然是很多工程项目的主力。原因很直接:两阶段检测在精度上比单阶段稳,尤其对行人这种小目标、密集目标,RPN 先提候选框再分类回归,误检率控制得好。这份资源提供的是完整可跑的 Faster-RCNN 实现,支持 resnet50、mobilenetv2、vgg 三种特征提取骨干,自带数据集切分脚本、训练验证脚本、多 GPU 训练脚本和详细注释,还附了项目报告和 mAP 记录。不是那种只丢几个 .py 的残缺包,是直接能动手训练一版模型出来的完整工程。
适合谁?想学 Faster-RCNN 原理但不想从零手写网络的人,做课程设计需要可复现项目的学生,以及要在自有数据集上做车辆/行人/交通信号检测的工程入门者。我拆完整个包之后,先把它的文件结构和核心实现逻辑理清楚,再把训练到验证的完整流程走一遍,最后把最容易翻车的坑列出来——这几个坑我几乎每个都踩过。
2. 网络结构拆解:RPN、RoIHead 和骨干网络的分工与选型
2.1 从 network_files 看懂 Faster-RCNN 的模块划分
这个包的network_files目录是我最先看的地方,因为它直接对应 Faster-RCNN 的经典结构:RPN 在做区域提议,RoIHead 在做分类和回归,image_list 和 boxes 负责数据组织。值得先说的是rpn_function.py里的RegionProposalNetwork类,它完成了 anchor 的生成、前景背景二分类和 bounding box 回归。RPN 的核心逻辑简单说就是:在特征图的每个位置上铺一组不同尺度和长宽比的 anchor,然后预测这些 anchor 是前景还是背景,同时回归出更精确的候选框。
# rpn_function.py 核心逻辑简化 proposals = self.rpn_head(anchors, pred_obj_logits, pred_bbox_deltas) # self.rpn_head 是 RPNHead 实例 # anchors 来自 AnchorGenerator,负责生成不同尺度/长宽比的 anchor # pred_obj_logits 是所有 anchor 的前景分数 # pred_bbox_deltas 是所有 anchor 的坐标修正量这里的proposals就是 RPN 输出的候选区域,后面会经过filter_proposals按分数排序、做 NMS、限制数量。我一般把 RPN 的输出数量关注在 2000 附近,训练时多一些没关系,推理时可以降到 300 左右提速。image_list.py里的ImageList类处理的是批量图片尺寸不一致的问题,它把所有图 resize 到同一尺寸再做后续计算,这是从 torchvision 参考实现里继承下来的做法,理解这个结构对调试 shape 不匹配的问题很有帮助。
2.2 三种骨干网络的选型逻辑:resnet50 是默认首选
backbone目录提供了三份特征提取网络:resnet50_fpn_model.py、mobilenetv2_model.py、vgg_model.py。配套的feature_pyramid_network.py实现 FPN 多尺度特征融合。在交通目标检测场景里,我的建议是直接用 resnet50 + FPN 作为首选配置,因为车辆和行人的尺寸差异大,交通信号灯又是极小目标,FPN 的多尺度特征能同时照顾到不同尺寸的目标。mobilenetv2 适合嵌入式或推理速度要求高的场景,vgg 在三者中精度和速度都不占优。
# train_res50_fpn.py 中的骨干加载部分 backbone = resnet50_fpn_backbone() # 返回的是带 FPN 的 ResNet50 特征提取网络 model = FasterRCNN(backbone=backbone, num_classes=args.num_classes + 1) # num_classes 需要 +1,因为 Faster-RCNN 默认有背景类这里有个容易忽略的点:num_classes必须加上背景类。数据集的类别是车辆、行人、交通信号三类,那num_classes要传 4。这个包在pascal_voc_classes.json里维护类别映射,切数据的时候也是按这个文件走的,两个地方必须保持一致,不然训练出来类别索引是错位的。
2.3 定制数据集读取:my_dataset.py 的标注格式约定
my_dataset.py是自定义 Dataset,负责把图片和标注文件读进来。这个工程的数据集是 PASCAL VOC 格式,每张图对应一个 XML 标注文件。默认的split_data.py会把数据集按比例切分成训练集和验证集,我拆包的时候看它默认是 8:2 切分,这个比例对交通场景数据集来说够用。
# split_data.py 核心逻辑 train_percent = 0.8 # 按 8:2 切分训练集和验证集 # 生成 train.txt 和 val.txt 两个索引文件 # 每一行是图片路径(不含扩展名)如果你要换自己的数据集,核心工作是把标注转成 VOC 格式,然后用split_data.py重新生成索引文件。常见做法是先用 LabelImg 标注,输出 PASCAL VOC 格式的 XML,然后放到VOCdevkit对应的目录结构里。这个包没有给你标注工具,但数据组织方式是标准的,照着 VOC 的目录规范摆放就行。
2.4 训练入口参数说明:从 train_res50_fpn.py 看完整训练流程
训练脚本的参数集中在train_res50_fpn.py的命令行参数里。我把它拆开后列成了参数表,方便新手直接照抄配置。
| 参数 | 默认值 | 说明 |
|---|---|---|
--data-path | 默认指向 VOC 数据集目录 | 训练数据根目录 |
--num-classes | 按数据集实际类别填写 | 不含背景类,脚本内部会 +1 |
--epochs | 通常 20~30 | 训练轮数 |
--batch-size | 2~4 | 受显存限制,8G 显存建议 2 |
--lr | 0.01 左右 | 初始学习率 |
--output-dir | 默认 save_weights | 模型保存目录 |
--pretrained | True | 加载 ImageNet 预训练权重,强烈建议开启 |
# 单卡训练示例 python train_res50_fpn.py --data-path /path/to/VOCdevkit --num-classes 3 --epochs 30 --batch-size 2 --lr 0.005训练过程中模型权重会按 epoch 保存到save_weights目录,每轮的 loss 曲线可以通过plot_curve.py画出来。record_mAP.txt记录了每个 epoch 的验证 mAP,我每次训练完第一件事就是看这个文件里 mAP 是否稳步上升,而不是只看 loss 下降——loss 下降但 mAP 震荡的情况我遇到过太多次了。
3. 环境配置与数据准备:从零把训练链路跑通
3.1 版本组合的选择:Python 3.8 + Pytorch 1.6 以上是安全区
环境这块,项目说明里写得很明确:Python 3.6/3.7/3.8,Pytorch 必须 1.6.0 或以上,因为混合精度训练是 1.6 才正式支持的。我实测下来 Python 3.8 + Pytorch 1.8 的组合最稳。操作系统建议 Ubuntu,Windows 能跑但 pycocotools 的安装会多出不少事。Windows 用户直接用pip install pycocotools-windows,这个包不需要额外装 Visual Studio 就能编过。
提示:GPU 训练是硬性建议。CPU 训练这个工程不是不能跑,但一个 epoch 可能要几小时,完全没有实战意义。显存 8G 以下的卡建议 batch-size 设 2,不然显存溢出直接 OOM。
# requirements.txt 安装 pip install -r requirements.txt # 核心依赖:pytorch>=1.6.0, torchvision, pycocotools, opencv-python, Pillow装 pycocotools 是训练脚本里 coco_eval 的硬依赖,不装的话训练能跑但验证阶段会报 ImportError。Linux 上直接pip install pycocotools就行,如果编译报错,先装sudo apt install python3-dev再试。
3.2 数据集目录摆放:VOC 格式必须严格对齐
数据集目录结构必须严格遵循 PASCAL VOC 的规范,这个包里的 dataset 已经按照正确的结构组织好了。如果你要换自己的数据,目录长这样:
VOCdevkit/ ├── VOC2007/ │ ├── JPEGImages/ # 所有原图 │ ├── Annotations/ # 所有 XML 标注文件 │ ├── ImageSets/ │ │ └── Main/ # train.txt, val.txtmy_dataset.py读取数据时就是按这个结构找JPEGImages和Annotations的。最容易翻车的地方是路径拼接错一层——训练时日志里的图片数量为 0,或者读到不存在的文件,基本就是目录层级不对。
3.3 训练启动到产物生成:从命令行到 save_weights 目录
训练脚本跑起来之后,产物主要在save_weights目录:每个 epoch 结束会保存一份.pth权重文件,另外record_mAP.txt会实时追加这个 epoch 的验证结果。我在实际项目中习惯把脚本改成只保留最后一个 epoch 的权重,避免训练几十轮后磁盘被占满。
# 查看训练过程输出 tail -f record_mAP.txt # 每行格式类似: [epoch: 15] mAP: 0.782, lr: 0.002验证阶段的输出也值得留意,validation.py会逐类打印 AP 值,我的习惯是分车辆、行人、交通信号三个类别分别看 AP,不要只看整体 mAP。行人 AP 明显低于车辆是很常见的,这提示你可能需要增加行人样本或调整 FPN 中小目标的特征融合权重。
4. 推理预测与模型验证:predict.py 的使用和可视化输出
4.1 加载权重做单图检测:predict.py 的两种写法
predict.py是单张图片推理脚本,也是我第一次跑通整个项目的关键步骤。模型的加载逻辑和训练脚本保持一致,用create_model创建网络结构,然后load_state_dict加载权重。
# predict.py 核心推理逻辑 model.eval() # 切换到 eval 模式,关闭 dropout 和 BN 的 training 统计 with torch.no_grad(): # 推理阶段不计算梯度 predictions = model(image_list) # predictions 包含 boxes, labels, scores # boxes 是预测框坐标,scores 是置信度分数推理结果会通过draw_box_utils.py画到原图上,保存为_result.jpg文件。test目录里已经放好了几张测试图和对应的结果图,我拆包时直接用训练好的权重跑了一遍测试集,输出结果和包内自带的result_faster.txt基本一致,说明权重和代码版本是匹配的。
4.2 置信度阈值和 NMS 参数怎么调
推理时有两个参数直接影响可视化效果:置信度阈值和 NMS 的 IoU 阈值。默认配置里置信度阈值是 0.5,低于这个分数的框会被过滤掉。交通信号灯是小目标,检测分数普遍偏低,如果发现红绿灯经常漏检,把阈值降到 0.3 试试,代价是误检会多一点。
# draw_box_utils.py 中的阈值设置 threshold = 0.5 # 低于此分数的目标将被过滤 # NMS 的 IoU 阈值通常在 0.4~0.5 之间 # 值越小,重叠框被抑制得越狠这里有一个我在工程里反复遇到的教训:NMS 阈值不要乱调。0.4 和 0.5 之间对最终结果的影响远小于置信度阈值。如果出现两个同类目标重叠导致漏检,优先调置信度而不是 NMS。行人在密集场景下本来就互相遮挡,NMS 阈值调太低会把相邻的行人框并掉一个。
4.3 用 validation.py 在测试集上做客观验证
光看图不够,要客观评估模型好坏得跑validation.py。它会在验证集上逐类计算 AP,最后汇总 mAP。包内record_mAP.txt记录了一份训练过程中的 mAP 数据,我看了下数值,在默认数据集上训练到 30 个 epoch 左右,整体 mAP 在 0.7 以上是正常的,其中车辆的 AP 通常最高,交通信号灯最低——这个现象和交通信号灯尺寸小、样本数量少直接相关。
python validation.py --weights save_weights/model_15.pth --num-classes 3验证脚本输出的逐类 AP 表格我建议每次训练完都截图保存。对比不同版本权重的逐类 AP,能看出来你调参改的是哪个类别的效果,比单看一个 mAP 数字有用得多。
5. 常见问题与避坑:训练翻车现场排查手册
5.1 GPU 显存溢出(OOM)
现象:训练到第几个 batch 就报CUDA out of memory,程序直接退出。反复调整batch_size也没有根本改善。
原因:交通场景的原始图片分辨率普遍偏高,例如 1920x1080 的输入图经过 FPN 多尺度处理后中间特征图占用非常大。batch_size=2在 ResNet50 + FPN 结构下也是比较极限的配置,根因是输入尺寸太大,不是网络本身的问题。
解决:我采用的方案是先把训练图片最长边缩放到 960 左右。这个包的transforms.py里有 resize 逻辑,修改max_size参数即可。代价是检测精度略有损失,但显存占用大幅下降,训练速度提上来之后整体收益是正的。
5.2 训练 loss 降不下去
现象:loss 在前几个 epoch 快速下降后进入平台期,后续训练 mAP 始终不涨。
原因:最常见的是学习率设置偏大,或数据集类别不均衡。交通场景里车辆和行人的样本量可能是交通信号灯的几十倍,模型把大部分精力花在了车辆上。
解决:先把lr从 0.01 降到 0.005 试跑 5 个 epoch,观察 loss 曲线是否继续下降。类别不均衡的问题可以给交通信号灯类别增加样本,或使用train_multi_GPU.py里的多卡训练来加速迭代实验。我一般不推荐上来就用进阶的损失函数修改,先调 lr 和 epochs,效果不够再加数据集增强。
5.3 预测结果全部是背景类
现象:推理时 labels 全部为 0(背景),输出的框几乎没有有效目标。
原因:90% 是类别数设置错了。--num-classes 3传入后脚本内部会 +1 变成 4,但你的数据集实际只有 2 类,那第 3 类就永远不会出现实体目标,模型会把所有预测归到背景或错位类别。
解决:先确认pascal_voc_classes.json里的类别列表和数据集标注完全一致,再确认传给训练脚本的num_classes等于列表长度。我习惯先打印一次训练集的类标签分布,确认每个类别都有足够的样本再开训练。
5.4 pycocotools 安装失败
现象:Linux 上报gcc: error: x86_64-linux-gnu-gcc: No such file,Windows 上报编译错误。
原因:pycocotools 需要编译 Cython 扩展,系统里缺少编译工具链。Windows 上很多人卡在这一步是去折腾 Visual Studio Build Tools,其实没必要。
解决:Linux 上执行sudo apt install build-essential python3-dev后重装。Windows 上直接用pip install pycocotools-windows,这个包已经是编译好的二进制,不需要本地编译环境。装完验证方式:python -c "from pycocotools.coco import COCO"不报错即成功。
5.5 自定义数据集训练时读不到图片
现象:训练日志显示样本数量为 0,或者报FileNotFoundError。
原因:my_dataset.py里的路径是基于root参数拼接的,VOC 目录结构少一层就会找不到 JPEGImages 和 Annotations。另一个常见原因是split_data.py生成的是相对路径,如果你移动了数据集目录,索引文件里的路径就失效了。
解决:把数据集的绝对路径传给--data-path参数,千万别传相对路径。移动数据集后重新跑一遍split_data.py重新生成索引,不要手动编辑 train.txt 和 val.txt。
6. 把训练结果导出成可用的检测服务:一个实用的部署思路
训练出满意的权重之后,真正要落到项目里需要把它封装成对外接口。我自己习惯做一个简单的推理类,把模型加载、预处理、后处理封装成一个detect()方法,这样不管是接摄像头流还是批量处理图片都方便。
# 部署推理类封装示例 import torch import numpy as np from PIL import Image class TrafficDetector: def __init__(self, weights_path, num_classes, device="cuda"): self.device = torch.device(device if torch.cuda.is_available() else "cpu") self.model = create_model(num_classes).to(self.device) self.model.load_state_dict(torch.load(weights_path, map_location=self.device)) self.model.eval() self.classes = ["background", "vehicle", "person", "traffic_light"] def detect(self, img_path, score_threshold=0.5): # 预处理:读图、resize、转 tensor img = Image.open(img_path).convert("RGB") img_tensor = self.transform(img).unsqueeze(0).to(self.device) with torch.no_grad(): pred = self.model(img_tensor)[0] # 按置信度阈值过滤 keep = pred["scores"] >= score_threshold boxes = pred["boxes"][keep].cpu().numpy() labels = pred["labels"][keep].cpu().numpy() scores = pred["scores"][keep].cpu().numpy() return boxes, labels, scores一是模型加载和推理在__init__完成后就把网络结构固定下来了,后续调用detect()不需要再碰网络逻辑,性能损耗最小。二是map_location参数保证了服务器上没有 GPU 也能用 CPU 做推理,这在部署阶段很关键。三是推理循环里一定要包torch.no_grad(),不包的话显存会被计算图慢慢拖满,跑批量检测到后面就会 OOM。
调用这个类的方式很简单,对着测试图直接调detect()就能拿到框坐标。出来的boxes可以直接丢给 OpenCV 画框,也可以转成 JSON 供 Web 接口返回。我一般在接入摄像头时把视频帧按固定间隔抽帧,传入detect()里做目标检测,再把结果叠加到原始帧上。要注意的是推理耗时——ResNet50 骨干在 GPU 上单帧大概 50-80 毫秒,CPU 上会到 1-2 秒,想实时得用 TensorRT 或 ONNX 加速,这是后话。
最后说我从这个项目里沉淀下来的一个习惯:每次跑一个新的数据集,我会先跑 5 个 epoch 做一个快速实验,确认 loss 曲线正常走了、mAP 表格能出数、推理脚本能画出结果图,才敢放心跑完整的 30 个 epoch。因为一次完整训练在单卡上可能要花几个小时,前面哪个环节断了都白等。这个做法让我少熬了很多个通宵,从那以后我每次拿到新的目标检测工程项目都强制走一遍「小 epoch 冒烟 → 完整训练 → 逐类 AP 对比」的流程,希望帮到你。
本文还有配套的精品资源,点击获取