花卉识别实战:基于YOLOv8的数据集构建与模型训练

花卉识别实战:基于YOLOv8的数据集构建与模型训练 简介图像分类是计算机视觉的基础任务之一其核心原理是通过卷积神经网络提取图像特征并映射到类别标签。在真实工程场景中数据质量与规模往往决定模型性能上限数据增强与迁移学习是缓解数据不足、提升泛化能力的有效手段。从人脸识别到植物识别图像分类技术已广泛应用于各行业。本文以花卉识别为例完整讲解基于YOLOv8分类模式的实战流程涵盖数据集构建、目录规范、训练调参与效果验证帮助读者快速落地一个可复现的图像分类项目。1. 我为什么要整理这套花卉识别项目它到底解决了什么前阵子有个做植物科普的朋友找我说他们想做一个拍张照片就知道花叫什么的小工具。我第一反应是这需求听上去简单但真落地会有不少细节问题。市面上现成的图像分类教程大多用MNIST手写数字、CIFAR-10这类通用数据集来演示跑完一遍你确实学会了流程可一到真实场景就抓瞎背景太乱、光照变化大、同类花不同品种长得完全不一样。手写数字和真实花卉的复杂度完全不在一个量级。于是我决定自己动手做一套相对完整的花卉识别项目从数据集构建、标注整理到训练源码编写、调参优化再到模型推理验证一条龙跑通。这个项目我规划成了一个小系列这篇就是系列的第一篇侧重点放在花卉数据集的完整整理流程 一套可直接复现的训练源码。标题里的01就是第一期的意思后面会继续做模型部署、移动端适配、类别扩充这些延伸内容。先说结论这个项目我做的是图像分类任务选定了5个常见花卉类别起步——雏菊(daisy)、蒲公英(dandelion)、玫瑰(rose)、向日葵(sunflower)、郁金香(tulip)。每类我收集了差不多600到800张图片共约3500张按8:2拆分成训练集和验证集。模型训练用的YOLOv8的分类模式配合迁移学习在一张入门级显卡上大概半小时就能完成训练最终验证集准确率在92%左右。这套配置对入门深度学习图像任务的人来说是一个性价比很高的参考案例。适合谁来参考我觉得有这几类人想做图像分类但不知道从哪找合适数据集的已经跑过MNIST但想换一个更有挑战性数据集的想用YOLOv8训练自己数据集但被各种教程绕晕的以及纯粹想收藏一份干净、可直接用的花卉图片集的朋友。在往下走之前你先记住我开头这句话这种东西真正花时间的不是训练而是数据。我这个项目里数据整理的时间大概是模型训练时间的五倍。你要是急着看训练代码可以直接跳到第3节但我强烈建议你先把第2节读完不然你很难理解为什么后期模型会有那些表现。2. 花卉图片集的采集与整理整个项目最容易被低估的环节2.1 数据从哪里来公开数据集为主自采补充为辅很多新手一上来就想用爬虫去图库网站抓图我劝你冷静。爬虫抓图有两个问题很难绕开一是版权很多图片素材有明确的使用授权限制拿来做开源项目会有隐患二是质量参差不齐网图里混着大量漫画图、插画图、带巨大水印的图你可能要花大量时间清洗得不偿失。我实际用下来最靠谱的方案是以公开数据集打底 少量自采图片补充。这次选择的是Kaggle上很有名的花卉数据集Flowers Recognition它包含了5个类别共4000多张图片标注已经做好了按目录分好类。这个数据集使用起来很方便省去了大量清洗和标注工作。同时为了提升模型的泛化能力我又自己拍了一部分不同天气、不同角度、不同背景的花卉照片补充到各个类别里主要是增加那些手机随手拍风格的图片让模型在真实场景下不至于太脆弱。Kaggle下载这个操作本身很简单登录后在对应数据集页面点Download就行。建议下载后用脚本解压并统计一下完整度确保图片没有损坏。我自己写了一个小脚本遍历所有图片并尝试用PIL打开打不开的当作坏图删除这个步骤虽然笨但能省掉训练时突然崩掉的麻烦。2.2 目录结构和文件命名规范数据集处理的第一步就是定目录规范。一个清晰、可预期的目录结构会让后面所有步骤都舒服很多。我这里用了这样的结构flowers/ ├── train/ │ ├── daisy/ │ │ ├── daisy_001.jpg │ │ ├── daisy_002.jpg │ │ └── ... │ ├── dandelion/ │ ├── rose/ │ ├── sunflower/ │ └── tulip/ ├── val/ │ ├── daisy/ │ ├── dandelion/ │ ├── rose/ │ ├── sunflower/ │ └── tulip/ └── test/ ├── daisy/ ├── dandelion/ ├── rose/ ├── sunflower/ └── tulip/train是训练集val是验证集test我单独留了一部分用来做最终效果实测。很多人把val和test混为一谈我建议从一开始就分开val用于训练过程中边训边评估、调超参test只用在最后验证绝不能在调参过程中反复看test的结果否则整个测试集就脏了。这是一个很重要的学术习惯虽然做工程有时候可以放松一点但保留这个习惯能防止你后期在模型效果评估上自欺欺人。至于文件命名我统一用类别名_序号的格式比如rose_023.jpg。这样无论在哪个目录下看到文件名都能立刻知道这张图属于哪个类方便排查问题。2.3 数据划分脚本怎么写我当时用了一个Python脚本做数据划分核心逻辑其实不复杂读取每个类别目录下的所有图片随机打乱后按比例分割。这里有一个小细节值得注意——随机种子一定要固定。否则你每次跑脚本划分出来的数据集都不一样不同实验之间的对比就不公平了。import os import random from pathlib import Path import shutil random.seed(42) # 固定随机种子保证可复现 source_dir Path(raw_flowers) target_dir Path(flowers) train_ratio 0.8 val_ratio 0.1 test_ratio 0.1 categories [p.name for p in source_dir.iterdir() if p.is_dir()] for category in categories: src_category_dir source_dir / category images list(src_category_dir.glob(*.jpg)) list(src_category_dir.glob(*.jpeg)) list(src_category_dir.glob(*.png)) random.shuffle(images) train_count int(len(images) * train_ratio) val_count int(len(images) * val_ratio) train_images images[:train_count] val_images images[train_count:train_count val_count] test_images images[train_count val_count:] for split_name, split_images in zip([train, val, test], [train_images, val_images, test_images]): dest_category_dir target_dir / split_name / category dest_category_dir.mkdir(parentsTrue, exist_okTrue) for img in split_images: dest_path dest_category_dir / f{category}_{int(img.stem.split(_)[-1]):03d}.jpg shutil.copy(img, dest_path)这个脚本跑完之后可以顺手统计一下每个类别的图片数量确保类别之间不要差距太大。类别严重不均衡会导致模型偏向于样本量大的类别后面是麻烦事。我这次每个类别大约是600~800张虽然有一点点差距但还在可接受范围内。2.4 数据增强给模型加练的关键手段图片集只有3500多张对深度学习模型来说不算多尤其是对参数量较大的模型很容易过拟合。因此在训练源码里我必须加上数据增强。数据增强的本质是对原始图做各种合理的变换让模型见到更多长得不一样但语义一样的图片相当于给模型加练。YOLOv8的分类训练内置了丰富的数据增强策略包括随机翻转、旋转、缩放、色彩抖动、随机擦除等。我实际对比过增强开与不开验证集准确率差距大概有8到10个点这个差距已经非常大。所以你在配置训练时一定要确认数据增强这部分是正常开启的。除了内置增强我还在代码里做了一个比较实用的操作把所有图片统一缩放到224x224并做归一化。分类模型对输入尺寸没有检测模型那么敏感224x224是目前最通用的分类输入尺寸既能保留足够细节训练开销又不大。如果你显存比较充裕也可以试384x384通常能再涨一点准确率但训练时间会增加近一倍。3. 训练源码的核心模块解析不只看懂还要会改3.1 为什么选YOLOv8而不是纯手工搭建CNN我知道很多教程会从零教你用PyTorch搭一个卷积神经网络来做分类比如写一个两层的CNN。这个过程对理解卷积原理确实有好处但如果你目标是快速得到一套能用的识别工具我建议直接用成熟框架。理由很简单自己从零搭的CNN在3500张图片上很难训练出高精度除非你的网络设计参考了ResNet、VGG这些经典结构而且训练时的取舍很多入门者踩坑概率很高。YOLOv8虽然是目标检测框架但它也提供了完整的分类训练模式也就是yolo classify子命令。它的分类模型基于改进的CSPDarkNet结构配合了一系列训练技巧比如自动数据增强、学习率调度、EMA等。这些技巧你自己手写可能要写几百行才能勉强追平而用YOLOv8一条命令就能调用。如果你更倾向直接用PyTorch的torchvision接口用ResNet50做迁移学习也是完全可行的效果同样不错。YOLOv8和ResNet50这条路线我都实测过下面说说两者对比对比项YOLOv8 classify自己写ResNet迁移学习代码量几条命令就能跑需要写数据加载、训练循环、评估逻辑默认数据增强内置开箱即用需要手动加Transforms训练日志自带图表和指标记录需要自己写Log调参灵活度偏中主要调超参完全可控上手门槛低中等我的结论如果只想快速做出可用模型优先YOLOv8如果想练手、想深入理解训练细节用ResNet迁移学习自己写。我这套源码最终以YOLOv8为主因为它的工程化程度高实际产出更稳定。3.2 准备训练配置文件YOLOv8用YAML文件来组织数据集信息它的分类模式也需要一个数据集配置文件。我把这个文件放在项目的configs/flowers.yaml里path: ./flowers train: images/train val: images/val test: images/test names: 0: daisy 1: dandelion 2: rose 3: sunflower 4: tulip注意path字段可以是绝对路径也可以是相对路径相对路径是相对于你执行训练命令的工作目录。如果路径写错了YOLOv8会提示找不到数据集文件这个错误很容易排查。names里的类别顺序一旦确定就尽量不要再改因为如果后面做目标检测训练类别顺序和标注文件是要对应的。3.3 训练命令与关键参数接下来就是重头戏启动训练。YOLOv8分类训练的命令非常简洁yolo classify train dataconfigs/flowers.yaml modelyolov8n-cls.pt epochs50 imgsz224 batch32 lr00.01我来解释一下这个命令里的几个关键参数以及为什么这么设modelyolov8n-cls.pt这是YOLOv8分类模型的预训练权重n代表nano版本模型最小、速度最快适合入门级GPU。用预训练权重做迁移学习模型一开始就具备通用视觉特征提取能力收敛速度和精度都比从零随机初始化强得多。如果你想追求更高精度可以换yolov8s-cls.pt或yolov8m-cls.pt但需要的显存和训练时间也相应增加。epochs50训练轮数。5类别的小数据量任务50轮已经足够。我实际观察到40轮左右验证集损失基本就不再下降了再多训练容易过拟合。当然你可以开早停(patience参数)让它在效果不再提升时自动停止。batch32批量大小。在单卡8G显存环境下224输入分辨率配batch32是没问题的。如果你的显存只有4G建议降到16显存充足的话可以加到64通常batch越大训练越稳定。lr00.01初始学习率。YOLOv8默认的初始学习率就是0.01配合它内置的余弦退火调度器使用。一般情况下不需要动这个值只有训练震荡时才考虑降低。训练启动后日志会实时打印每一轮的loss、准确率等信息。训练结束后模型权重会保存在runs/classify/train/weights/best.pt和last.pt。best.pt代表验证集上准确率最高的权重last.pt代表最后一轮权重正常推理应该用best.pt。这是最容易被新手忽略的细节。3.4 如何修改源码适配自己的数据YOLOv8的好处是大部分逻辑封装好了但你难免会有改代码的需求。比如我为了直观看到每个类别的分类效果在训练循环外面加了一段混淆矩阵绘制代码用sklearn的confusion_matrix实现。你如果要在自己的项目里改最需要关注的几个点类别数量变化后names字段要同步修改如果想用更高分辨率的图片提升精度imgsz参数可以调到320甚至448但要留意GPU显存如果训练过程中loss不降优先排查学习率而不是急着加网络层数。4. 训练实战记录完整跑一遍以及踩过的那些坑4.1 第一次训练的意外loss下降却出现过拟合我第一版训练直接用了默认参数50轮跑完日志看起来一切正常训练损失从1.6一路降到0.1训练准确率也接近99%。但一看验证集准确率只有82%而且从第30轮开始就一直卡在80%左右上不去。这就是典型的过拟合信号——训练集学得再好验证集泛化不出来。过拟合的本质是模型背下来了训练集中的特有模式却没有学到可泛化的规律。比如它可能记住了训练集里玫瑰图片大多有某种绿色背景一旦验证集里出现白色背景的玫瑰就容易判断错。怎么解决我依次做了三件事增强数据增强强度把YOLOv8的hsv_h、hsv_s、hsv_v等色彩增强参数适当调大让模型对颜色变化不敏感。对花卉而言颜色是重要特征但也不能过分依赖因为同一种花在不同光照下颜色差异本身就很大。调整学习率计划把初始学习率从0.01降到0.005让模型学得更保守一些。提前停止训练开启早停机制验证集loss连续10轮不下降就自动终止训练。经过这三项调整第二次训练的验证集准确率提升到了88%左右虽然还没达到我的目标但已经能看出方向是对的。4.2 类别混淆问题向日葵和蒲公英为什么分不清验证集准确率上来之后我开始逐个类别分析混淆矩阵发现两个问题向日葵(sunflower)和蒲公英(dandelion)之间存在明显混淆玫瑰(rose)和郁金香(tulip)也存在少量交叉。这个结果其实合理。向日葵和蒲公英都属于菊科植物花瓣是放射状排列从俯拍视角看形状非常相似尤其是还没完全盛开的状态。玫瑰和郁金香的混滑则主要是因为两者都有红色系品种颜色接近时模型容易抓错特征。我采取的应对策略很实际补充这些容易混淆类别的图片并且特意选择不同拍摄角度、不同生长状态、不同背景的照片。数据层面增加区分度比在模型层面做任何花哨操作都有效。这个逻辑和人的学习一样——你看的样本多了自然认得准。4.3 训练过程中的loss震荡排查训练到20轮左右时我遇到过一次loss震荡的现象loss曲线像心电图一样上下剧烈跳动训练准确率也跟着大起大落。这个问题我在不少群里也看到新人问过其实大多数情况下就是学习率太高了。YOLOv8默认使用余弦退火学习率初期学习率高如果batch设得又小梯度更新方向就可能不稳定导致loss震荡。我当时的解决办法是把初始学习率从0.01降到0.005同时把batch从16提升到32。batch提升后每个批次计算的梯度更接近真实梯度方向更新更平稳。loss震荡问题随即消失。这个地方值得泥注意调参时要先判断问题出在学习率太高还是batch太小这两者经常同时发生但优先调整学习率会更有效。我把这次排查过程整理成一个表格方便你以后遇到类似问题能快速定位现象可能原因调整方案loss下降慢学习率过低适当提高lr0loss震荡不收敛学习率过大降低lr0保持batch不变或增大batchtrain准确率远高于val准确率过拟合增强数据增强、加入Dropout、提前停止val loss先降后升训练轮数过多开启早停最佳权重在best.pt某些类别准确率显著低于其他类别样本量不足或特征相似补充该类图片做类别均衡处理4.4 最终运行的完整记录最终稳定版本的训练配置如下yolo classify train dataconfigs/flowers.yaml modelyolov8n-cls.pt epochs60 imgsz224 batch32 lr00.005 patience15训练过程大约耗时28分钟最终在验证集上的准确率为91.8%每个类别的识别结果分布如下类别准确率daisy94.2%dandelion90.1%rose93.0%sunflower88.5%tulip92.3%准确率最低的就是向日葵和蒲公英和混淆矩阵分析的一致。这也是我下一期计划重点优化的对象。5. 模型推理与效果验证训练完成只是第一步5.1 单张图片快速测试训练好后我先跑了几张测试集之外的图片用YOLOv8自带的预测命令yolo classify predict modelruns/classify/train/weights/best.pt sourcetest_images/unknown_flower.jpg输出会直接给出图片所属类别以及置信度。这个命令输出很直观你会在终端看到类似rose 0.92这样的结果意思是模型判断这张图是玫瑰置信度92%。但我提醒一下置信度高不代表一定对。尤其是模型对某个类别有偏好时置信度会虚高。我遇到过一张向日葵的图被模型以0.87的置信度判断成蒲公英当时就是类别混淆和数据偏差共同导致的。所以单张测试可以快速验证流程但真正评估模型好坏要看整体指标不是一两张感人案例。5.2 批量评估与量化指标为了在测试集上做一个彻底的效果检验我写了段脚本遍历测试集目录把所有图片的预测结果和真实标签做对比输出整体的准确率、每类别精确率和召回率import torch from pathlib import Path from ultralytics import YOLO model YOLO(runs/classify/train/weights/best.pt) test_dir Path(flowers/test) results {name: {correct: 0, total: 0} for name in [daisy, dandelion, rose, sunflower, tulip]} for category_dir in test_dir.iterdir(): if not category_dir.is_dir(): continue category category_dir.name for img_path in category_dir.glob(*.jpg): pred model.predict(str(img_path), verboseFalse) pred_label pred[0].names[pred[0].probs.top1] results[category][total] 1 if pred_label category: results[category][correct] 1 for category, stats in results.items(): acc stats[correct] / stats[total] * 100 print(f{category}: {acc:.1f}% ({stats[correct]}/{stats[total]}))整体准确率算下来是91.8%和训练时验证集数据很接近说明模型没有在验证集上作弊泛化能力是真实的。5.3 画混淆矩阵找出错得最离谱的图片除了准确率我还用matplotlib把混淆矩阵画了出来。这个方法强烈推荐你实验完都试一下它比只看一个准确率数字有用得多——它告诉你模型到底在哪些地方犯迷糊。我分析了错分图片后发现一个有意思的现象模型会把所有带棕色花蕊的黄色花都归到向日葵即使那其实是一朵金鸡菊会把所有花瓣密集重叠的粉红色花都归到玫瑰即使那是月季。这个现象说明模型在训练过程中学到了具有辨识度的颜色纹理组合并不能真正理解这是什么科属的植物。所以如果你后续要识别相似物种单纯靠增加数据可能还不够可能需要考虑引入更细粒度的分类模型或者用目标检测先把花朵主体框出来再分类——这样可以让分类网络忽略复杂背景的干扰。6. 这次项目的可复用经验和下一步打算6.1 如果你要复现这套项目我建议你用这个顺序很多人拿到一套训练源码后喜欢先跑命令跑完再回头问这模型干什么的数据哪来的。我的建议反过来先花一晚上把数据集的结构看明白再跑通验证集评估脚本最后才跑训练。因为训练过程本身是黑盒你要是连输入输出长什么样都不知道训练出了性能问题也无从排查。复现时最需要留意的几个点确认Python版本和YOLOv8版本兼容我在Python 3.9上用的ultralytics 8.0版本一切正常数据集的路径一定要和YAML配置里的path一致否则会报数据集为空使用预训练权重时注意模型后缀是-cls不是-det别下载成检测权重显存不够时优先降低batch不要优先降低imgsz因为输入尺寸对模型精度影响更大。6.2 这套数据集和源码还能怎么扩展当前这套5类花卉识别只是第一步我从一开始就留了扩展空间。后续可以做这几件事类别扩充从5类扩展到Oxford 102 Flowers的全部102个类别但训练时间和数据量都会线性上涨从分类升级到检测在分类数据集的基础上用LabelImg或X-AnyLabeling标注花朵位置转成YOLO检测格式训练一个目标检测模型这样即使图片里有多种花也能同时识别做细粒度识别只针对某一种花做品种级识别比如月季品种分类这种任务比大类分类更难需要更多专业数据部署到移动端或Web把best.pt转成ONNX再转成NCNN或TensorRT Lite格式就能跑在手机或浏览器里。我尤其推荐第二个方向。目标检测和图像分类在工程体验上完全是两码事分类只需要判断图里有什么检测需要定位花在哪里。你的场景如果是一张图里可能同时出现多种花那分类模型根本扛不住必须上检测。而做好检测的前提就是你已经有了一套熟悉的数据集和训练管线这套花卉数据刚好就是理想的练手对象。6.3 最后分享一个我踩过很多次才记住的教训数据整理阶段宁可慢不可乱。我最早做过六个类别的分类结果因为两个类别的图片没有仔细清洗混入了大量语义重复的图导致模型在训练时反复被两个长得一模一样的类别拉扯val准确率死活上不了70%。后来我花了两天把图片一张一张过了一遍筛掉了重复图、错类图、严重过曝或模糊的图重新训练后准确率直接跳到90%以上。数据集的脏程度决定了模型效果的上限这话不夸张。你往后做任何图像项目第一个该盯死的永远是数据。这一期的内容到这里就基本结束了。整套源码和数据集我自己还在持续改进下一步我打算试试在这个基础上跑目标检测训练看看能不能把单张图中的多花识别也做出来。如果你已经照这篇文章把训练跑通了遇到什么问题或者有想让我优先更新的方向都欢迎在评论区聊。本文还有配套的精品资源点击获取