如果你以为花卉识别只是一个图像分类问题,那说明你还没有遇到真实场景。
很多新手第一次做花卉识别的毕业设计或练手项目,第一反应都是拿 ResNet、VGG 搭一个分类模型,把图片丢进去,输出一个花的名字。这个思路没有错,但它默认了一个前提:图片里只有一朵花,而且花是主体。现实是,你拿手机对着花坛拍一张照片,里面可能有玫瑰、有雏菊、有蒲公英挤在一起。这时候分类模型就无能为力了,正确的做法是目标检测——先定位每一朵花的位置,再判断每一朵花是什么品种。
这篇文章要讲的,就是如何把“检测 + 桌面应用”这条路完整走通:用 YOLOv5 或 YOLOv8 训练一个花卉检测模型,再用 PySide6 写一个带图形界面的桌面程序,支持加载图片、调用摄像头实时识别、结果可视化,最后还能打包给其他人使用。
先说结论:这套系统的真正难点不在训练,也不在界面,而在“模型和界面的工程化集成”。训练脚本跑通只需要半天,真正消耗时间的是数据集整理、模型推理封装、界面线程设计、异常处理和模型导出部署。下面我会按照从零到一的顺序,把每个环节拆开讲清楚,并给出可以直接复制的代码。
1. 这篇文章真正要解决的问题
先明确场景。花卉识别系统通常有两类需求:
- 单花识别:图片里只有一朵花,识别品种。这种需求用分类模型就够了。
- 多花检测:图片或视频里有多朵花,需要把每一朵花框出来,再给出品种。这种需求必须用目标检测模型。
本文针对的是第二种,也是实际项目里更常见的情况。你会发现,身边很多“花卉识别”课题看起来简单,真正落地时卡住的往往是这几个问题:
- 数据集从哪来:花卉品种多、相似度高,公开数据集和自定义数据要如何组织成 YOLO 能识别的格式。
- 模型选型:YOLOv5 和 YOLOv8 到底怎么选,训练参数如何调整才不玄学。
- 界面卡死:PySide6 界面里直接跑检测,窗口会无响应,必须用多线程处理。
- 模型部署:训练好的
.pt文件怎么在桌面程序里加载,如何导出成 ONNX 让 CPU 也能跑得动。
什么样的读者最适合读这篇文章?
- 正在做花卉识别、目标检测类毕业设计的学生。
- 想从“只会跑通训练脚本”进阶到“能做出完整桌面应用”的深度学习初学者。
- 需要给检测模型套一层 GUI 但不知道从哪下手的开发者。
如果你只是想学会model.train()那几行命令,网上教程一大把。但如果你想做一个能给别人演示、能实际运行、出了问题能排错的完整系统,这篇文章能帮你少走很多弯路。
2. 核心技术选型:YOLOv8、YOLOv5 与 PySide6
2.1 YOLOv8 与 YOLOv5 怎么选
YOLOv5 和 YOLOv8 都出自 Ultralytics 团队,但它们的定位和使用方式有明显差异。
| 对比维度 | YOLOv5 | YOLOv8 |
|---|---|---|
| 维护方式 | 源码仓库 Clone 使用 | 统一通过pip install ultralytics使用 |
| 网络结构 | CSPDarknet + PANet,C3 模块 | C2f 模块,网络结构更精简 |
| Anchor 机制 | Anchor-Based | Anchor-Free,免去 anchor 调参 |
| 训练体验 | 需要手动管理项目结构和依赖 | API 统一,数据配置和训练入口更清晰 |
| 自定义数据 | 需要写 yaml 并理解源码目录 | 同样需要 yaml,但封装更友好 |
| 生态延续性 | 老项目常用,资料多 | 新项目首选,更新活跃 |
从新项目角度,我更推荐直接上 YOLOv8。原因有两个:
第一,YOLOv8 的安装和使用成本低很多。不需要 clone 源码仓库、不需要手动 install requirements,一条pip install ultralytics就能把训练、验证、预测、导出全部搞定。对初学者来说,这省掉的不是几分钟,而是十几个报错。
第二,YOLOv8 的 API 设计更统一。训练、预测、导出的入口都是同一个YOLO对象,写出来的代码更简短,和 PySide6 集成时也更不容易出错。
但 YOLOv5 也有存在价值。如果你手上已经有跑通的 YOLOv5 老项目,或者导师指定要用 v5,那继续用完全没问题。本文的代码思路对两个版本都适用,核心差异只在安装方式和个别代码写法上。
2.2 为什么用 PySide6 而不是 Tkinter 或 PyQt5
桌面 GUI 的选择同样需要判断。
Tkinter 虽然 Python 自带、零安装,但界面风格老旧,做不出像样的识别展示效果,而且多线程处理图像显示时 API 比较别扭。
PyQt5 和 PySide6 功能几乎一样,但 PyQt5 的授权协议是 GPL,闭源商用有限制;PySide6 是 Qt 官方支持的 Python 绑定,使用 LGPL 协议,更稳妥。从 Python 版本支持来看,PySide6 也是目前维护最积极的。
选 PySide6 还有一个现实原因:Qt 的信号槽(Signal/Slot)机制非常契合“界面点击 – 后台推理 – 回传结果”这种异步场景。后面你会看到,用QThread把推理放到后台线程,再用Signal把结果传回界面,整个代码结构很清晰。
3. 系统总体架构设计
在写任何代码之前,先把系统分成三层,后面做起来才有条理:
- 界面层(PySide6):负责用户交互,包括加载模型、选择图片、打开摄像头、显示检测结果。
- 逻辑层(推理封装):把 YOLO 模型的加载和预测封装成一个
FlowerDetector类,供界面层调用。 - 模型层(YOLOv8/YOLOv5):训练好的权重文件,支持
.pt格式,也可以导出为.onnx后用 CPU 推理。
这三层之间的关系是单向依赖:界面层调用逻辑层,逻辑层调用模型层。不要把 YOLO 代码直接写进界面文件里,否则后续维护和模型更换都会很痛苦。
完整的系统功能规划如下:
- 支持加载本地训练好的
.pt/.onnx权重。 - 支持打开单张图片进行检测,并在界面上绘制边界框和品种名称。
- 支持打开摄像头进行实时检测。
- 识别结果包括:花品种、置信度、检测框坐标。
- 支持批量处理文件夹内图片,便于验收和评估。
4. 环境准备与依赖安装
4.1 基础环境要求
推荐环境如下:
- 操作系统:Windows 10/11 或 Ubuntu 20.04+。
- Python 版本:3.9 或 3.10。
- GPU:建议 NVIDIA 显卡,显存 4GB 以上。GTX 1660 Ti(6GB)这类显卡跑 YOLOv8n 或 YOLOv5s 足够;没有 GPU 也能用 CPU 跑,但训练速度会慢很多。
- CUDA:如果使用 GPU,需要安装与 PyTorch 匹配的 CUDA 版本。
4.2 创建虚拟环境
强烈建议使用 conda 创建独立环境,不要直接装在系统 Python 里,否则依赖冲突会把你逼疯。
conda create -n flower python=3.10 -y conda activate flower4.3 安装 PyTorch
PyTorch 的安装方式要按 GPU 和 CPU 区分。先打开 PyTorch 官网获取当前稳定版本对应的安装命令,思路是:
# GPU 版本示例(具体版本请以官网为准) pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # CPU 版本 pip install torch torchvision注意:不要先装 torch 再装 ultralytics,而是先确认 torch 可用,再装检测框架。
4.4 安装 YOLOv8 依赖与 PySide6
pip install ultralytics pip install pyside6 pip install opencv-python安装完成后,可以快速验证:
python -c "from ultralytics import YOLO; print(YOLO.__name__)" python -c "from PySide6.QtWidgets import QApplication; print('PySide6 OK')"如果导入成功,说明环境已经准备好。
这里有一个常见的坑:国内网络环境下 PyTorch 用默认 pip 源下载很慢,可以换成清华源或阿里源,速度会快很多。
5. 数据集准备与格式转换
5.1 数据来源与标注
花卉检测数据集主要有两个来源:
- 公开数据集:例如 Oxford 102 Flowers,以及各类花分类数据集。需要说明的是,很多公开数据集是分类格式,要转成检测格式(非目标检测标注)需要额外标注。如果做的是小 demo,可以先从公开检测数据集中筛选花卉类别。
- 自建数据集:自己拍摄或网络下载(注意版权和许可),然后用标注工具标注。
推荐标注工具类别如下:
| 工具 | 类型 | 适用场景 |
|---|---|---|
| LabelImg | 桌面工具 | 单机标注,生成 VOC XML 或 YOLO TXT |
| labelme | 桌面工具 | 支持多边形标注,适合不规则轮廓 |
| CVAT | Web 工具 | 团队标注,需要部署或在线版 |
不管用哪种工具,最终都要转成 YOLO 需要的格式:每张图片对应一个同名的.txt文件,每一行写:
类别ID 中心点x 中心点y 宽度 高度其中 x、y、w、h 都是相对于图片宽度的归一化值。
5.2 数据集目录结构
推荐按下面的结构组织数据:
datasets/flower/ ├── images/ │ ├── train/ │ │ ├── rose_001.jpg │ │ └── ... │ └── val/ │ ├── rose_002.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── rose_001.txt │ │ └── ... │ └── val/ │ ├── rose_002.txt │ └── ... └── flower.yaml然后写一个 YOLO 数据配置文件flower.yaml:
# 文件路径:datasets/flower/flower.yaml path: ./datasets/flower # 数据集根目录 train: images/train # 训练集图片目录 val: images/val # 验证集图片目录 nc: 5 names: 0: rose 1: sunflower 2: tulip 3: daisy 4: dandelion注意nc和names必须与标注时的类别编号对应。如果你只识别 3 种花,就改成 3,并把 names 改成对应的品种名。
5.3 数据增强与样本量问题
深度学习模型对样本量很敏感。花卉识别的难点在于同类花在不同光照、角度、遮挡情况下差异很大,如果样本太少,模型很容易过拟合——训练集上准确率很高,验证集上表现很差。
如果样本量不足,优先做两件事:
- 人工采集多样性:同一品种尽量覆盖不同角度、远近、光照、背景。
- 开启数据增强:YOLOv8 训练时自带了 Mosaic、翻转、色彩抖动等增强策略,不需要自己额外写增强代码。只有当你发现模型过拟合明显时,再考虑增加
hsv_h、hsv_s、degrees等参数。
另外提醒一句:样本数量少的缺点不是“模型训练不动”,而是“模型泛化能力差”。你训练时看到的 loss 下降很可能只是记住了训练集,而不是学会了花的本质特征。
6. 模型训练与调参要点
6.1 训练脚本
环境装好、数据准备好之后,训练代码其实非常短。新建一个train.py:
# 文件路径:train.py from ultralytics import YOLO def main(): # 加载预训练权重,yolov8n.pt 是轻量版 # 可选:yolov8n.pt、yolov8s.pt、yolov5su.pt 等 model = YOLO("yolov8n.pt") model.train( data="datasets/flower/flower.yaml", epochs=100, # 轮数,数据集小可以适当减少 imgsz=640, # 输入图片尺寸 batch=16, # batch size,显存小就调小 device=0, # 0 表示第一块 GPU,CPU 训练写 device="cpu" workers=4, # 数据加载线程数 lr0=0.01, # 初始学习率 patience=20, # 如果 20 轮验证集没有提升就提前停止 project="runs/flower", name="yolov8n_flower", pretrained=True, # 使用 COCO 预训练权重做迁移学习 ) if __name__ == "__main__": main()6.2 训练参数怎么调
- epochs:不是越大越好。样本量小的数据集,100 轮左右基本够;太大反而过拟合。用
patience做早停是非常实用的做法。 - batch:显存不足时的第一调整对象。6GB 显存跑
yolov8n建议 batch 不超过 16;显存不够就降到 8 或 4。 - imgsz:640 是默认值,也是速度和精度的平衡点。不要为了追求准确率盲目调成 1280,推理速度会明显下降。
- device:有 GPU 一定用 GPU。GTX 1660 Ti 跑
yolov8n训练完全没有问题。 - lr0:迁移学习场景下默认 0.01 一般没问题。如果发现 loss 变成 NaN,优先把学习率降到 0.001 再试。
6.3 增量训练
如果你后续新增了花卉品种,不想从零开始训练,可以在之前训练好的权重基础上继续训练:
model = YOLO("runs/flower/yolov8n_flower/weights/best.pt") model.train( data="datasets/flower_v2/flower.yaml", epochs=50, imgsz=640, batch=16, device=0, )这就是常说的“增量训练”或“微调”,能明显缩短训练时间。但要注意:如果新增类别改变了nc,模型的最后一层输出维度会变化,此时建议在预训练权重的基础上重新完整微调若干轮,而不是简单接着旧模型继续训练。
7. 推理模块封装
训练完成后,runs/flower/yolov8n_flower/weights/目录下会生成best.pt和last.pt。best.pt是验证集上表现最好的权重,后续部署就用它。
为了让 PySide6 界面不直接依赖 ultralytics 的 API,我们把推理逻辑封装成一个独立的类,新建detector.py:
# 文件路径:detector.py from pathlib import Path import cv2 import numpy as np from ultralytics import YOLO class FlowerDetector: """花卉检测器封装类""" def __init__(self, weights: str, conf_thres: float = 0.35, iou_thres: float = 0.45): self.weights = Path(weights) self.conf_thres = conf_thres self.iou_thres = iou_thres self.model = YOLO(str(self.weights)) self.names = self.model.names # 类别名称映射 def detect_image(self, image: np.ndarray): """ 对输入图片进行检测 :param image: BGR 格式的 numpy 数组 :return: boxes(ndarray), confs(ndarray), clses(ndarray) """ results = self.model.predict( source=image, conf=self.conf_thres, iou=self.iou_thres, verbose=False, ) result = results[0] boxes = result.boxes.xyxy.cpu().numpy() # [x1, y1, x2, y2] confs = result.boxes.conf.cpu().numpy() # 置信度 clses = result.boxes.cls.cpu().numpy().astype(int) # 类别 ID return boxes, confs, clses def draw_result(self, image: np.ndarray, boxes, confs, clses) -> np.ndarray: """在原图上绘制检测框和标签""" canvas = image.copy() for box, conf, cls_id in zip(boxes, confs, clses): x1, y1, x2, y2 = [int(v) for v in box] label = f"{self.names[cls_id]} {conf:.2f}" cv2.rectangle(canvas, (x1, y1), (x2, y2), (34, 139, 34), 2) cv2.putText( canvas, label, (x1, max(y1 - 8, 10)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (34, 139, 34), 2, ) return canvas这个封装有一个好处:如果以后你要换模型,比如从 YOLOv8 换成 YOLOv5,只需要在FlowerDetector内部调整,界面层完全不用动。
8. PySide6 桌面端集成
8.1 线程设计:界面为什么卡死
直接把detect_image()拖到主窗口里调用,你会发现点完按钮界面立刻无响应。原因是目标检测是 CPU/GPU 密集操作,会阻塞 Qt 的事件循环。
正确做法是使用QThread把推理放到后台线程,推理完成后通过信号把结果图片传回主线程。下面给出一个可运行的 PySide6 主窗口示例,包含加载模型和打开图片两个核心功能:
# 文件路径:main_window.py import sys import cv2 from PySide6.QtCore import Qt, QThread, Signal from PySide6.QtGui import QImage, QPixmap from PySide6.QtWidgets import ( QApplication, QFileDialog, QHBoxLayout, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget, ) from detector import FlowerDetector class InferThread(QThread): """后台推理线程,避免阻塞主界面""" result_ready = Signal(object) def __init__(self, detector: FlowerDetector, image): super().__init__() self.detector = detector self.image = image def run(self): boxes, confs, clses = self.detector.detect_image(self.image) canvas = self.detector.draw_result(self.image, boxes, confs, clses) self.result_ready.emit(canvas) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("花卉识别检测系统") self.setMinimumSize(900, 640) self.detector = None self.current_image = None self.thread = None self.init_ui() def init_ui(self): central = QWidget() self.setCentralWidget(central) layout = QVBoxLayout(central) # 顶部操作栏 top_bar = QHBoxLayout() self.load_btn = QPushButton("加载模型") self.open_btn = QPushButton("打开图片") self.status_label = QLabel("等待加载模型") self.load_btn.clicked.connect(self.load_model) self.open_btn.clicked.connect(self.open_image) top_bar.addWidget(self.load_btn) top_bar.addWidget(self.open_btn) top_bar.addStretch() top_bar.addWidget(self.status_label) layout.addLayout(top_bar) # 图片显示区域 self.image_label = QLabel("请先加载模型并打开图片") self.image_label.setAlignment(Qt.AlignCenter) self.image_label.setStyleSheet("border: 1px solid #999;") layout.addWidget(self.image_label, stretch=1) def load_model(self): weights, _ = QFileDialog.getOpenFileName( self, "选择模型权重", "", "Model Files (*.pt *.onnx)" ) if not weights: return self.detector = FlowerDetector(weights) self.status_label.setText(f"模型已加载:{weights}") def open_image(self): if self.detector is None: self.status_label.setText("请先加载模型") return path, _ = QFileDialog.getOpenFileName( self, "选择图片", "", "Images (*.jpg *.png *.bmp)" ) if not path: return self.current_image = cv2.imread(path) self.run_inference(self.current_image) def run_inference(self, image): if self.thread is not None and self.thread.isRunning(): return self.status_label.setText("正在识别...") self.thread = InferThread(self.detector, image) self.thread.result_ready.connect(self.show_result) self.thread.start() def show_result(self, canvas): rgb = cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB) h, w, ch = rgb.shape qimg = QImage(rgb.data, w, h, ch * w, QImage.Format.Format_RGB888) pixmap = QPixmap.fromImage(qimg) self.image_label.setPixmap( pixmap.scaled( self.image_label.size(), Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation, ) ) self.status_label.setText("识别完成") if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec())8.2 摄像头实时检测
如果你想支持摄像头,原理完全一样,只是多一个定时器或循环读取视频帧的逻辑。核心思路是:QTimer每隔一段时间读取一帧,丢给InferThread识别,再把结果显示到image_label上。
一个简化版的摄像头预览逻辑:
from PySide6.QtCore import QTimer # 在 MainWindow 中增加 def start_camera(self): if self.detector is None: self.status_label.setText("请先加载模型") return self.cap = cv2.VideoCapture(0) # 0 表示默认摄像头 self.timer = QTimer(self) self.timer.timeout.connect(self.update_frame) self.timer.start(50) # 每 50ms 读一帧,约 20 FPS def update_frame(self): ret, frame = self.cap.read() if ret: self.run_inference(frame)这里要特别注意:摄像头的 VideoCapture 尽量在主线程只做读取,真正的检测还是要放到InferThread中,否则画面会明显掉帧。
9. 运行验证与效果评估
9.1 启动与验收
运行方式:
python train.py # 先训练 python main_window.py # 再启动界面界面加载模型后,打开一张包含花朵的图片,预期效果是:每朵花被绿色框框住,框上标注品种名和置信度。
验收标准可以从几个维度判断:
- 检测框是否正确:框是否紧贴花朵主体,是否漏检、误检。
- 置信度是否合理:正常场景下置信度一般在 0.6 以上;如果普遍低于 0.4,说明模型训练不足或数据集质量太差。
- 界面是否流畅:点击识别后窗口不卡顿,图片能正常刷新。
9.2 用验证集评估模型
不要只看一两张图片的效果,要用验证集跑指标。在训练脚本目录下执行:
yolo predict model=runs/flower/yolov8n_flower/weights/best.pt source=datasets/flower/images/val也可以写脚本统计 mAP、precision、recall。这些指标的意义在于:单张图效果好的模型,验证集上不一定好;只有验证集 mAP 稳定,模型才具备实用价值。
9.3 训练过程怎么看
训练完成后,runs/flower/yolov8n_flower/目录下会生成results.png,里面包含 loss 曲线、精度曲线等。判断标准:
- 训练 loss 和验证 loss 都持续下降,最后趋于平稳,说明训练正常。
- 验证 loss 下降后在某个点开始回升,而训练 loss 还在降,说明过拟合,应该停在这个点之前。
- 如果 loss 一开始就是 NaN,基本可以判定学习率过大或数据有问题。
10. 常见问题与排查思路
实战中,下面这些问题出现频率最高,可以直接对照排查。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 训练时 CUDA out of memory | batch 或 imgsz 设置过大 | 查看 GPU 显存占用 | 调低 batch,或换更小的模型如 yolov8n |
| 训练 loss 变成 NaN | 学习率过高、数据异常 | 查看训练日志和 loss 曲线 | 降低 lr0,检查数据集标注是否含空标签 |
| 打开图片后界面卡死 | 推理阻塞了主线程 | 观察 CPU/GPU 占用是否打满 | 把推理放到 QThread 中执行 |
| 摄像头打开失败 | 设备被占用或索引错误 | 单独测试cv2.VideoCapture(0) | 更换摄像头索引或释放被占用设备 |
| 检测不到花 | 置信度阈值过高或模型没训练好 | 降低 conf_thres 测试 | 重新训练或扩充数据 |
| 所有框都很小但不准确 | 输入图片尺寸过大,目标过小 | 调整 imgsz 或检查标注 | 重新标注大目标区域,或用高分辨率训练 |
| 加载 onnx 模型失败 | 缺少 onnxruntime 依赖 | 检查异常堆栈 | pip install onnxruntime |
| 训练集很小但验证集准确率极低 | 过拟合,数据多样性不够 | 观察 loss 曲线分离 | 增加数据增强,采集更多场景图片 |
排错的第一原则是:看完整报错日志,不要只看最后一行。尤其遇到依赖问题时,优先打印pip list确认包版本。
11. 工程化最佳实践
做一个能演示的 demo 很容易,做一个能稳定跑的系统才是工程问题。以下几点建议来自实际项目中的通用经验,值得在动手前就考虑进去。
11.1 用配置文件管理参数
不要把所有参数硬编码在main_window.py里。建议用一个config.py或 YAML 文件统一管理:
# 文件路径:config.py MODEL_PATH = "runs/flower/yolov8n_flower/weights/best.pt" CONF_THRES = 0.35 IOU_THRES = 0.45 SUPPORTED_EXTS = [".jpg", ".jpeg", ".png", ".bmp"]后续换模型、调阈值,只需要改配置,不需要动界面代码。
11.2 模型加载只做一次
FlowerDetector的初始化中已经加载了模型。千万不要在每张图片识别时都重新YOLO(weights),那会导致每次识别都重新加载权重,速度慢得无法接受。
11.3 做好异常捕获
识别图片时可能遇到损坏图片、非图片文件、模型与类别不匹配等情况。建议在推理线程中捕获异常,并把错误信息通过信号返回给界面:
def run(self): try: boxes, confs, clses = self.detector.detect_image(self.image) canvas = self.detector.draw_result(self.image, boxes, confs, clses) self.result_ready.emit(canvas) except Exception as exc: self.error_ready.emit(str(exc))11.4 模型导出与部署
如果目标机器没有 GPU,建议把 PyTorch 模型导出为 ONNX,推理速度会好很多:
# 文件路径:export.py from ultralytics import YOLO model = YOLO("runs/flower/yolov8n_flower/weights/best.pt") model.export(format="onnx", imgsz=640, simplify=True)导出后在FlowerDetector中加载.onnx文件即可。要注意的是,onnx 导出后类别名需要自己在推理代码中维护,onnx 模型本身不携带中文类别名。
11.5 数据集版本管理
训练数据一变,模型就得重训。建议给数据集打版本号,比如flower_v1、flower_v2,训练结果目录也用日期命名。这样你回看实验结果时,能知道当前模型是用什么数据、什么参数训练出来的。
11.6 生产环境安全边界
如果这个系统要部署给非技术人员使用,注意两点:
- 只开放必要的文件选择范围,避免用户误选非图片文件导致程序崩溃。
- 不对生产环境执行不可逆操作,比如不提供“删除模型”“覆盖数据集”这类功能。所有修改前先备份,所有权限按最小化原则来设置。
12. 总结与后续学习方向
到这里,一条完整的技术链路已经跑通了:数据集整理 → YOLOv8 训练 → 推理模块封装 → PySide6 桌面界面 → 摄像头实时检测 → ONNX 导出。这套思路不只适用于花卉识别,换成其他目标检测场景,比如水果识别、零件缺陷检测、车辆识别,只需要换数据集和类别配置,代码骨架完全复用。
如果你正在做毕业设计,我建议把重点放在“数据—训练—评估—部署”这条完整链路的后半段。很多同学做到了训练出 best.pt 就收手,但真正拉开差距的,是你能否在答辩现场打开一个能选图、能出结果、能实时检测的界面。界面本身不难,但模型封装、线程通信、异常处理这三件事,是检验你理解程度的分水岭。
接下来值得深入的方向有三个:
- 把模型从
yolov8n升级到yolov8s或yolov8m,对比精度和速度的权衡。 - 研究 ONNX 或 TensorRT 部署,把推理时间进一步压下来。
- 给系统加上批量识别文件夹的功能,并输出 CSV 格式的统计结果,这在真实项目中非常实用。
建议先把文章里的代码复制到本地跑通,再去叠加自己的功能。环境问题、数据问题、版本问题,基本都在上面的常见问题表里了,对照排查即可。这套“目标检测 + 桌面应用”的组合,会是你完整掌握深度学习落地流程的一个很好的开端。