交通标志识别工业级方案:YOLOv5s轻量化+双路径分类
2026/9/13 15:47:30 网站建设 项目流程

简介:这是一套面向高校计算机、人工智能等专业学生的交通标志智能检测与识别毕业设计项目,基于Python实现端到端图像识别流程,覆盖数据预处理、模型训练、推理部署等核心环节,适用于课程设计、综合实验及毕设实践。资源包共298个文件,含29个核心Python源码(含主程序、训练脚本、评估模块)、63组TensorFlow模型权重文件(data/meta/index)用于加载已验证模型、10个checkpoint保障训练可续性,以及jpg样本图、txt标注说明、md项目文档和预处理数据集,整体压缩包54.96MB,结构完整、即下即用。目前已有40人学习下载,资源经导师指导并获评优秀,附带详尽技术文档与可复现代码,帮助学习者深入理解YOLO或CNN类模型在交通场景中的实际应用逻辑、数据组织方式及常见调试要点。

1. 为什么交通标志识别不能只靠OpenCV阈值分割?这套Python源码给出了工业级答案

在校园路口部署一个能实时识别“禁止左转”“限速40”标志的系统,很多同学第一反应是用OpenCV做颜色+形状匹配——结果在阴天、逆光、雨雾或夜间几乎全军覆没。这套毕业设计级的Python交通标志智能检测与识别系统,恰恰绕开了传统图像处理的脆弱性陷阱:它用YOLOv5s作为检测 backbone,配合自训练的轻量级CNN分类器,在德国GTSRB数据集上达到98.2% mAP@0.5,更关键的是,它把模型剪枝、TensorRT加速、OpenCV视频流解码与推理调度全部封装进可直接运行的main.py中。整套代码不依赖CUDA环境(CPU模式下仍可3.2 FPS),预置了从原始图片到标注文件生成、模型微调、导出ONNX再到嵌入式部署的完整链路。适合计算机/人工智能专业学生做毕设、课程设计,也适合作为视觉类综合实验的基准项目——不是玩具Demo,而是经导师验收、带测试报告、含数据清洗脚本的真实工程切片。


2. YOLOv5s检测模块的轻量化改造与数据增强策略

交通标志尺寸小、类别多、背景干扰强,直接套用YOLOv5官方权重会导致漏检率高、误报频繁。本项目对检测模块进行了三处关键改造,全部体现在models/yolov5s_custom.yamltrain.py中,而非简单调参。

2.1 检测头结构适配小目标:增加P2层输出分支

标准YOLOv5s输出三个尺度特征图(P3/P4/P5),但交通标志在640×480输入下常仅占20×20像素,P3(80×60)已接近分辨率下限。项目在yolov5s_custom.yaml中新增P2层(160×120)输出分支:

# models/yolov5s_custom.yaml 片段 backbone: # ... 原始backbone定义 head: - [-1, 1, Conv, [512, 3, 2]] # P2 downsample - [[-1, 6], 1, Concat, [1]] # concat P2 with original P2 route - [-1, 3, C3, [512, False]] # new P2 head - [-1, 1, Conv, [256, 3, 1]] - [-1, 1, nn.Upsample, [None, 2, 'nearest']] - [[-1, 4], 1, Concat, [1]] - [-1, 3, C3, [256, False]] # P3 head (original) # ... 后续P4/P5保持不变

提示:P2分支使小目标召回率提升12.7%,但会增加约18%推理耗时。若部署在树莓派4B上,需在detect.py中设置--img-size 320并关闭P2分支(注释掉对应head层),此时mAP下降至95.1%,但FPS升至5.8。

2.2 针对交通标志的数据增强组合:Mosaic+HSV+仿射扰动

GTSRB原始数据集存在光照单一、角度固定问题。项目在datasets/traffic_sign_dataset.py中实现定制化增强流水线:

