基于YOLOv11的106种鲜花识别检测系统:从训练到部署全流程实战 简介这份资源面向计算机视觉研究人员、软件工程师及园艺与植物研究从业者提供一套基于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 就是 0rose 就是 1标签里写错一位模型学出来的就是错位的类别。# 校验标签文件检查 class_id 是否越界、坐标是否归一化 import os def check_labels(label_dir, nc106): 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, fclass_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 个类别名本身就累。参数说明nc106要和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, axis0) outputs sess.run(None, {input_name: img}) print(输出张量形状:, [o.shape for o in outputs])逻辑说明--simplify会调用 onnx-simplifier 精简计算图去掉冗余节点部署时更省资源但偶尔会引入兼容问题如果简化后推理结果异常去掉这个参数重导一次对比。验证脚本里预处理必须和训练时一致BGR 转 RGB、归一化到 0~1、HWC 转 CHW少一步结果就偏。输出张量形状通常是[1, 4nc, 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不指定会走错分支。评估结果会打印每个类别的 AP106 类里哪几类 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], labelbox_loss) axes[0, 0].plot(data[epoch], data[train/cls_loss], labelcls_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)], colorgreen) axes[0, 1].set_title(Precision) axes[0, 1].grid() axes[1, 0].plot(data[epoch], data[metrics/recall(B)], colorred) axes[1, 0].set_title(Recall) axes[1, 0].grid() axes[1, 1].plot(data[epoch], data[metrics/mAP50(B)], colororange) axes[1, 1].set_title(mAP50) axes[1, 1].grid() plt.tight_layout() plt.savefig(eval_curves.png, dpi150) plt.show()逻辑说明data.columns [c.strip() for c in data.columns]这行是踩坑后加的YOLOv11 导出的 csv 列名有时带前导空格直接data[epoch]会 KeyError。四个子图分别看 loss 收敛、precision 走势、recall 走势、mAP 走势。健康的训练是 loss 平滑下降、mAP 平滑上升并在后期趋平。如果 mAP 在某轮突然掉多半是学习率调度或数据里有脏样本。参数说明dpi150保证保存的图够清晰写报告能用。列名里的(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, pathruns/train/flower_exp/weights/best.pt, sourcelocal) 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上传图片检测, commandupload_image) btn.pack(pady30) root.mainloop()逻辑说明模型加载提到函数外面是血泪经验——原来写在detect_flowers里每点一次上传就重新加载一遍权重等十几秒用户体验极差。model.conf和model.iou是两个关键阈值conf 控制多确信才算检测到iou 控制重叠框合并。鲜花场景里花朵密集时iou 调低一点0.4能减少漏检但可能保留重复框。参数说明sourcelocal表示从本地加载模型不走网络。conf0.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 一直贴地现象训练跑了几十个 epochbox_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对象支持augmentTrue参数开启后推理时会自动做 TTA# 开启 TTA 推理召回通常有提升速度下降约 2-3 倍 results model(image, augmentTrue)逻辑说明augmentTrue会让模型对原图、翻转图、缩放图分别推理再 NMS 融合对小目标和遮挡目标召回提升明显。代价是推理时间增加实时场景要权衡。我一般只在离线批量检测时开GUI 交互场景保持关闭。验证 TTA 有没有用别凭感觉。固定一个测试集分别跑augmentFalse和augmentTrue对比 mAP50 和 recall。如果 recall 涨了但 precision 掉太多说明融合时 NMS 的 iou 阈值要调。这套对比我每次改推理参数都会走一遍不然改了等于没改纯靠玄学。从那以后我每次动推理参数都强制走一遍「固定测试集 对比 mAP/recall」的流程不靠单张图的感觉下结论。希望帮到你。本文还有配套的精品资源点击获取