简介:一套基于YOLOv5算法的道路交通标识识别系统,面向毕业设计、期末大作业与课程设计场景,提供完整Python源码与配套数据集。从模型训练、推理验证到图形化界面,覆盖全流程;代码逐段注释,新手也能理解核心逻辑,便于二次开发和功能扩展。压缩包共266个文件,约423.33MB,主要文件类型包括53个py源码、59个yaml模型配置、10个pt权重、jpg/jpeg/png图像数据集、6个csv训练指标、5个sh脚本及dockerfile等,源码、配置与图像数据分区存放,检索方便。项目已通过严格调试,可快速部署运行,内置预训练权重和整理好的数据集,能直接用于交通标志识别演示或模型训练;同时提供训练日志与评估结果,方便复现实验和撰写论文。代码中还包含数据预处理、模型定义、训练测试脚本以及界面交互逻辑,模块划分清晰,便于按需修改。目前已有50人浏览学习,这份高分毕设项目对希望快速搭建深度学习应用或研究YOLOv5识别原理的读者很有参考价值。
1. 别急着写代码:先想清楚这套毕设到底要交付什么?
每年这个时节能看到一大批“道路交通标识识别”的毕设题目,绝大多数人第一反应是去找源码、下数据集、跑通一个detect.py,然后截图写报告。做过一次你就会发现:真正决定毕设能不能落到“高分”的,是你能不能把 YOLOv5 从“能跑”变成“在你的数据集上稳定收敛、推理结果经得起问辩”。这篇笔记不打算复述 YOLOv5 的论文,而是按我实际做完一版“基于 YOLOv5 的道路交通标识识别系统”的顺序,把数据集怎么整理、模型怎么调、训练踩了什么坑、最后怎么导出推理,一条条讲清楚。适合正在做毕设、或者想拿这套方案做课程设计的同学;如果你已经跑通过 YOLOv5,可以直接跳到第 5 章看避坑记录,那几段是我重复训练十几轮之后才总结出来的。
先说结论:这套系统的核心难点不在 YOLOv5 本身,而在于交通标识这种“小目标 + 类别多 + 真实场景遮挡严重”的数据分布。你选的每个参数——输入尺寸、anchor、置信度阈值、NMS 的 IoU——都会被这个分布放大成肉眼可见的漏检或误检。所以接下来的每一章,我都会先讲参数为什么这么定,再给可以直接抄的配置和命令。
2. 交通标识识别为什么绕不开 YOLOv5:选型、原理与后处理里真正卡人的环节
2.1 小目标密集场景下,YOLOv5 的 Backbone 和 Neck 各自扛了什么活
道路交通标识的检测任务有一个很明显的特征:目标尺寸跨度极大。同一张 1920×1080 的街景图里,远处的限速牌可能只有 20×20 像素,近处的标志牌能占到 200×300。传统的两阶段检测器(Faster R-CNN 系列)对小目标确实更稳,但推理速度压不下来,毕设答辩现场如果用 CPU 跑实时视频流,基本就卡成幻灯片了。YOLOv5 能在精度和速度之间取得平衡,靠的是它这套结构组合:Backbone 用 CSPDarknet 提特征,Neck 用 PANet 做多尺度融合,Head 在三个尺度上分别输出大、中、小目标的预测结果。
我一般会给同学画一张简化的网络结构图帮助他们理解:Backbone 对输入图做 5 次下采样,越往后的特征图语义越强、空间分辨率越差;Neck 里的 FPN 自顶向下传递语义信息,PANet 再自底向上传回空间位置信息。交通标识这种既需要“认出这是限速 40”又需要“精确定位它在画面左上角”的任务,恰好同时依赖语义和位置两个维度,所以 PANet 的融合对最终 mAP 的贡献非常明显。不建议上来就换成更重的 Backbone,比如 CSPResNeXt,参数量翻倍,但交通标识本身纹理简单、类别差异主要靠颜色和形状,轻量网络足够。
2.2 从预测到输出:YOLOv5 后处理的三个参数,漏检误检全看这里
很多人调试 YOLOv5 只看训练 loss,忽略了推理阶段的后处理——这是典型的黑匣子思维。实际上,detect.py里那几行 NMS 代码决定了一个检测框能不能出现在最终画面上。YOLOv5 的 Head 在每个网格上预测出边界框坐标、目标置信度(obj score)和类别概率,三个值相乘得到最终的置信度分数。然后 NMS 按类别逐个抑制重叠框,用的是 IoU 阈值。
我通常会直接改data/hyps/hyp.scratch-low.yaml里的conf_thres和iou_thres,但这两个值在训练和推理阶段的表现差异很大。训练时我习惯把conf_thres设到 0.001,这样能保证所有候选框都参与 loss 计算,不会因为阈值太高把难样本直接滤掉;推理时再提到 0.25 左右。如果发现某个类别(比如“禁止停车”)总是被旁边的“禁止长时间停车”挤掉,不要急着调iou_thres,先看一眼两个类别的标注框是不是在标注阶段就画歪了。后处理本身不产生信息,它只是把模型已经学到的信息按你给的规则筛一遍——所以规则给得对不对,直接决定输出质量。
2.3 Python 侧的训练管线:为什么我坚持用官方仓库而不是魔改版
YOLOv5 的官方仓库在 GitHub 上长期维护,这本身就是它适合毕设的最大理由。用pip install -r requirements.txt就能装齐依赖,Python 版本建议固定在 3.8~3.10,torch 版本选和你的显卡驱动匹配的稳定版。许多同学喜欢去找“加了注意力机制”“优化了损失函数”的魔改源码,我的经验是:先跑通原版,再谈魔改。原版仓库的训练、验证、导出三个流程是解耦的,train.py产出的best.pt可以直接喂给val.py和export.py,这个链路非常干净。魔改版往往改坏了某个配置文件,你连报错都分不清是代码问题还是自己环境问题。
这里有一个很实在的点:YOLOv5 的detect.py --weights best.pt --source xxx.jpg这条命令能让你在拿到模型后五分钟内看到可视化结果,这在写毕设进度报告时非常有用。每调一轮参数,就把验证集里典型的十几张图跑一遍,看检测框的覆盖情况,而不是只盯着一张 mAP 曲线图。后面第 6 章我会讲我怎么把这套流程固化成一个简单的验证脚本。
3. 把数据准备做扎实:TT100K 与自建数据集的取舍、目录结构与标注转换脚本
3.1 公开数据集怎么选:TT100K 和 CCTSDB 各自的边界在哪里
交通标识识别领域常用的公开数据集主要有 TT100K(腾讯交通标志数据集)和 CCTSDB(长沙理工交通标志数据集)。TT100K 包含 1 万张左右的真实街景图,标注了上百个类别,但它有一个很坑的地方:类别数量多且分布极不均衡。限速标志有上万实例,“禁止鸣喇叭”可能只有几十个样本。直接用全量 100 多类训练,一是小类目根本学不出来,二是类别之间外形相似(比如限速 40、60、80),模型容易在相似类上反复震荡。
我的做法是降类。把 TT100K 整理成毕设要求的 8~12 个主类,例如“限速”“禁止停车”“禁止通行”“注意行人”“路口优先”“施工”“公交专用”等。这样既保留了真实场景的复杂度,又让模型在小样本类别上有足够的数据支撑。CCTSDB 的优点是标注格式统一、类别只有三大类(警告、禁令、指示),适合做快速基线;但它的图片多为行车记录仪截图,分辨率偏低,直接拿来训练小目标检测效果不够好。我的建议是:用 CCTSDB 做通线验证,用整理后的 TT100K 做最终训练,再加上一部分自采数据补足场景多样性。
3.2 自建数据集的完整流程:从爬图、清洗到标注的每一步怎么做
自建数据集的流程没有捷径,但可以按部就班做得很扎实。第一步是收集图片。你可以用手机在城市道路拍摄一段时间,也可以从开源街景数据里截取。注意两点:一是要覆盖不同光照条件,黄昏和逆光场景对交通标识识别影响很大;二是要保证同一类标识在画面里有不同的尺寸和角度,避免模型只是记住了某个固定位置和大小。
第二步是清洗。把模糊、过曝、目标占比过小的图片删除。第三步是标注,推荐用 LabelImg,它能直接输出 PASCAL VOC 格式的 XML 文件,后续转 YOLO 格式很方便。标注时我建议遵循统一规则:目标完全被遮挡超过三分之一就不要标;两个同类标识挨在一起时各自独立标注;标识在画面边缘且一半出画时,按可见部分标注完整的框。这套规则能有效减少标注噪声——噪声是训练里最隐蔽的翻车原因,模型学了错误的边界,mAP 会低得莫名其妙。
3.3 VOC XML 转 YOLO txt:一个半小时就能写完的转换脚本与四个边界坑
YOLOv5 需要的标签格式是每个图片对应一个 txt 文件,每行是class x_center y_center width height,坐标都归一化到 0~1。转换脚本的核心逻辑不复杂:解析 XML 里的bndbox坐标,除以图片宽高,然后写入对应的 txt。我每次写这类脚本都会特别注意四个边界问题,这里直接列成代码内的注释。
import os import xml.etree.ElementTree as ET def convert_voc_to_yolo(xml_path, out_txt_path, class_mapping): tree = ET.parse(xml_path) root = tree.getroot() # 注意: 图片尺寸必须从 XML 的 size 节点读取, 不要用 PIL 现量 # 因为部分数据集图片带 EXIF 旋转信息, 直接读宽高会和实际标注对不上 img_w = int(root.find('size/width').text) img_h = int(root.find('size/height').text) lines = [] for obj in root.iter('object'): name = obj.find('name').text if name not in class_mapping: # 不在映射表里的类别直接跳过, 而不是抛异常 # 毕设里常会遇到数据集标注了"其他"这类无效类别 continue cls_id = class_mapping[name] xmlbox = obj.find('bndbox') xmin = float(xmlbox.find('xmin').text) ymin = float(xmlbox.find('ymin').text) xmax = float(xmlbox.find('xmax').text) ymax = float(xmlbox.find('ymax').text) # 归一化前做边界裁剪: 有些标注框会超出图像边界 xmin = max(0, min(xmin, img_w - 1)) xmax = max(0, min(xmax, img_w - 1)) ymin = max(0, min(ymin, img_h - 1)) ymax = max(0, min(ymax, img_h - 1)) # 防止标注退化成一个点: 宽高小于 1 像素的框直接丢弃 if xmax - xmin < 1 or ymax - ymin < 1: continue x_center = ((xmin + xmax) / 2) / img_w y_center = ((ymin + ymax) / 2) / img_h box_w = (xmax - xmin) / img_w box_h = (ymax - ymin) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}") with open(out_txt_path, 'w', encoding='utf-8') as f: f.write('\n'.join(lines)) # class_mapping 示例: {"speed_limit": 0, "no_parking": 1, "no_entry": 2 ...}这个脚本里四个边界坑分别是:图片尺寸从 XML 读取而不是用 PIL 现量、类别映射表里缺失的类别直接跳过而非中断、标注框越界要裁剪、退化框要丢弃。每一个都对应我在实践中碰到过的真实事故——最惨的一次是某个数据集的 XML 里xmax比图片宽度还大,转出来的 txt 里有一半框的坐标超过 1,训练时 loss 直接发散。你在写自己的转换脚本时,把这四条规则加进去,能省掉后面排查数据的一大半时间。
3.4 数据集目录结构与 train/val 划分:一次到位,避免训练中反复返工
YOLOv5 对数据集的目录结构有约定,建议直接按官方要求来,后面data.yaml写路径时最省事。标准结构是images/train、images/val、labels/train、labels/val四个目录,图片和标签文件名一一对应(不含扩展名)。划分比例我习惯用 8:2,但有一个原则:同一个场景的连拍帧必须全部放进同一个集合,不能一部分在 train、一部分在 val。否则模型在训练时已经见过几乎相同的画面,验证指标虚高,答辩时被问“你怎么证明模型泛化能力”会很被动。
划分完成后,用下面这段代码快速自检一遍:检查每个 txt 是否有对应的图片、坐标是否都在 [0,1] 区间、类别 ID 是否越界。这个自检步骤跑一遍只要几秒钟,但它能避免后面训练到一半才发现标签错乱的尴尬。
import os def validate_dataset(img_dir, label_dir, num_classes): img_files = {os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.endswith('.jpg')} label_files = {os.path.splitext(f)[0] for f in os.listdir(label_dir) if f.endswith('.txt')} # 检查图片与标签是否能一一对应 orphan_labels = label_files - img_files missing_labels = img_files - label_files print(f"缺失标签的图片: {len(missing_labels)}, 没有对应图片的标签: {len(orphan_labels)}") for f in os.listdir(label_dir): if not f.endswith('.txt'): continue with open(os.path.join(label_dir, f), 'r') as fp: for line in fp: parts = line.strip().split() cls_id = int(parts[0]) coords = [float(x) for x in parts[1:]] if cls_id < 0 or cls_id >= num_classes: print(f"类别越界: {f}") if any(c < 0 or c > 1 for c in coords): print(f"坐标越界: {f}")4. 训练自己的数据集:yaml 配置、超参数含义与 loss 曲线判读方法
4.1 从零改到能跑通的最小配置文件:data.yaml 和模型 yaml 该怎么写
用 YOLOv5 训练自己的数据集,第一步是改两个 yaml 文件。第一个是数据配置文件,告诉训练脚本你的图片和标签在哪个目录、有几类、类别名字叫什么。第二个是模型配置文件,决定网络宽度、深度和 anchor 尺寸。很多毕设翻车就是 anchor 没改:COCO 的默认 anchor 是给 80 类通用目标设计的,应用到交通标识这种长宽比接近 1:1、偏小尺寸的目标上,匹配率低,训练前期收敛极慢。
先看data.yaml的写法,以data/tt100k_custom.yaml为例:
# 训练集和验证集的图片路径, 注意用绝对路径最稳 # 相对路径在 export 模型或换机器推理时容易失效 train: /home/user/datasets/tt100k/images/train val: /home/user/datasets/tt100k/images/val # 类别数量必须与标签文件中的最大索引+1一致 nc: 10 # 类别名称顺序必须与转换脚本里的 class_mapping 顺序一致 names: ['speed_limit', 'no_parking', 'no_entry', 'pedestrian', 'yield', 'stop', 'construction', 'bus_lane', 'warning', 'guide']nc和names是最容易出错的点:标签文件里class_id是从 0 开始编号的,如果你的类别有 10 类,nc: 10对应的就是 0~9;如果不小心把nc写成了最大类 ID 的数值(比如 9),训练时类别数少了,loss 里的分类分支就会越界报错或者静默学习错误映射。names的顺序则直接影响可视化时框上显示的类别名,和推理代码里的CLASSES列表必须一致。
再看模型 yaml。以models/yolov5s.yaml为例,官方给的基础配置文件里有一组可调参数:depth_multiple控制网络深度,width_multiple控制通道数。表格里列出来我常用的三组配置对显存和速度的影响:
| 模型配置 | depth_multiple | width_multiple | Batch=16 时显存占用(约) | 推理速度(V100, 约) |
|---|---|---|---|---|
| yolov5s | 0.33 | 0.50 | 6 GB | 2.5 ms |
| yolov5m | 0.67 | 0.75 | 11 GB | 4.0 ms |
| yolov5l | 1.0 | 1.0 | 21 GB | 6.5 ms |
交通标识识别不是细粒度分类任务,目标纹理简单,yolov5s通常已经足够。如果检测小目标(如 20×20 像素的远距离限速牌)效果不佳,优先增大输入分辨率而不是换更大的模型——分辨率提升对 mAP 的收益往往比参数量翻倍更明显。我一般会把yolov5s.yaml复制一份命名为yolov5s_custom.yaml,只改nc为 10,其他参数不动,先跑一个基线版本,再逐步调。
4.2 训练命令与超参数拆解:epochs、batch-size、img-size、lr 到底影响什么
YOLOv5 的训练入口是train.py,参数很多,但真正需要理解透的也就五六个。我在命令行里跑过几十轮实验,把最常改的参数列成一张表,后面针对你的数据集可以直接抄:
| 参数 | 我的默认值 | 什么时候改 | 改错会怎样 |
|---|---|---|---|
--img | 640 | 小目标多时改 768 或 896 | 显存翻倍, 耗时变长 |
--batch | 16 | 根据显存调整, 显存不足改 8 | 太大 OOM, 太小收敛慢 |
--epochs | 100 | 数据集小且简单可降到 60 | 太少欠拟合, 太多易过拟合 |
--data | data/tt100k_custom.yaml | 固定 | 不说了, 写错直接报错 |
--weights | yolov5s.pt | 想更快收敛用官方预训练权重 | 不加载预训练权重收敛慢很多 |
--hyp | data/hyps/hyp.scratch-low.yaml | 调 lr 和增强参数 | 乱改易发散 |
常用的一条训练命令长这样:
python train.py \ --data data/tt100k_custom.yaml \ --cfg models/yolov5s_custom.yaml \ --weights yolov5s.pt \ --batch-size 16 \ --img 640 \ --epochs 100 \ --workers 4 \ --project runs/train \ --name tt100k_v1这条命令里的几个关键点,--workers 4是数据加载的进程数,Windows 环境下如果报多进程相关错误,改成--workers 0最稳;Linux 下建议保持至少 2,否则数据读取会成为训练瓶颈。--epochs 100配上早停策略(YOLOv5 的patience参数,默认 100 太大,我一般显式设--patience 20),意思是验证集指标连续 20 轮不提升就自动停止,能省掉大量无效训练时间。
损失函数的细节不需要死磕,但你要知道它是三部分加权求和:box 回归损失(CIoU)、目标置信度损失(BCEWithLogitsLoss)、分类损失(BCEWithLogitsLoss)。YOLOv5 的hyp.scratch-low.yaml里box: 0.05、cls: 0.5、obj: 1.0是默认权重,我的经验是交通标识场景下cls可以适当提高到 0.7。因为这个数据集类别间差异较小(限速 40 和限速 60 的图案只有数字不同),分类分支需要更强的监督信号才能区分相似类别。
4.3 训练时的三张图怎么看:loss、mAP 和混淆矩阵
训练结束后,runs/train/{name}/目录下会生成一组曲线图和指标文件。很多同学只会看results.png里 loss 曲线有没有下降,但这远远不够。我的习惯是按三步看训练是否健康。第一步看train/box_loss和val/box_loss:如果 val loss 在某个 epoch 后持续上升而 train loss 还在降,说明过拟合已经开始了,应该考虑加数据增强或减小模型容量。第二步看mAP@0.5和mAP@0.5:0.95的差距:如果前者很高(比如 0.9)但后者很低(比如 0.4),说明检测框位置不够精准,锚框回归欠佳,需要调高--img分辨率或者增加训练轮数。
第三步看混淆矩阵(confusion_matrix.png)。这是最容易暴露数据问题的图。如果两个类别(比如“禁止停车”和“禁止长时间停车”)在混淆矩阵里交叉误检,第一反应应该是去看这两个类别的标注质量,而不是加训练时间。我在做 TT100K 时遇到过同一个标识在两张图片里一个标成“禁止停车”一个标成“禁止通行”,这种标注冲突不改,模型永远学不会。
关于超参数搜索,YOLOv5 提供了train.py --evolve做遗传算法调参,但我建议毕设阶段不要碰。它需要多组并行训练,单卡上跑一轮要十几个小时,最后收益可能只有 1~2 个点的 mAP。更实际的调参路径是:先跑基线 → 看混淆矩阵排查数据 → 改img和cls权重 → 再跑一轮,这个循环两三轮就足够拿到体面的结果。
5. 训练避坑指南:5 个我反复踩过的坑,按“现象 → 原因 → 解决”逐一给你排查方法
5.1 损失值训到一半变 NaN:先查标签和数据增强,再查学习率
现象:训练进行到 20~30 轮,box_loss突然变成nan,曲线直接断崖,之后所有指标归零。
原因:最常见的是标注数据里有“脏数据”——坐标全为 0、边界框宽高为负数,或者转换脚本没做边界裁剪导致坐标异常。其次是数据增强开启后(hyp里的hsv_h、degrees等),某些极端增强组合让输入变成纯黑或纯白图像,模型输出梯度爆炸。
解决:排查分两步。第一步跑我第 3 章给的数据校验脚本,检查labels目录所有 txt 坐标是否在 [0,1] 区间、宽高是否为 0;第二步把hyp.scratch-low.yaml里的hsv_h: 0.015、hsv_s: 0.7、hsv_v: 0.4先调低一半,跑一个短实验看是否恢复正常。如果短实验仍然nan,再把--lr从默认 0.01 降到 0.001。这个顺序是我排查时的固定套路,每次都奏效。
5.2 mAP 看着很高,但实际预测一个小目标都找不到
现象:val.py报的mAP@0.5有 0.85,但拿一张真实街景图去detect.py预测,远处的限速牌完全没框出来,只检出近处的大标识。
原因:训练和推理的输入尺寸不一致,以及后处理阈值设置不合理。train.py的--img 640会在训练时做随机缩放和裁剪,模型见过不同尺度的目标;但推理时detect.py默认--img 640,如果你的测试原图是 1920×1080,缩放到 640 后远处的小目标已经缩小到几个像素,信息丢失严重。
解决:推理时提高缩放尺寸,python detect.py --img 1280 --conf 0.25。另外确认train.py训练时是否开了--multi-scale,如果开了,模型对尺度变化适应力更强,但训练时间明显变长。部署到边缘设备时(比如 RK3588 上的 NPU 推理,热门前沿里经常聊的 yolov5 量化),小目标丢失会更严重,量化前要先在整数化校准集里多放一些小目标图,不然输出检测框会少到没法用。
5.3 同一张图反复训练导致验证集指标虚高
现象:训练过程正常,但val指标一路飙升,比测试时手动目测的效果好得多。
原因:数据集划分时没有按“场景隔离”。比如你自采的视频序列,每隔 5 帧保存一张图,随机划分后训练集和验证集里出现了连续帧;模型等于已经看过验证集的内容,指标自然不真实。
解决:按时间或地点分组划分。视频抽帧数据按视频 ID 分组,同一视频的帧全部进入训练集或验证集;街景数据按街道或拍摄段划分。划分完成后,用os.path校验 train 和 val 里是否有重复文件名的图片。这一步做扎实,答辩时才有底气说模型泛化能力强。
5.4 类别不均衡导致少数类压根没框
现象:混淆矩阵里,样本数多的类别(比如限速)表现很好,样本数少的类别(比如施工)recall 接近 0。
原因:YOLOv5 默认的类别损失是加权 BCE,但权重和样本数量成反比的逻辑在这种高度不均衡的数据集上不够。TT100K 里施工类可能只占 1% 的实例,模型为了降低整体 loss,直接把它当背景忽略。
解决:先做类目合并(如把不同限速值合成一个“限速”类),减少类别数量;再去hyp.scratch-low.yaml里加一个类别权重列表,或者用最简单的办法——复制少数类样本,在datasets.py里按类别做上采样。如果毕设答辩不需要区分限速具体数值,合并类别是最省力且最有效的手段。
5.5 Windows 上训练中途卡死,GPU 利用率掉到 0%
现象:训练跑了十几个 epoch 后突然卡住,不报错,GPU 利用率归零,只能强杀进程。
原因:YOLOv5 的数据加载用了torch.utils.data.DataLoader,默认num_workers在 Windows 上可能触发多进程死锁。另外如果开了--cache(把图片缓存进内存),数据集太大时内存可能被吃满,系统开始疯狂交换。
解决:Windows 下把--workers设为 0,或者 2 但不要更高;--cache建议只在图片总量小于 2GB 时使用。训练前的磁盘 IO 瓶颈也常见,把数据集放到 SSD 上,不要放在机械硬盘里——这个看着很不技术,但真的能显著缩短训练时间。
6. 让模型在答辩现场说清楚话:推理可视化、置信度校准与边缘部署验证
模型训练完不等于系统做完。毕设答辩或项目演示时,你真正需要的是一个能让老师看懂“系统在哪里工作、哪里不工作”的推理脚本。我的习惯是绕过detect.py的默认输出方式,单独跑一个可控的 Python 脚本,把每张测试图的检测数量、各类别置信度分布、检漏情况全部打印出来。这个脚本的核心逻辑很简单:加载best.pt,对图片做letterbox预处理,前向推理,然后 NMS 后处理。
import torch import cv2 from models.experimental import attempt_load from utils.general import non_max_suppression, scale_coords # 加载模型, 注意 device 要和导出格式匹配 model = attempt_load('runs/train/tt100k_v1/weights/best.pt', map_location='cpu') model.eval() img0 = cv2.imread('test_road.jpg') # letterbox: 保持宽高比缩放到 640, 不足部分填充灰边 img = cv2.resize(img0, (640, 640)) # 简化的缩放逻辑 img_tensor = torch.from_numpy(img).permute(2, 0, 1).float() / 255.0 img_tensor = img_tensor.unsqueeze(0) with torch.no_grad(): pred = model(img_tensor)[0] # conf_thres=0.25, iou_thres=0.45 是推理默认值 det = non_max_suppression(pred, conf_thres=0.25, iou_thres=0.45, classes=None) # 输出每个检测框的坐标、置信度和类别 for *xyxy, conf, cls in det[0]: print(f"类别: {int(cls)}, 置信度: {conf:.3f}, 框: {xyxy}")这个脚本可以作为毕设系统里的“推理核心模块”,后面接 GUI、接视频流都是在这个结果上做文章。至于导出成 ONNX 或者 RKNN 做边缘部署,那是另一条路,我自己的实践是:先确保 PyTorch 推理的 mAP 达标,再做导出和量化。量化之后 mAP 掉 2~3 个点是正常现象,不用慌,关键是确认小目标类别掉得不算多。
最后说一句这些年做检测项目的习惯:每改一次参数,就把训练结果目录名带上一两个关键参数,比如tt100k_v1_img640_cls0.7,留着以后回看。别都堆在默认的exp、exp2里。这个习惯让我少烧了很多时间重新对比实验,希望也能帮到你。
本文还有配套的精品资源,点击获取