简介:基于Traffic-Net的交通拥堵程度识别源码与配套数据集打包下载,面向人工智能、计算机等相关专业的课程设计、毕业设计及入门进阶学习者。项目以Python深度学习实现,提供完整可运行的训练代码,可用于交通场景图像分类或拥堵等级识别等实验任务,也适合在此基础上做二次开发。压缩包共10个文件,主要包括Python脚本(模型训练与预测)、JSON配置文件(模型结构与参数)、Markdown/TXT说明文档(使用说明与常见错误记录),整体仅6KB,结构精简,便于快速部署与调试。目前已有49人学习下载。解压后即可对照文档运行,资源还附带了排错提示,可帮助使用者规避环境配置中的常见问题;对于希望深入理解Traffic-Net网络设计或完成课设报告的同学,这是一份低门槛、高参考价值的起步资料。
1. Traffic-Net 做什么:给路口摄像头一张截图,说出堵不堵
交通拥堵识别,最容易被做成一个“看起来很壮丽”的工程:接几十路摄像头、上智能分析平台、做大屏可视化。真到落地那天,问题全冒出来——边缘盒子算力不够、夜间和雨雪天误报率飙升、标注数据得一张张框汽车。Traffic-Net 的定位恰好是把这件事缩到最小:它不检测车辆也不跟踪轨迹,只对整张路口截图做分类,输出“畅通、缓行、拥堵、严重拥堵”四个等级。你需要的不是重型服务器,而是一张低分辨率的 JPEG 帧,和一个几十 MB 的模型权重。适合谁?想用轻量级 CNN 跑通“摄像头图像 → 拥堵等级”这条链路的人,不管是做毕设、做园区闸口改造,还是给已有监控系统加一个道路状态输出。放在本文的场景里,你拿到的就是一个可训练源码包加一批分类好的交通图像数据集,能在本地机器上复现出完整的训练闭环。
2. 网络结构为什么这样搭:轻量不是省事,是边缘端逼出来的
先用一句话把这个包里的核心立住:Traffic-Net 是为了“单张图分类”设计的轻量卷积网络,不是检测器。很多人一提到交通识别就转头去跑 YOLOv5、YOLOv8 训练自己的数据集,画框、做目标检测,再自己算拥堵指数。这就是第一个弯路——检测只是手段,拥堵等级才是业务结果,而 Traffic-Net 直接预测结果,建模成本低得多,也更容易在低算力设备上跑起来。
2.1 主干结构:卷积 + 深度可分离卷积 + 池化,为什么不用现成 MobileNet 直接跑
Traffic-Net 的主干是典型的“堆卷积块 + 下采样”结构。输入统一缩放成 224×224×3,前两层用普通 3×3 卷积快速把空间尺寸降下来,中段用深度可分离卷积减少参数量,最后接全局平均池化,不再接全连接层。全局平均池化是这套结构里最关键的一手:它把最后一个特征图直接压成一个长度等于类别数的向量,省掉了全连接层里动辄上百万的权重矩阵。
对比直接用 MobileNetV2 这类现成网络,Traffic-Net 的优势不在精度而在可控。MobileNetV2 输入尺寸和通道数绑得比较死,改输入分辨率要动全局池化后的结构;而 Traffic-Net 的结构里,你可以把最后一层特征图从 7×7 换成 14×14,池化后照样输出四个数,精度和速度自己平衡。对一张 224×224 的路口图,常见做法是保持 8 倍下采样,也就是最后特征图 28×28,整个模型的参数量控制在 2M 左右,比 MobileNetV2 的 3.4M 还少一截,更关键的是它没有需要单独调宽度的超参数,结构简单,训练时的玄学问题少。
我一般会用 ResNet18 当迁移学习的基底做对比实验,但最终部署还是会回到 Traffic-Net。ResNet18 的瓶颈在第一个 7×7 大卷积上,它非常吃显存,你把它扔到显存只有 4G 的旧显卡上,batch 只能开到 16,训练效率很难看。Traffic-Net 的第一层就是 3×3 卷积,显存占用平滑很多,batch 开到 64 也没有压力。
2.2 训练策略与迁移学习:是加载 ImageNet 权重还是从零训练
这是每个拿到源码包的人都要做的决定。Traffic-Net 在 ImageNet 上没有一个出厂自带的预训练权重,因为它是针对交通场景自研的小网络。但你不必因此放弃迁移学习,常见操作是取 ResNet18 的前几层卷积权重,把通道数匹配的部分拷过来,余下随机初始化。你的数据集如果只有几千张图,这个操作能明显稳住前几个 epoch 的 loss 曲线;如果图超过两万张,从零训练反而更稳,因为模型的输入分布(监控视角、低照度、雨雾)和 ImageNet 的日常物体分布差得太远,预训练特征在早期反而帮倒忙。
如果你嫌拷权重麻烦,直接把 Traffic-Net 的深度加深也是一条路。源码包里的网络结构文件通常只包含基础版本,你复现的时候可以在第三个卷积块后面再叠一个同样结构的 block,通道数翻倍,参数量会增加一半,但对夜间场景的适应能力會好不少。这个取舍后面会细讲。
3. 把原始视频变成数据集:帧截图、csv 打标签和目录划分
拿到源码包之后,直接解压后第一件事不是跑训练,而是先看清数据集长什么样。包里是一个按类别分好的图片文件夹加一份 CSV 标签表,每行记录“图片文件名, 等级标签, 拍摄时间, 道路编号”。这个格式和你自己从监控录像里做数据是完全一致的,所以这一章直接按“从零准备数据集”来讲,比单纯介绍包里有什么更有用。
3.1 从监控视频抽帧:采样率与“同一条路不同时段”的取舍
先把原始视频变成候选图片。常见做法是用 FFmpeg 按间隔抽帧,命令就三行:
mkdir -p frames ffmpeg -i road01.mp4 -vf "fps=1/10" -q:v 2 frames/road01_%03d.jpg说明一下这里的两个点:fps=1/10 表示每 10 秒抽一帧,你可以按 1/30 或 1/5 去调整。10 秒一帧对拥堵识别够用,因为拥堵状态是分钟级的,5 秒一帧只会让相邻帧高度相似,白白增加数据冗余。q:v 2 是 JPEG 质量参数,数字越小质量越高,建议固定 2,别用默认值,默认值在画面里有大量车辆纹理时会压出明显色块,影响后面的模型收敛。
抽帧之后千万别急着全部打标。先每段视频抽 20 帧出来看一遍,确认画面里有没有黑屏、断电重启、球机转动到天空这些无效画面。无效帧在数据集里占比一旦超过 5%,训练出来的模型在真实路口会频繁跳变。这一步是后面所有标注工作的后悔药。
3.2 标签写入 csv 与训练集、验证集、测试集划分
打标环节最可靠的做法不是一张张看名字改,而是先把抽好的帧导入一份 CSV,人工只标“等级”列:
filename,level,time,road_id road01_001.jpg,0,2024-11-20 08:15:00,R01 road01_002.jpg,1,2024-11-20 08:15:10,R01 road01_003.jpg,2,2024-11-20 08:15:20,R01level 的取值建议固定为 0=畅通、1=缓行、2=拥堵、3=严重拥堵。不要用文字标签,训练代码里做字符串到索引的映射会多一层出错的可能,直接存数字。划分训练集时按时间切,不要按文件随机切。比如同一个路口的早高峰视频,前面 8 分钟进训练集、后面 2 分钟进验证集,这样验证集才真正模拟了“模型没见过的时刻”的分布。随机切分在时间序列数据上是自欺欺人,因为相邻帧几乎一模一样,验证集分数会虚高。
from sklearn.model_selection import train_test_split df = pd.read_csv("labels.csv") # 按时间排序后按比例切,防止随机切分导致的数据泄漏 train_df, val_df = train_test_split( df.sort_values("time"), test_size=0.15, shuffle=False, stratify=None )这段代码里 stratify 刻意留空是有原因的。交通拥堵数据天然类别不均衡,畅通帧通常占大头,如果按类别比例强制抽样到训练集和验证集,会让某个严重拥堵时刻的连续帧被拆得七零八落,验证集里全是训练集里同一段画面的前后帧。正确做法是先按一段连续时间作为整体去分,保证同一个时间窗口的视频帧只出现在一侧。
3.3 数据增强的度:随机裁剪、翻转与色彩扰动
数据增强是这个小数据集项目里最值得投入时间的一步。对 Traffic-Net 这种小网络来说,没有增强撑不住真实场景的多样性。下面是我常用的增强管道,基于 PyTorch 的 torchvision 实现:
import torchvision.transforms as T train_transform = T.Compose([ T.RandomResizedCrop(224, scale=(0.7, 1.0)), T.RandomHorizontalFlip(p=0.5), T.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.2, hue=0.05), T.ToTensor(), T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])RandomResizedCrop 的 scale 参数要解释一下。对交通路口画面,如果你裁剪到 0.5,很可能把画面中的一条车道完整裁掉,模型反而学到了局部特征,对整体拥堵判断不利。0.7 是经验值,既能做尺度扰动,又不至于破掉路口的空间关系。ColorJitter 亮度扰动 0.3 在白天数据上效果明显,但如果你发现夜间帧训练时验证集 loss 不降,这个值要降到 0.1 以下,否则夜间低照度特征会被增强管道洗掉。验证集不要加任何随机增强,只用 Resize(224) 和 Normalize,否则你看到的验证指标里混合了增强噪声,无法定位模型真实能力。
4. 用 Traffic-Net 训练一个拥堵等级模型:PyTorch 代码与参数
数据准备妥当,进入训练环节。不同源码包的组织方式差别不小,但核心都是“读数据 → 建模型 → 训练循环 → 存 checkpoint”,下面按这个主线给出一份可以直接替换的骨架代码,你拿到任何一份 Traffic-Net 源码包,都能用这套骨架把它跑起来。
4.1 数据加载器:txt 标签映射与归一化
先把标签从 CSV 变成 PyTorch 能直接读的 Dataset。源码包里常用的是 ImageFolder 式目录结构,也就是 train/0、train/1、train/2、train/3 这样的子目录,但 CSV 里带了时间信息,直接用 ImageFolder 会把时间信息丢掉。我更推荐自己写一个轻量 Dataset 子类,把 time 字段一并读进来,后面做时间维度分析时不用重新对齐:
from torch.utils.data import Dataset from PIL import Image class TrafficDataset(Dataset): def __init__(self, df, img_dir, transform=None): self.df = df.reset_index(drop=True) self.img_dir = img_dir self.transform = transform def __len__(self): return len(self.df) def __getitem__(self, idx): row = self.df.iloc[idx] img = Image.open(f"{self.img_dir}/{row['filename']}").convert("RGB") if self.transform: img = self.transform(img) return img, int(row["level"])这里有个容易翻车的位置,就是convert("RGB")。有的监控帧是灰度图,有的带 alpha 通道,如果你不强行转 RGB,到模型里通道数会不一致,训练时直接崩。这类问题只在训练到一半的时候冒出来,报错信息还特别隐蔽,所以提前在这行挡掉。
4.2 训练循环:loss、优化器、学习率调度与 checkpoint
Traffic-Net 是四分类任务,损失函数直接选交叉熵。优化器选用 Adam 误打误撞能训出来,但如果你想让结果更稳、对学习率不那么敏感,SGD 加 momentum 才是常见落地做法:
import torch import torch.nn as nn model = TrafficNet(num_classes=4) criterion = nn.CrossEntropyLoss() optimizer = torch.optim.SGD( model.parameters(), lr=0.01, momentum=0.9, weight_decay=1e-4 ) scheduler = torch.optim.lr_scheduler.CosineAnnealingLR( optimizer, T_max=30, eta_min=1e-5 ) best_acc = 0.0 for epoch in range(30): model.train() train_loss = 0.0 for imgs, labels in train_loader: imgs, labels = imgs.to(device), labels.to(device) optimizer.zero_grad() outputs = model(imgs) loss = criterion(outputs, labels) loss.backward() optimizer.step() train_loss += loss.item() scheduler.step() val_acc = evaluate(model, val_loader, device) if val_acc > best_acc: best_acc = val_acc torch.save({ "epoch": epoch, "model_state_dict": model.state_dict(), "optimizer_state_dict": optimizer.state_dict(), "best_acc": best_acc, }, "traffic_net_best.pth") print(f"Epoch {epoch:02d}, Loss: {train_loss/len(train_loader):.4f}, Val Acc: {val_acc:.4f}")代码里两个设计直接关系到你能不能顺利训完。第一个是 CosineAnnealingLR,T_max 为 30,意味着学习率在 30 个 epoch 内从 0.01 余弦降到 1e-5。对交通拥堵这种类别差异不算尖锐的任务,学习率不降下来,最后几个 epoch 的验证集准确率会反复横跳。第二个是每轮都在验证集上跑一次并保存最优权重,不要等全部训完才保存,不然中途断电你前面十个 epoch 的白训了。checkpoint 里记得同时存优化器状态,断点续训时如果不恢复优化器,学习率调度会乱套。
4.3 三个必须说明的训练参数:batch size、学习率与 epoch 的联动
batch size、学习率、epoch 三者不是独立参数,而是一组联动值。源码包里如果给了默认参数,通常对应的是“一张普通游戏显卡”的配置。你对这三个参数做调整时,记住下面这几条对应关系:
batch size 设为 32 或 64 都合理,但要注意学习率要跟着 batch size 线性缩放。你从 batch 64 降到 batch 32,学习率也从 0.01 降到 0.005,否则小 batch 下的梯度噪声大,loss 曲线会像心电图。epoch 数量上,Traffic-Net 不是那种需要几百轮的大模型,核心特征是模型小、收敛快,30 轮左右验证集就基本稳定;如果你发现第 20 轮之后验证集准确率原地不动,不要盲目加 epoch,先去看学习率是不是已经降到 eta_min,如果是,直接改 T_max 而不是加轮数。
weight_decay=1e-4 是为防止小数据集上的过拟合,但如果你训练数据只有两三千张,把 weight_decay 调到 5e-4 会更好,Traffic-Net 的卷积核数量少,正则太弱会在第 10 轮以后明显看到训练 loss 和验证 loss 开始分道扬镳。
5. 训练避坑记录:5 个翻车现场和补救办法
这部分是实际训练中反复遇到、且源码包自带的 README 里基本不会写的问题。每一条都是“现象 → 原因 → 解决”的真实流程,你可以直接把对应检查手段当默认排错清单。
5.1 翻车现场:文件按名称排序后,第 10 天永远排在前面
第一坑出现在抽帧后文件排序上。用sorted(os.listdir("frames"))对 road01_001.jpg、road01_002.jpg … road01_010.jpg 排序,得到的结果是 road01_001、road01_002、road01_010,字符串排序把 010 排到了 002 前面。如果你的 CSV 是用文件名拼接生成的,前几张图和后面标签就对不上;训练时看着 loss 在降,实际学的全是错位对应。
解决:抽帧时文件名直接补零到三位以上,或者读列表后按数字部分重新排序。用sorted(frames, key=lambda x: int(re.search(r'(\d+)', x).group()))处理最稳,不要相信视觉上的顺序。
5.2 翻车现场:训练 loss 下降但验证 loss 反弹
这个现象最经典,表现为第二周训练时 loss 曲线一路向下,验证集 loss 却从第 8 轮开始往上走。原因是数据量有限时,Traffic-Net 的浅层特征在第 8 轮左右开始记住训练集里特定路口的纹理,比如某个路口独有的路面标志、栏杆颜色。
解决分两步。第一步先加验证集多样性,把连续时间帧的划分边界往前提,让验证集包含另一个路口的画面;第二步配合 4.3 的 weight_decay 调大。如果调完第二轮验证集还反弹,就回到 3.3 的增强管道把 RandomResizedCrop 的 scale 下限降低到 0.6,强迫模型看更多局部特征。不要一上来就换更深的网络,Traffic-Net 在几千张数据上不是欠拟合,是过拟合。
5.3 翻车现场:夜间帧把模型带崩
夜间监控画面整体偏暗,车辆只亮出一对车灯轮廓,拥堵和缓行在视觉上的差距远小于白天。你要是把白天夜间数据混在一起直接训,验证结果白天 90% 夜夜 60%,整体指标不好不坏,但一部署到实际场景就现原形。
解决:训练集里单独建一个 time_period 列标记“白天/夜间”,训练时按 0.7/0.3 的比例采样,而不是按原始帧数比例。代码上简单实现就是给 Dataset 加一个采样权重。如果夜间数据实在不够,用 3.3 里的 ColorJitter 把亮度扰动调到 0.05 以下,然后对夜间帧做直方图均衡化预处理,比在模型里加注意力模块更实际。
5.4 翻车现场:模型永远预测“畅通”
这是类别不平衡的典型症状,训练数据里畅通帧占 80%,模型学到的捷径就是全部输出畅通,整体准确率还高达 79%。验证集准确率同样虚高,看起来一切正常。
解决:不用改动模型结构,直接在损失函数里加类别权重。nn.CrossEntropyLoss(weight=torch.tensor([1.0, 2.0, 4.0, 6.0]))是个可参考的起点,权重比按训练集各类别占比的倒数做平滑。注意权重别拉太狠,严重拥堵的权重超过 8 之后,模型会反过来把所有缓行都判成严重拥堵,误报比漏报更难看。调整后同时把评估指标从准确率换成每类召回率的均值,才能看清真实效果。
5.5 翻车现场:测试集准确率不错,部署到摄像头就翻车
测试集里 90%,接到真实路口实时画面上就乱跳。原因通常是测试集和真实画面存在分辨率差异——测试集是直接从视频里截的帧,部署时走的却是 RTSP 流解码后的画面,后者经过缩放、隔行处理甚至 H.265 压缩,纹理细节已经变了。另一大来源是角度,测试集里一个固定机位,部署机位偏了 10 度,模型没见过。
解决:先在本地录制一条模拟 RTSP 流,用部署代码跑一遍,把输出逐帧存下来对比。如果识别结果频繁在畅通和缓行之间跳变,就在模型输出后加一个长度为 15 帧的中值滤波,取 15 帧内出现次数最多的类别作为最终结果。这类后处理写在部署代码里,比重新训练简单管用得多。
6. 验证与部署:用混淆矩阵验收,再决定要不要导出 ONNX
训练结束后最后一公里是验证与部署。没有谁能单靠 val_acc 就确定一个模型能上线,尤其是拥堵等级这种本身就存在模糊边界的分类任务。
6.1 分类任务验收:top-1 准确率和 Kappa 系数一起看
对四分类任务,我建议同时打印 top-1 准确率和 Cohen’s Kappa 系数。top-1 只反映整体正确率,而 Kappa 衡量的是“除去随机一致后的一致性”,对于类别不平衡的拥堵数据,Kappa 掉到 0.6 以下说明模型虽然准确率有 85%,但内部是在瞎猜。计算方式不复杂:
from sklearn.metrics import cohen_kappa_score preds_all = [] labels_all = [] model.eval() with torch.no_grad(): for imgs, labels in val_loader: outputs = model(imgs.to(device)) preds = outputs.argmax(dim=1).cpu().numpy() preds_all.extend(preds) labels_all.extend(labels.numpy()) kappa = cohen_kappa_score(labels_all, preds_all, weights="quadratic")weights="quadratic" 会让“把严重拥堵判成畅通”的惩罚远大于“把严重拥堵判成拥堵”,这符合业务直觉。交通拥堵等级是顺序尺度,相近类别之间的误判可以容忍,跨越度大的误判必须罚重。Kappa 值在 0.7 以上就具备灰度上线条件,0.8 以上可以直接部署。
6.2 导出 ONNX 并做一次性推理
Traffic-Net 训练完怎么部署取决于目标硬件。最常见的做法是导出 ONNX 格式给推理框架用,PyTorch 自带导出逻辑:
import torch.onnx model.eval() dummy_input = torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, "traffic_net.onnx", input_names=["input"], output_names=["output"], dynamic_axes={"input": {0: "batch"}, "output": {0: "batch"}}, opset_version=11 )dynamic_axes 允许推理时 batch 不固定,方便你做单帧推理。ONNX 导出后先不要直接丢上生产,用 onnxruntime 在本地跑一张夜间验证帧与 PyTorch 输出做对比,两者 logits 的差值应在 1e-4 级以内,超出这个量级基本是某个算子在导出时被替换成了精度更低的版本。这个检查能帮你省掉在边缘盒子上来回烧固件的时间。
6.3 那个迁移学习习惯:每份新数据先扫一遍脏样本
最后说一个我保留至今的训练习惯:不管拿到什么数据集,第一轮训练只跑 3 个 epoch,然后找出验证集里置信度最高但预测错误的那 20 张图,逐个放大看。这 20 张图通常能暴露 80% 的标签错误——比如“严重拥堵”帧里其实是车祸现场临时停车,“畅通”帧里是红灯等待的静止车队。Traffic-Net 权重小、训练快,这个扫脏样本的流程成本很低,却比调十轮参数有用得多。
实际跑过几个城市的交通数据之后,我的感受是:模型结构从来不是拥堵识别的主要瓶颈,数据的时段覆盖度和标签质量才是。如果你把这一整个流程在本地完整跑通,会发现自己对“模型为什么翻车”的判断力提升比模型本身的精度提升还值。希望这篇笔记能帮你少走一段弯路,把 Traffic-Net 真正用起来。
本文还有配套的精品资源,点击获取