
简介这是一份基于Python实现的交通标志检测与识别完整项目面向计算机相关专业正在做毕业设计的学生以及需要项目实战练习的学习者。项目经导师指导并认可包含全部源码、文档说明、数据集与训练模型调试确保可运行可直接作为毕设、课程设计或期末大作业使用。压缩包共247个文件大小55.01MB主要文件类型包括Python脚本py、模型检查点checkpoint以及TensorFlow数据分片如data-00000-of-00001、index、meta其中py脚本覆盖数据加载、特征提取、模型训练与测试等环节另有jpg测试图片与txt说明辅助理解整体结构清晰。已有163人浏览学习便于按模块查阅与二次开发。对正在准备交通标志识别课题的学生而言这份资源提供了从数据处理到模型部署的完整闭环既能支撑论文实验也能用于实践练手是一份可直接落地的完整方案。1. 交通标志检测与识别这可能不是纯 Python 项目但性价比确实高交通标志检测与识别在毕设和求职项目里出现的频率比多数人想象的高不少。这活儿听上去像深度学习入门三件套之一但它实际覆盖了目标检测主流玩法用 ResNet 或 VGG 这类骨干网络提取特征再用 Faster R-CNN 或 YOLO 系做候选框回归和分类。训练集来自公开数据集比如 GTSRB 或 LISA部署时要么跑在本地 GPU要么导出成 ONNX 喂给边缘设备。这个压缩包的构成基本是源码训练、验证、推理脚本、文档说明环境配置和操作流程、数据子集或预处理后的标注文件、模型权重训练好的 checkpoint。适合三类人急交毕设的学生、想快速跑通目标检测流程的从业者、准备面试项目复盘的老手。预算吃紧、不想从零看论文的人这包能省下不少冷启动时间。先说清楚一件事想拿它直接当产品交付还差得远但想拿它应付答辩和面试足够了。2. 先搞懂资源根目录结构别急着跑 train.py先看你拿到的是什么打开解压后的文件夹第一眼通常是checkpoint/、data/、dataset/、src/或code/、一份requirements.txt或environment.yml外加 PDF 或 Word 写成的毕业设计说明书。多数毕设项目包的目录结构是下面这个模子. ├── checkpoint/ # 训练时保存的模型权重 │ ├── faster_rcnn_resnet50.pth │ └── training_log.txt ├── data/ │ ├── train/ # 按类别分文件夹存放训练图像 │ │ ├── speed_limit_30/ │ │ ├── stop/ │ │ └── ... │ ├── val/ │ └── test/ ├── dataset/ │ └── annotations/ # VOC 格式 XML 或 YOLO 格式 txt ├── src/ │ ├── train.py │ ├── detect.py │ ├── model.py │ ├── utils.py │ └── config.py ├── docs/ ├── requirements.txt └── README.md从目录结构就能反推这个项目走的是哪条技术路线有annotations/目录说明数据不是直接从 Torchvision 或 TFDS 拉流而是用户自己标注过config.py存在说明模型超参数集中在里面不是散落在训练脚本各处checkpoint/说明已经跑通过训练不是只给了代码没给权重。这是一份典型的、可复现的完整工程结构。2.1 数据标注逻辑未标注数据占 80%别高兴那是坑交通标志数据集最常见的标注格式有三种VOC 格式的 XML、YOLO 格式的 txt、COCO 格式的 JSON。毕设包里 80% 的概率是 VOC 或 YOLO因为它们最容易手工标注也最早被工具支持。!-- VOC 格式标注文件示例data/annotations/stop_001.xml -- annotation foldertrain/folder filenamestop_001.jpg/filename size width1280/width height720/height /size object namestop/name bndbox xmin452/xmin ymin310/ymin xmax612/xmax ymax470/ymax /bndbox /object /annotation# YOLO 格式标注示例data/annotations/stop_001.txt # 每行类别ID 中心点x 中心点y 宽度 高度均为归一化值 0 0.4156 0.5417 0.1250 0.2222VOC 里xmin/ymin/xmax/ymax是像素绝对坐标解析时直接用不需要缩放YOLO 里存的是相对图片宽高的比例值训练时 DataLoader 读取原图后要把框坐标按(x * w)还原成像素值。很多新手在从 VOC 转 YOLO或者反过来转的时候翻车多半就是忘了归一化和反归一化这步。动手前先翻注释文件的头部几行判断是绝对坐标还是相对坐标再决定要不要写转换脚本。2.2 数据集挑选别照单全收把类别数砍到 5 类以内再训练交通标志公开数据集有 GTSRB德国、LISA美国、TT100K中国。TT100K 更适合国内毕设或项目展示因为它的标志风格和国内道路一致GTSRB 是单目标分类数据集原版只有裁剪好的标志图没有带位置的检测框如果项目要跑检测直接用 GTSRB 一般得先自己跑一遍目标检测标签制作流程。# src/config.py 中的类别配置示例 # 把原本 40 类砍到 5 类训练收敛速度和最终精度都会明显改善 CLASSES [ stop, # 0 speed_limit_30, # 1 speed_limit_60, # 2 pedestrian_crossing, # 3 no_entry # 4 ] NUM_CLASSES len(CLASSES)类别砍到 5 类以内是这类项目里最常见的提分手段。原因有两个一是公开数据集中不同类别的样本数量极不均衡比如 stop 标志可能几千张某类限速标志只有一两百张类别一多模型很容易把少样本类别学成一个背景二是输出头Head的通道数直接跟类别数挂钩类别越多模型参数越多小数据集上过拟合越明显。如果你的包自带 40 类权重而你只想拿它做答辩演示我一般建议你按上面这个配置跑一次微调而不是直接用原权重。2.3 模型结构识别从 checkpoint 文件名反推你的代码路径拿到checkpoint/下的.pth文件看一眼文件名里的骨干网络你就知道这份资源的代码是按哪条路走的。比如faster_rcnn_resnet50.pth是两阶段方法适合做毕设答辩能讲 region proposal 原理yolov5s.pt或yolov8n.pt是单阶段方法胜在速度快适合做 Web 演示或边缘部署场景。# 快速查看 checkpoint 里存了哪些层确认网络结构是否和代码匹配 cd src python - EOF import torch ckpt torch.load(../checkpoint/faster_rcnn_resnet50.pth, map_locationcpu) if state_dict in ckpt: keys list(ckpt[state_dict].keys()) else: keys list(ckpt.keys()) for k in keys[:10]: print(k) EOF大概率看到backbone.body.conv1.weight、roi_heads.box_predictor.cls_score.weight这类键名说明几乎可以确定是 torchvision 里的 Faster R-CNN 实现。如果一开始报错说缺少classifier键基本可以肯定权重是用torchvision.models.detection.fasterrcnn_resnet50_fpn保存的而你的代码里写的是backbone层结构键名对不上就会报缺失。确认结构后再动手改代码是从 checkpoint 反推项目技术路线最有效的办法。3. 训练前必经流程环境对齐、权重预热、参数验证这一章是整个项目能不能跑通的分水岭。很多资源包下载下来能跑不是因为代码写得多好而是环境、依赖、数据对齐恰好一致。你换台机器内存、CUDA、torch 版本稍有不同报错就跟着来了。所以与其直接双击 README 里的命令我建议你先把训练前这套「环境三连」走完环境重装一遍、权重做一次预热推理、参数做一次小规模验证。3.1 环境重建优先用 requirements.txt而不是盲选最新版本所有毕设项目的requirements.txt都是为了把环境锁定在作者跑通时的版本。这一步省了后面会有各种灵异报错。# 建议用 conda 建一个新的干净环境避免把系统 Python 环境搞乱 conda create -n traffic_sign python3.8 -y conda activate traffic_sign # 先安装 CUDA 版 PyTorch保证和本机 CUDA 驱动匹配 pip install torch1.10.0cu113 torchvision0.11.0cu113 -f https://download.pytorch.org/whl/torch_stable.html # 再安装项目依赖 pip install -r requirements.txt上面这个torch1.10.0cu113是老项目的经典版本组合如果你的资源包本身就是两年前写的大概率用的是这个起步如果你的本机驱动是 CUDA 12 以上装这个老版本反而会报驱动不兼容。判断依据很简单看checkpoint文件里 PyTorch 版本号或者看requirements.txt里 torch 那一行的约束。如果驱动太新导致老版本 torch 用不了可以直接升到torch2.0.1cu118但这时候就必须小心torchvision的 API 在 0.15 之后默认把FasterRCNN的min_size这些参数改成了关键字参数代码里如果还在用老式位置传参会被 TypeError 拦下来。3.2 单张图像试推理训练前必须做验证三件事训练前先跑通一次推理看起来多此一举实际上能一次性覆盖三类问题数据读取链路是否正常、预处理是否报错、模型前向传播是否兼容当前 torch 版本。这段代码能省去训练跑十几个 epoch 之后才发现数据加载报错的惨剧。# src/quick_check.py # 用途训练前验证单张图像能否完成前向推理 import torch from PIL import Image import torchvision.transforms as T from src.model import get_model # 初始化模型从 checkpoint 反推出的结构 model get_model(num_classes5, pretrainedFalse) state torch.load(../checkpoint/faster_rcnn_resnet50.pth, map_locationcpu) model.load_state_dict(state[state_dict] if state_dict in state else state) model.eval() # 读一张测试图片并做预处理 img Image.open(../data/test/stop_001.jpg).convert(RGB) transform T.Compose([ T.ToTensor(), ]) input_tensor transform(img).unsqueeze(0) with torch.no_grad(): outputs model(input_tensor) print(outputs) # 预期输出一个包含 boxes / labels / scores 的 dict这段代码能验证三件事一是get_model里写的网络结构能不能成功加载 checkpoint 权重二是从图像读到 Tensor 的预处理链路有没有问题三是前向推理在当前 torch 版本下能不能正常出结果。如果get_model里用pretrainedTrue但 torchvision 版本变了就会在加载权重的瞬间报 URL 连接失败如果你拿到的权重本身是在 5 类上训练的但get_model里写的num_classes40那么cls_score层的权重尺寸会对不上直接抛size mismatch。这两个错不在训练前跑一次推理是不可能提前暴露的。3.3 训练参数全景表epoch、batch size、学习率、IOU 阈值各是什么角色训练脚本里的参数看起来只有十几行但每个参数在不同框架下的默认值差异极大。YOLO 系和 Faster R-CNN 系在这几个参数上的习惯完全不同拿一套模板去套另一套模型会有明显的症状差异。先看表再动手改。这个表也从侧面回答了为什么同类项目拿你的权重去复现效果总比论文里差一点。参数Faster R-CNN 常见值YOLOv5/v8 常见值参数作用翻车现象epochs/num_epochs12~20100~300整体训练轮数轮数过少loss 没收敛就保存轮数过多小数据集过拟合严重batch_size2~4受显存限制16~64每次迭代送入的图片数显存溢出OOM时优先减半learning_rate0.005~0.020.01~0.001梯度下降步长过大会训练震荡loss 不降反升momentum0.90.937梯度平滑系数新手一般不用动动量过高会放大低学习率下的抖动num_workers2~44~8数据加载进程数设太大会把 CPU 打满反而拖慢训练score_thresh0.05训练/ 0.5推理0.25训练/ 0.45推理置信度阈值推理阈值设太高小目标直接全部漏检nms_iou_thresh0.50.45 / 0.5非极大值抑制的重复框阈值设太低会把同一物体的多个框全删掉这张表的用途是「对照检查」训练日志里 loss 不降先查学习率而不是查网络结构显存爆了先调 batch size而不是换模型推理时目标漏检先调score_thresh而不是盲目换更深的骨干网络。很多毕设项目里作者可能把训练参数写死在某一段区间了你解压之后直接跑是没问题的但稍微改一下数据分布和类别数原参数可能就不是最优解。跑之前对着这张表把训练日志里的实际参数核一遍是系统工程里的「读日志前先看图」原则。4. 核心代码走读与参数调优照着这份打比盲改强十倍手上有源码最大的优势不是「能跑」而是「能改」。改的前提是知道每一块代码在干什么。毕设项目代码通常分四块数据加载器、模型定义、训练循环、评估逻辑。把它们拆开来看每块都有几个关键调试点改对了分数提升立竿见影。这里用从资源包里提炼出的通用结构来走一遍具体命名以你实际的src/文件夹为准。4.1 数据加载器检查图像尺寸和归一化先解决「图像发绿」和「尺寸混乱」# src/dataset.py 中的关键片段 from torch.utils.data import Dataset from PIL import Image import torchvision.transforms as T class TrafficSignDataset(Dataset): def __init__(self, img_dir, ann_dir, transformsNone): self.img_dir img_dir self.ann_dir ann_dir self.transforms transforms def __len__(self): return len(os.listdir(self.img_dir)) def __getitem__(self, idx): img_path ... ann_path ... img Image.open(img_path).convert(RGB) boxes, labels self.parse_annotation(ann_path) # 解析 VOC/YOLO 标注 if self.transforms: img, boxes, labels self.transforms(img, boxes, labels) return img, {boxes: boxes, labels: labels}这段代码里最常见的改法是调整self.transforms但改之前先看数据标注坐标是像素值VOC还是归一化值YOLO。如果是 VOC图像尺寸变化后框坐标必须跟着缩放否则训练时就会出现框和标志对不上的问题如果是 YOLO 的归一化坐标图像 resize 后坐标不需要变化。毕设包里最容易翻车的点是这数据集作者可能已经 resize 过图片并同步改了标注但代码里的 transforms 又做了一次随机裁剪第二次裁剪后没同步坐标变换导致正样本被裁掉一半。这类问题在训练日志里通常表现成「loss 不降验证集 AP 在 0.1 徘徊」很磨人。# 推荐的训练时 transforms 组合图像短边统一到 800长边按比例缩放受限到 1333 from torchvision.models.detection.faster_rcnn import FastRCNNPredictor def get_transform(train): t [] t.append(T.ToTensor()) if train: t.append(T.RandomHorizontalFlip(0.5)) return T.Compose(t)从 torchvision 官方 Faster R-CNN 的实现看它内部会自动把输入缩放到min_size800, max_size1333的范围所以ToTensor()之后不需要额外 resize。如果你从别的项目拷贝了一段Resize((224, 224))强行把交通标志缩成正方形小标志会直接因为像素不足而完全丢失。看到这种代码直接删掉 resize 那行让他内部的min_size机制接管。4.2 模型定义知道哪一层的输出要改别把整个网络推倒重来# src/model.py 中的关键片段 import torchvision from torchvision.models.detection import FasterRCNN from torchvision.models.detection.rpn import AnchorGenerator def get_model(num_classes, pretrainedTrue): # 用 resnet50 做骨干网络torchvision 直接提供 Faster R-CNN 封装 backbone torchvision.models.resnet50(pretrainedpretrained) # 把最后一层全连接去掉只保留 backbone 的卷积特征提取部分 backbone torch.nn.Sequential(*list(backbone.children())[:-1]) # 自定义 RPN 的 Anchor 尺寸 anchor_generator AnchorGenerator( sizes((32, 64, 128, 256, 512),), aspect_ratios((0.5, 1.0, 2.0),) ) # 定义 ROI 池化输出大小和分类头 roi_pooler torchvision.ops.MultiScaleRoIAlign( featmap_names[0], output_size7, sampling_ratio2 ) model FasterRCNN( backbone, num_classesnum_classes, rpn_anchor_generatoranchor_generator, box_roi_poolroi_pooler ) return model如果你从 checkpoint 文件名里看到了 ResNet50 字样那你模型定义大概长这样。两个关键调试点一是AnchorGenerator的sizes这是 RPN 阶段预设框的基准尺寸交通标志在整幅图像里占比通常偏小把 512 去掉或把 32 改成 16对小目标的召回率会有看得见的提升二是最后num_classes一定是实际类别数比如 5 类背景加四类标志就是 5 类如果你误把背景也算成一类那最终类别数是NUM_CLASSES 1得在调用处填对数字否则推理输出会多出一个人为的背景类别导致stop标志被分类成背景输出全为空。4.3 训练循环loss 权重和梯度裁剪毕设血泪经验都在这里训练循环写到loss sum(loss_dict.values())就完事的大有人在但在交通标志这个场景里类别不均衡和边界框回归误差是两个主要矛盾。两类损失需要平衡不当加权的直接后果是模型只学会识别样本量最多的类别。# src/train.py 中的训练步核心逻辑 optimizer.zero_grad() loss_dict model(images, targets) # 返回 dict: loss_classifier / loss_box_reg / loss_objectness / loss_rpn_box_reg losses sum(loss for loss in loss_dict.values()) losses.backward() # 梯度裁剪处理训练前期 loss 突然变成 nan 的灵异现象 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm5.0) optimizer.step()clip_grad_norm_(max_norm5.0)的意思是把所有参数的梯度拼接成一个向量如果它的 L2 范数超过 5就等比缩放回 5。它防的是训练前几个 iteration 出现极端梯度导致权重一步跳到损失函数的「悬崖」区域从此 loss 都是 nan。对交通标志检测这种小数据集项目几乎每五个训练里就会遇到一次这种「梯度爆炸」。加了这行代码之后训练过程就像装了安全阀。我把loss_dict里各损失的权重列在下面方便你在日志里对照loss 项来源权重现象调整建议loss_classifierROI 分类头交叉熵1.0分类不准类别失衡时调高到 1.5~2.0loss_box_reg边界框回归 Smooth L11.0框位置偏框不稳时调高到 1.2~1.5loss_objectnessRPN 是否有目标1.0RPN 召回太低小目标多时保持 1.0 或略增loss_rpn_box_regRPN 框回归1.0RPN 框抖动一般不动这个权重表是经验值方向不是绝对真理。实测里多数时候保持 1.0 不变就能收敛得不错但如果你想冲高分、追求边界框更稳loss_box_reg加到 1.5 通常是有效的一招。注意别把loss_classifier加得过高否则模型会倾向于把所有候选框都压到一个最常见的类别上这在类别失衡的数据集里是灾难。4.4 从训练到验证mAP 计算脚本和验证集分布坑验证逻辑通常单独一个eval.py或validate.py核心是计算 mAPmean Average Precision。但很多毕设项目里的验证集是从训练集随机分出来的类别分布可能跟测试集完全一致验证分数会虚高。# src/eval.py 中 mAP 计算的伪逻辑 from torchvision.ops import box_iou def compute_map(predictions, ground_truths, iou_thresh0.5): # 计算每个预测框和真值框的 IoU ious box_iou(predictions[boxes], ground_truths[boxes]) # 按置信度降序排列逐框判断是否命中 ... return ap_per_class, mAP验证集分布坑的意思是假设你的训练集里 stop 标志占了 60%验证集也是随机切分出来的那验证集里 stop 标志也差不多 60%。模型只要把 stop 学好验证 mAP 就虚高看起来 0.8 了但一到真实场景限速牌、人行横道、禁行标志比例更均匀表现立刻掉到 0.4 以下。想获得真实评估手动抽一组专门做验证集合保证每一类都有相当数量且背景场景多样。这个动作花不了多少时间但对答辩现场演示效果影响却是决定性的。5. 避坑指南毕设项目里最常见的 6 个翻车点按症状对号入座这一章写的都是解压这类资源包时最常遇到的坑。每条都是「现象 → 原因 → 解决」的结构。多数坑和环境、数据有关并不是代码本身有错。对号入座能省下大量试错时间。5.1 坑一训练时loss直接变nan现象训练跑到第几个 iteration 之后loss 变成nan后续全部权重作废。原因最常见的是学习率设置过大或上一轮梯度爆炸。另一个容易被忽略的原因是数据里有损坏的图片比如 JPEG 解码失败后 PIL 返回了全黑图输入到网络后数值不稳定。解决先加clip_grad_norm_保底然后把学习率降到原值的 1/10 再试再逐个检查数据目录里有没有损坏图片用PIL.Image.open().verify()遍历一遍。# 一行命令排查损坏图片 python -c from PIL import Image import os for root, dirs, files in os.walk(../data): for f in files: if f.endswith(.jpg) or f.endswith(.png): try: Image.open(os.path.join(root, f)).verify() except Exception: print(损坏文件:, os.path.join(root, f)) 5.2 坑二KeyError: cls_score或size mismatch现象加载.pth权重时报错提示Missing key(s): cls_score.weight或者size mismatch for roi_heads.box_predictor.cls_score.weight。原因权重是在某个 num_classes 数量下训练的而你代码里的模型输出头类别数不一致。这个坑几乎 100% 源于把模型定义里的最后一层类别数从 5 改成了 20但没同步改训练的标签映射。解决要么改权重重新训练或手动截掉最后一层并随机初始化要么改代码让get_model的num_classes和 checkpoint 训练时完全一致。最佳做法是写一个快速脚本打印 checkpoint 里roi_heads.box_predictor.cls_score.weight的 shape从 shape 就能反推原模型是什么类别数。python - EOF import torch ckpt torch.load(../checkpoint/model.pth, map_locationcpu) w ckpt[state_dict][roi_heads.box_predictor.cls_score.weight] print(类别数 , w.shape[0]) # shape[0] 就是类别数含背景 EOF5.3 坑三推理时一张图都检测不出来框都是空的现象模型跑通了也能输出结果但outputs[0][boxes]是空的scores是空的。原因大概率是推理时的置信度阈值设太高。很多人从测试代码里直接沿用score_thresh0.9但自己的模型根本没能训练到那个置信度水平。解决将推理阈值先降下来看效果。从score_thresh0.3开始逐步往下调到 0.1直到能看到框为止。在 Fast R-CNN 系列里这个参数通常在model.eval()时的detect()函数中YOLO 系则在 NMS 前处理阶段。5.4 坑四RuntimeError: CUDA out of memory现象训练或推理时直接爆显存torch.cuda.OutOfMemoryError。原因batch size 太大或图像分辨率太大。Faster R-CNN 的特征图会存多级金字塔显存占用比 YOLO 高很多。解决batch size 直接减半从 4 减到 2 或 1如果还爆把config.py里的min_size从 800 降到 640再不行就用 CPU 训练速度感人但毕设撑得住。这坑跟模型结构无关属于资源规划问题。5.5 坑五Dataset not found或路径写死导致的报错现象明明文件就在data/下但FileNotFoundError报的是别的路径或者是反斜杠路径在 Linux 下失效。原因代码里大概率用了绝对路径比如D:/毕设项目/...或C:/Users/...换电脑必炸。解决动手先把所有路径改成相对路径。最稳的是在config.py里定义一个BASE_DIR os.path.dirname(os.path.abspath(__file__))然后所有数据路径都用os.path.join(BASE_DIR, ...)拼接。这一步做完了换机器、换系统都不怕。5.6 坑六验证分数很高但实际测试图效果很差现象验证集 mAP 0.78自己随便拍的照片一张都检测不出来。原因验证集是从训练集随机切分的分布完全一致自己拍的照片角度、光照、背景完全不同等于跨域测试。这不算 bug是数据分布问题。解决把测试图片先统一处理成和训练集分布相近再做推理保持光照充足、相机正对标志、避免强反光。如果你的包里有图像增强代码可以对测试图先用一次增强再推理效果会好不少。做毕设答辩的时候建议直接用验证集里的测试图和从网上下载的 3~5 张干净标志图各测一次cover 住两种场景的质疑。6. 写给实战派模型导出的检查和答辩演示的收尾习惯源码跑通了坑也填了接下来要做的不是直接关机。一个完整的毕设资源包除了训练脚本和权重还应该有合理的导出链路和一套能复现的验证流程。这章的「导出」不是指打包成 exe而是把 PyTorch 模型转换为可移植的推理格式以及把训练过程中的产物整理成能讲清楚、能现场演示的状态。6.1 从 PyTorch 到 ONNX导出前必须做这两项检查如果毕业设计或项目演示要求你现场推理OpenCV 的 DNN 模块加载 ONNX 是最省事的路线比 Flask 起服务再调 PyTorch 稳定太多。PyTorch 模型导出 ONNX 的兼容性问题大部分出在动态轴batch 维度和预处理差异上。下面是推荐做法# src/export_onnx.py import torch from src.model import get_model model get_model(num_classes5, pretrainedFalse) state torch.load(../checkpoint/faster_rcnn_resnet50.pth, map_locationcpu) model.load_state_dict(state[state_dict] if state_dict in state else state) model.eval() # 固定输入尺寸导出后 OpenCV 调用不需要动态维度简单最稳 dummy_input torch.randn(1, 3, 800, 800) torch.onnx.export( model, dummy_input, ../checkpoint/traffic_sign.onnx, opset_version11, input_names[input], output_names[boxes, labels, scores], dynamic_axes{input: {0: batch_size}} )导出之前先做两项检查。第一项是model.eval()必须先调用如果留在trainingTrue状态导出的图里可能带着 dropout 或 batchnorm 的训练逻辑推理效果和训练时不一致。第二项是导出的 ONNX 在 OpenCV DNN 里的输入预处理必须和训练时一致。训练时用的是T.ToTensor()把像素从 0~255 归一化到 0~1OpenCV 里也要做一次除以 255训练时ToTensor()会把通道从 HWC 转为 CHWOpenCV 的blobFromImage默认就是 HWC 到 CHW如果这里忘了归一化推理结果就是乱的特征图数值全偏到一个尺度上。如果你资源包里的 checkpoint 是 40 类的导出前先跑 3.2 里的快速推理脚本把类别数对齐到和 checkpoint 一致再把arc固定下来否则导出完加载同样会报错。导出完成后用 OpenCV 跑一次# src/opencv_infer.py import cv2 import numpy as np net cv2.dnn.readNetFromONNX(../checkpoint/traffic_sign.onnx) img cv2.imread(../data/test/stop_001.jpg) h, w img.shape[:2] blob cv2.dnn.blobFromImage(img, scalefactor1.0/255.0, size(800, 800), swapRBTrue) net.setInput(blob) outputs net.forward() for i in range(len(outputs[0])): score outputs[2][0][i] if score 0.5: box outputs[0][0][i] * [w, h, w, h] # 还原到原图尺度 cv2.rectangle(img, (int(box[0]), int(box[1])), (int(box[2]), int(box[3])), (0, 255, 0), 2)这一段用来和 PyTorch 的推理结果做交叉验证。正常情况两者在同类图片上输出应当高度一致如果 ONNX 推理的 score 普遍低于 PyTorch 0.1~0.2优先检查blobFromImage的scalefactor是否写成了1.0等于没归一化。这是导出项目里最多人踩的坑比模型结构本身更容易出问题。6.2 答辩演示的标准化流程把这一步做成肌肉记忆毕设答辩现场出事故的九成不是代码跑不通而是中途操作失误。没有验证集的随手图片、模型当前是训练模式、显卡显存被其他程序占满、类别人名打印出来和 PPT 对不上任何一个小环节掉链子现场都会变成救火现场。我的习惯是固定成一条「演示前五连」先跑quick_check.py确认当前机器能正常推理一张图把测试图片统一放到demo/images/目录只用这个目录里的图做演示启动推理时先看输出置信度如果全部低于 0.3现场先别硬辩把阈值降到 0.1 再看一眼用 ONNX OpenCV 作为演示主链路PyTorch 推理作为备选链路哪个稳用哪个确保模型处于eval()模式不要在演示前不小心跑了model.train()。这套流程看起来琐碎但它的价值在于零思考成本演示时精神紧绷不应该把精力花在回忆阈值调哪里、数据目录在哪。把这套动作固化成脚本之后上台前只需要按顺序跑一遍稳定性和心理状态都会好很多。6.3 最后一个习惯训练产物强制走一遍「压缩包完整性」验证我从第一次做这类项目开始就养成了一个条件反射式的习惯把整个项目目录压缩、再解压到一个干净的新目录然后从头跑一遍快速推理。原因是学生时代被坑过好几次项目在自己机器上跑得好好的发给别人或拿去打印店演示结果要么缺了某个 checkpoint要么路径写死成绝对路径要么数据文件夹少了一半。压缩再解压这一遍能强制性地把所有相对路径、环境配置、数据文件暴露在明处任何依赖外部位置的隐藏问题都会在这一步现形。从那以后我每次交付训练产物都要强制走一遍压缩包完整性验证。交通标志检测与识别的资源包价值不在于「拿到就能跑」而在于跑通之后你能讲清楚每一层在做什么。这套完整流程从环境重装、数据检查、训练参数、避坑到这里全部走下来你能交付的不只是一个能用的模型权重而是一套可以复现、可以展示、禁得住追问的完整项目。常用资源包的更多细节和对应代码都在下面的链接里。希望帮到你。本文还有配套的精品资源点击获取