☰
基于YOLOv11的106种鲜花识别检测系统:从训练到部署全流程实战
2026/10/6 11:08:07 网站建设 项目流程

简介:这份资源面向计算机视觉研究人员、软件工程师及园艺与植物研究从业者,提供一套基于YOLOv11的106种鲜花识别检测系统完整方案,帮助读者掌握从环境搭建、数据集准备到模型训练、导出与GUI落地的全流程技术细节。包内共1个docx文档,约40KB,内容涵盖项目介绍、数据集配置文件、模型训练与ONNX导出、性能评估、可视化指标曲线以及基于Tkinter的图形界面实现等模块,并附有完整代码整合与未来改进方向、注意事项等实践建议。目前已有137人学习,适合希望将YOLO系列模型应用于实际识别任务、需要一份可参照的端到端项目文档的读者,可据此快速理解模型配置、训练方法与评价指标,并迁移到其他物体识别场景中。

1. 从 106 类鲜花识别说起:这套 YOLOv11 检测系统到底能干什么

园艺场里做品种清点,最头疼的不是花不开,而是开得太像。月季和玫瑰、雏菊和洋甘菊,人眼扫一遍都得愣两秒,更别说让临时工去分。这套基于 YOLOv11 的 106 种鲜花识别检测系统,解决的就是这个场景:给一张图,框出每朵花的位置,同时告诉你它属于 106 个类别里的哪一个。它不是单纯的图像分类,而是目标检测——一张图里有多朵花、互相遮挡、大小不一,它都要能框住并分类。

项目本身是一份完整的工程包:训练脚本、数据集配置、ONNX 导出、评估指标可视化,外加一个 Tkinter 图形界面。适合两类人:一类是刚接触 YOLOv11、想找一个能跑通的完整项目练手的人;另一类是做园艺、农业、植物科研,需要一个可改可扩的识别底座的人。106 类这个量级不算小,类别多了之后,模型能不能分清近缘品种,才是真正要验证的东西。

2. 环境准备与数据集组织:把 106 类标签喂对

2.1 依赖安装与 YOLOv11 代码库拉取

环境这一步翻车的概率比想象中高,尤其是 torch 和 onnxruntime 的版本对不上。我一般会先建一个干净的虚拟环境,再按顺序装依赖,避免和系统里已有的包打架。

# 创建并激活虚拟环境,隔离依赖 python -m venv flower_env source flower_env/bin/activate # Windows 用 flower_env\Scripts\activate # 安装核心依赖,torch 系列版本要匹配 CUDA pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 pip install onnx onnxruntime opencv-python matplotlib pandas # 拉取 YOLOv11 代码库并安装依赖 git clone https://github.com/YourGitHubYOLOv11.git cd YourGitHubYOLOv11 pip install -r requirements.txt

逻辑说明:torch 单独用官方 index 装,是为了拿到和本机 CUDA 匹配的版本,混装容易出CUDA error: no kernel image is available。onnx 和 onnxruntime 放在后面装,是因为它们对 numpy 版本有要求,先装 torch 再装这两个,pip 的依赖解析会更稳。requirements.txt里通常锁了 ultralytics 的版本,别手动升级,升级后 API 可能变。

参数说明:--index-url指定的是 PyTorch 官方源,cu121 对应 CUDA 12.1,如果你本机是 CUDA 11.8,把 cu121 换成 cu118。Tkinter 在多数 Python 发行版里是自带的,如果import tkinter报错,Linux 下补sudo apt install python3-tk。

2.2 数据集目录结构与 YOLO 标签格式

106 类鲜花,数据来源常见的是 Oxford Flowers 102 再加自采数据补齐。Oxford Flowers 102 本身是分类数据集,要转成检测格式,得先有框。常见做法是用分类标签反推——整张图就是一朵花,框就是整图边界,再人工修一批多花图。目录结构必须严格按 YOLO 的约定来:

