基于YOLOv8的花卉识别系统:从数据集标注到界面部署全流程
简介基于YOLOv8的智能花卉识别系统是一套面向毕业设计或课程设计的完整目标检测方案整合了源码、可视化界面、完整数据集与部署教程适用于计算机视觉、人工智能等方向的在校生或开发者进行项目实践与功能扩展。压缩包共97个文件以70个Python脚本为核心涵盖模型训练、检测推理及可视化页面逻辑同时包含4个PyTorch模型权重文件、5个XML配置文件、2个文本说明及1个演示视频整体仅24.21MB结构清晰便于按模块调用。系统运行后可产出核心指标曲线、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果及标签分布图等训练评估图表帮助使用者直观理解YOLOv8的检测效果。目前已有87人学习下载资源内代码均经测试运行成功适合作为项目初期演示、课设作业或毕设答辩的可靠支撑。1. 为什么毕业设计都选「花卉识别」而不是「通用目标检测」每年毕设季都会有一批同学拿到同一个方向图像识别。但真正动手时你会发现通用目标检测的数据集动辄几十G、类别上百个训练一轮要几小时调参全靠玄学最后演示界面还是黑框框。而花卉识别正好卡在一个舒服的位置——类别数适中10到20类足够、单张图片目标小且清晰、数据量几百张就能训练出可演示的效果。这个「基于YOLOv8的智能花卉识别系统」zip包核心就是把「YOLOv8模型 可视化界面 完整数据集 部署教程」打包成一个解压就能跑的方案。它解决的不是论文创新点而是「如何在有限时间内交付一个能现场演示、能写进论文、评审老师看得懂的完整系统」。适合三类人做毕设的本科生、选课程设计的专科生、以及想快速验证YOLOv8落地流程但不想从零搓数据的开发者。接下来我会按「系统拆解 → 数据准备 → 模型训练 → 界面部署 → 问题排查」的顺序把这个方案完整拆开讲。2. 拆开zip看系统YOLOv8识别系统的三个必备模块拿到任何一个「YOLOv8 可视化界面」的毕设项目先不要急着跑按模块拆开看。这类系统不管包装成什么样底层都是三个部分界面层负责与人交互推理层负责加载模型做检测数据层负责给模型投喂训练样本。把这三个模块的关系理清楚后面部署和改代码才有方向。2.1 界面层为什么可视化界面选 PyQt5 而不是 Web 页面毕设答辩的现场环境通常没有外网评审老师习惯看到的也是桌面程序——打开一个窗口、点按钮选图片、界面弹出检测框。所以这类系统的界面层绝大多数是用PyQt5或Tkinter写的少数用Gradio做Web端但Web端容易被质疑工作量不足桌面端更稳妥。PyQt5比Tkinter好在三点文件对话框是现成的QLabel可以直接显示图片QThread做线程也不难写。Tkinter写起来更快但不适合展示检测框叠加效果因为画矩形框和文字标注要自己用Canvas绘代码量反而上去了。如果你拿到手的zip里界面是PyQt5写的后面想加「批量识别」「摄像头识别」这类功能扩展成本也比较低。界面层还要考虑一件事推理不能放在主线程。选完图片后模型推理可能要几百毫秒到几秒如果直接在主线程里跑界面会卡成「未响应」这是毕设演示时最容易翻车的地方。正确的做法是开一个QThread做推理结果通过信号传回主线程更新界面。2.2 推理层YOLOv8 相比之前版本改了什么训练和推理的核心是YOLOv8。这个版本相比YOLOv5最大的变化是把Anchor-Based换成了Anchor-Free也就是模型不再预先定义一堆不同尺寸的锚框而是直接预测目标中心点和宽高。好处是收敛更快、不用调anchor参数对新手更友好。另一个值得说的点是C2f模块替换了C3模块。C2f结构引入了更多梯度流分支特征提取能力更强同等的模型体积下精度略有提升。如果你的毕设论文需要写「为什么要选YOLOv8」这两个点就是最直接的论据。推理层还有一个隐藏选择是用PyTorch直接加载.pt权重做推理还是转换成ONNX后用onnxruntime推理。毕设项目里两种都有。PyTorch方案简单但要求本机Python版本和PyTorch版本匹配ONNX方案部署更稳而且后面可以接OpenVINO或RKNN做边缘设备加速。我的建议是如果zip里已经给了ONNX模型优先用ONNX跑界面省去一堆依赖冲突。2.3 数据层花卉数据集的文件结构长什么样拿到数据集先看目录结构不用急着打开每一张图片。YOLO格式的数据集结构非常固定常见的是这样flower_dataset/ ├── images/ │ ├── train/ # 训练图片jpg或png │ └── val/ # 验证图片用于每轮评估 ├── labels/ │ ├── train/ # 每个图片对应一个同名txt │ └── val/ ├── data.yaml # 数据集配置文件 └── classes.txt # 类别名清单部分项目放在data.yaml里labels目录里每个txt文件名和图片名一致内容是YOLO格式的标注每行一个目标五个数字分别是「类别ID 中心点x 中心点y 宽 高」前两个是相对图片宽高的比例值。比如0 0.5 0.5 0.3 0.3表示一张图上有一个类别0的花中心点在图片正中间宽高各占30%。拿到手先检查一件事类别数量是否和data.yaml里的nc字段一致。大部分花卉识别数据集的类别数在10到20之间常见的玫瑰、向日葵、郁金香、菊花等。如果你的毕设题目是「XX花卉识别」注意data.yaml里的names列表顺序必须和标注txt里的类别ID严格对应这个顺序错了模型会训练得「很努力但全错」而且很难排查。2.4 部署方式CPU机器能不能跑很多同学拿到zip第一反应是「我笔记本没有NVIDIA显卡能跑吗」。答案是能但有前提模型必须选最小的n或s版本推理尺寸降到416或320。YOLOv8n在CPU上用ONNX跑单张图片大约200到500毫秒演示场景完全够用。这类毕设zip自带的部署教程一般分两种CPU环境版和GPU环境版。CPU版通常用conda建一个Python 3.9环境然后装ultralytics、PyQt5、onnxruntime几个包就能跑。GPU版需要额外装CUDA和cuDNN这对没配过环境的人来说是第一个大坑——所以如果是第一次做先看教程标题里有没有「CPU版本」字样没有的话按CPU流程先跑通再说。3. 把数据集换成自己的花卉图片标注、转换与格式清洗zip自带的数据集通常够你完成毕设演示但如果你想写「本文构建了XX花卉数据集」这类内容就得自己标注一批图片。这章讲完整流程从哪里找图片、怎么标注、怎么转成YOLO格式、怎么校验标签每一步都给了可直接用的脚本。3.1 用 labelme 标注并转换成 YOLO 格式常见做法是用labelme标注因为它画多边形比labelImg画矩形更灵活花的花瓣边缘不规整时polygon能贴合得更好。但labelme保存的是JSON文件YOLO训练需要的是txt所以中间必须过一层转换脚本。标注的时候注意两点一是类别名要统一比如不要一会在JSON里写「rose」一会写「Rose」转换脚本会按类别名归类大小写不一致会被当成两个类二是每张图只标花的主体区域不要把花盆和叶子大片框进去否则模型学到的特征会偏向花盆颜色。下面这个转换脚本是把labelme的JSON转换成YOLO格式txt的标准写法import json import os def labelme_to_yolo(json_path, out_dir, class_map): 将labelme标注的JSON文件转换为YOLO格式的txt class_map: 类别名到整数ID的映射例如 {rose: 0, sunflower: 1} if not os.path.exists(out_dir): os.makedirs(out_dir) with open(json_path, r, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] lines [] for shape in data[shapes]: label shape[label] if label not in class_map: continue points shape[points] # 取多边形外接矩形把点坐标转为框坐标 xs [p[0] for p in points] ys [p[1] for p in points] xmin, xmax min(xs), max(xs) ymin, ymax min(ys), max(ys) # 归一化到0~1区间YOLO格式要求中心点坐标宽高 cx (xmin xmax) / 2 / img_w cy (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h # 越界保护把超出图片边界的值修正到边界内 cx max(0, min(1, cx)) cy max(0, min(1, cy)) w max(0, min(1, w)) h max(0, min(1, h)) lines.append(f{class_map[label]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) txt_name os.path.basename(json_path).replace(.json, .txt) with open(os.path.join(out_dir, txt_name), w, encodingutf-8) as f: f.write(\n.join(lines)) class_map {rose: 0, sunflower: 1, tulip: 2, daisy: 3} json_dir labelme_json/ out_dir labels/train/ for file in os.listdir(json_dir): if file.endswith(.json): labelme_to_yolo(os.path.join(json_dir, file), out_dir, class_map)这个脚本的关键逻辑从JSON里读图片宽高把多边形标注的外接矩形坐标换算成中心点坐标加宽高并做归一化和越界保护。cx max(0, min(1, 1))这行容易被忽略但实际标注时鼠标很容易点出图片边界坐标变成负数或大于1这会导致训练时loss变成NaN后面第五章还会再提。转换完成后把txt放到labels目录图片放到images目录名字保持一致数据集就算能用。建议转换后顺手抽查10张图对应的txt用OpenCV画框验证坐标是否正确不要直接开训练。3.2 按8:1:1划分数据集并生成 data.yaml数据集的划分比例不用太纠结训练集、验证集、测试集按8:1:1是常态。验证集用于每轮训练后评估mAP测试集是最后做最终效果验证。这个zip里如果已经帮你划好了你直接沿用如果自己建数据集写个小脚本按比例随机复制文件到对应目录import os import random import shutil random.seed(42) src_img all_images/ src_lbl all_labels/ out_base flower_dataset/ for subset, ratio in [(train, 0.8), (val, 0.1), (test, 0.1)]: os.makedirs(f{out_base}/images/{subset}, exist_okTrue) os.makedirs(f{out_base}/labels/{subset}, exist_okTrue) images os.listdir(src_img) random.shuffle(images) n_train int(len(images) * 0.8) n_val int(len(images) * 0.9) # 前80%为train再10%为val剩下为test for idx, img in enumerate(images): name os.path.splitext(img)[0] if idx n_train: subset train elif idx n_val: subset val else: subset test # 图片和同名标注文件一起复制 shutil.copy(os.path.join(src_img, img), f{out_base}/images/{subset}/{img}) lbl_file f{name}.txt if os.path.exists(os.path.join(src_lbl, lbl_file)): shutil.copy(os.path.join(src_lbl, lbl_file), f{out_base}/labels/{subset}/{lbl_file})划分完记得确认每个子集的labels里有内容的文件数别出现「train有100张图但只有80个txt」的情况。之后写data.yaml这个文件定义了数据路径、类别数和类别名是训练命令的入口# data.yaml 数据集配置文件 path: /home/user/flower_dataset # 数据集根目录的绝对路径 train: images/train # 相对path的训练图片目录 val: images/val # 相对path的验证图片目录 test: images/test # 可选测试集 nc: 4 # 类别总数必须和names长度一致 names: [rose, sunflower, tulip, daisy]特别注意path字段要写绝对路径。很多人在自己机器上训练时改了相对路径换到zip自带的教程里又用了别人的绝对路径导致训练一启动就报错找不到图片。第一次运行时建议用yolo check datadata.yaml验证一下路径配置是否正确。3.3 训练命令与关键参数设置训练用的是ultralytics库提供的命令行工具核心参数不多但每个都影响结果。典型训练命令如下yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs100 \ batch16 \ imgsz640 \ patience20 \ projectruns/train \ nameflower_exp \ device0逐行解释一下参数datadata.yaml指向刚才写好的数据集配置文件。modelyolov8n.pt用的是预训练权重作为起点n代表nano最小版适合CPU训练和演示。如果你的电脑有显卡可以换yolov8s.pt精度更高但训练时间翻倍。epochs100训练轮数花卉数据集小80到120轮足够。再多容易过拟合表现为验证集mAP不再涨。batch16批大小CPU训练建议8到16调太大会内存溢出。GPU显存8G以上可以用32。imgsz640输入图片缩放到640x640这是精度和速度的平衡点。patience20早停连续20轮验证集损失不下降就自动停止防止过度训练。device0用第一块GPUCPU机器改成devicecpu。训练开始后终端会实时打印每轮的box_loss、cls_loss、mAP等指标。训练结束后在runs/train/flower_exp/目录下会生成一堆文件最关键的是weights/best.pt验证集表现最好的权重、results.png损失曲线和mAP曲线和confusion_matrix.png混淆矩阵。这里提一下「yolov8画损失函数曲线图」这个常见需求很多同学以为要自己写脚本画loss曲线其实results.png已经自动包含box_loss、cls_loss、dfl_loss三条曲线的变化图直接拿到论文里用就行。如果觉得ultralytics默认的图不够好看可以把训练过程中保存的results.csv拖到Excel里自己再画一版。训练完先别急着关终端用这个命令做一次快速验证yolo detect predict modelruns/train/flower_exp/weights/best.pt \ sourceimages/val/000001.jpg \ conf0.5 \ saveTruesaveTrue会把画好框的预测图存到runs/detect/predict/打开看一眼如果框的位置基本贴合花瓣区域说明数据集和训练参数都是正常的。如果大面积漏检或误检先不要调参数回上一节检查标注质量——在数据量小的时候标注问题导致的效果差远比参数问题多。3.4 模型导出为 ONNX 格式为可视化界面做准备训练的权重是PyTorch格式界面程序不一定能直接加载。为了避免在写界面时还要配PyTorch环境通常会把best.pt导出成ONNX格式用onnxruntime来推理部署时少踩一半的坑。导出命令很简单yolo export modelruns/train/flower_exp/weights/best.pt formatonnx dynamicTruedynamicTrue表示不固定输入尺寸这样界面里可以随意传不同大小的图片。导出成功后会生成best.onnx体积只有几十MB。接下来第四章的界面推理直接加载这个ONNX文件不再依赖PyTorch。如果你的毕设论文里有一章要写「模型部署」导出ONNX这个步骤一定要写进去它是部署流程里的标准动作。再往深走还能继续转TensorRT或OpenVINO但那是加分项对毕设来说ONNX已经够用。4. 可视化界面落地推理线程、界面布局和一键启动界面的核心功能就三个选图片、显示检测结果、展示类别和置信度。不要一开始就想做视频识别、批量识别、统计图表先把这三件事跑通后面再加功能。这章给出界面代码的核心段并解释为什么要这样写。4.1 推理线程封装避免界面卡死的标准写法界面卡死是毕设演示翻车的第一大原因。原因几乎都是推理代码直接写在了按钮的槽函数里而PyQt的主线程负责绘制界面推理阻塞了事件循环界面就显示「未响应」。正确的做法是把推理放进QThread封装成一个独立的推理线程。下面代码是这种项目里常见的封装方式from PyQt5.QtCore import QThread, pyqtSignal import onnxruntime as ort import numpy as np import cv2 class InferThread(QThread): finished_signal pyqtSignal(object) # 推理完成后发信号回主线程 error_signal pyqtSignal(str) # 异常信息信号 def __init__(self, onnx_path, img_path, conf_thres0.5): super().__init__() self.onnx_path onnx_path self.img_path img_path self.conf_thres conf_thres self.session None def letterbox(self, img, new_shape(640, 640)): 缩放图片并填充灰边保持原始宽高比不变 h, w img.shape[:2] ratio min(new_shape[0] / h, new_shape[1] / w) new_w, new_h int(w * ratio), int(h * ratio) resized cv2.resize(img, (new_w, new_h)) # 上下左右补灰边到640x640 dw new_shape[1] - new_w dh new_shape[0] - new_h top, bottom dh // 2, dh - dh // 2 left, right dw // 2, dw - dw // 2 padded cv2.copyMakeBorder( resized, top, bottom, left, right, cv2.BORDER_CONSTANT, value(114, 114, 114) ) return padded, scale, pad def run(self): try: if self.session is None: self.session ort.InferenceSession(self.onnx_path) img cv2.imread(self.img_path) img_input, scale, pad self.letterbox(img) # HWC转CHW并归一化 img_data np.transpose(img_input, (2, 0, 1)).astype(np.float32) / 255.0 img_data np.expand_dims(img_data, axis0) input_name self.session.get_inputs()[0].name outputs self.session.run(None, {input_name: img_data}) # outputs里包含边界框、置信度、类别ID此处省略NMS后处理 self.finished_signal.emit(outputs) except Exception as e: self.error_signal.emit(str(e))这段代码的关键点有两个。letterbox函数解决的是「输入图片尺寸不统一」的问题直接把任意大小的图拉伸到640x640会导致目标变形、检测精度下降所以先等比缩放再补灰边。copyMakeBorder填充的灰色值用114这是YOLO训练时默认的填充色。InferenceSession只创建一次放到run方法外面会更优因为重复创建会拖慢整个推理流程。接口路径用self.session.get_inputs()[0].name动态获取不要写死不同版本的ONNX模型输入名可能不同。4.2 主窗口按钮、图片展示与检测框绘制主窗口布局一般是「左侧图片显示区 右侧参数区和控制按钮」。选图片用QFileDialog显示检测结果要自己叠加矩形框和标签。这个叠加不能直接修改原图然后丢给QLabel最好在内存中绘制后把结果转为QImage再显示界面才不会闪from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QPushButton, QFileDialog, QVBoxLayout, QWidget from PyQt5.QtGui import QImage, QPixmap, QPainter, QPen, QFont from PyQt5.QtCore import Qt class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(YOLOv8 花卉识别系统) self.setMinimumSize(900, 600) self.image_label QLabel(点击下方按钮选择图片) self.image_label.setAlignment(Qt.AlignCenter) self.image_label.setStyleSheet(background-color: #f0f0f0;) self.btn QPushButton(选择图片并识别) self.btn.clicked.connect(self.select_and_detect) layout QVBoxLayout() layout.addWidget(self.image_label) layout.addWidget(self.btn) container QWidget() container.setLayout(layout) self.setCentralWidget(container) def draw_result(self, img, boxes, labels, scores): 在原图上绘制检测框和标签输入是BGR的numpy数组 # 用QPixmap显示前先把检测结果画到图上 for (x1, y1, x2, y2), label, score in zip(boxes, labels, scores): cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) text f{label} {score:.2f} cv2.putText(img, text, (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) h, w, ch img.shape # BGR转RGB再显示 img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) qimg QImage(img_rgb.data, w, h, 3 * w, QImage.Format_RGB888) self.image_label.setPixmap( QPixmap.fromImage(qimg).scaled( self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation ) )这段代码里的cv2绘制是用OpenCV完成的比用QPainter画省事也更适合不懂Qt绘图的新手。注意QImage.Format_RGB888要求数据是RGB顺序而OpenCV默认是BGR所以必须做一次cvtColor否则显示出来颜色会诡异发蓝发红。4.3 置信度阈值与类别筛选界面要留两个可调参数界面控制区建议放两个参数置信度阈值和IoU阈值。尤其是置信度阈值现场演示时如果检测出一堆低分框直接把阈值从0.5调到0.7画面立刻干净很多。我一般会把这两个参数做成一个带滑块的控件而不是让用户手填数字。原因是滑块操作直观演示时不用停下来想而且能让评审老师觉得系统设计细致。滑块的变化实时传递给推理线程推理线程在下一次推理时使用新阈值。4.4 一键启动脚本环境装了就能跑一个完整可交付的zip最后一定要带一个启动脚本比如run.py或start.sh内容就是检查依赖、载入模型、启动窗口。常见做法是写一个requirements.txt和run.pyimport sys from PyQt5.QtWidgets import QApplication from main_window import MainWindow if __name__ __main__: app QApplication(sys.argv) window MainWindow() window.show() sys.exit(app.exec_())启动方式就是两步pip install -r requirements.txt python run.py如果zip里的部署教程是CPU版本requirements.txt里通常会包含ultralytics、onnxruntime、PyQt5、opencv-python这几个核心包。装完直接运行如果报错绝大多数问题集中在第五章要讲的那些坑里。5. 部署与运行避坑指南5 个高频翻车现场这章是血泪经验按「现象 → 原因 → 解决」写。无论你拿到的zip是哪一版这五个问题几乎绕不开。5.1 界面选完图片后就闪退运行界面程序点击按钮选择图片后程序直接崩溃退出终端里报QThread: Destroyed while thread is still running。原因是推理线程对象被当作局部变量在按钮槽函数里创建后没有保持引用Python垃圾回收直接销毁了还在运行的线程。解决把推理线程对象保存为窗口类的成员变量例如self.infer_thread InferThread(...)并在线程结束时把成员置空。def select_and_detect(self): # 保证旧线程先退出 if hasattr(self, infer_thread) and self.infer_thread.isRunning(): self.infer_thread.requestInterruption() self.infer_thread InferThread(self.onnx_path, self.img_path) self.infer_thread.finished_signal.connect(self.show_result) self.infer_thread.start()5.2 训练时loss变成nan或者mAP一直是0训练开始几轮后box_loss变成nan或者说mAP从头到尾都是0看着loss在降但预测结果全错。前者的原因是标注坐标越界或出现负数模型在计算损失时出现异常值后者绝大多数是类别ID和类名对不上或者data.yaml的names顺序和标注不一致。比如标注文件里的类别0是玫瑰data.yaml里names第一个写的是菊花。解决写脚本批量检查标签是否存在负数、大于1的值、空标签并核对类别映射后再训练。def validate_labels(label_dir, nc): 检查标签文件格式、越界坐标、类别ID是否超范围 bad_files [] for txt in os.listdir(label_dir): path os.path.join(label_dir, txt) with open(path) as f: lines f.readlines() for line in lines: parts line.strip().split() if len(parts) ! 5: bad_files.append((txt, 字段数不为5)) break cls_id int(parts[0]) if cls_id 0 or cls_id nc: bad_files.append((txt, f类别ID越界: {cls_id})) break cx, cy, w, h map(float, parts[1:]) if any(not (0 v 1) for v in [cx, cy, w, h]): bad_files.append((txt, 坐标越界)) break return bad_files还有一个小概率但很隐蔽的原因数据缓存。重复训练同一个数据集时ultralytics会缓存标签文件如果你中途改了标签缓存没更新会导致训练用的还是旧标注。遇到这种情况删除项目目录下的labels.cache文件再重新训练。5.3 CPU推理速度太慢界面卡几秒才出结果运行界面后点一张图片要等两三秒才显示框连滑块拖动都跟着卡。原因是模型用的是s或m版本而且输入尺寸保持640不变。CPU推理yolov8s要接近1秒m版本直接奔着3秒去。解决把模型换成yolov8n的ONNX版推理尺寸降到416或320。在ONNX推理代码中letterbox的new_shape参数从(640, 640)改成(416, 416)或(320, 320)速度能提升2到3倍花卉识别这种单目标小物体场景精度损失很小。如果想更快可以用Intel的OpenVINO替代纯ONNX推理。导出命令yolo export modelbest.pt formatopenvino会生成一个XML和一个BIN文件推理时用openvino runtime加载CPU推理速度通常是ONNX的两倍以上。但要注意OpenVINO虽然推理快对环境依赖更挑部署教程里如果没有相关说明建议毕设阶段不要主动引入把功能稳住比追性能更重要。5.4 PyInstaller 打包exe后运行提示缺少模型文件做好了界面想打包成exe交给同学或答辩老师用。打包后双击运行提示model.onnx not found或No such file or directory。原因是PyInstaller打包时只收集了Python代码项目里的best.onnx和data.yaml这些资源文件没有被打进去运行时报错。解决在打包命令里用--add-data显式把模型文件加进去并在代码里用sys._MEIPASS兼容打包和源码两种运行路径import sys import os def resource_path(relative_path): 兼容PyInstaller打包后的资源路径 base getattr(sys, _MEIPASS, os.path.abspath(.)) return os.path.join(base, relative_path) onnx_path resource_path(models/best.onnx)打包命令pyinstaller -w -F run.py \ --add-data models/best.onnx:models \ --add-data data.yaml:.-F是打包成单文件-w是不显示控制台窗口。加了--add-data之后模型文件会被塞进exe里运行时自动释放到临时目录sys._MEIPASS指向的就是这个临时目录。5.5 换了一个数据集训练类别标签和之前的混在一起第二次训练的时候拿着新的数据集按教程跑命令发现模型预测出来的类别名还是上一次的或者训练过程报错类别数不匹配。原因是data.yaml没改或者改了一部分但另一个地方还有旧的引用。常见的是上次生成过labels.cache里面存着旧数据集的类别信息新数据集的标签格式不一致时不会重新识别。解决删掉项目根目录下的datasets缓存文件和.cache文件确认输入命令里引用的data.yaml是当前数据集目录下的那个不是从别的路径复制来的。排查方法很简单打印data.yaml的内容看看path指向的是不是你正在用的图片所在目录。cat data.yaml # path: /home/user/flower_dataset # nc: 4 # names: [rose, sunflower, tulip, daisy]还有一种隐蔽情况标注txt里的类别ID是1到10但data.yaml里names只写了9个名字训练时报错index out of range。这种多半是标注时用了labelme的class_map没改全建议按5.2里的脚本再整体校验一遍。6. 最后再加一步批量验证和边缘部署值得做吗如果以上流程都跑通了你的毕设已经有「数据集构建、模型训练、系统设计、界面实现、打包部署」完整闭环。但想让系统看起来更完整还差最后一步批量验证模型效果并统计指标。界面程序是单张单张地测没法对整个验证集做系统性评估。建议写一个批量测试脚本遍历整个验证集统计各类别的平均精度和单张推理耗时import os import time from ultralytics import YOLO model YOLO(runs/train/flower_exp/weights/best.pt) val_dir flower_dataset/images/val total_time 0 detect_count 0 file_count 0 # 统计检测框数 # 每张图检测出目标数可作为系统效果的直观参考 for img_name in os.listdir(val_dir): img_path os.path.join(val_dir, img_name) t0 time.time() results model.predict(img_path, conf0.5, verboseFalse) dt time.time() - t0 total_time dt file_count 1 detect_count len(results[0].boxes) print(f共处理 {file_count} 张图) print(f平均耗时 {total_time / file_count * 1000:.1f} ms/张) print(f平均每张检出 {detect_count / file_count:.1f} 个目标)跑完这个统计你能拿到两个硬数据写进论文单张推理耗时的平均值以及验证集上平均每张图检出的目标数。加上训练时自动生成的mAP指标和混淆矩阵整个系统的实验章节就完整了。然后说一个方向问题如果你做完这个项目还有余力可以把模型往边缘设备上迁移比如rk3588这类带NPU的开发板。YOLOv8本身够轻量转成RKNN格式后可以跑实时流虽然配置环境又是一轮折腾但做出来就是「嵌入式 深度学习」双亮点。我个人的习惯是拿到任何这类zip包第一件事不是跑界面而是先跑通命令行推理。命令行能出结果说明环境、权重、数据集都没问题这时候再套界面层界面一旦有问题排查范围就缩小到界面代码本身。这个习惯救了我很多次希望你也能用上。希望帮到你。本文还有配套的精品资源点击获取