这次我们来看一个非常典型的深度学习落地项目:基于 YOLOv8 和 PyQt5 的车牌检测识别系统。这个选题在毕业设计、公司内部安防项目、停车场道闸原型验证里都很常见。它不追求研究前沿,重点是把目标检测、字符识别和桌面 GUI 三者串成一条可用的技术链路。
项目核心其实就两层:先用 YOLOv8 做车牌区域定位,再对定位到的车牌区域做字符识别。前者解决“车牌在哪”,后者解决“车牌号是什么”。界面部分用 PyQt5 实现,负责图片上传、实时画面展示、结果可视化和管理交互。如果材料完整,这套系统还能支持摄像头实时识别、批量图片处理、检测结果导出等功能。
这篇文章会按实际开发顺序展开:先讲整体架构和技术选型,再给出一份可用的 YOLOv8 训练/推理代码,然后实现 PyQt5 界面调用模型,接着对识别结果做效果验证,最后补充接口封装、常见报错排查和工程化建议。整个流程跑通后,你手上就有一套可以直接演示的车牌检测识别系统。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 项目类型 | 桌面端车牌检测识别系统 |
| 技术栈 | Python、YOLOv8、PyQt5、OpenCV |
| 主要功能 | 车牌区域检测、车牌字符识别、图片/实时视频检测、结果可视化 |
| 检测模型 | YOLOv8 系列,推荐 YOLOv8n/s 小模型起步 |
| 字符识别 | 可选用 PaddleOCR、LPRNet 或传统模板匹配,按需替换 |
| 显卡要求 | NVIDIA GPU 可加速训练和推理,纯 CPU 也能跑但速度偏慢 |
| 显存占用 | 推理约 1-4G,训练视模型规格而定,实际需按本机测试 |
| 支持平台 | Windows / Linux 均可,Windows 下 PyQt5 调试更顺手 |
| 启动方式 | Python 脚本启动,可打包为 exe |
| API 接口 | 可封装为 Flask/FastAPI 本地服务 |
| 批量任务 | 支持批量图片检测,结果导出 CSV/JSON |
| 适合场景 | 毕设演示、停车场道闸原型、安防监控辅助、算法入门实践 |
从材料看,网友关心的热点集中在 YOLOv8 训练自己的数据集、GTX 1660 Ti 这类入门显卡能不能跑、PyQt5 安装和使用常见问题、环境配置细节。这篇就把这些点全部覆盖到。
2. 适用场景与使用边界
这套系统适合三类人:第一次做毕业设计、需要用 YOLOv8 做完整应用的在校学生;要在工控机上做算法验证的视觉工程师;以及刚接触 PyTorch 和 OpenCV、想了解深度学习项目怎么拼装的开发者。
它解决的问题是一套完整的自动化识别链路:YOLOv8 负责找车牌,OCR 或分类模型负责读字符,PyQt5 负责把结果展示给用户。三个模块解耦,任何一个都可以单独替换。
不适合什么场景?不适合没有授权前提下的公共道路车辆抓拍,不适合对识别精度有百分之百要求的计费系统。车牌识别受光照、角度、模糊、车牌脏污影响较大,现场环境复杂时需要补充图像预处理和多帧投票策略。
版权、隐私、安全边界必须说清楚。采集车辆图片时,如果涉及真实车辆、车主信息、道路场景,需要获得相应授权。社区道闸、校园停车场这类受控环境下的测试是常见合法场景,但公开道路批量抓拍需要严格遵守当地法规。人脸、车牌信息属于敏感个人信息,处理时需要脱敏、加密存储并限制访问权限。该系统只能用于合法合规的测试环境。
3. 环境准备与前置条件
3.1 硬件要求
训练和推理的硬件门槛差别很大。
| 硬件项 | 最低要求 | 推荐配置 |
|---|---|---|
| CPU | 支持 AVX2 的 x86 处理器 | 8 核以上 |
| GPU | NVIDIA GTX 1060 6G | RTX 3060 及以上 |
| 显存 | 4G | 8G+ |
| 内存 | 16G | 32G |
| 磁盘 | 10G 可用空间 | SSD |
从热词来看,很多人关心 GTX 1660 Ti 这类 6G 显存显卡能否跑 YOLOv8。结论是:完全可以。YOLOv8n、YOLOv8s 在 6G 显存上训练小数据集没有问题,推理更轻松。如果显存不够,优先降低 batch size 和图像尺寸,而不是换模型。
3.2 Python 环境
推荐使用 conda 创建独立环境,避免污染系统 Python:
conda create -n license_plate python=3.10 -y conda activate license_platePython 版本推荐 3.9 或 3.10。PyQt5 在 3.10 下兼容性很好,PyTorch 对 3.10 的支持也完善。
3.3 核心依赖安装
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 pip install ultralytics opencv-python pyqt5 pyqt5-tools pip install paddleocr paddlepaddle # 字符识别方案,可选 pip install flask fastapi uvicorn # API 封装,可选如果 CUDA 版本不是 12.1,去 PyTorch 官网选择对应的安装命令。CPU 用户可以只装 CPU 版,但训练速度会明显变慢。
3.4 项目目录规划
建议按照下面的结构组织文件,后续维护和打包都方便:
license_plate_system/ ├── models/ # 存放训练好的权重 ├── datasets/ # 数据集 │ ├── images/ │ └── labels/ ├── ui/ # PyQt5 界面代码 ├── core/ # 检测识别核心逻辑 ├── output/ # 检测结果输出 ├── main.py # 程序入口 └── requirements.txt4. YOLOv8 车牌检测模型训练与推理
4.1 数据集准备
车牌检测是典型的目标检测任务,只需要标注车牌区域的边界框。数据集可以从两个方向获取:公开数据集(如 CCPD、CRPD)和自建数据。
自建数据集的标注格式按 YOLO 格式组织:
- 每张图片对应一个同名
.txt标签文件 - 每行内容为:
class x_center y_center width height - 坐标值均为归一化后的比例
示例:
0 0.5123 0.4782 0.3187 0.0921类别编号从 0 开始。如果只检测车牌,就是一个单类别数据集。
4.2 数据集配置
创建一个dataset.yaml文件:
path: D:/plate_data/datasets train: images/train val: images/val names: 0: license_plate4.3 训练模型
使用 YOLOv8 官方 CLI 训练:
yolo train model=yolov8n.pt data=dataset.yaml epochs=100 imgsz=640 batch=16 device=0如果显存只有 6G,建议:
yolo train model=yolov8n.pt data=dataset.yaml epochs=100 imgsz=640 batch=8 device=0训练完成后,最优权重保存在runs/detect/train/weights/best.pt。
训练过程中的损失曲线可以用tensorboard或直接查看runs/detect/train下的曲线图文件。如果发现过拟合早,适当降低 epochs 或增强数据。
4.4 模型推理验证
训练好的模型可以直接用 Python 调用:
from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") results = model.predict("test.jpg", conf=0.5, save=True, imgsz=640) print(results[0].boxes.xyxy) print(results[0].boxes.conf)conf=0.5表示置信度阈值,低于这个值的检测框会被过滤。如果画面中误检多,可以调高到 0.6-0.7;如果漏检多,可以降到 0.3-0.4。
4.5 用一个通用预训练模型调试流程
如果暂时没有车牌数据集,也可以先用官方 COCO 预训练模型验证代码链路,再替换成自己的车牌模型:
from ultralytics import YOLO model = YOLO("yolov8n.pt") results = model.predict("any_image.jpg", save=True)这段代码的意义是验证 Ultralytics 环境是否配置正确。等车牌模型训练好后,直接把权重路径换掉即可。
5. 基于 PyQt5 的界面设计与实现
5.1 界面整体布局
PyQt5 界面建议分为四个区域:
| 区域 | 功能 |
|---|---|
| 操作按钮区 | 打开图片、开始检测、实时识别、清空结果 |
| 图像显示区 | 左侧为原图,右侧为检测标注图 |
| 结果显示区 | 显示识别出的车牌号码、置信度和耗时 |
| 日志状态栏 | 显示当前状态,如模型加载是否完成、检测失败原因 |
5.2 主窗口代码实现
import sys import cv2 import numpy as np from PyQt5.QtWidgets import (QApplication, QMainWindow, QPushButton, QLabel, QVBoxLayout, QHBoxLayout, QWidget, QTextEdit, QFileDialog) from PyQt5.QtGui import QImage, QPixmap from ultralytics import YOLO class PlateWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("车牌检测识别系统 - YOLOv8 + PyQt5") self.setGeometry(100, 100, 1200, 700) self.model = YOLO("runs/detect/train/weights/best.pt") self.init_ui() def init_ui(self): central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) btn_layout = QHBoxLayout() self.btn_open = QPushButton("打开图片") self.btn_detect = QPushButton("开始检测") self.btn_clear = QPushButton("清空结果") btn_layout.addWidget(self.btn_open) btn_layout.addWidget(self.btn_detect) btn_layout.addWidget(self.btn_clear) main_layout.addLayout(btn_layout) image_layout = QHBoxLayout() self.label_original = QLabel("原图") self.label_result = QLabel("检测结果") self.label_original.setMinimumSize(480, 360) self.label_result.setMinimumSize(480, 360) image_layout.addWidget(self.label_original) image_layout.addWidget(self.label_result) main_layout.addLayout(image_layout) self.text_result = QTextEdit() self.text_result.setReadOnly(True) main_layout.addWidget(self.text_result) self.btn_open.clicked.connect(self.open_image) self.btn_detect.clicked.connect(self.detect_plate) self.btn_clear.clicked.connect(self.clear_result) def open_image(self): file_path, _ = QFileDialog.getOpenFileName(self, "选择图片", "", "Images (*.jpg *.png *.jpeg *.bmp)") if file_path: self.image_path = file_path img = cv2.imread(file_path) img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) h, w, ch = img_rgb.shape bytes_per_line = ch * w qt_img = QImage(img_rgb.data, w, h, bytes_per_line, QImage.Format_RGB888) self.label_original.setPixmap(QPixmap.fromImage(qt_img).scaled(self.label_original.size(), aspectRatioMode=1)) def detect_plate(self): if not hasattr(self, 'image_path'): self.text_result.append("请先打开图片") return results = self.model.predict(self.image_path, conf=0.5, imgsz=640) result_img = results[0].plot() h, w, ch = result_img.shape bytes_per_line = ch * w qt_img = QImage(result_img.data, w, h, bytes_per_line, QImage.Format_RGB888) self.label_result.setPixmap(QPixmap.fromImage(qt_img).scaled(self.label_result.size(), aspectRatioMode=1)) for box in results[0].boxes: conf = float(box.conf[0]) self.text_result.append(f"检测到车牌,置信度: {conf:.2f}") def clear_result(self): self.label_original.clear() self.label_result.clear() self.text_result.clear() if __name__ == "__main__": app = QApplication(sys.argv) window = PlateWindow() window.show() sys.exit(app.exec_())这里先把检测框画出来。界面运行正常后,再在检测回调里加入车牌字符识别逻辑。界面和模型解耦是很重要的一条设计原则,界面只负责展示,识别逻辑写在单独模块中。
6. 车牌字符识别实现
YOLOv8 检测出车牌区域后,需要从裁剪图像中读取车牌号。常用的方案有三种:
| 方案 | 优点 | 缺点 |
|---|---|---|
| PaddleOCR | 识别效果好,中文支持完善 | 模型较大,依赖较多 |
| LPRNet / CRNN 专用模型 | 轻量、速度快 | 需要额外训练 |
| 模板匹配 + 分类器 | 可控性强 | 对角度和颜色敏感 |
对于毕设或原型演示,推荐先用 PaddleOCR 快速验证链路,再根据实际效果优化。
6.1 PaddleOCR 车牌识别
import cv2 from paddleocr import PaddleOCR ocr = PaddleOCR(use_angle_cls=False, lang="ch") def recognize_plate(image, box): x1, y1, x2, y2 = [int(v) for v in box[:4]] crop = image[y1:y2, x1:x2] # 车牌区域通常较小,可以放大提升识别率 crop = cv2.resize(crop, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC) result = ocr.ocr(crop, cls=False) if result and result[0]: text = result[0][0][1][0] score = result[0][0][1][1] return text, score return "", 0.06.2 整合到 YOLOv8 推理流程
def detect_and_recognize(image_path, model, ocr): image = cv2.imread(image_path) results = model.predict(image_path, conf=0.5, imgsz=640) for box in results[0].boxes: xyxy = box.xyxy[0].cpu().numpy() plate_text, score = recognize_plate(image, xyxy) print(f"车牌号: {plate_text},置信度: {score:.2f}")这一步是系统最有实用价值的部分:检测出车牌位置后立即读取字符内容,最终在界面上显示“车牌号 + 置信度 + 检测耗时”。实际开发时要注意车牌区域裁剪后图像尺寸很小,直接送 OCR 模型效果可能不理想,稳妥的做法是放大 2 到 3 倍并做灰度化、直方图均衡化等预处理。
7. 功能测试与效果验证
系统逐步开发完成后,需要建立一套可重复的测试流程。下面按测试维度拆开说明。
7.1 环境测试
先跑最简单的版本,确认依赖有效:
import torch import cv2 from ultralytics import YOLO print("CUDA 可用:", torch.cuda.is_available()) print("PyTorch 版本:", torch.__version__) print("OpenCV 版本:", cv2.__version__) model = YOLO("yolov8n.pt") print("YOLOv8 环境正常")如果 CUDA 不可用,训练和推理都会退回 CPU。此时可以工作,但速度慢。入门级场景可以先 CPU 验证流程,再用 GPU 跑正式训练。
7.2 图片检测测试
测试目的:验证 YOLOv8 模型能否在单张图片中准确定位车牌。
操作步骤:
- 准备多张不同类型车牌的图片,包括蓝牌、绿牌新能源、黄牌、倾斜车牌、夜间车牌。
- 使用训练好的模型逐张推理。
- 查看检测框是否贴合车牌边缘。
预期结果:
- 车牌位置正确画出检测框。
- 置信度分数在正常范围。
- 无明显漏检和误检。
判断成功的标准:在测试集上 mAP50 达到 0.9 以上(具体以材料情况和数据质量为准)。如果明显低于这个水平,优先补充困难样本再训练。
7.3 车牌字符识别测试
测试目的:验证裁剪区域字符读取的准确率。
测试用例设计:
| 用例编号 | 测试图片 | 预期结果 | 判定标准 |
|---|---|---|---|
| T1 | 清晰蓝牌 | 读取全部字符 | 字符完全一致 |
| T2 | 新能源绿牌 | 读取“D”或“F”开头字符 | 字符完全一致 |
| T3 | 夜间低光照 | 尽可能读取一致 | 允许 1-2 位字符误差 |
| T4 | 倾斜角度大于 30 度 | 读取稳定 | 允许算法加校正后对比 |
| T5 | 车牌脏污 | 读取稳定 | 记录失败场景继续优化 |
实测过程中,字符识别最容易在以下环节失败:
- 车牌面积太小,字符模糊。
- 车牌倾斜严重,需要仿射变换校正。
- 蓝色车牌和绿色车牌的字符对比度不同,需要针对性处理。
7.4 PyQt5 界面交互测试
测试流程按照用户真实操作一步步执行:
- 点击“打开图片”。
- 选择测试图片,确认原图正确显示。
- 点击“开始检测”。
- 等待推理完成,确认右侧标注图显示。
- 检查结果文本框内是否显示车牌号。
- 点击“清空结果”,确认所有区域被清理。
常见问题:
- 点击按钮后界面卡死:推理过程阻塞了 GUI 线程。解决方法是把推理放到
QThread中。 - 图片显示被拉伸变形:设置
scaledContents时保持宽高比。 - 无法加载模型:检查路径是否包含中文,OpenCV 和 Ultralytics 对中文路径兼容性较差。
7.5 实时摄像头识别测试
在 PyQt5 中调用摄像头识别时,核心问题是视频流读取和界面刷新不能互相阻塞。推荐使用QThread采集帧,通过信号将帧传递到主线程:
import cv2 from PyQt5.QtCore import QThread, pyqtSignal class CameraThread(QThread): frame_signal = pyqtSignal(object) def __init__(self): super().__init__() self.cap = cv2.VideoCapture(0) self.running = True def run(self): while self.running: ret, frame = self.cap.read() if ret: self.frame_signal.emit(frame) self.msleep(30) def stop(self): self.running = False self.cap.release()摄像头实时识别的性能瓶颈在 YOLOv8 推理耗时上。当 GPU 推理每帧耗时在 30ms 左右时,界面能做到流畅显示。CPU 推理每帧可能需要 200-500ms,体验会明显下降。
8. 接口 API 与批量任务
8.1 批量图片检测
批量任务对毕设演示和实际工程都非常重要。核心逻辑就是遍历目录、逐张推理、结果汇总:
import os import csv from ultralytics import YOLO model = YOLO("best.pt") def batch_detect(input_dir, output_dir, csv_path): os.makedirs(output_dir, exist_ok=True) results_rows = [] for img_name in os.listdir(input_dir): if not img_name.lower().endswith(('.jpg', '.jpeg', '.png')): continue img_path = os.path.join(input_dir, img_name) results = model.predict(img_path, conf=0.5, imgsz=640) save_path = os.path.join(output_dir, img_name) results[0].save(save_path) for box in results[0].boxes: x1, y1, x2, y2 = [float(v) for v in box.xyxy[0].cpu().numpy()] conf = float(box.conf[0]) results_rows.append([img_name, x1, y1, x2, y2, conf]) with open(csv_path, 'w', newline='', encoding='utf-8') as f: writer = csv.writer(f) writer.writerow(["image", "x1", "y1", "x2", "y2", "conf"]) writer.writerows(results_rows)批量任务建议加断点续跑逻辑:处理前先检查输出目录中是否已存在同名结果,存在则跳过。图片数量多时,这个逻辑能节省大量时间。
8.2 API 服务封装
YOLOv8 模型加载后会占用显存,如果每个请求都重新加载模型,效率很低。正确做法是启动时加载模型,推理时复用。
使用 FastAPI 封装:
from fastapi import FastAPI, UploadFile, File from ultralytics import YOLO import cv2 import numpy as np app = FastAPI() model = YOLO("runs/detect/train/weights/best.pt") @app.post("/detect") async def detect_plate(file: UploadFile = File(...)): contents = await file.read() nparr = np.frombuffer(contents, np.uint8) img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) results = model.predict(img, conf=0.5, imgsz=640) plates = [] for box in results[0].boxes: x1, y1, x2, y2 = [float(v) for v in box.xyxy[0].cpu().numpy()] conf = float(box.conf[0]) plates.append({"box": [x1, y1, x2, y2], "confidence": conf}) return {"plates": plates}启动服务:
uvicorn api_server:app --host 127.0.0.1 --port 8000调用测试:
curl -X POST -F "file=@test.jpg" http://127.0.0.1:8000/detect接口服务隔离了界面和识别逻辑。前端可以是 PyQt5,也可以替换成 Web 页面、微信小程序或 Android 应用。服务启动后,建议限制访问范围为本地或局域网,避免模型接口被外部随意调用。
9. 资源占用与性能观察
9.1 显存占用观察方法
观察指标时不要只看任务管理器。用下面的命令获取准确显存占用:
nvidia-smi -l 1-l 1表示每秒刷新一次。推理过程中,显存占用会随着 batch size 和图片分辨率变化。图像尺寸从 640 提升到 1280,显存占用可能翻倍。如果显存不足,优先缩小imgsz,而不是换更复杂的模型。
9.2 推理速度测试
Ultralytics 原生支持速度测试,在predict中加入参数即可:
results = model.predict(img_path, imgsz=640, device=0) print(results[0].speed)speed字典包含预处理时间(preprocess)、推理时间(inference)和后处理时间(postprocess)。不同显卡的差距主要在inference阶段。
从热词中网友的普遍反馈看,GTX 1660 Ti 这类 6G 入门显卡跑 YOLOv8n 推理,单帧耗时可以控制在几十毫秒到一百多毫秒之间,足以满足桌面演示和轻度实时场景。更精确的数据需要在自己的机器上测试,不同驱动、不同 CUDA 版本都会影响结果。
9.3 降低资源占用的办法
| 手段 | 说明 |
|---|---|
| 使用 YOLOv8n | 参数量最少,显存和耗时最低 |
| 降低推理分辨率 | 从 640 降到 480,速度明显提升,但小目标容易漏检 |
| 降低 batch size | 显存不够时的首选 |
| 启用半精度推理 | model.predict(..., half=True) |
| 限制视频帧处理频率 | 摄像头场景每 2 帧处理 1 帧即可 |
10. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 启动程序后提示找不到模块 | 依赖未安装或环境未激活 | 检查pip list中是否有 PyQt5、ultralytics | 重新安装依赖 |
| 模型加载失败 | 权重路径错误、路径含中文或空格 | 打印模型文件路径,确认文件存在 | 改为纯英文路径 |
| CUDA 不可用 | 显卡驱动版本过旧或 PyTorch 版本不匹配 | 运行torch.cuda.is_available() | 更新驱动或重装对应 CUDA 版 PyTorch |
| 显存不足 | batch size 过大或分辨率过高 | 查看nvidia-smi显存占用 | 减小 batch、降低 imgsz |
| PyQt5 界面点击后无响应 | 推理阻塞了 GUI 线程 | 观察窗口是否卡死 | 将推理放入 QThread |
| 摄像头打不开 | 索引错误或未授权访问 | 尝试cv2.VideoCapture(0)/cv2.VideoCapture(1) | 修改摄像头索引 |
| 识别车牌号完全错误 | 字符识别模型不匹配或预处理缺失 | 单独测试 OCR 模型 | 更换识别模型,增加缩放和灰度化预处理 |
| 批量处理卡住 | 个别图片路径异常或格式损坏 | 加入 try/except 并输出当前文件 | 跳过异常文件,继续处理 |
| 端口被占用 | 之前启动的服务未关闭 | netstat -ano -p tcp 端口 | 关闭旧进程或换端口 |
| API 请求超时 | GPU 推理排队 | 查看服务端日志 | 限制并发数,增加超时时间 |
11. 最佳实践与使用建议
11.1 训练阶段
第一次训练不要追求最好精度。先用 YOLOv8n 和 50 张图片把完整流程跑通,确认数据格式、配置文件、训练命令都没问题,再扩展数据量。
数据质量比数据量更重要。同一个车牌尽量不要重复太多,但倾斜、遮挡、夜间、模糊的车牌一定要有。这些困难样本决定了系统在现场环境下的真实表现。
11.2 工程化方面
模型权重文件、测试图片、输出结果分目录管理。权重按数据量和训练时间命名,例如best_100epoch_1000data.pt,方便回溯。
批量任务必须加日志。每处理 100 张图片打印一次进度,失败时把异常信息写入日志文件,而不是直接让程序中断。
接口服务启动后,用 PyInstaller 打包时将模型文件一起打包,但需要注意路径适配。更稳妥的方案是打包成精简客户端,模型文件放在外部路径,便于更新。
11.3 合规使用方面
这里再强调一次:车牌数据涉及个人信息和公共安全,只能在合法授权的环境内采集、存储和使用。公开数据集的授权条款需要在使用前确认。人脸和车牌等生物/个人信息必须脱敏处理,不能让无关人员随意访问检测结果。
11.4 界面体验优化方向
YOLOv8 模型本身只做检测,后续的字符识别、结果统计、数据库存储、道闸联动都可以分步扩展。PyQt5 界面可以从单一图片检测扩展为一个完整的管理端:
- 加入历史识别记录表格。
- 支持结果导出 Excel。
- 加入登录和权限控制。
- 加入识别正确/错误的人工复核按钮。
这些功能会显著提升系统完成度,也更适合作为毕设演示或产品原型。
12. 总结与下一步
这套系统最值得尝试的点在于:它打通了目标检测、字符识别和桌面 GUI 的完整链路,是深度学习落地的标准模板。你把车牌换成安全帽、口罩、工业缺陷,整个框架基本不用大改。PyQt5 负责界面,YOLOv8 负责定位,OCR 负责细读,三者组合后扩展性非常强。
最先应该验证的功能是环境配置是否跑通,然后跑一张测试图片确认检测框正常显示。最容易踩的坑是 PyQt5 界面刷新卡顿和模型文件路径错误,前者用 QThread 解决,后者统一改用英文路径。
下一步可以朝两个方向扩展:把字符识别模型从通用 OCR 换成专用轻量车牌识别模型,进一步提升速度和准确率;或者加入车辆颜色、车型识别,构建更完整的车辆结构化信息平台。
建议收藏备用。无论是做毕设、搭原型还是入门 YOLOv8,这套代码结构都能直接改、直接跑。