YOLOv8+PySide6:构建车型识别桌面检测系统完整实战
很多做目标检测项目的人在模型训练完这一步会突然卡住模型跑通了指标也还行但这只是一个“能在终端里打印坐标”的实验品。真正要交给用户、放进实际业务流程的东西应该是一个能双击打开、能选图片、能开摄像头识别、能看清结果的桌面程序。如果选了Web方案还得搭后端、写前端、处理跨域如果选了命令行方案又没法给非技术背景的人用。把YOLOv8/YOLOv5的训练成果和PySide6桌面界面结合起来是目前做视觉类小工具很务实的路线。它不需要你去理解前端工程化那一整套体系也不需要额外维护一套HTTP服务模型在本地加载界面本地渲染逻辑清晰交付简单。这篇文章会从技术选型、环境搭建、模型封装、界面开发、线程处理到常见坑点完整拆解一个“基于YOLO系列模型的车型识别检测系统”是怎么做出来的。我的观点很明确YOLO模型本身不是这个项目的天花板模型和界面之间的桥接方式才是。很多人训练完模型把权重文件一扔缺的不是算法能力而是把算法封装成产品的工程能力。所以这篇的主线不是教你重新训练一遍YOLO而是把“已经训练好的模型如何用PySide6做成桌面检测工具”这件事讲透。1. 车型识别系统到底要做什么先对齐场景。这里说的“车型识别”不是车牌识别也不是只看出来“这里有一辆车”而是要判断车辆的类别。一个典型的识别需求是区分轿车最常见的四门三厢车。SUV运动型多用途车车身高、空间大。跑车低车身、双门设计、外观夸张。卡车货车、货柜车、泥头车等商用车。如果训练数据用的是COCO预训练权重模型默认识别80类目标其中“car”和“truck”是直接可用的。但“SUV”和“跑车”这类精细化分类COCO里并没有单独的类别需要自己构建数据集训练。所以这里要区分两种情况第一种是快速演示系统。目标不是细分车型而是做到“车辆检测 基本分类”直接用YOLOv8的COCO预训练权重把“car”和“truck”识别出来再通过简单的宽高比规则判断倾斜角度和车辆形态做粗分类。这种方案好处是零数据成本坏处是“跑车”和“轿车”之间容易混淆。第二种是正式项目系统。目标是细分出行车、SUV、跑车、卡车四类。这种方案必须自建数据集用LabelImg或X-AnyLabeling标注转成YOLO格式后单独训练。工作量集中在数据收集和清洗模型结构本身不需要改动。从项目结构看一个完整的车型识别检测系统包含四个模块数据模块训练数据集、验证集、标签文件。模型模块基于YOLOv8或YOLOv5训练好的权重文件。推理模块加载模型、预处理图像、执行推理、解析结果。界面模块PySide6写的桌面程序负责图片选择、实时摄像头显示、检测结果可视化、统计信息展示。后面每个模块都会讲到。要注意的是车型识别任务的难点不在模型参数量而在类别间的相似性SUV和轿车在侧面视角下容易混淆跑车和轿车在远处视角下特征不明显。因此数据集的质量比网络结构升级更重要。2. YOLOv8还是YOLOv5技术选型建议在车型识别项目中YOLOv5和YOLOv8都是可行的方案不存在“新版一定压过旧版”的绝对结论。要结合自己的部署条件和维护能力来选择。YOLOv5的特点是生态成熟、资料多、工程化程度高。如果你之前用的就是YOLOv5训练过的模型或者团队里有大量基于v5的代码积累直接做桌面端封装是最顺的。v5的权重文件是.pt格式导出ONNX也很方便PySide6调用时只需要用torch.load加自定义模型类或者通过export.py转成ONNX后配合ONNX Runtime推理。YOLOv8的特点是API设计更友好、训练代码更简洁、内置功能更完整。使用ultralytics这个Python包可以很轻松地完成训练、验证、导出、推理。v8把很多细节封装好了不需要你再手写NMS、锚框生成这些逻辑开发效率更高。在车型识别这个任务上YOLOv8单从API层面讲更适合做桌面项目。原因有两点ultralytics.YOLO这个类自带推理方法传入图片路径或numpy数组就能返回结果对象结果对象里封装了boxes、names、probs等属性省去了大量后处理代码。v8的模型格式统一训练、验证、导出、部署的命令设计清晰对做工程的人来说是加分项。但如果你要在CPU环境部署而且对推理速度有要求YOLOv5s的模型体积和计算量会比YOLOv8s稍轻。这不是说v8更重而是v5的轻量化选择经过更长时间的调优。要在实际设备上测试对比不能只看发版说明。选型结论写在这里如果是个人项目或快速原型直接选YOLOv8如果是要在两三年前的老设备上CPU推理还要考虑旧硬件兼容性可以保留YOLOv5的选项。2.1 两种方案的对比对比维度YOLOv5YOLOv8代码风格工程化较重配置文件多API简洁封装度高训练体验需要理解配置文件结构一行命令启动训练结果解析需要自己处理output张量结果对象直接提供解析属性导出部署支持导出ONNX、TorchScript支持导出ONNX、TorchScript、TF Lite车型识别适配适合已有v5模型的项目适合从零开始的新项目社区资料极多增长迅速教程丰富CPU推理效率相对更轻目标检测精度更高这个表不是绝对评价核心要理解的是在“车型识别 PySide6桌面端”这个具体组合里YOLOv8的开发效率更突出YOLOv5的优势更多体现在历史包袱和轻量部署场景。3. PySide6在车型识别项目里是怎么定位的PySide6是Qt for Python的官方绑定库对应Qt 6框架。它和PyQt5相比最大的优势是授权协议更友好PySide6是LGPL协议可以用于商业闭源项目而PyQt是GPL协议商用场景需要购买商业授权。对做车型识别工具交付给甲方或公司内部使用的人来说这个差异很关键。很多人问“PySide6和PySide有什么区别”其实PySide6就是PySide2的下一代版本对应Qt 6。PySide2对应Qt 5PySide6对应Qt 6。目前新项目直接用PySide6即可除非你的依赖库还没有适配Qt 6。在车型识别项目中PySide6承担的是交互编排的角色用户点击“选择文件”按钮界面弹出文件选择对话框。用户选择一张图片界面显示原图。点击“开始检测”程序把图片传给YOLO模型模型返回检测框。界面在图片上绘制矩形框和标签同时显示检测到的类型和数量。如果打开摄像头界面还要将摄像头帧捕获并连续送进模型推理再以视频帧的形式实时刷新。这里有一个新手容易踩的坑YOLO推理是耗时操作如果在PySide6的主线程里执行推理界面会直接卡死。图片小还好一旦视频流连续推理主线程被阻塞窗口会显示“未响应”。解决方法是使用QThread或QRunnable把推理放到工作线程中通过信号把结果传回主线程更新界面。所以PySide6项目里界面设计只是第一步线程模型设计才是决定项目质量的关键。4. 系统架构与数据流设计在写代码之前先把系统架构想清楚。一个成熟的车型识别桌面系统可以按下面的方式分层------------------ | UI 层 | | QMainWindow | | 按钮、标签、表格 | ----------------- | 信号/槽 v ------------------ | 控制器层 | | 调用推理模块 | | 管理线程 | ----------------- | v ------------------ | 推理服务层 | | YOLOModel | | 图片路径 - 结果 | ----------------- | v ------------------ | 模型层 | | YOLO权重文件 | | ONNX/PT | ------------------这个分层的目的很清晰界面只负责展示和接收用户操作不直接接触模型推理服务只负责模型调用不关心界面怎么画框模型文件是独立的以后换模型只需要替换参数。在数据流层面图片模式和摄像头模式存在差异图片模式的数据流比较简单选择图片 - QPixmap显示原图 - 点击检测 - 图片路径传给推理线程 - YOLO返回检测框、类别、置信度 - 主线程更新界面、绘制结果、更新统计摄像头模式的数据流是循环的打开摄像头 - OpenCV读取帧 - 帧转换为RGB格式 - 推理线程处理 - 返回结果 - 主线程绘制检测框 - 显示在QLabel上 - 继续下一帧设计这个数据流时建议把“读帧”和“推理”放在同一个工作线程。因为OpenCV的VideoCapture.read()本身是阻塞的如果把它放在主线程摄像头花屏或延迟时界面也会卡。干脆整个摄像头循环都放工作线程主界面只负责接收结果帧并刷新。5. 环境准备与依赖安装环境这块不需要复杂化只要搞清楚了版本关系和安装命令基本不会出大问题。5.1 软件环境清单操作系统Windows 10/11 或 Ubuntu 20.04/22.04推荐Windows做桌面工具调试。Python版本推荐3.9到3.11。PySide6目前对3.12的兼容性在逐步完善但部分依赖库可能滞后稳妥用3.9或3.10。深度学习框架PyTorch具体版本和CUDA版本相关。如果只用CPU推理安装CPU版即可。目标检测库ultralytics用于YOLOv8模型加载和推理。GUI框架PySide6提供Qt窗口组件和信号槽机制。图像处理库opencv-python用于摄像头帧捕获和图像格式转换。科学计算库numpy用于坐标和数组运算。5.2 安装命令创建虚拟环境是一个好习惯。Windows下用venv即可python -m venv venv venv\Scripts\activate激活环境后升级pippython -m pip install --upgrade pip然后安装依赖pip install ultralytics pip install pyside6 pip install opencv-python pip install numpy如果在Linux环境下激活命令不同source venv/bin/activateCUDA版本的PyTorch需要单独去PyTorch官网选择对应命令不建议直接用pip默认安装因为默认安装的是CPU版本。如果你有NVIDIA显卡且要训练模型再按照官网提示安装GPU版本。验证安装是否成功可以执行import ultralytics from PySide6.QtWidgets import QApplication import cv2 print(ultralytics:, ultralytics.__version__) print(OpenCV:, cv2.__version__)能正常打印版本号说明依赖没问题。这里要提醒一点不要在一台机器上同时装PySide6和PyQt5。两者都是Qt绑定库同时存在会导致程序崩溃或加载到错误的Qt库。如果之前装过PyQt5先用pip uninstall PyQt5 PyQt5-Qt5 PyQt5-sip清理干净再装PySide6。6. 模型加载与推理封装模型是项目的核心引擎。为了不让界面代码里混入YOLO相关逻辑建议单独用一个类封装模型推理。这里给出的示例以YOLOv8为基础用法简洁适合配合PySide6开发。如果你用的是YOLOv5权重差别主要在模型加载和结果解析部分等下会说明。6.1 YOLOv8推理封装类新建一个detector.py文件# -*- coding: utf-8 -*- 文件路径detector.py 模型推理封装负责加载YOLO模型执行目标检测返回标准结果 from ultralytics import YOLO class VehicleDetector: 车型识别检测器的封装类 加载YOLOv8权重提供 detect_image 方法 def __init__(self, model_path: str, conf_thres: float 0.45): :param model_path: 权重文件路径例如 models/best.pt :param conf_thres: 置信度过滤阈值 self.model YOLO(model_path) self.conf_thres conf_thres self.names self.model.names def detect_image(self, image_path: str): 对图片文件进行目标检测 :param image_path: 图片路径 :return: (框列表, 类别列表, 置信度列表) results self.model(image_path, confself.conf_thres) boxes_list [] classes_list [] scores_list [] for result in results: if result.boxes is None: continue boxes result.boxes.xyxy.cpu().numpy() classes result.boxes.cls.cpu().numpy().astype(int) scores result.boxes.conf.cpu().numpy() for box, cls, score in zip(boxes, classes, scores): x1, y1, x2, y2 box boxes_list.append([float(x1), float(y1), float(x2), float(y2)]) classes_list.append(int(cls)) scores_list.append(float(score)) return boxes_list, classes_list, scores_list def detect_frame(self, frame_bgr): 对OpenCV读取的BGR帧进行目标检测 :param frame_bgr: OpenCV读取的帧 :return: (处理后的BGR帧, 框列表, 类别列表, 置信度列表) results self.model(frame_bgr, confself.conf_thres) boxes_list [] classes_list [] scores_list [] for result in results: if result.boxes is None: continue boxes result.boxes.xyxy.cpu().numpy() classes result.boxes.cls.cpu().numpy().astype(int) scores result.boxes.conf.cpu().numpy() for box, cls, score in zip(boxes, classes, scores): x1, y1, x2, y2 box boxes_list.append([float(x1), float(y1), float(x2), float(y2)]) classes_list.append(int(cls)) scores_list.append(float(score)) return frame_bgr, boxes_list, classes_list, scores_list这个类做了三件事在初始化时加载模型self.model.names可以直接拿到类别名称字典。detect_image接收图片路径返回三个Python列表。为什么要转成普通列表而不是直接返回result对象因为Qt信号传自定义对象容易被忽略列表类型在信号槽传递中兼容性最好。detect_frame接收OpenCV的BGR帧直接在内存里推理不需要写临时文件。如果你用的是YOLOv5训练出的模型有两种接入方式方式一把.pt权重传给YOLO类但v5的模型结构不是ultralytics包的内置结构通常需要v5仓库的torch.hub.load(ultralytics/yolov5, custom, path...)加载。方式二先用v5的export.py把权重导出成ONNX再用onnxruntime加载。这种方式更推荐因为ONNX脱离PyTorch推理框架部署更独立。6.2 类别名称的处理在车型识别项目里类别名称的顺序非常重要。YOLO训练时类别ID从0开始递增模型输出的cls是整数索引要在显示时映射回名称。如果用的是COCO预训练的v8模型“car”对应类别2“truck”对应类别7。但如果你自己训练了一个包含轿车、SUV、跑车、卡车的模型类别顺序是由data.yaml中的names字段决定的# 文件路径datasets/vehicle.yaml train: datasets/vehicle/train/images val: datasets/vehicle/val/images nc: 4 names: 0: sedan 1: suv 2: sports_car 3: truck训练完成后model.names会自动读入这个顺序。在界面里显示时直接用self.detector.names[cls]即可。7. PySide6界面设计与多线程实现界面设计是车型识别系统里最影响“用户感知”的部分。一个干净、响应快的界面比模型精度更能让用户觉得“这软件不错”。7.1 主界面布局这里用一个最简单的QMainWindow布局包含左侧图片显示区域用QLabel显示原始图片或检测结果。右侧操作按钮区和信息展示区。底部状态栏显示当前状态和推理耗时。用Qt Designer设计界面文件.ui然后转成py文件是更规范的工程做法。但为了减少环境依赖先手写布局逻辑更透明# -*- coding: utf-8 -*- 文件路径main.py 车型识别系统主程序 import sys from PySide6.QtWidgets import ( QApplication, QMainWindow, QWidget, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, QFileDialog, QMessageBox, QGroupBox, QTableWidget, QTableWidgetItem, QHeaderView ) from PySide6.QtGui import QPixmap, QImage from PySide6.QtCore import Qt, QThread, Signal from detector import VehicleDetector class DetectThread(QThread): 推理线程避免YOLO推理阻塞主界面 result_ready Signal(QImage, dict) error_occurred Signal(str) def __init__(self, detector, image_path): super().__init__() self.detector detector self.image_path image_path def run(self): try: boxes, classes, scores self.detector.detect_image(self.image_path) # 这里简化处理真正的绘制可以放在主线程 # 通过信号把结果传出去 result_dict { boxes: boxes, classes: classes, scores: scores, path: self.image_path } img QImage(self.image_path) self.result_ready.emit(img, result_dict) except Exception as e: self.error_occurred.emit(str(e))这个线程类把检测结果通过Signal传回主线程。QThread的run方法在单独的线程执行所有耗时操作都在run里完成。主窗口类class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(车型识别检测系统 - YOLOv8 PySide6) self.resize(1000, 640) self.detector None self.classes_color_map {} self.init_ui() def init_ui(self): central_widget QWidget() self.setCentralWidget(central_widget) layout QHBoxLayout(central_widget) # 左侧图片显示区 left_layout QVBoxLayout() self.image_label QLabel(请选择图片) self.image_label.setAlignment(Qt.AlignCenter) self.image_label.setMinimumSize(640, 480) self.image_label.setStyleSheet(border: 1px solid #aaa; background-color: #f8f8f8;) left_layout.addWidget(self.image_label) # 右侧操作区 right_layout QVBoxLayout() self.btn_open QPushButton(选择图片) self.btn_detect QPushButton(开始检测) self.btn_detect.setEnabled(False) self.btn_open.clicked.connect(self.open_image) self.btn_detect.clicked.connect(self.start_detect) right_layout.addWidget(self.btn_open) right_layout.addWidget(self.btn_detect) # 统计图表 table_group QGroupBox(检测统计) table_layout QVBoxLayout() self.result_table QTableWidget(0, 2) self.result_table.setHorizontalHeaderLabels([车型, 数量]) self.result_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) table_layout.addWidget(self.result_table) table_group.setLayout(table_layout) right_layout.addWidget(table_group) right_layout.addStretch() layout.addLayout(left_layout) layout.addLayout(right_layout) def open_image(self): file_path, _ QFileDialog.getOpenFileName( self, 选择图片, , 图片文件 (*.jpg *.jpeg *.png *.bmp) ) if file_path: self.image_path file_path pixmap QPixmap(file_path) if pixmap.isNull(): QMessageBox.warning(self, 错误, 无法加载图片) return scaled_pixmap pixmap.scaled( self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation ) self.image_label.setPixmap(scaled_pixmap) self.btn_detect.setEnabled(True) self.statusBar().showMessage(图片加载完成点击开始检测) def start_detect(self): if not hasattr(self, image_path): return self.btn_detect.setEnabled(False) self.statusBar().showMessage(检测中...) if self.detector is None: model_path models/best.pt self.detector VehicleDetector(model_path) self.thread DetectThread(self.detector, self.image_path) self.thread.result_ready.connect(self.on_result_ready) self.thread.error_occurred.connect(self.on_error) self.thread.start() def on_result_ready(self, img, result_dict): boxes result_dict[boxes] classes result_dict[classes] scores result_dict[scores] self.statusBar().showMessage(f检测完成共识别到 {len(boxes)} 个目标) self.btn_detect.setEnabled(True) # 统计数量 from collections import Counter counter Counter(classes) self.result_table.setRowCount(0) for cls_id, count in counter.items(): row self.result_table.rowCount() self.result_table.insertRow(row) class_name self.detector.names[cls_id] self.result_table.setItem(row, 0, QTableWidgetItem(class_name)) self.result_table.setItem(row, 1, QTableWidgetItem(str(count))) # 显示图片此处演示只显示原图实际绘制框要再封装 pixmap QPixmap.fromImage(img) scaled_pixmap pixmap.scaled( self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation ) self.image_label.setPixmap(scaled_pixmap) def on_error(self, msg): QMessageBox.critical(self, 检测错误, msg) self.btn_detect.setEnabled(True) def main(): app QApplication(sys.argv) window MainWindow() window.show() sys.exit(app.exec()) if __name__ __main__: main()7.2 线程设计的核心逻辑上面的示例代码里最值得关注的是DetectThread这个类。YOLO推理是一个CPU/GPU密集操作如果直接在槽函数里调用detect_image主窗口的界面就会卡顿。尤其是摄像头视频流模式问题更加明显。把推理放到独立的QThread中有三个好处界面不会卡死用户能正常关闭窗口或取消操作。推理过程中可以继续响应其他UI事件。摄像头模式可以连续读帧、推理、发信号不阻塞界面刷新。使用信号槽时要注意信号携带的数据尽量是Python基础类型或QImage、QPixmap这类Qt原生类型。如果你要传递自定义的YOLO结果对象可能需要在多个模块间引用处理不当容易出现内存泄漏或线程安全问题。7.3 绘制检测框上面的示例里没有真正把检测框画到图片上只统计了数量。绘制检测框需要用到QPainterdef draw_boxes(self, image: QImage, boxes, classes, scores, names): 在QImage上绘制检测框和标签 from PySide6.QtGui import QPainter, QPen, QColor, QFont painter QPainter(image) painter.setFont(QFont(Arial, 12)) for box, cls, score in zip(boxes, classes, scores): x1, y1, x2, y2 [int(v) for v in box] color self.classes_color_map.get(cls, QColor(0, 255, 0)) pen QPen(color, 2) painter.setPen(pen) painter.drawRect(x1, y1, x2 - x1, y2 - y1) class_name names[cls] label f{class_name} {score:.2f} painter.setBrush(QColor(0, 0, 0, 150)) painter.drawText(x1, y1 - 8, label) painter.end() return image注意QPainter只能绘制QImage不能直接在QPixmap上写。所以流程是先用QImage加载原图绘制完检测框后再转成QPixmap显示。7.4 摄像头识别模式摄像头识别是车型识别系统的加分项。在交通场景监控、停车场入口等实际应用中实时视频检测比单张图片识别更有价值。摄像头模式的代码思路如下class CameraThread(QThread): frame_ready Signal(QImage) def __init__(self, detector, camera_index0): super().__init__() self.detector detector self.camera_index camera_index self.running True def run(self): import cv2 cap cv2.VideoCapture(self.camera_index) if not cap.isOpened(): return while self.running: ret, frame cap.read() if not ret: break frame, boxes, classes, scores self.detector.detect_frame(frame) # 绘制检测框 for box, cls, score in zip(boxes, classes, scores): x1, y1, x2, y2 [int(v) for v in box] color (0, 255, 0) cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2) class_name self.detector.names[cls] cv2.putText( frame, f{class_name} {score:.2f}, (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2 ) # OpenCV BGR转RGB再转QImage rgb_image cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb_image.shape bytes_per_line ch * w qt_image QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888) self.frame_ready.emit(qt_image) cap.release() def stop(self): self.running False self.wait()摄像头线程启动后在run方法里循环处理视频帧。这个线程的设计要特别注意资源释放关闭窗口时必须调用stop()来停止线程并释放摄像头否则摄像头会被一直占用下次打开时可能报错。8. 运行结果与验证方法整个项目搭建完成后验证分为三个层次。8.1 模型推理验证先不启动界面直接用脚本验证模型本身能否正常工作from detector import VehicleDetector detector VehicleDetector(models/best.pt) boxes, classes, scores detector.detect_image(test_images/suv_01.jpg) print(检测到目标数:, len(boxes)) for box, cls, score in zip(boxes, classes, scores): print(f类别: {detector.names[cls]}, 置信度: {score:.2f}, 坐标: {box})如果输出结果正常说明模型路径、依赖库、类别映射都没有问题。这一步通过后再启动界面程序排查范围就锁定在界面代码。启动界面python main.py预期结果窗口正常弹出点击“选择图片”能打开文件对话框选择一张车辆图片后点击“开始检测”界面不卡死状态栏显示检测完成表格中列出车型和数量。8.2 验证判断标准一个成功的车型识别检测系统需要同时满足以下几点图片能正常加载显示格式兼容。点击检测按钮后界面保持响应可以拖拽窗口。检测结果表格数据正确类别名称是中文或英文模型名称不显示数字ID。模型置信度阈值合理不会出现大量漏检或误检。关闭窗口时进程能正常退出不残留后台Python进程。如果发现窗口关闭后Python进程还在多半是QThread没有正确停止。在closeEvent里加入线程清理逻辑def closeEvent(self, event): if hasattr(self, thread) and self.thread.isRunning(): self.thread.stop() event.accept()9. 常见问题与排查方法把最容易踩的坑整理成排查表你可以在实际操作中按这张表快速定位问题。问题现象可能原因排查方式解决方案模型加载失败权重路径错误检查model_path是否绝对路径或相对路径正确用os.path.abspath()打印确认实际路径导入ultralytics报错pip安装的是源码包而不是官方发布版查看pip list中ultralytics版本号pip install ultralytics -U界面启动后闪退PySide6与PyQt5冲突检查环境中是否有PyQt5彻底卸载PyQt5后重装PySide6点击检测后无反应模型推理阻塞主线程观察按按钮后窗口是否变灰卡住改用QThread执行推理检测框绘制位置偏移缩放显示图片但绘制用的是原图坐标检查显示时是否缩放了图片在绘制时按原图尺寸绘制QImage不缩放摄像头黑屏摄像头索引错误换camera_index1或其他索引测试遍历可用摄像头索引或用设备管理器确认摄像头关闭后再次打开失败线程未释放摄像头查看代码是否调用cap.release()重启时调用线程stop()并等待线程结束推理速度很慢CPU推理且模型太大查看模型参数量和推理耗时换YOLOv5s或YOLOv8s轻量化模型或导出ONNX用onnxruntime推理COCO模型识别不出SUVCOCO没有SUV类别查看model.names前几个类别需要自建数据集微调训练只靠预训练无法细分信号传递多个大字数据慢信号携带过多结果数据调试信号参数大小只传框坐标和类别索引不要传大图对象9.1 一个容易被忽略的问题类别ID错位很多人在自训练模型后界面能跑但显示的标签和实际车辆对不上。比如明明检测的是SUV界面却显示“轿车”。这个问题几乎都是训练时data.yaml的names顺序和界面代码里的硬编码类别顺序不一致导致的。比如训练时用的names: 0: sedan 1: suv而界面代码里手工写了class_names [轿车, 跑车, SUV, 卡车]索引完全对不上。解决方法是永远不要手工维护一份类别顺序而是直接从model.names读取class_names self.detector.names这样不管训练时类别顺序怎么定义界面显示都和使用时保持一致。10. 模型训练后的导出与Qt调用建议这是真正的工程实践经验。很多人的模型训练完会直接拿.pt文件在PySide6里加载。这种方式在开发阶段可以但如果要考虑交付有几个问题.pt文件依赖PyTorch环境目标机器要装完整PyTorch框架安装包大。不同机器要安装相同版本的PyTorch、ultralytics兼容性问题多。推理速度受到Python解释器影响性能不如优化后的推理引擎。导出ONNX并用ONNX Runtime推理是更专业的做法。导出命令yolo export modelmodels/best.pt formatonnx opset12然后使用onnxruntime加载import onnxruntime as ort import numpy as np import cv2 session ort.InferenceSession(models/best.onnx) input_name session.get_inputs()[0].name input_shape session.get_inputs()[0].shape def preprocess(frame): img cv2.resize(frame, (input_shape[2], input_shape[3])) img img[:, :, ::-1] # BGR转RGB img img.transpose(2, 0, 1) img np.ascontiguousarray(img, dtypenp.float32) img / 255.0 img np.expand_dims(img, axis0) return img def infer(frame): input_tensor preprocess(frame) outputs session.run(None, {input_name: input_tensor}) return postprocess(outputs)ONNX推理模式让程序不再依赖ultralytics包以后做打包、分发、加密模型都更方便。不过ONNX的后处理需要自己实现包括解码、NMS等代码量会多一些。对于“先跑通”阶段还是直接用ultralytics包更高效。10.1 YOLOv5模型导出ONNXYOLOv5的导出命令是python models/export.py --weights best.pt --include onnx --opset 12导出后的ONNX模型可以在PySide6程序里用onnxruntime统一加载。对于YOLOv5和YOLOv8同时管理的团队统一走ONNX接口是对上层界面最友好的方式。11. 最佳实践与工程建议11.1 项目目录结构一个规范的车型识别项目目录结构建议如下vehicle_detection_system/ ├── main.py # 程序入口 ├── detector.py # 模型推理封装 ├── camera_thread.py # 摄像头线程 ├── detect_thread.py # 图片推理线程 ├── draw_utils.py # 绘图工具 ├── models/ │ ├── best.pt # 训练好的权重 │ └── best.onnx # 导出的ONNX权重 ├── datasets/ │ └── vehicle/ │ ├── train/ │ └── val/ ├── test_images/ │ └── suv_01.jpg ├── requirements.txt └── README.md11.2 requirements.txtultralytics pyside6 opencv-python numpy11.3 五个必须遵守的原则第一模型和界面分离。detector.py不导入任何PySide6模块。这样测试模型逻辑时不需要GUI环境后续要改成命令行工具或Web后端模型代码可以直接复用。第二所有耗时操作进线程。模型推理、摄像头读取、文件解析都属于耗时操作不要放在主线程的槽函数里。判断标准很简单如果某个操作可能超过100毫秒就应该考虑线程化。第三界面显示图片要在主线程更新。在线程里直接操作QLabel.setPixmap是线程不安全的。信号槽机制天然解决了这个问题在工作线程里发信号在主线程的槽函数里更新界面。第四类别名称从模型读取。不要手写硬编码的类别列表。模型类别顺序变了界面代码自动适应这是减少维护成本最重要的习惯。第五留存推理日志。在detect_image和detect_frame里加上简单的耗时统计输出到控制台或日志文件。排查性能问题时这些日志很有用。import time start time.time() boxes, classes, scores self.detector.detect_image(image_path) cost_ms (time.time() - start) * 1000 print(f[推理耗时] {cost_ms:.1f} ms)11.4 模型精度和速度的平衡车型识别系统的实际场景通常分为两类对单张图片做精细识别可以选择大一点的模型如YOLOv8m或YOLOv8l精度更高。摄像头实时监控必须选择轻量模型如YOLOv8s或YOLOv5s并控制输入尺寸建议640而不是1280。如果你要在CPU上做实时检测输入尺寸还要再适当下调比如从640降到416速度会明显提升但小目标识别能力会下降。这个取舍需要根据摄像头的安装距离和车辆在画面中的占比来定。11.5 数据集构建阶段的建议既然项目主题包含“轿车、SUV、跑车、卡车”四种车型数据集的构建影响着最终系统能不能交付。如果从零开始收集数据建议每个类别至少准备1000张以上图片且要覆盖不同角度、不同光照、不同天气。正面、侧面、斜后方视角都要包含因为侧面的SUV和轿车最难区分。从公开数据集中筛选车辆图片作为起点比如COCO、UA-DETRAC、BDD100K中的车辆样本。标注时保持一致的类别定义底盘高的算是SUV双门跑车和普通轿车要明确边界模糊样本尽量统一规则。标注完成后训练前先做一次数据分布检查看每个类别的图片数量是否均衡。类别不均衡会导致模型偏向于样本多的类别。11.6 安全与合规提醒车型识别系统在工程落地时涉及个人隐私和公共安全两个维度。采集摄像头数据做测试时要确保已获得相应授权不要在未经允许的区域进行长期采集。模型如果部署在公共道路场景要关注数据脱敏和存储期限。作为一个技术开发者至少应该做到测试数据来源合法系统记录的数据不泄露模型不用于任何违规用途。12. 总结与后续学习方向这一篇从技术选型、系统架构、模型封装、PySide6界面设计、线程模型到排查方法完整拆解了基于YOLOv8/YOLOv5和PySide6的车型识别检测系统。核心是要理解一个观点算法模型和桌面界面之间的桥接是这个项目真正的工程难点。模型训练只是前半程把模型变成用户能操作的软件需要掌握推理封装、线程调度、信号槽通信、绘图绘制这一整套桌面开发能力。文章里给出的代码可以直接复用detector.py是模型封装层匹配图片和摄像头两种场景main.py是主窗口和推理线程实现了图片检测和结果统计draw_utils.py负责绘制检测框camera_thread.py是视频流实时检测的参考实现。你可以按自己的模型权重路径和类别定义把代码调通。后续值得深入的方向有四个第一个方向是ONNX Runtime推理。脱离ultralytics包用onnxruntime独立部署程序体积更小、启动更快也更接近生产环境要求。第二个方向是针对细分车型的模型优化。如果发现“跑车”和“轿车”的混淆率比较高单纯调模型结构不一定有效可以尝试用加入关键点检测或者更丰富的注意力机制来提升区分能力。第三个方向是模型量化。把训练完的YOLO模型量化成INT8版本在边缘设备或CPU上大幅提速。这个方向适合有低成本部署需求的场景。第四个方向是增加车型属性识别。比如在检测出车辆类别后再识别颜色、朝向、车牌区域等附加信息。做停车场管理或智慧交通项目时这些信息往往会成为真正的业务需求。建议收藏备用。先把图片检测流程全部跑通再扩展摄像头模式和ONNX部署一步一步把一个“能跑通”的项目进化成一个“能交付”的系统。