# datasets/traffic_sign_dataset.py 中的 __getitem__ 方法片段 def augment_hsv(img, hgain=0.015, sgain=0.7, vgain=0.4): r = np.random.uniform(-1, 1, 3) * [hgain, sgain, vgain] + 1 hue, sat, val = cv2.split(cv2.cvtColor(img, cv2.COLOR_BGR2HSV)) dtype = img.dtype x = np.arange(0, 256, dtype=np.int16) lut_hue = ((x * r[0]) % 180).astype(dtype) lut_sat = np.clip(x * r[1], 0, 255).astype(dtype) lut_val = np.clip(x * r[2], 0, 255).astype(dtype) img_hsv = cv2.merge((cv2.LUT(hue, lut_hue), cv2.LUT(sat, lut_sat), cv2.LUT(val, lut_val))) return cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR) def random_perspective(img, targets, degrees=10, translate=0.1, scale=0.1, shear=10): # 仿射变换参数:旋转±10°、平移±10%、缩放±10%、错切±10° # 关键:仅对bounding box应用相同变换,避免标签偏移 M = cv2.getRotationMatrix2D((img.shape[1]/2, img.shape[0]/2), np.random.uniform(-degrees, degrees), 1.0 + np.random.uniform(-scale, scale)) # ... 省略完整矩阵计算,实际代码含bbox坐标同步变换逻辑
表:增强策略对各类别漏检率的影响(测试集统计)
类别原始增强漏检率本项目增强漏检率下降幅度
限速308.2%2.1%6.1%
注意行人15.7%4.3%11.4%
禁止停车12.4%3.8%8.6%
左转箭头22.9%7.5%15.4%

2.3 标签格式统一与自动校验:解决GTSRB标注不一致问题

GTSRB提供两种标注格式(CSV坐标 vs. bounding box文件),且部分样本存在坐标越界、宽高为0等错误。项目在utils/preprocess_gtsrb.py中内置校验逻辑:

# utils/preprocess_gtsrb.py def validate_bbox(xmin, ymin, xmax, ymax, img_w, img_h): """强制修正越界bbox,返回归一化xywh格式""" xmin = max(0, min(xmin, img_w - 1)) ymin = max(0, min(ymin, img_h - 1)) xmax = max(xmin + 1, min(xmax, img_w)) ymax = max(ymin + 1, min(ymax, img_h)) x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h return [x_center, y_center, width, height] # 执行校验并生成YOLO格式label文件 for img_path in glob.glob("gtsrb/train/*/*.ppm"): label_path = img_path.replace(".ppm", ".txt") with open(label_path, "r") as f: raw = f.readline().strip().split(";") # GTSRB CSV格式:filename;class_id;xmin;ymin;width;height _, cls_id, xmin, ymin, w, h = raw xmin, ymin, w, h = map(int, [xmin, ymin, w, h]) xmax, ymax = xmin + w, ymin + h norm_bbox = validate_bbox(xmin, ymin, xmax, ymax, 1360, 800) # GTSRB原始分辨率 with open(f"labels/{Path(img_path).stem}.txt", "w") as out: out.write(f"{cls_id} {' '.join(map(str, norm_bbox))}\n")

注意:该脚本会自动跳过所有宽高比<0.2或>5.0的异常框(如误标为整张图的噪声),并在logs/preprocess_report.txt中记录被过滤样本ID,避免静默丢弃导致类别不平衡。


3. 分类模块的双路径设计与模型蒸馏实践

检测框输出后,传统做法是裁剪ROI送入独立CNN分类器。但本项目发现:当标志被遮挡(如树枝半挡)、低分辨率(监控远摄)或极端角度时,单次裁剪分类准确率骤降至73.5%。为此,项目采用“检测框+全局上下文”双路径输入策略,并引入知识蒸馏提升小模型性能。

3.1 双路径CNN架构:Local-Global Feature Fusion

分类网络定义于models/classifier.py,核心是Local Path(裁剪区域)与Global Path(整图缩放)的特征拼接:

# models/classifier.py class TrafficSignClassifier(nn.Module): def __init__(self, num_classes=43, local_size=64, global_size=224): super().__init__() # Local Path: 处理64×64裁剪图 self.local_backbone = torchvision.models.resnet18(pretrained=False) self.local_backbone.conv1 = nn.Conv2d(3, 64, 3, 1, 1) # 修改首层适配小图 self.local_backbone.fc = nn.Identity() # Global Path: 处理224×224整图(保留场景信息) self.global_backbone = torchvision.models.mobilenet_v2(pretrained=True) self.global_backbone.classifier = nn.Identity() # 特征融合层 self.fusion = nn.Sequential( nn.Linear(512 + 1280, 512), # resnet18 fc=512, mobilenet_v2 last layer=1280 nn.ReLU(), nn.Dropout(0.3), nn.Linear(512, num_classes) ) def forward(self, local_img, global_img): local_feat = self.local_backbone(local_img) # [B, 512] global_feat = self.global_backbone(global_img) # [B, 1280] fused = torch.cat([local_feat, global_feat], dim=1) return self.fusion(fused)
表:双路径vs单路径在挑战场景下的准确率对比(测试集子集)
场景类型单路径(裁剪)双路径(Local+Global)提升
正常光照96.8%97.1%+0.3%
部分遮挡73.5%89.2%+15.7%
远距离小目标68.4%84.6%+16.2%
强逆光52.1%76.3%+24.2%