flower_data/ ├── images/ │ ├── train/ # 训练图,jpg/png │ ├── val/ # 验证图 │ └── test/ # 测试图 └── labels/ ├── train/ # 与 images/train 同名的 .txt ├── val/ └── test/

每个.txt标签文件里,一行代表一个目标,格式是class_id x_center y_center width height,后四个都是归一化到 0~1 的值。这里有个血泪经验:class_id必须从 0 开始,且和flower.yaml里names列表的下标严格对应。你写names: ["daisy", "rose", ...],那 daisy 就是 0,rose 就是 1,标签里写错一位,模型学出来的就是错位的类别。

# 校验标签文件:检查 class_id 是否越界、坐标是否归一化 import os def check_labels(label_dir, nc=106): bad = [] for fname in os.listdir(label_dir): if not fname.endswith('.txt'): continue with open(os.path.join(label_dir, fname)) as f: for line_no, line in enumerate(f, 1): parts = line.strip().split() if len(parts) != 5: bad.append((fname, line_no, '字段数不对')) continue cid = int(parts[0]) coords = [float(x) for x in parts[1:]] if cid < 0 or cid >= nc: bad.append((fname, line_no, f'class_id {cid} 越界')) if any(c < 0 or c > 1 for c in coords): bad.append((fname, line_no, '坐标未归一化')) return bad issues = check_labels('flower_data/labels/train') print(f'发现 {len(issues)} 处问题') for item in issues[:10]: print(item)

逻辑说明:训练前跑一遍这个校验,能挡掉大部分「训练 loss 不降」的玄学问题。字段数不对通常是标注工具导出格式选错了;class_id 越界多半是类别映射表没对齐;坐标没归一化是标注时用了像素值没除宽高。这三类问题在 106 类这种大类别数下特别容易出,因为人工核对 106 个类别名本身就累。

参数说明:nc=106要和flower.yaml里的nc一致,改类别数时两处一起改。label_dir指向 labels 下的 train/val/test 分别跑。

2.3 flower.yaml 配置文件的写法

配置文件是训练脚本和数据集之间的契约,写错一个字段,训练直接报错或者静默用错路径。

# flower.yaml train: ../flower_data/images/train val: ../flower_data/images/val test: ../flower_data/images/test nc: 106 names: 0: daisy 1: dandelion 2: rose # ... 依次列到 105 105: sunflower

逻辑说明:train/val用相对路径时,是相对于 YOLOv11 代码库根目录,不是相对于 yaml 文件本身,这点和很多人的直觉相反,路径写错会报Dataset not found。names用字典形式(0: daisy)比列表形式更不容易错位,因为键就是 class_id,肉眼能直接核对。106 个名字建议从标注工具的类别表直接导出,手敲必错。

参数说明:nc必须等于names的条目数,多一个少一个都会在训练启动时抛异常。如果只做验证不训练,test字段可以省,但train和val必须有。

3. 模型训练与 ONNX 导出:参数怎么设、导出怎么不翻车

3.1 训练命令与关键超参

训练这一步,命令本身不长,但每个参数都影响最终精度和训练时长。106 类、每类样本量不均的情况下,默认参数往往不够用。

# 基础训练命令 python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data flower.yaml \ --weights yolov11.pt \ --project runs/train \ --name flower_exp

逻辑说明:--weights yolov11.pt是从预训练权重起步,比从头训收敛快得多,106 类这种量级强烈建议用预训练。--project和--name决定输出目录,训练日志、权重、results.csv 都落在runs/train/flower_exp/下,后面评估和可视化都从这里取文件。

参数说明:--img 640是输入分辨率,鲜花检测里花朵在图中占比通常不大,640 是精度和速度的平衡点;如果小目标多(比如远景花田),可以提到 1280,但显存占用翻倍。--batch 16在 8G 显存上比较稳,显存不够就降到 8 或 4,别硬撑,OOM 中断后恢复训练麻烦。--epochs 100是起步值,看 results.csv 里 val loss 是否还在降,没降平就加到 150 或 200。

