恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
交通标志检测与识别:基于YOLO的完整项目实战解析
首页
资讯中心
/
交通标志检测与识别:基于YOLO的完整项目实战解析
交通标志检测与识别:基于YOLO的完整项目实战解析
发布时间:2026/10/1 12:33:19
简介基于Python的交通标志检测与识别项目源码包面向毕业设计、期末大作业及需要项目实战练习的计算机专业学习者提供一套经导师指导并认可的高分设计项目评审成绩98分覆盖交通标志数据集、模型权重与可运行源码并附有完整工程目录与调试说明可供直接编译运行。压缩包为zip格式共247个文件、约55MB其中包含29个Python源码文件、10个模型检查点、63组数据分片meta/data/index以及示例图片与文本说明文件类型层次分明便于按模块对照学习。已有37人学习下载。读者可借此理解交通标志识别从数据预处理、模型训练到检测输出的完整流程学习如何加载已有模型权重进行推理与测试也可基于源码与检查点快速二次开发或迁移至其他目标检测场景。项目难度适中内容经助教老师审定适合计算机相关专业学生作为课程设计、期末大作业或求职作品的基础工程也可作为进一步拓展识别类别的起点。1. 交通标志检测与识别为什么课设高分项目都爱选这条路交通标志检测与识别这个方向看着是计算机视觉的入门题真正动手的人才会被它绊住远处一个 40 像素的禁令标志逆光、倾斜、玻璃反光分类网络在标准数据集上刷得很高一换真实视频帧就翻车。这套基于 Python 的方案之所以能成为高分项目不是模型多新而是交付物完整——源码从标注转换、模型训练到推理演示一条线数据用 GTSRB 或 TT100K 这类公开数据集模型落在 YOLO 系最后拿出一份能当场跑、能讲清原理、能回答刁钻问题的成品。适合两类人课程设计或毕业设计想一次跑通、又愿意把细节吃透的学生以及想用最小成本把交通标志识别落到车载或路口抓拍演示的工程师。下面按数据选型、模型选型、训练、推理和验收的顺序拆开讲每个环节都给能直接抄的参数和大概率会踩的坑。2. 数据与模型选型GTSRB、TT100K 和 YOLO 系的两个关键决定2.1 数据集选型分类集和检测集别混用高分项目的首要质量指标是「数据是干净的」。交通标志方向最常出现在源码包里的公开数据集有三个GTSRB、TT100K、以及自己补拍的少量真实场景图。三者的形态完全不同选错等于在起点就给自己挖坑。数据集规模体量标注形式适合做什么GTSRB 德国交通标志5 万 张43 类单目标、居中、尺寸固定标签为 CSV先验证分类链路不适合直接做检测TT100K 街景交通标志10 万张街景其中有标注的约两万余张带包围框的 JSON/PKL 标注目标普遍小检测与识别项目首选场景真实自建补拍几十到几百张labelImg/labelme 导出 VOC 或 YOLO 格式补充本地真实标志答辩演示素材我在做这类项目时最常看到新手把 GTSRB 当检测数据集用训练时模型「识别」很准一上视频就框不住标志。原因在于 GTSRB 每张图本身就是从原图裁好的标志块它只能回答「这是什么」回答不了「在哪」。交通标志检测与识别要的是给定整帧画面同时输出位置和类别这必须靠带框标注的检测数据集才能训练出来。TT100K 是更贴近真实落地场景的选择。它来自街景采集标志在画面里通常只有几十像素背景里有树影、车流、灯杆干扰还有大量「难例」子集专门用来考验模型的鲁棒性。这个数据集有一个隐蔽问题相邻帧来自同一段连续街景相似度极高如果直接按顺序切 train/val/test验证集会泄漏进训练集导致 mAP 虚高、答辩现场露馅。我一般会先把所有图片文件名打乱再按 8:1:1 重新划分确保同一路段的画面不会横跨两个集合。另外不要迷信数据集自带的类别全集。TT100K 全量类别有上百种但很多类别样本量只有几十张直接全量训练会让模型严重偏向高频类。常见做法是只保留 15~25 个高频且语义不重叠的类别把「高频类 自建补充」组合成一个精简清单。这样做出来的模型训练更快、指标更高答辩时讲「我做了类别筛选」反而是加分项。2.2 模型选型为什么课设源码里 YOLO 系是绝对主力搜「目标检测项目源码」时你会发现交通标志项目里 YOLO 系占了九成以上这个现象不是跟风而是几个现实约束叠在一起的结果开源训练链路完整、单阶段模型本身同时解决「在哪」和「是什么」、部署到演示界面时对硬件要求相对宽松。模型方案参数量推理代价上手难度适合场景YOLOv5s / YOLOv8n约 3~7M低端 GPU 可训中端 CPU 可推理极低命令行一条跑通课设、毕设、快速落地演示YOLOv6按版本浮动与 v5/v8 接近低源码常带完整封装项目源码里高频出现SSD MobileNet6~7MCPU 友好中老代码多无 GPU 纯 CPU 演示HOG SVM 传统方案很低快低但上限低只处理规则形状复杂场景召回差我自己的判断标准很简单如果答辩要现场跑选 YOLOv5s 或 YOLOv8n 这类轻量版不用碰 large/x 型号。模型越大训练越慢显存越紧张而对交通标志这种目标尺寸偏小的场景大模型的收益远没有对小目标数据增强来得多。YOLO 系是单阶段检测器它在一个网络里同时回归框坐标和类别概率天然符合「检测与识别」这个标题的要求。传统两阶段做法先 HOGSVM 找候选框再 CNN 分类不是不能用但候选框阶段召回率很难超过 80%遇到遮挡和暗光就崩只适合课程里讲原理不适合当成一个要交付的「高分项目」。选型还有一个容易被忽略的点源码的成熟度。YOLOv5 的 train.py 和 val.py 脚本是公开沉淀多年的断点续训、日志、PR 曲线、混淆矩阵全帮你生成好了这些附属产物恰恰是答辩时最值钱的素材。一个能输出 results.png、confusion_matrix.png、PR_curve.png 的项目比手写一套推理代码但拿不出中间分析图的项目在评委眼里是完全两个档次。3. 把数据喂进 YOLO标注转换、训练参数与低显存机器的避坑经验3.1 用 Python 把 TT100K 的 JSON 转成 YOLO txt转换脚本与格式边界选定 TT100K 后第一件麻烦事就是标注格式。TT100K 的标注是 JSON 结构而 YOLO 训练需要的是每个图片同名的一个 txt 文件每行一个目标格式是类别id 中心x 中心y 宽 高且全部归一化到 0~1。这一步是纯体力活但格式细节错一个训练时 loss 直接异常。下面这个脚本是我常用的转换框架。拿到不同版本的 TT100K 时字段名可能略有差异所以脚本里保留了一次打印帮你先确认实际键名。import json import os from pathlib import Path # 类别到 id 的映射按你自己的 classes.txt 维护 category_to_id { p100: 0, # 禁止通行 p110: 1, # 禁止停车 p200: 2, # 限速 w55: 3, # 注意行人 # 按你的精简类别清单继续补充 } def convert_tt100k_json_to_yolo(json_path, img_dir, out_dir): with open(json_path, r, encodingutf-8) as f: data json.load(f) # 不同版本键名不一样先打印一层结构避免盲写 first_key list(data.keys())[0] print(顶层键示例:, first_key, 字段:, list(data[first_key].keys())) os.makedirs(out_dir, exist_okTrue) for img_name, info in data.items(): # 如果 JSON 里没有宽高就用图片实际尺寸兜底 img_path os.path.join(img_dir, img_name) w info.get(width) h info.get(height) if not w or not h: import cv2 tmp cv2.imread(img_path) h, w tmp.shape[:2] objects info.get(objects, []) lines [] for obj in objects: bbox obj[bbox] # 字段名在不同版本可能不同 x1, y1 bbox[xmin], bbox[ymin] x2, y2 bbox[xmax], bbox[ymax] cx ((x1 x2) / 2) / w cy ((y1 y2) / 2) / h bw (x2 - x1) / w bh (y2 - y1) / h cls_id category_to_id[obj[category]] # 归一化坐标可能出现小数位数问题保留6位足够了 lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) out_txt os.path.join(out_dir, Path(img_name).stem .txt) with open(out_txt, w, encodingutf-8) as f: f.write(\n.join(lines)) print(转换完成生成文件数:, len(os.listdir(out_dir)))这个脚本的逻辑分四步读 JSON、确认图片宽高、遍历每个目标的 bbox 做归一化、写出同名 txt。有几处必须注意。第一归一化坐标用的是(x1x2)/2 / 宽不是x1 宽/2两者数学等价但前者只用原始坐标不容易被宽高字段的类型错误影响。第二如果 JSON 没提供宽高用 cv2.imread 读一次真实尺寸否则中心点坐标会整体偏移训练出的框位置全错。第三category_to_id映射必须和后面 dataset.yaml 里的names顺序完全一致顺序反了模型不会报错但预测类别全部错位。转换完成后目录结构我一般这样组织mkdir -p TT100K/images TT100K/annotations TT100K/labels # images 放原图annotations 放原始 jsonlabels 放转换后的 txt # 最后在 TT100K 根目录放一个 classes.txt 记录类别清单这样组织有个额外好处后期如果要加自建数据只需把新图的标注也转成同名 txt 丢进 labels就能和 TT100K 混合训练不用重写任何脚本。3.2 train.py 里必调的五个参数epochs、batch、imgsz、lr 与多尺度数据就位后训练命令在 YOLOv5 体系里大概是这个样子。以 yolov5 的 train.py 为例v8/v11 的小命令参数名基本对齐差异不大# TT100K.yaml path: /data/TT100K # 数据集根目录 train: images/train # 相对 path 的训练图片目录 val: images/val # 相对 path 的验证图片目录 nc: 20 # 类别数量必须和 category_to_id 的长度一致 names: [p100, p110, p200, w55, ...] # 顺序必须和转脚本里的 id 一致python train.py \ --data TT100K.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --cache ram \ --device 0这里五个参数是影响最大的逐个说明我常用的取值逻辑参数我的常用值什么时候改--img640 起步小目标多就拉到 960/1280代价是显存和每 epoch 耗时成倍上涨--batch16显存不够就降 8同时用梯度累积补回--epochs100数据量少或迁移学习可以降到 50超过 150 容易过拟合--lr00.01从头训练保持默认自己数据微调用 0.005~0.01 更稳--cache ram显存够就开内存不足时去掉否则训练反而变慢--img是交通标志项目里最需要上心的参数。GTSRB 那种居中裁剪图不存在这个问题但 TT100K 里标志只占画面几十分之一640 输入会把 40 像素的标志压到 13 像素左右接近检测器对小目标的感知极限。我一般先在 640 上跑通流程确认无报错后再把分辨率拉到 960 重新训一轮mAP 往往能涨 3~5 个点。--lr0容易被忽略。用--weights yolov5s.pt做迁移学习时COCO 预训练权重里的特征提取层已经很强学习率保持默认 0.01 即可。但如果你改用了--weights 从头训练0.01 对随机初始化来说偏大loss 前期会很颠建议降到 0.005 以下。判断方式是看前 3 个 epoch 的 loss 曲线如果剧烈震荡不收敛第一嫌疑就是学习率。还有一个隐藏参数值得动--multi-scale。它是 YOLO 自带的多尺度训练每轮迭代随机缩放输入对交通标志这种尺度变化极大的场景非常有效。代价是每个 epoch 时间变长但通常不需要你手动设置具体尺度打开即可。3.3 低显存机器跑模型混合精度、梯度累积与老实降分辨率很多人的笔记本显卡只有 4~6G 显存一跑 960 分辨率就 OOM这时有三条路可以走优先级从高到低。混合精度 AMP 值得先确认。YOLOv5 较新版本默认开启 AMP能明显降低显存占用。但它在部分老显卡和老驱动上会「假死式训练」loss 能打印数值却几乎不动。遇到这种情况直接在命令里加--amp 0关掉混合精度别犹豫。我见过有人在这种状态下硬跑 50 个 epoch最后发现模型和随机初始化差不多。梯度累积适合 batch 必须很小的情况。比如 batch 只能设 4那就在训练超参里把 accumulate 设成 4等效于一次更新看过 16 张图。需要注意的是梯度累积只影响优化器更新频率不影响批归一化统计所以效果不完全等于大 batch但至少能稳住 loss 下降方向。如果以上都做完了还是 OOM老实把--img降到 320 或 480。交通标志不是密集小物体检测降分辨率对它的伤害没有对行人检测那么大训练一个可演示的模型完全足够。低显存不是玄学本质是在「输入信息量」和「模型容量」之间做取舍先把流程跑通再去追求极限指标。4. 推理不是跑个循环置信度、NMS 与视频帧率的三方博弈4.1 单帧推理conf 与 NMS 阈值先定再谈模型好坏训练完成后模型的好坏先不急着看 mAP直接用一张验证集图片跑推理。加载方式常见有两种源码里torch.hub.load加载本地权重或者项目自带的 detect 脚本。我平时调试用第一种可交互性更好import cv2 import torch # 加载训练好的 best.ptforce_reload 保证每次都读本地权重而不是缓存 model torch.hub.load(ultralytics/yolov5, custom, pathruns/train/exp/weights/best.pt, force_reloadTrue) # 全局置信度阈值和 NMS 阈值 model.conf 0.35 model.iou 0.45 # cv2 读出来是 BGRYOLO 训练时用的是 RGB这里必须转换 img cv2.imread(demo/frame_099.jpg) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) results model(img_rgb, size640) # xyxy[0] 是单张图的检测结果DataFrame 格式 pred results.pandas().xyxy[0] # 置信度二次筛选目的是剔除那些只靠 NMS 保留的弱检测 strong pred[pred[confidence] 0.5] print(strong[[xmin, ymin, xmax, ymax, confidence, class]])这里有两个阈值作用完全不同。model.conf是先过滤掉低置信度框model.iou是 NMS 在过滤后剩下的框里做去重。很多人只调 conf忽视 iou结果两个重叠框同时出现看起来像是模型「一个标志预测两次」。实际上 NMS 的 IoU 阈值设高了会保留更多重叠框设低了又会把真正相邻的两个标志误合成一个。对 TT100K 这类小目标数据远距离标志的置信度普遍在 0.3~0.5 之间因此我调试时一般用 conf0.35 看视觉效果不要直接用训练默认的 0.25否则画面上会堆满低置信度的虚框。但要注意展示参数和报告指标是两回事验证指标用的是 val.py 里默认的置信度扫描口径不是推理时这一个固定阈值。4.2 视频流检测预处理、跳帧与结果释放从单帧到视频新手最容易把代码写成「while 循环里套 single frame 推理」能跑但帧率惨不忍睹跑几分钟内存还会涨。视频推理有四个必需动作跳帧、降分辨率、关梯度、主动释放结果对象。from collections import deque import cv2 import torch # 模型加载略参考 4.1 cap cv2.VideoCapture(street.mp4) frame_id 0 skip 2 # 每 3 帧抽 1 帧检测演示时保证流畅 queue deque(maxlen2) # 保留最近两帧结果方便后续做平滑 with torch.no_grad(): # 推理阶段关闭梯度显存占用明显下降 while cap.isOpened(): ret, frame cap.read() if not ret: break frame_id 1 if frame_id % (skip 1) ! 0: continue # 转 RGB 降分辨率省略绘图 frame_rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results model(frame_rgb, size480) # 比 640 快一倍以上 pred results.pandas().xyxy[0] pred pred[pred[confidence] 0.3] # 这里只做筛选画框、计数放到后面的绘图函数里 queue.append(pred) # 当前帧的 results 对象持有大 tensor不释放会累积 del results cap.release()逻辑说明跳帧把推理频率从每帧一次降到每三帧一次对标志这种静态目标几乎没有损失尺寸从 640 降到 480推理耗时能降到原来的六成左右torch.no_grad()和del results是显存和内存的双保险少了后者在长时间视频上内存会一路涨到系统卡死。4.3 CPU 帧率不足时的策略降分辨率、间隔采样和队列缓冲如果演示环境没有 GPUYOLOv5s 在 CPU 上运行时640 输入单帧耗时少则几十毫秒、多则上百毫秒。这时候想保持画面流畅靠的不是换模型而是给处理管线做「时间预算」。我常用的组合是推理分辨率降到 480、跳帧数提到 5、把绘图和推理拆到两个线程。绘图线程只画上一帧的结果推理线程持续处理最新帧。这个方案在普通笔记本上能把画面帧率稳定在 20fps 左右虽然检测结果有延迟但演示效果远好于卡成幻灯片。另一个容易忽略的点是输入帧本身。摄像机采集到的画面如果带鱼眼畸变标志边缘会被拉伸检测框贴合度会变差。做演示前先用 OpenCV 的cv2.undistort做一次去畸变或者干脆在采集时避开广角镜头比调模型阈值有效得多。5. 训练与验收避坑五个让我返工到凌晨的坑5.1 Windows 下训练卡在第一个 epochdataloader 在作祟现象命令跑起来后loss 打印出来但进度条长时间不动CPU 占用却很高过一阵子又突然恢复。原因Windows 下 PyTorch 的 DataLoader 默认采用 spawn 方式启动多进程每轮 epoch 都要重新派生子进程。TT100K 图片数量多、单张体积小子进程反复启动的开销会被放大表现就是「训练像死了一样」。解决在训练命令中把 workers 设为 0--workers 0先保证流程跑通。如果数据量大确实需要多进程补上if __name__ __main__:保护并确认代码没有被 IDE 的交互式环境干扰。我一般在课设阶段直接 workers0省得排查。5.2 小目标被 resize 洗掉mAP 卡在 0.6 上不去现象训练 loss 正常下降验证集 mAP0.5 却一直卡在 0.6 左右检查预测结果发现远处的标志全没检出。原因原图 2048 宽压到 640 后一个 40 像素的标志缩成 12 像素低于多数检测器对小目标的感知下限。mAP 卡住不是模型学不会而是输入信息量被我亲手丢了。解决训练分辨率从 640 提到 960显存不够就配合 AMP 和梯度累积。再不够把图片切成重叠块分别训练和推理最后合并检测结果。切块方案演示时略显复杂但它是唯一不牺牲小目标信息的终极手段。5.3 组合禁令标志一个框装两个类别现象圆形禁令标志内嵌一个小限速牌标注时为了省事整体框成一个大框训练后模型对这个框时而预测「禁令」时而预测「限速」。原因一个框内同时包含两个有效语义标注的类别和内容互相矛盾模型只能学到「这个位置两者皆可」置信度自然上不去。解决把组合标志拆成两个独立框——外圈大框标禁令类别内圈小框标限速类别或者干脆只保留外圈主类别放弃内圈语义。交通标志检测的标注原则是「一个语义一个框」尤其是答辩演示时组合标志框得准不准直接影响评委对你的第一印象。5.4 少数类类别直接隐身现象混淆矩阵里高频类别限速、禁止停车对角线颜色很深低频类别部分警告标志几乎全部被分到背景。原因TT100K 自带长尾分布高频类和低频类样本量差距能到几十倍。模型在训练时占主导的类别把特征空间占满了少数类的梯度信号被淹没。解决两种手段配合。一是按类别权重重采样把低频类在 dataloader 里多抽几轮二是对少数类做复制粘贴增强把该类小图随机贴到训练图背景区域。贴图时做一点 alpha 融合避免目标边缘硬切否则模型会学到「边缘锐利 该类别」的误导特征。5.5 颜色阈值前置处理的翻车现象有同学为了「提高速度」先用红色蒙版提取圆盘区域再做检测晴天效果不错阴天或夜间召回率直接崩。原因颜色阈值是人工调死的交通标志在逆光、LED 灯光、阴影下的真实色相变动范围远超固定阈值能覆盖的空间。用颜色过滤做检测前置等于在光照变化最剧烈的场景里加了一个最不稳定的模块。解决不要用颜色做前置把颜色抖动作为训练增强。YOLO 超参文件里的hsv_h、hsv_s默认值可以适当调大让模型自己学到颜色不变性。需要夜间场景时直接往训练集里掺真实夜间帧比任何颜色预处理都有效。6. 把项目从「能跑」做到「耐问」指标口径与演示的落地经验答辩和验收时评委最爱问的三个问题几乎固定mAP 是怎么算的、为什么选这个模型、演示视频里为什么有的框不画。提前把指标口径和演示策略准备好比临时翻源码有用得多。验证阶段用 val.py 拿到准确率、召回率、mAP0.5、mAP0.5:0.95 这几个数是基本动作python val.py \ --data TT100K.yaml \ --weights runs/train/exp/weights/best.pt \ --img 640 \ --conf 0.001 \ --iou 0.45val.py 里 conf 默认 0.001是为了在低置信度下扫描出完整 PR 曲线。报告指标时明确写「mAP0.5 0.87IoU 阈值 0.5置信度扫描口径」这一句话就能让报告的专业度上一个台阶。mAP0.5:0.95 是更严格的指标对框的精度要求高课设场景如果数值不高不必硬撑如实报告即可。演示时有一个我反复踩过的坑视频里置信度阈值设得过高导致远处小标志全部不画框评委一句「为什么漏检」就卡住了。演示阈值我一般固定在 0.25~0.3 之间宁可多画几个低置信度框也不要漏掉真实目标。画面里出现误报时随口解释一句「这是置信度阈值调的较低目的是保证召回」比沉默着翻代码强得多。PR 曲线和混淆矩阵这两张图建议放在答辩 PPT 的结果页。混淆矩阵能直观展示少数类的问题PR 曲线则能说明「你为了召回做了哪些取舍」。讲的时候用一句话串联模型在 mAP0.5 上表现稳定短板集中在远距离小目标和少数类下一步计划用切块推理和重采样来解决。这句话既承认了不足又展示了分析能力比「我的模型效果很好」有力得多。做这类项目最受益的一个习惯是每换一个数据集划分或训练参数都把 val.py 输出里的指标复制到同一张表里保留每一次的实验记录。答辩时被问「这个 0.87 是怎么调出来的」直接调出三行对比数据比任何口头解释都有说服力。这个表也让你在后续查问题时不至于靠记忆复盘哪个参数改过。希望帮到你也祝你的模型一次收敛。本文还有配套的精品资源点击获取