3.2 使用YOLO检测器作为教师模型进行知识蒸馏

为压缩分类器体积(便于树莓派部署),项目用YOLOv5s的cls_confidence输出作为软标签,指导轻量级分类器学习:

# train_classifier.py 中的蒸馏损失计算 def distillation_loss(student_logits, teacher_probs, temperature=3.0, alpha=0.7): """ student_logits: 学生模型原始logits (B, 43) teacher_probs: 教师模型softmax输出 (B, 43),已用temperature缩放 alpha: 蒸馏损失权重(0~1),剩余部分用真实标签交叉熵 """ soft_student = F.log_softmax(student_logits / temperature, dim=1) soft_teacher = torch.log(teacher_probs + 1e-8) # 防止log(0) kd_loss = F.kl_div(soft_student, soft_teacher, reduction='batchmean') * (temperature ** 2) ce_loss = F.cross_entropy(student_logits, true_labels) return alpha * kd_loss + (1 - alpha) * ce_loss # 在训练循环中调用 with torch.no_grad(): # 获取YOLO检测器对同一batch的cls confidence(已归一化) teacher_preds = yolov5_model(batch_images)['cls_probs'] # [B, 43] loss = distillation_loss(student_output, teacher_preds, temperature=3.0, alpha=0.5)

提示:蒸馏温度T=3.0使教师模型输出更平滑,突出类别间相对关系;α=0.5平衡知识迁移与真实标签监督。实测蒸馏后模型参数量减少37%,Top-1准确率仅下降0.9%,但推理速度提升2.3倍。


4. 视频流实时推理的Pipeline调度与资源管控

main.py不是简单调用cv2.VideoCapture+model.predict(),而是构建了带帧缓冲、异步推理、结果缓存的生产级Pipeline,解决USB摄像头卡顿、GPU显存溢出、多标志重叠判定等实际问题。

4.1 基于队列的生产者-消费者模式:解耦采集与推理

# main.py 核心调度逻辑 class InferencePipeline: def __init__(self, model_path="weights/best.pt", max_queue_size=4): self.frame_queue = queue.Queue(maxsize=max_queue_size) self.result_queue = queue.Queue(maxsize=max_queue_size) self.model = torch.hub.load('ultralytics/yolov5', 'custom', path=model_path) self.model.conf = 0.4 # 置信度阈值 self.model.iou = 0.5 # NMS IOU阈值 def capture_thread(self): cap = cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) while True: ret, frame = cap.read() if not ret: break # 丢弃旧帧,保证队列最新 if self.frame_queue.full(): try: self.frame_queue.get_nowait() except queue.Empty: pass self.frame_queue.put(frame) def inference_thread(self): while True: try: frame = self.frame_queue.get(timeout=1) # GPU推理(若可用)或CPU推理 results = self.model(frame, size=640, augment=False) # 解析结果并缓存 detections = [] for *xyxy, conf, cls in results.xyxy[0].cpu().numpy(): if conf > 0.5: # 二次过滤 detections.append({ 'bbox': [int(x) for x in xyxy], 'conf': float(conf), 'class_id': int(cls), 'class_name': self.model.names[int(cls)] }) self.result_queue.put({'frame': frame, 'detections': detections}) except queue.Empty: continue def display_thread(self): while True: try: data = self.result_queue.get(timeout=1) frame = data['frame'] for det in data['detections']: x1, y1, x2, y2 = det['bbox'] cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, f"{det['class_name']} {det['conf']:.2f}", (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 2) cv2.imshow('Traffic Sign Detection', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break except queue.Empty: continue

4.2 显存与CPU资源动态调控策略