如果类别极不均衡(有的花几百张,有的几十张),可以在命令里加--cls 0.7提高分类损失权重,或者用--fraction控制采样比例。这些不是必选项,但类别数一多,长尾问题就冒出来,值得试。

3.2 训练过程监控与中断恢复

训练跑起来不是就不管了,results.csv 是唯一的黑匣子,得会看。

# 实时看训练日志尾部 tail -f runs/train/flower_exp/results.csv # 中断后恢复训练(YOLOv11 支持 last.pt 续训) python train.py \ --resume runs/train/flower_exp/weights/last.pt \ --data flower.yaml

逻辑说明:results.csv每行一个 epoch,列包括train/box_loss、train/cls_loss、metrics/precision、metrics/recall、metrics/mAP50等。判断训练是否健康,看 box_loss 和 cls_loss 是否整体下降,mAP50 是否上升。如果 loss 震荡剧烈,多半是学习率偏高或 batch 太小。--resume从 last.pt 续训,会保留优化器状态和 epoch 计数,比重新加载 best.pt 再训要连贯。

参数说明:last.pt是最近一个 epoch 的权重,best.pt是验证指标最好的权重。续训用 last.pt,推理部署用 best.pt,别搞混。如果训练中途改了数据集或类别数,不能 resume,必须重训,否则类别维度对不上。

3.3 导出 ONNX 与推理验证

ONNX 导出的意义是脱离 PyTorch 环境部署,比如 C++ 或边缘设备。导出本身一条命令,但导出后的验证不能省。

# 导出 ONNX,固定 batch 为 1 便于部署 python export.py \ --weights runs/train/flower_exp/weights/best.pt \ --img 640 \ --batch-size 1 \ --include onnx \ --simplify
# 用 onnxruntime 验证导出模型能否正常推理 import onnxruntime as ort import numpy as np import cv2 sess = ort.InferenceSession('runs/train/flower_exp/weights/best.onnx') input_name = sess.get_inputs()[0].name img = cv2.imread('test_flower.jpg') img = cv2.resize(img, (640, 640)) img = img[:, :, ::-1].transpose(2, 0, 1).astype(np.float32) / 255.0 img = np.expand_dims(img, axis=0) outputs = sess.run(None, {input_name: img}) print('输出张量形状:', [o.shape for o in outputs])

逻辑说明:--simplify会调用 onnx-simplifier 精简计算图,去掉冗余节点,部署时更省资源,但偶尔会引入兼容问题,如果简化后推理结果异常,去掉这个参数重导一次对比。验证脚本里预处理必须和训练时一致:BGR 转 RGB、归一化到 0~1、HWC 转 CHW,少一步结果就偏。输出张量形状通常是[1, 4+nc, 8400],4 是框坐标,nc 是类别数,8400 是候选框数量。

参数说明:--batch-size 1是部署常用设置,如果服务端要批量推理可以设大,但 ONNX 的动态 batch 需要导出时指定--dynamic。--img 640必须和训练时一致,否则精度掉得厉害。

4. 性能评估与指标可视化:mAP 之外还要看什么

4.1 val.py 评估与指标解读

训练完不评估,等于没训。评估命令跑一遍,拿到 mAP50、mAP50-95、precision、recall 四个核心数。

# 在验证集上评估 best.pt python val.py \ --weights runs/train/flower_exp/weights/best.pt \ --data flower.yaml \ --img 640 \ --task val

逻辑说明:mAP50是 IoU 阈值 0.5 时的平均精度,反映「框得差不多对」的能力;mAP50-95是 IoU 从 0.5 到 0.95 每隔 0.05 取一次的平均,更严格,反映框的精准度。鲜花检测里,如果 mAP50 高但 mAP50-95 低,说明框的位置不够准,可能是标注框偏大或偏小。precision高recall低,说明模型保守,漏检多;反过来则是误检多。

参数说明:--task val明确走验证流程,有些版本默认 task 是 train,不指定会走错分支。评估结果会打印每个类别的 AP,106 类里哪几类 AP 特别低,就是需要补数据或调参的重点。

