
简介这是基于YOLOv5构建的车型识别系统完整工程包包含可运行的源码和基于PyQt5开发的图形操作界面。系统支持轿车、SUV、商务车三种车型以及奥迪、宝马、大众、奔驰、丰田五种品牌识别并集成摄像头实时识别、历史记录保存与识别目标数量统计功能同时提供按用户图片训练自定义模型的脚本适合深度学习初学者、计算机视觉方向学生及有车辆识别需求的开发者参考学习。资源共34364个文件压缩包约546.93MB其中以py源码、pyc编译文件、C头文件、qml界面文件及dll动态库为主也包含模型权重pth、训练日志events、配置yaml和说明文档等目录结构较完整便于按模块查阅。目前已有4715人学习下载。除GitHub公开的YOLOv5基础源码外系统界面与业务逻辑均为作者原创可作为课程设计、毕业设计或实际项目二次开发的完整参考方案也可从中提取界面设计、训练流程与数据标注思路。1. 车型识别为什么绕不开YOLOv5停车场收费、出入口闸机、二手车估价、物流园区货车分类……这些场景都有一个共同的硬需求在摄像头画面里把车辆所属的型号类别认出来而且必须实时完成。车型识别不是目标检测的简单换皮它要求模型兼顾俯视、侧视、夜间补光等复杂视角又能在算力有限的工控机上保持流畅帧率。YOLOv5恰好是这套需求里性价比最高的选择它比两阶段检测器更快比YOLOv8更轻量PyTorch生态下源码结构清晰、资料齐全最利于做二次开发和界面集成。下面直接讲这套方案怎么落地从网络结构理解、数据准备、训练自己的数据集到源码组织与推理封装、操作界面实现最后给出部署压测时可以调的参数与验证技巧。2. YOLOv5网络结构与车型识别模型训练2.1 先看懂YOLOv5网络结构再动手做车型识别之前先把YOLOv5网络结构理清。YOLOv5整体由backbone、neck、head三部分组成常见版本是v5s和v5m。backbone采用CSPDarknet核心是C3模块——通过跨阶段局部连接减少重复梯度计算同时用Focus或6x6卷积做下采样。neck采用PANet在FPN基础上增加自底向上的路径聚合让高层语义信息与低层空间细节充分融合。head沿用anchor-based检测头输出三种尺度检测框分别对应大中小目标。车型识别中车辆往往占据画面较大面积但车灯、进气格栅这类细粒度特征能否被有效保留取决于neck的融合能力。实际使用时我会把默认anchor换成与车辆长宽比更接近的尺寸这一步往往能让mAP提升1到3个百分点。常见做法是用源码里的k-means脚本对训练集重新聚类anchor再写回模型yaml配置。2.2 车型识别数据集分类粒度决定训练成本车型识别一般有三种分类粒度。粗粒度只区分轿车、SUV、货车、客车每类几十张就能出效果。中粒度区分品牌如大众、丰田、本田每类需要两三百张。细粒度区分具体型号年份比如帕萨特2019款与2020款每类至少上千张且视角必须覆盖车头、车尾、侧身。粒度选得越高训练成本和标注成本都会成倍上升动手前先跟需求方确认现场到底需要认到哪一层。分类粒度类别数示例每类最少样本适合场景粗粒度4~8类50~100出入口道闸、车位管理中粒度10~30类200~500停车场品牌统计细粒度50类以上1000二手车估值、保险定损如果项目预算有限常见做法是先用开源数据集如BIT-Vehicle、CompCars子集做预训练再用现场摄像头截图补充微调。标注工具推荐LabelImg导出PASCAL VOC格式后转成YOLOv5要求的txt格式。每张图中车辆标注框应尽量贴合车身外沿不要包含大面积背景否则会引入噪声。2.3 训练自己的数据集从环境到命令2.3.1 环境配置与验证YOLOv5源码对环境依赖并不苛刻建议Python 3.9以上、CUDA 11.x、PyTorch 1.10以上。安装命令如下git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txtrequirements.txt会列出torch、opencv-python、matplotlib、pyyaml、pandas、tqdm等依赖。GPU版本要注意先单独安装匹配CUDA的PyTorch再执行上面的pip install否则可能装上CPU版训练速度慢一个数量级。安装完成后验证python -c import torch; print(torch.cuda.is_available())返回True说明GPU可用。接下来准备数据目录YOLOv5需要images和labels两个目录txt标注每行格式为 class x_center y_center width height坐标归一化到0~1。目录结构如下vehicle_data/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/2.3.2 数据目录与标注格式在yolov5根目录下创建数据集配置yaml文件# vehicle.yaml train: vehicle_data/images/train val: vehicle_data/images/val nc: 6 names: [sedan, suv, truck, bus, van, pickup]nc是类别总数names与标注文件里class id的顺序一一对应顺序颠倒会导致训练后预测结果张冠李戴。我通常会写一个脚本检查labels和images的文件名一一对应避免出现悬空标注。2.3.3 训练命令与超参数修改训练命令python train.py \ --data vehicle.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --device 0weights指定预训练权重推荐yolov5s.pt做初步验证img为输入分辨率车辆检测用640x640即可batch受显存限制显存小于8G时改成8epochs在车型这类中等复杂度任务上100轮足够收敛。--patience用于早停当验证集loss连续指定轮数不降时自动停止--cos-lr配合学习率预热使用能让后期收敛更平稳。YOLOv5的超参数存放在data/hyps/hyp.scratch-low.yaml中最常改的是lr0初始学习率、mosaic马赛克增强概率以及hsv_h、hsv_s颜色扰动。车型识别尤其是侧面车型图片mosaic增强对提升泛化能力有明显作用但如果现场采集图片数量少反而建议关闭mosaic避免模型看到太多拼接噪声。我的经验是样本量低于500时关闭mosaic并把hsv_h、hsv_s调低防止颜色扰动让细粒度分类失效。提示训练时在命令行加--project与--name把每次实验的权重和曲线分开存放回滚对比时不用重新跑一遍。3. 源码项目结构与推理核心代码3.1 读懂源码目录再改代码拿到一套基于YOLOv5的车型识别源码先不要急着跑预测脚本先看目录结构。常见的组织方式如下carbrand_system/ ├── models/ │ ├── common.py │ ├── experimental.py │ └── yolo.py ├── utils/ │ ├── datasets.py │ ├── general.py │ └── torch_utils.py ├── weights/ │ └── best.pt ├── ui/ │ ├── main_window.py │ └── camera_thread.py ├── core/ │ ├── detector.py │ └── recognizer.py ├── data/ │ └── vehicle.yaml └── main.py其中core/detector.py是核心推理封装ui/下面是操作界面逻辑。网上常见的免费Python源码项目里车型识别项目结构大同小异核心区别在于是直接调用detect.py还是封装成类。直接调用detect.py的缺点是每次推理都要重新解析命令行参数、重新加载模型封装成类则可以长期驻留权重界面切换视频源时不用重复加载。3.2 封装一个推理核心Detector类下面这段代码是车型识别系统最常用的推理封装写法import torch import cv2 import numpy as np from models.experimental import attempt_load from utils.general import non_max_suppression class VehicleDetector: def __init__(self, weights_path, devicecpu, img_size640, conf_thres0.3): self.device torch.device(device) self.model attempt_load(weights_path, map_locationself.device) self.model.eval() self.img_size img_size self.conf_thres conf_thres self.names self.model.names def preprocess(self, src_img): img cv2.cvtColor(src_img, cv2.COLOR_BGR2RGB) img cv2.resize(img, (self.img_size, self.img_size)) img img.transpose(2, 0, 1) # HWC - CHW img torch.from_numpy(img).float() / 255.0 return img.unsqueeze(0).to(self.device) def detect(self, frame): x self.preprocess(frame) with torch.no_grad(): pred self.model(x)[0] det non_max_suppression(pred, self.conf_thres, 0.45) results [] if det[0] is not None: for *box, score, cls_id in det[0]: x1, y1, x2, y2 [int(v) for v in box] results.append({ bbox: (x1, y1, x2, y2), score: float(score), class_id: int(cls_id), class_name: self.names[int(cls_id)] }) return results代码逻辑说明attempt_load是YOLOv5源码中加载模型的标准入口能自动处理不同版本权重文件。preprocess把OpenCV读入的BGR图像转成RGB、缩放到640并归一化到0~1。detect方法里non_max_suppression第一个参数是网络原始输出第二个是置信度阈值第三个是NMS的IoU阈值。返回的结果是字典列表界面层直接读取class_name和bbox即可绘制。这里两个参数容易被忽略conf_thres设在0.1会导致大量误检框设在0.6会导致严重漏检车辆场景推荐0.3到0.35。NMS的IoU阈值0.45在车辆密集时段可以下调到0.4减少重叠框。另外如果preprocess时缩放了图像框坐标要按比例还原到原图常见做法是用utils.general里的scale_coords我这里直接按原图推理省去还原步骤。格式体积加载方式适用场景.pt14~40MBtorch.load/attempt_load开发调试、PyTorch推理.onnx14~40MBonnxruntime跨平台、CPU部署.engine8~20MBTensorRTJetson/GPU边缘端3.3 视频流与模型推理的同步操作界面里实时视频流和推理不能放在同一个线程常见做法是单独开一个线程读摄像头用队列缓冲帧界面主线程只负责取帧和推理。代码示例如下import threading import queue class CameraThread(threading.Thread): def __init__(self, src0, max_queue5): super().__init__() self.cap cv2.VideoCapture(src) self.q queue.Queue(maxsizemax_queue) self.running True def run(self): while self.running and self.cap.isOpened(): ret, frame self.cap.read() if not ret: break if not self.q.full(): self.q.put(frame) def stop(self): self.running False self.cap.release()max_queue设为5是有讲究的队列太长画面会滞后好几秒停车场道闸场景会出现车已通过、画面还停在上一辆。队列满时直接丢新帧比阻塞读取更合理——实时侦测场景丢帧比延迟更可接受。摄像头断线时read会持续返回False要在run里加重连计数超过阈值后尝试重新打开设备。4. 操作界面PyQt5与YOLOv5的集成4.1 界面需要哪些功能区操作界面是源码项目里最影响交付观感的部分。基于PyQt5做的车型识别操作界面功能区一般包括四个左侧画面显示区、右侧识别结果列表、下方视频源控制栏、底部状态栏。识别结果列表用QTableWidget展示车辆类别、置信度、时间戳和截图路径方便事后按时间检索。控制栏提供打开本地视频、打开摄像头、停止识别、保存截图四个按钮。PyQt5的UI线程与推理线程必须分离。常见做法是主线程跑事件循环推理放到独立线程画面刷新交给QTimer定时器。如果直接在槽函数里调用detect方法界面会随着推理耗时出现明显卡顿鼠标操作都响应不过来。4.2 主窗口核心代码下面是一段可直接套用的主窗口初始化代码覆盖加载模型、启动摄像头线程、定时刷新画面from PyQt5.QtWidgets import QMainWindow, QLabel from PyQt5.QtCore import QTimer from core.detector import VehicleDetector from ui.camera_thread import CameraThread class MainWindow(QMainWindow): def __init__(self): super().__init__() self.detector VehicleDetector( weights/best.pt, devicecuda, conf_thres0.3) self.video_label QLabel(self) self.video_label.setMinimumSize(960, 540) self.camera CameraThread(src0, max_queue5) self.camera.start() self.timer QTimer(self) self.timer.timeout.connect(self.update_frame) self.timer.start(30) def update_frame(self): if self.camera.q.empty(): return frame self.camera.q.get() results self.detector.detect(frame) frame self.draw_boxes(frame, results) self.show_frame_on_label(frame) self.refresh_result_table(results)代码逻辑说明QTimer每30毫秒触发一次update_frame即目标帧率约33FPS。如果推理耗时超过30毫秒timer会积压调用导致画面抖动。常见解法是在update_frame入口加一个“上一帧是否还在处理”的布尔标志正在处理就直接return丢帧而不是排队。窗口关闭时记得在closeEvent里调用camera.stop()否则子线程不退出程序无法正常结束。4.3 绘制检测框与结果输出绘制检测框的代码要跟推理解耦单独写一个方法便于复用def draw_boxes(self, frame, results): color_map { sedan: (0, 255, 0), truck: (0, 165, 255), bus: (255, 0, 0), } for r in results: x1, y1, x2, y2 r[bbox] color color_map.get(r[class_name], (200, 200, 200)) cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2) label f{r[class_name]} {r[score]:.2f} cv2.putText(frame, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) return framecv2.rectangle的坐标用的是原图坐标因为前面detect方法对原图直接推理没有做缩放。如果改用了resize预处理坐标必须乘回缩放比例否则框会明显偏位。标签文本里置信度保留两位小数界面观感上比全精度数字干净得多。4.4 如何通过界面操作完成数据集的自动标注界面集成自动标注功能能大幅压低人工标注成本。常见做法是在界面上增加“自动标注当前帧”按钮点击后模型跑一次检测检测框直接写入YOLOv5格式的txt标注文件人工复核修正后再存入正式训练集。写入代码要保持归一化格式def export_auto_labels(self, img_path, results, out_txt): img cv2.imread(img_path) if img is None: return h, w img.shape[:2] lines [] for r in results: x1, y1, x2, y2 r[bbox] cx ((x1 x2) / 2) / w cy ((y1 y2) / 2) / h bw (x2 - x1) / w bh (y2 - y1) / h lines.append( f{r[class_id]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(out_txt, w) as f: f.write(\n.join(lines))这段代码把界面上的检测结果直接落盘成训练标注。注意只有置信度高于0.5的框才应写入自动标注集低于阈值的框多半是误检写进去会污染训练集。实践中我会把自动标注输出目录与手工复核目录分开界面显示“待复核数”计数复核通过的文件再移动到train/labels。5. YOLOv5车型识别系统的压测与部署调优5.1 模型导出与体积优化训练结束后的best.pt在YOLOv5s下约14MBYOLOv5m约40MB。界面程序直接加载pt没问题但需要提升吞吐时建议导出为ONNX或TensorRT引擎。导出命令python export.py --weights weights/best.pt --include engine --device 0engine格式在Jetson上能直接用TensorRT加速FP16精度下体积减半、单帧速度提升两倍以上。如果现场只有CPU导出onnx后用onnxruntime加载比PyTorch推理快20%到40%。5.2 边缘端算力平台的选择很多毕设选题会用STM32做边缘端车辆检测但YOLOv5s在STM32这类MCU上跑一次前向需要数秒实时性根本达不到。更合理的路线是选带GPU或NPU的开发板Jetson Nano配合TensorRT推理引擎FP16精度下单帧约30ms带车位管理或道闸控制绰绰有余。选型时先确认算力还是功耗优先再做量化方案不要一开始就上INT8。5.3 验证方法与动态阈值技巧部署完成后不要只看平均帧率要统计逐帧延迟分布。用benchmark脚本记录每帧推理耗时观察P99延迟比平均值更有参考意义。参考数值Jetson Nano FP16下P99应小于50ms超过时优先检查CPU与GPU之间数据拷贝的频次。还要重点测试无车辆帧的误检率。把一段空画面循环播放统计连续出现彩色框的帧数误检会表现为短暂闪烁体验很差。我的做法是让conf_thres动态自适应连续N帧没有高置信度检测时自动提高阈值检测到车后再降回0.3。把这段逻辑写进主循环的帧后处理里对比开关前后的误检日志通常能减少四成以上的空目标闪烁。本文还有配套的精品资源点击获取