花卉识别实战:基于YOLOv8的数据集构建与模型训练
2026/8/28 21:20:33 网站建设 项目流程

简介:图像分类是计算机视觉的基础任务之一,其核心原理是通过卷积神经网络提取图像特征并映射到类别标签。在真实工程场景中,数据质量与规模往往决定模型性能上限,数据增强与迁移学习是缓解数据不足、提升泛化能力的有效手段。从人脸识别到植物识别,图像分类技术已广泛应用于各行业。本文以花卉识别为例,完整讲解基于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(parents=True, exist_ok=True) 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 data=configs/flowers.yaml model=yolov8n-cls.pt epochs=50 imgsz=224 batch=32 lr0=0.01

我来解释一下这个命令里的几个关键参数,以及为什么这么设:

  • model=yolov8n-cls.pt:这是YOLOv8分类模型的预训练权重,n代表nano版本,模型最小、速度最快,适合入门级GPU。用预训练权重做迁移学习,模型一开始就具备通用视觉特征提取能力,收敛速度和精度都比从零随机初始化强得多。如果你想追求更高精度,可以换yolov8s-cls.ptyolov8m-cls.pt,但需要的显存和训练时间也相应增加。
  • epochs=50:训练轮数。5类别的小数据量任务,50轮已经足够。我实际观察到40轮左右验证集损失基本就不再下降了,再多训练容易过拟合。当然你可以开早停(patience参数)让它在效果不再提升时自动停止。
  • batch=32:批量大小。在单卡8G显存环境下,224输入分辨率配batch=32是没问题的。如果你的显存只有4G,建议降到16;显存充足的话可以加到64,通常batch越大训练越稳定。
  • lr0=0.01:初始学习率。YOLOv8默认的初始学习率就是0.01,配合它内置的余弦退火调度器使用。一般情况下不需要动这个值,只有训练震荡时才考虑降低。

训练启动后,日志会实时打印每一轮的loss、准确率等信息。训练结束后,模型权重会保存在runs/classify/train/weights/best.ptlast.ptbest.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%左右上不去。这就是典型的过拟合信号——训练集学得再好,验证集泛化不出来。

过拟合的本质是模型"背下来"了训练集中的特有模式,却没有学到可泛化的规律。比如它可能记住了训练集里玫瑰图片大多有某种绿色背景,一旦验证集里出现白色背景的玫瑰,就容易判断错。

怎么解决?我依次做了三件事:

  1. 增强数据增强强度:把YOLOv8的hsv_h、hsv_s、hsv_v等色彩增强参数适当调大,让模型对颜色变化不敏感。对花卉而言,颜色是重要特征,但也不能过分依赖,因为同一种花在不同光照下颜色差异本身就很大。
  2. 调整学习率计划:把初始学习率从0.01降到0.005,让模型学得更保守一些。
  3. 提前停止训练:开启早停机制,验证集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下降慢学习率过低适当提高lr0
loss震荡不收敛学习率过大降低lr0,保持batch不变或增大batch
train准确率远高于val准确率过拟合增强数据增强、加入Dropout、提前停止
val loss先降后升训练轮数过多开启早停,最佳权重在best.pt
某些类别准确率显著低于其他类别样本量不足或特征相似补充该类图片,做类别均衡处理

4.4 最终运行的完整记录

最终稳定版本的训练配置如下:

yolo classify train data=configs/flowers.yaml model=yolov8n-cls.pt epochs=60 imgsz=224 batch=32 lr0=0.005 patience=15

训练过程大约耗时28分钟,最终在验证集上的准确率为91.8%,每个类别的识别结果分布如下:

类别准确率
daisy94.2%
dandelion90.1%
rose93.0%
sunflower88.5%
tulip92.3%

准确率最低的就是向日葵和蒲公英,和混淆矩阵分析的一致。这也是我下一期计划重点优化的对象。

5. 模型推理与效果验证:训练完成只是第一步

5.1 单张图片快速测试

训练好后,我先跑了几张测试集之外的图片,用YOLOv8自带的预测命令:

yolo classify predict model=runs/classify/train/weights/best.pt source=test_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), verbose=False) 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 如果你要复现这套项目,我建议你用这个顺序

很多人拿到一套训练源码后喜欢先跑命令,跑完再回头问"这模型干什么的""数据哪来的"。我的建议反过来:先花一晚上把数据集的结构看明白,再跑通验证集评估脚本,最后才跑训练。因为训练过程本身是黑盒,你要是连输入输出长什么样都不知道,训练出了性能问题也无从排查。

复现时最需要留意的几个点:

  1. 确认Python版本和YOLOv8版本兼容,我在Python 3.9上用的ultralytics 8.0版本一切正常;
  2. 数据集的路径一定要和YAML配置里的path一致,否则会报数据集为空;
  3. 使用预训练权重时注意模型后缀是-cls,不是-det,别下载成检测权重;
  4. 显存不够时优先降低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%以上。

数据集的"脏"程度决定了模型效果的上限,这话不夸张。你往后做任何图像项目,第一个该盯死的永远是数据。

这一期的内容到这里就基本结束了。整套源码和数据集我自己还在持续改进,下一步我打算试试在这个基础上跑目标检测训练,看看能不能把单张图中的多花识别也做出来。如果你已经照这篇文章把训练跑通了,遇到什么问题,或者有想让我优先更新的方向,都欢迎在评论区聊。

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

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

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

立即咨询