4.2 用 results.csv 画四联评估曲线

训练日志里的 results.csv 是最原始的数据,画成图才能一眼看出趋势。项目里给的 matplotlib 脚本可以直接用,但列名要对准。

import matplotlib.pyplot as plt import pandas as pd data = pd.read_csv('runs/train/flower_exp/results.csv') # 去掉列名首尾空格,YOLOv11 导出的 csv 列名常带空格 data.columns = [c.strip() for c in data.columns] fig, axes = plt.subplots(2, 2, figsize=(12, 8)) axes[0, 0].plot(data['epoch'], data['train/box_loss'], label='box_loss') axes[0, 0].plot(data['epoch'], data['train/cls_loss'], label='cls_loss') axes[0, 0].set_title('Training Loss') axes[0, 0].legend() axes[0, 0].grid() axes[0, 1].plot(data['epoch'], data['metrics/precision(B)'], color='green') axes[0, 1].set_title('Precision') axes[0, 1].grid() axes[1, 0].plot(data['epoch'], data['metrics/recall(B)'], color='red') axes[1, 0].set_title('Recall') axes[1, 0].grid() axes[1, 1].plot(data['epoch'], data['metrics/mAP50(B)'], color='orange') axes[1, 1].set_title('mAP50') axes[1, 1].grid() plt.tight_layout() plt.savefig('eval_curves.png', dpi=150) plt.show()

逻辑说明:data.columns = [c.strip() for c in data.columns]这行是踩坑后加的,YOLOv11 导出的 csv 列名有时带前导空格,直接data['epoch']会 KeyError。四个子图分别看 loss 收敛、precision 走势、recall 走势、mAP 走势。健康的训练是 loss 平滑下降、mAP 平滑上升并在后期趋平。如果 mAP 在某轮突然掉,多半是学习率调度或数据里有脏样本。

参数说明:dpi=150保证保存的图够清晰,写报告能用。列名里的(B)表示 bounding box 指标,如果版本不同列名可能是metrics/mAP_0.5,以实际 csv 表头为准,别硬套。

4.3 混淆矩阵与类别级分析

106 类,光看总体 mAP 不够,得知道哪几类在互相混。

# 评估时生成混淆矩阵 # val.py 跑完后会在输出目录生成 confusion_matrix.png # 也可以用 pandas 读类别级 AP 做排序 import pandas as pd # 假设评估输出了 per-class AP 的 csv ap_data = pd.read_csv('runs/train/flower_exp/per_class_ap.csv') ap_data = ap_data.sort_values('ap50') print('AP 最低的 10 类:') print(ap_data.head(10))

逻辑说明:混淆矩阵能看出「玫瑰被认成月季」这类近缘混淆,这是 106 类鲜花识别最典型的问题。AP 最低的类别,优先补该类样本,或者检查该类标注是否有系统性错误。常见做法是把 AP 低于 0.3 的类别单独拉出来,人工看几十张预测结果,判断是数据问题还是模型容量问题。

参数说明:per-class AP 的导出方式因版本而异,有的版本在 val.py 输出里直接打印,有的需要加--verbose。如果没有现成 csv,可以从混淆矩阵图里读,或者改 val.py 加一段导出逻辑。

5. Tkinter GUI 与完整流程整合:从命令行到能点的界面

5.1 GUI 界面搭建与图像上传

命令行跑通之后,套一个 Tkinter 界面,非技术用户也能用。项目给的 GUI 代码是骨架,实际用要补几处。