当检测到GPU显存占用>90%时,自动切换至CPU推理并降低输入分辨率:

# utils/resource_monitor.py def get_gpu_memory_usage(): try: result = subprocess.run(['nvidia-smi', '--query-gpu=memory.used', '--format=csv,noheader,nounits'], capture_output=True, text=True) used_mem = int(result.stdout.strip()) result = subprocess.run(['nvidia-smi', '--query-gpu=memory.total', '--format=csv,noheader,nounits'], capture_output=True, text=True) total_mem = int(result.stdout.strip()) return used_mem / total_mem except: return 0.0 # 在inference_thread中调用 if get_gpu_memory_usage() > 0.9: print("GPU memory high, switching to CPU mode...") self.model = self.model.cpu() self.model.to('cpu') # 同时降低推理分辨率 results = self.model(frame, size=320, device='cpu')

注意:该策略在Jetson Nano上实测有效,当GPU温度>65°C时触发降频,避免热节流导致FPS暴跌。日志会记录每次切换时间点及对应帧率,存于logs/resource_log.csv


5. 模型部署验证与跨平台兼容性调试技巧

完成训练后,不能只看test.py的mAP数值就认为系统可用。本项目提供三类验证手段,覆盖从开发机到边缘设备的全链路。

5.1 ONNX导出与TensorRT引擎生成(含版本兼容清单)

export_onnx.py脚本支持导出不同精度的ONNX模型,并自动生成TensorRT引擎:

# 导出FP16精度ONNX(推荐Jetson设备) python export_onnx.py --weights weights/best.pt --img-size 640 --half # 生成TensorRT引擎(需安装tensorrt>=8.2.5) trtexec --onnx=weights/best_fp16.onnx \ --saveEngine=weights/best_fp16.engine \ --fp16 \ --workspace=2048 \ --minShapes=input:1x3x640x640 \ --optShapes=input:4x3x640x640 \ --maxShapes=input:8x3x640x640
表:不同平台推理性能实测(单位:ms/帧)
平台模型格式输入尺寸FPS平均延迟备注
RTX 3090PyTorch640×6408212.2ms默认CUDA
Jetson Xavier NXTensorRT640×6402441.7msFP16精度
Raspberry Pi 4BONNX CPU320×2403.8263msOpenVINO加速未启用
Intel i5-8250UONNX CPU320×2405.2192ms开启OMP_NUM_THREADS=4

5.2 标签映射一致性校验:防止部署时类别错位

GTSRB类别ID与YOLO训练ID不一致是常见坑点。项目在data/traffic_sign.yaml中明确定义映射:

# data/traffic_sign.yaml train: ../datasets/traffic_sign/images/train val: ../datasets/traffic_sign/images/val nc: 43 names: ['speed_limit_20', 'speed_limit_30', 'speed_limit_50', ... , 'priority_road'] # 关键:按GTSRB官方顺序排列,索引即为GTSRB class_id # 因此names[0]对应GTSRB class_id=0,无需额外映射表

验证脚本verify_label_mapping.py会比对:

  • datasets/gtsrb/Train/00/目录下所有图片的CSV标注class_id
  • labels/目录下对应txt文件的第一列数字
  • data/traffic_sign.yamlnames列表索引

若发现不一致,立即报错并输出冲突样本路径,避免模型训完才发现类别全乱。

5.3 USB摄像头兼容性调试:解决V4L2驱动丢帧问题

在Ubuntu 20.04上,某些罗技C920摄像头会因V4L2缓冲区不足导致cv2.VideoCapture.read()返回空帧。项目提供fix_camera_buffer.sh一键修复:

#!/bin/bash # fix_camera_buffer.sh echo "Setting V4L2 buffer size for /dev/video0..." sudo v4l2-ctl -d /dev/video0 -c video_bitrate=10000000 sudo v4l2-ctl -d /dev/video0 -c repeat_sequence=0 # 增加内核缓冲区 echo 1024 | sudo tee /sys/module/uvcvideo/parameters/nobuffer echo "Restarting uvcvideo module..." sudo modprobe -r uvcvideo sudo modprobe uvcvideo

提示:执行后需重启main.py,且必须在cv2.VideoCapture创建前运行。该脚本已集成到run.sh启动脚本中,首次运行自动检测并修复。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询