import cv2 import tkinter as tk from tkinter import filedialog, messagebox import torch # 全局加载模型,避免每次检测都重新加载 model = torch.hub.load('YourGitHubYOLOv11', 'custom', path='runs/train/flower_exp/weights/best.pt', source='local') model.conf = 0.25 # 置信度阈值 model.iou = 0.45 # NMS IoU 阈值 def detect_flowers(image_path): image = cv2.imread(image_path) if image is None: messagebox.showerror('错误', '图像读取失败,检查路径') return results = model(image) output_image = results.render()[0] cv2.imshow('Flower Detection', output_image) cv2.waitKey(0) cv2.destroyAllWindows() def upload_image(): file_path = filedialog.askopenfilename( filetypes=[('Image files', '*.jpg;*.jpeg;*.png')]) if file_path: detect_flowers(file_path) root = tk.Tk() root.title('Flower Detection System') root.geometry('320x160') btn = tk.Button(root, text='上传图片检测', command=upload_image) btn.pack(pady=30) root.mainloop()

逻辑说明:模型加载提到函数外面,是血泪经验——原来写在detect_flowers里,每点一次上传就重新加载一遍权重,等十几秒,用户体验极差。model.conf和model.iou是两个关键阈值,conf 控制多确信才算检测到,iou 控制重叠框合并。鲜花场景里花朵密集时,iou 调低一点(0.4)能减少漏检,但可能保留重复框。

参数说明:source='local'表示从本地加载模型,不走网络。conf=0.25是通用起点,误检多就提到 0.4,漏检多就降到 0.15。results.render()[0]返回画好框的 numpy 数组,直接给 cv2.imshow 显示。

5.2 完整流程串联与目录约定

把训练、导出、评估、GUI 串成一条线,目录约定要统一,不然脚本之间找不到文件。

project/ ├── flower_data/ # 数据集 ├── flower.yaml # 数据配置 ├── YOLOv11/ # 代码库 │ ├── train.py │ ├── val.py │ ├── export.py │ └── runs/train/flower_exp/ │ ├── weights/best.pt │ ├── weights/best.onnx │ └── results.csv └── gui_app.py # GUI 入口

逻辑说明:所有输出集中在runs/train/flower_exp/下,GUI 和评估脚本都从这里取权重,路径写死一处,改的时候只改一处。常见做法是把flower_exp这个实验名做成变量,训练、评估、GUI 共用,避免手改路径漏改。

参数说明:如果换实验名,训练命令的--name、评估的--weights路径、GUI 里的path三处同步改。建议在项目根目录放一个config.py存这些路径常量,比散落在各脚本里强。

6. 避坑与常见问题排查:106 类项目里最容易翻车的五处

6.1 训练 loss 不降,mAP 一直贴地

现象:训练跑了几十个 epoch,box_loss 和 cls_loss 几乎不动,mAP50 在 0.01 附近晃。

原因:九成是标签和类别映射对不上。106 类里只要有一批标签的 class_id 整体偏移,模型就学不出有效分类。另一个可能是flower.yaml里nc和实际类别数不一致,模型输出维度错了。

解决:先跑 2.2 里的标签校验脚本,确认 class_id 范围和坐标归一化。再核对flower.yaml的nc和names条目数。如果都没问题,把学习率从默认 0.01 降到 0.001 试一个短训,看 loss 是否开始动。

6.2 导出 ONNX 后推理结果全乱

现象:PyTorch 下检测正常,导出 ONNX 后用 onnxruntime 跑,框的位置和类别全不对。

原因:预处理不一致。训练时 YOLOv11 内部做了 BGR→RGB、归一化、letterbox 缩放,导出后这些不在计算图里,需要手动补。少做 letterbox 会导致框坐标整体偏移。

解决:用 3.3 的验证脚本,严格按 BGR→RGB、/255、HWC→CHW 的顺序预处理。如果还不对,检查导出时--img是否和推理时 resize 的尺寸一致。letterbox 的补边逻辑要自己实现,不能简单 resize。

6.3 GUI 点击上传后卡死无响应

现象:Tkinter 界面点上传按钮后,窗口卡住,过很久才弹出检测结果,或者直接无响应。

原因:模型加载和推理都在主线程里跑,Tkinter 的主循环被阻塞。加上每次检测重新加载模型,双重卡顿。

解决:模型加载提到全局,只加载一次。推理如果还是慢,把detect_flowers放到独立线程里跑,用threading.Thread包一层,主线程只负责界面刷新。注意 cv2.imshow 在子线程里可能有问题,常见做法是把渲染结果传回主线程显示。

6.4 某些类别 AP 极低,其他类别正常

现象:总体 mAP50 有 0.7,但个别类别 AP 只有 0.1 甚至 0。

原因:长尾分布。106 类里总有几类样本特别少,模型见得太少学不会。或者这几类的标注质量差,框不准、漏标多。

解决:先看这几类的样本量,少于 100 张的考虑补数据或用数据增强(旋转、色彩抖动、 mosaic)。再看标注,随机抽 20 张人工核对。如果样本量实在补不上,可以在损失里给这些类加权,或者接受它们 AP 低,在应用层做后处理兜底。

6.5 训练中断后 resume 报类别数不匹配

现象:训练跑到一半中断,用--resume last.pt续训,报number of classes mismatch。

原因:中断后改了flower.yaml的nc或names,或者换了数据集。resume 要求模型结构和数据配置完全一致。

解决:确认flower.yaml和中断前一致。如果确实要改类别,不能 resume,只能从预训练权重重新训。养成习惯:训练启动前把flower.yaml备份一份,resume 时对比。

7. 进阶技巧:用置信度分层和 TTA 把召回再拉一截

基础流程跑通之后,真正拉开差距的是推理阶段的调优。106 类鲜花识别里,最影响实际可用性的是召回——漏掉一朵花,比认错品种更让人难受。我一般会在两个地方下手:置信度分层和测试时增强(TTA)。

置信度分层的意思是,不用一个全局 conf 阈值卡所有类别。做法是评估完后,从 per-class AP 数据里读出每类的 precision-recall 曲线,对召回优先的类别把 conf 调低,对误检多的类别调高。实现上可以在 GUI 的检测函数里,按类别 id 查一张阈值表:

# 按类别设置不同置信度阈值,召回优先的类调低 CONF_TABLE = {i: 0.25 for i in range(106)} CONF_TABLE[3] = 0.15 # 该类漏检多,降低阈值 CONF_TABLE[17] = 0.40 # 该类误检多,提高阈值 def detect_with_per_class_conf(image_path): image = cv2.imread(image_path) results = model(image) # 过滤:只保留置信度高于该类阈值的框 det = results.xyxy[0].cpu().numpy() keep = [d for d in det if d[4] >= CONF_TABLE.get(int(d[5]), 0.25)] # keep 里每行是 x1,y1,x2,y2,conf,cls return keep

逻辑说明:results.xyxy[0]拿到的是原始检测框,每行六个值。CONF_TABLE按类别 id 存阈值,get兜底默认 0.25。这样比全局阈值灵活,代价是要先做一轮评估拿到 per-class 数据。阈值表不用一次调到位,先按 AP 排序,AP 低的类降 0.05 试,看召回涨没涨、误检能不能接受。

TTA 是推理时对同一张图做多种变换(水平翻转、多尺度),把结果融合。YOLOv11 的model对象支持augment=True参数,开启后推理时会自动做 TTA:

# 开启 TTA 推理,召回通常有提升,速度下降约 2-3 倍 results = model(image, augment=True)

逻辑说明:augment=True会让模型对原图、翻转图、缩放图分别推理再 NMS 融合,对小目标和遮挡目标召回提升明显。代价是推理时间增加,实时场景要权衡。我一般只在离线批量检测时开,GUI 交互场景保持关闭。

验证 TTA 有没有用,别凭感觉。固定一个测试集,分别跑augment=False和augment=True,对比 mAP50 和 recall。如果 recall 涨了但 precision 掉太多,说明融合时 NMS 的 iou 阈值要调。这套对比我每次改推理参数都会走一遍,不然改了等于没改,纯靠玄学。

从那以后我每次动推理参数,都强制走一遍「固定测试集 + 对比 mAP/recall」的流程,不靠单张图的感觉下结论。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询