简介:这是一份基于Python与Shell的YOLOv5花卉识别模型源码,面向深度学习和计算机视觉初学者及进阶开发者,可用于快速搭建花卉智能检测与分类任务,也可作为实战项目学习目标检测的工程化实现。资源共102个文件,压缩包仅1.19MB,核心包括40个YAML配置(模型结构、训练超参与数据集定义)、32个Python源码(数据加载、模型训练/验证/推理)、11个YML模板,以及5个Shell自动化脚本、Dockerfile与Jupyter教程等,覆盖从环境配置、数据预处理到模型训练、容器化部署的完整链路。目前已有370人学习,适合希望借助YOLOv5落地目标检测方案、研究配置文件与训练流水线,或参考Docker部署与工程化目录组织的读者。该资源结构清晰、体量紧凑,既可用于课程设计或毕业设计参考,也能在此基础上进行二次开发,扩展更多花卉类别或应用场景。
1. 一个把“训练”和“运维”绑在一起的源码项目,凭什么值得你跑一遍
给你一个热门项目的源码,里面有Python训练的完整流程,也有Shell脚本负责批处理和自动备份,标题叫“基于Python与Shell语言的yolov5花卉识别模型设计源码”。如果你只把它当一份目标检测教程,你会漏掉一半价值。做花卉识别,YOLOv5只是那颗引擎,真正决定你晚上能不能睡安稳觉的,是Shell那半边——数据集准备、日志归档、断点续训、批量调参,全靠它兜底。
这东西解决的是两类人的问题:一是想用YOLOv5跑通自己的数据集、但被环境和坑劝退的新手;二是已经跑通但嫌训练过程太“手工作坊”、想脚本化的熟手。你不需要一张顶配显卡,也不需要买标注服务,一个小规模花卉数据集加一块普通GPU就能跑完闭环。我不会假装见过这份源码的每一行——我只按标题里锁死的技术路线,把“Python训练 + Shell编排”这个组合从头到尾拆给你看。
2. 为什么是YOLOv5而不是分类网络:先想清楚“识别”这两个字的重量
2.1 花卉识别到底要回答几个问题:分类、定位与计数
很多人提到花卉识别,第一反应是拿ResNet或者EfficientNet做个图像分类,判断“这是一朵玫瑰”。但如果你手里拿的是园区巡检拍回来的一整片花坛照片,里面同时有月季、雏菊和蒲公英,分类网络只能告诉你“最像的那一种”,告诉你每朵花在哪里、一共有几朵,它做不到。这时候你需要的是目标检测:输出每个目标的类别、置信度,以及一个边界框(bounding box)。这个从“图里有什么”到“图里有什么、在哪儿、有多大”的跨越,就是花卉识别项目必须用检测模型的原因。
YOLOv5在同类检测模型里胜出,靠的不是某个单项指标,而是综合性价比。它的网络结构是CSPDarknet骨干加PANet特征金字塔加YOLO Head,三者协同做到“一次前向推理直接输出全部框”。比起两阶段的Faster R-CNN,它在推理速度上快一个量级;比起同系列的YOLOv8、YOLOv9,它生态最成熟、教程最多、坑基本都被踩平了,尤其适合做源码级学习——你能轻松找到网络结构图、损失函数实现和训练日志的每一个细节。
2.2 Python在整个项目里的职责边界:训练、推理与评估
在这个标题的项目里,Python负责的是算法链路。从数据读取(DataLoader)、数据增强(Mosaic、MixUp等)到模型构建(Model)、损失计算和反向传播,再到验证集评估(mAP计算),全部由Python代码实现。你用python train.py启动训练,用python detect.py跑推理,用python val.py做验证,这三个入口文件本身就说明了项目的结构分层——训练、检测、验证三者解耦,你改数据增强策略不需要动推理逻辑,换评估指标也不用碰训练循环。
这里有个容易被忽略的细节:YOLOv5的Python代码对版本极其敏感。requirements.txt里锁定了torch版本、opencv版本、numpy版本,少看一个都可能在import torch时报出一串CUDA相关的红字。我见过最典型的一个翻车是:用户用系统自带的Python 3.11去跑YOLOv5的旧版本代码,结果torch装上了但torchvision版本不匹配,训练到第10个epoch直接段错误。后面我会给出具体的避坑方式,但你先记住大原则——千万不要用系统Python直接裸跑,一定要建虚拟环境。
2.3 Shell语言补的是什么位:编排、批处理与自动化
Shell在这个项目里干的活,在官方仓库里几乎找不到成体系的教程,但实际项目中却离不开。训练之前,你需要准备数据集目录结构、划分训练集和验证集、统计每张图片的尺寸和类别分布;训练之中,你可能需要每隔一段时间把日志归档、把当前最佳权重复制到备份目录;训练之后,你需要批量跑多组超参数实验,把每组的结果汇总成一张表格。这些操作如果每次用命令行手动做,第一周你会觉得没问题,第二周开始你就会漏记参数,第三周你就开始怀疑自己到底跑过哪几个组合了。
常见做法是用Shell脚本把这些操作编成可重复执行的任务。比如一个prepare_data.sh专门做数据集划分和标签检查,一个train_pipeline.sh负责串联训练、日志归档和权重备份,配合crontab还能做到定时训练和结果推送。这也是一个“能上手跑通”和“能持续迭代”之间的分水岭——前者只需要Python,后者必须让Shell进场。
3. 搭出可复现的环境:conda建虚拟环境、vscode调试与Shell做目录初始化
3.1 conda创建独立环境:为什么不用pip全局安装
不夸张地说,YOLOv5的90%报错都和环境有关。torch、torchvision、CUDA、cuDNN这四个东西的版本组合,每一组配对关系不匹配,训练就会以各种姿势崩溃。用conda创建虚拟环境,等于给自己吃了一颗后悔药——环境装坏了,删掉重建,两分钟回到干净状态。
conda create -n yolov5_flower python=3.8 -y conda activate yolov5_flower pip install torch==1.12.1 torchvision==0.13.1 --index-url https://download.pytorch.org/whl/cu113 git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt这段命令的逻辑是:先建一个名为yolov5_flower的Python 3.8环境并激活;然后安装指定版本的torch和torchvision——注意--index-url指向的是CUDA 11.3对应的PyTorch轮子包,这样能保证CPU和GPU两种模式都可用;最后克隆YOLOv5官仓并安装依赖。参数说明:Python版本不要选3.9以上,部分YOLOv5分支对旧代码兼容性不够好;cu113对应CUDA 11.3,如果你的显卡驱动版本较新,可以改成cu118或cu121,但不要高版本驱动配低版本轮子,会静默失败。
环境建好后,第一次跑python train.py请务必加上--device cpu先把流程走通一遍。这一步不是为了训练效果,是为了验证代码链路本身没有断裂。一个数据集的读取错误、一个标签格式问题,在GPU上跑会浪费你半小时才发现,CPU上几秒钟就会报错。
3.2 vscode远程调试:改代码的体验直接决定效率
训练代码跑起来之后,你一定会频繁修改数据集路径、改超参数、临时加打印。如果每次改完都用命令行重跑整个脚本,输出日志刷屏、错误定位靠眼睛找,效率极低。我的习惯是在vscode里配置Python解释器指向conda虚拟环境,然后直接用调试功能打断点看中间变量。
{ "python.defaultInterpreterPath": "/opt/conda/envs/yolov5_flower/bin/python", "python.terminal.activateEnvironment": true, "python.terminal.activateEnvInCurrentTerminal": true }这段是vscode的settings.json配置片段,核心作用是把默认解释器指向conda虚拟环境的Python路径,并且让终端自动激活该环境。这样你在vscode里新建终端、跑脚本、做调试,用的都是同一个环境,不会出现“终端里能import torch、调试器里却报ModuleNotFoundError”的经典分裂。参数说明:defaultInterpreterPath的路径按你的conda安装位置调整——Windows下通常在C:\Users\用户名\anaconda3\envs\yolov5_flower\python.exe,Linux下按实际conda info --envs的输出填。activateEnvInCurrentTerminal设为true之后,每次打开新终端都会自动执行conda activate yolov5_flower,省去手输的麻烦。
3.3 Shell脚本做目录初始化:数据集结构一步到位
YOLOv5对数据集目录结构有严格要求——images和labels下各自分出train和val子目录,图片和对应的txt标注文件名必须完全一致,否则训练时找不到标签。这个结构手敲容易错,敲错了训练日志里只会显示“0 labels”,不报错但结果全废。我用一个Shell脚本在每次新项目启动时自动生成目录骨架。
#!/bin/bash # init_dataset.sh - 初始化YOLOv5花卉数据集目录结构 PROJECT_NAME=$1 mkdir -p ${PROJECT_NAME}/{images/{train,val},labels/{train,val}} mkdir -p ${PROJECT_NAME}/backup touch ${PROJECT_NAME}/data.yaml echo "目录创建完成:${PROJECT_NAME}" find ${PROJECT_NAME} -type d | sort这个脚本的逻辑很直白:接受一个项目名称参数,用mkdir -p一次性创建嵌套目录,backup用来存放训练权重备份,touch生成一个空的data.yaml,最后用find列出生成的目录树供你核对。参数说明:${PROJECT_NAME}是位置参数$1,你在运行时写成bash init_dataset.sh flower_v1即可。{images/{train,val},labels/{train,val}}是Shell的花括号展开语法,等价于四条mkdir -p命令,省得写长。注意脚本执行前先加执行权限chmod +x init_dataset.sh,否则会报Permission denied——这是新手最容易碰到的第一个Shell坑。
这段脚本的价值在于:目录结构变成代码了。你换一台机器、换一个数据集,跑一遍脚本就得到完全一致的结构,不用脑子里记“当时我建过哪些文件夹”。这就是Shell在项目里的第一个落点。
4. 训练自己的花卉数据集:从收集图片到修改yaml配置再到调整超参数
4.1 数据集从哪来:公开数据集起步、自采数据补盲区
训练花卉识别模型,数据集的第一个来源是公开数据集。Oxford 102 Flowers这类经典花卉数据集包含102个类别的图像,适合做迁移学习的起点——用它的预训练权重在你的小规模数据上微调,比从零训练收敛快得多。但公开数据集有个天然缺陷:它的拍摄背景、角度和光照都偏向“标准照”,真到了园区、花坛、野外这种复杂场景,模型的泛化能力会明显掉链子。
所以更稳妥的策略是“公开数据打底、自采数据补盲”。你用手机拍不同时段、不同天气、不同角度的花卉照片,每类拍200到500张,重点拍那些背景杂乱的、被遮挡的、花和叶子混在一起的。这些小众但真实的样本,往往是决定模型能不能落地的关键。一个1000张图的精致数据集,不如2000张“丑图”管用,这就是数据多样性的价值。图片收集完成后,统一用python脚本做尺寸检查、去掉损坏图片、去除重复帧,再交给标注工具处理。
4.2 用labelImg标注并转换为YOLO格式:四个边界坑要留意
标注工具我用labelImg,轻量、无门槛、导出格式可控。打开一张图片,框出每一朵花,选择类别,保存——它会同时生成一个与图片同名的txt文件,内容格式为:class_id x_center y_center width height。这四个数值都是归一化后的比例值,范围在0到1之间,不是像素坐标。这里隐藏着四个高频踩坑点。
第一个坑,类别ID从0开始计数,不是从1开始。假设你的data.yaml里类别顺序是['rose', 'tulip', 'daisy'],那么rose的ID是0,tulip是1,daisy是2。标注时选错顺序,模型会学到错误的映射关系,推理时张冠李戴。第二个坑,x_center和y_center是边界框中心点的比例坐标,不是左上角坐标。手写标注文件时最容易把x_center写成x_min,导致框整体偏移。第三个坑,宽度和高度也是归一化值,等于框的像素宽度除以图片宽度,不是像素值。第四个坑,txt标注文件必须和图片同名同路径层次——images/train/a.jpg对应labels/train/a.txt,少任何一个都匹配不上。
import os def check_labels(img_dir, label_dir): """检查图片和标签文件是否一一对应,并检查标注坐标是否越界""" imgs = set(f.split('.')[0] for f in os.listdir(img_dir)) labels = set(f.split('.')[0] for f in os.listdir(label_dir)) missing = imgs - labels extra = labels - imgs if missing: print(f"缺少标签: {missing}") if extra: print(f"多余标签: {extra}") # 检查坐标是否在[0,1]范围内 for f in os.listdir(label_dir): with open(os.path.join(label_dir, f), 'r') as fp: for line in fp: parts = line.strip().split() if len(parts) != 5: print(f"格式错误: {f} -> {line}") try: vals = [float(v) for v in parts[1:]] if any(v < 0 or v > 1 for v in vals): print(f"坐标越界: {f} -> {line}") except ValueError: print(f"非数字内容: {f} -> {line}") print("检查完成")这段代码的核心作用是在训练前把标注文件做一道体检。先比对图片和标签的文件名集合,找出缺的或多余的;再逐行解析标注内容,确认每一行都有5个值、后4个值都是0到1之间的浮点数。参数说明:img_dir和label_dir分别传你的图片目录和标签目录。这个脚本跑一遍,能把大多数标注阶段埋下的雷提前引爆,而不是等到训练完看mAP才发现不对。这里有必要多说一句:不要拿这个脚本去检查那是因为没标注的空文件——那会有提示,但不算错,训练时YOLOv5会把空标签按背景处理。
4.3 修改data.yaml和模型yaml:关键参数逐个说清楚
训练前的第二个配置文件是data.yaml,它告诉YOLOv5三件事:训练集图片放哪、验证集图片放哪、一共有几个类别。内容示例如下:
# data.yaml - 花卉识别数据集配置 train: /home/user/flower_v1/images/train val: /home/user/flower_v1/images/val nc: 3 names: ['rose', 'tulip', 'daisy']train和val分别指向训练集和验证集的图片目录,而不是上一级目录,这是新手最容易搞错的地方——写成/home/user/flower_v1/images会让YOLOv5去找images/images/train,直接报路径不存在。nc是类别总数,必须和names列表长度一致;names的顺序就是标注文件里class_id的映射顺序,一旦定下来就不要中途改,否则之前的标注全废。
模型配置文件用默认的yolov5s.yaml起步就够,重点调这几个参:depth_multiple控制网络深度,width_multiple控制通道宽度。如果你只有一个中等规模的数据集,把这两个参数都调成0.5左右,得到一个极轻量的网络,训练速度快、不容易过拟合;数据集足够大再换回1.0的标准版。在这个项目的“设计源码”语境下,模型结构本身的改动不是重点——你是在用它做花卉识别,核心工作量在数据、参数和流程上。
4.4 训练命令的每个参数都在控制什么
跑训练是YOLOv5项目里最“仪式感”的一步,命令看起来不长,但每个参数都值得仔细确认:
python train.py --data data.yaml --cfg models/yolov5s.yaml --weights yolov5s.pt --epochs 200 --batch-size 16 --imgsz 640 --device 0 --patience 30 --project flower_v1 --name exp1 --cos-lr --multi-scale拆开看:--data指定你刚改好的数据集配置文件;--cfg指定模型结构配置文件;--weights是预训练权重路径,这里用yolov5s.pt会从官网自动下载,也可以换成你自己之前训练得到的best.pt做断点续训。--epochs设多少取决于你的数据量和显存——数据集小、类别少,200轮足够收敛,太多反而过拟合;--batch-size是单卡每次喂给模型的图片数,显存不够就降到8,不要硬撑。--imgsz训练图片尺寸,640是速度和精度的平衡点;--device是GPU编号,0代表第一张卡;--patience是早停机制——验证集mAP连续30轮不提升就自动停止,省时间。--cos-lr开启余弦退火学习率调度,收敛更平稳;--multi-scale开启多尺度训练,每10个batch随机改变输入尺寸,增强模型对目标大小变化的鲁棒性。
这些参数我不建议你一次性全开。第一次跑就用--epochs 50 --batch-size 8 --imgsz 640 --patience 20先验证流程,确认数据集没问题、loss在下降,再逐步加参数跑正式实验。用“能跑通”的配置去排查代码问题,用“优化后”的配置去刷精度,两件事不要混在一起做。
5. 用Shell把训练编排成流水线:批量调参、日志归档与断点续训
5.1 Shell的for循环在超参数搜索里的玩法
花卉识别的超参数搜索是个体力活。学习率、batch size、输入尺寸、锚框尺寸,每个组合测一轮,手跑会让人疯掉。Shell的for循环就是干这个的。
#!/bin/bash # hyper_search.sh - 遍历多组超参数组合并依次训练 for lr in 0.01 0.001 0.0001; do for bs in 8 16; do echo "========== 当前实验: lr=${lr}, batch=${bs} ==========" python train.py \ --data data.yaml \ --cfg models/yolov5s.yaml \ --weights yolov5s.pt \ --epochs 100 \ --batch-size ${bs} \ --lr0 ${lr} \ --project flower_v1 \ --name lr${lr}_bs${bs} done done这个脚本的核心逻辑是嵌套循环:外层遍历3个学习率,内层遍历2个batch size,一共跑6组实验。每个实验独立命名,日志和权重都归档在flower_v1/lr0.01_bs8这样带参数的目录下,不会互相覆盖。参数说明:--lr0是YOLOv5的初始学习率参数,默认0.01,花卉数据集偏小的话降到0.001更稳。--name参数决定实验子目录名,用lr${lr}_bs${bs}嵌入变量,比叫exp1有意义得多——三个月后回看,你能一眼知道这个实验跑的是什么。
说到for循环,很多人刚开始写Shell会踩一个语法坑:for lr in 0.01 0.001中间的分隔符是空格不是逗号,写成0.01,0.001会导致整个列表被当成一个元素。另外循环体里的变量引用一定要加${},虽然$lr也能写,但加上大括号能让变量名边界更清晰,尤其当变量后面紧跟着其他字符时。
5.2 用shift处理位置参数,把训练脚本变成通用工具
shift命令在Shell里操作位置参数——每次执行shift,所有$2变$1,$3变$2,以此类推,原来的$1直接消失。看起来简单,但在设计可复用训练脚本时,它是处理不定长参数的利器。
#!/bin/bash # train_generic.sh - 通用训练入口,支持任意数量参数 PROJECT_NAME=$1 shift EPOCHS=$1 shift EXTRA_ARGS="$@" echo "项目: ${PROJECT_NAME}, 训练轮数: ${EPOCHS}" echo "额外参数: ${EXTRA_ARGS}" python train.py \ --data ${PROJECT_NAME}/data.yaml \ --epochs ${EPOCHS} \ --project ${PROJECT_NAME} \ ${EXTRA_ARGS}这个脚本的用法是./train_generic.sh flower_v1 150 --batch-size 16 --device 0。第一轮shift把flower_v1弹出,剩下150 --batch-size 16 --device 0;第二轮shift把150弹出,剩下的全存在$@里传给训练脚本。参数说明:$@表示所有剩余参数,在双引号中会保留每个参数的原始边界,不会把带空格的参数拆碎。这样做的好处是:脚本本身不需要感知每一个YOLOv5的参数——新增参数、调整参数直接写在命令行末尾,脚本自动透传,不用改代码。
说实话,“不感知全部参数”这个设计思路,和YOLOv5官方仓库本身的解析方式是一脉相承的——调用方发起什么参数,底层就解析什么参数。你在Shell层做一层薄封装,后续换模型、换框架时,这层封装改动最小。
5.3 断点续训与权重备份:Shell里的后悔药
训练跑到一半,显存溢出、断电、手动Ctrl+C中断,这种事谁都碰过。YOLOv5在--resume参数上给了你一颗后悔药:只要训练目录下存在last.pt,运行python train.py --resume flower_v1/exp1/weights/last.pt就能从上次中断的epoch继续训练,学习率调度、优化器状态都会恢复。
但断点续训有个隐藏前提:你的训练日志目录必须完整。如果exp1目录被你手动删除过,last.pt不在了,那就回天乏术。所以我习惯用Shell脚本做一个自动备份策略——每次训练结束,把best.pt复制到独立备份目录,文件名带上训练完成时间。
#!/bin/bash # backup_weights.sh - 训练结束后自动备份最佳权重 TIMESTAMP=$(date +"%Y%m%d_%H%M%S") SOURCE="./flower_v1/exp1/weights/best.pt" BACKUP_DIR="./flower_v1/backup" mkdir -p ${BACKUP_DIR} cp ${SOURCE} ${BACKUP_DIR}/best_${TIMESTAMP}.pt echo "权重已备份: ${BACKUP_DIR}/best_${TIMESTAMP}.pt" # 同时生成一个说明文件,记录训练参数 cp "./flower_v1/exp1/opt.yaml" "${BACKUP_DIR}/opt_${TIMESTAMP}.yaml"脚本的逻辑是:取当前时间戳拼进文件名,避免覆盖——同一天跑多次实验,每次备份的文件名都不同;把opt.yaml一并复制,这个文件是YOLOv5训练结束后自动生成的训练参数快照,包含你当时设置的epochs、batch size、学习率等全部超参数。备份了它,等于备份了那次实验的“说明书”,三个月后你想复现当时的模型,直接看这个yaml就能还原。参数说明:date +"%Y%m%d_%H%M%S"是Shell里最常用的时间戳格式,%Y四位年份、%m两位月份、%d日期、%H小时、%M分钟、%S秒,拼接后用${}包围才能赋值给变量——注意不要写成TIMESTAMP=date +...或者TIMESTAMP= "...",前者是把命令输出当字符串处理,后者会报未预期标记的语法错误。
这一步做完,你的训练就不再有“跑完就丢”的心病。权重文件有了备份,参数有了快照,下次再想复现或者继续调优,全都有迹可循。
6. 训练实战避坑:从绿化带拍出来的照片到模型稳定收敛
6.1 数据集的“野”才是正常的——背景杂乱、遮挡频发与多目标混杂
第一类高频问题出现在数据层面。你在绿化带拍的照片,不会像数据集里那样干净——花朵被叶子遮挡是常事,一朵月季旁边可能紧挨着另一朵月季,背景里的绿色植物和花朵颜色极度相近。这时候标注的边界框如果太小或者太偏,模型学到的特征会混乱。
现象:训练开始后train loss在下降,但val mAP一直徘徊在0.2以下上不去;推理时一张图里同一个目标被输出好几个框,置信度都还不低。 原因:标注框框住了“花的一部分”,或者框内混入了太多背景,加上样本里遮挡比例过高,模型学会了用背景纹理凑数。 解决:检查标注框质量,重点看那些面积占比小于图片1%的框;对严重遮挡的图片做翻转变换,让模型学会在部分可见时仍能识别;如果某类样本的遮挡比例太高,单独补拍该类别的无遮挡照片拉平比例。
6.2 显存不足和batch-size的玄学关系
现象:RuntimeError: CUDA out of memory,进程直接被杀。 原因:训练图像尺寸越大、batch size越大,占用的显存越指数级上升。很多人一上来就设--batch-size 32 --imgsz 640,小显卡直接崩。 解决:先把batch-size降到8跑通,再逐步往上加——16不够加到更小而不是硬上。或者按比例缩放:显存8GB用--batch-size 8 --imgsz 640,16GB用--batch-size 16。还有一个偏方是开启--cache ram把数据缓存到内存而不是GPU,能从侧面缓解显存压力。
6.3 模型不收敛:学习率是个放大器,数据集才是病根
现象:loss曲线震荡剧烈,训练到最后loss不减反增,val mAP周期性跌到0。 原因:学习率太高或太低都有可能导致不收敛。太高会震荡越过最优点,太低则loss长时间不动像个死水塘。但更常见的原因在数据层面——标注文件里出现了大量空文件,或者类别ID和data.yaml对不上,导致模型学到“每张图都输出0个框”的摆烂策略。 解决:先用--epochs 20 --batch-size 8小跑一轮,观察train/box_loss是否在前5个epoch内明显下降;如果纹丝不动,立刻停下来检查标注——不是等训练结束再看。把学习率从0.01降到0.001,大多数花卉类小数据集都会变得温顺。
6.4 导出和部署时路径硬编码的坑
现象:训练时一切正常,按python detect.py --source test.jpg推理也正常,但换到部署脚本里加载模型就报FileNotFoundError。 原因:YOLOv5的权重文件在训练时记录了绝对路径,如果训练和部署在不同目录下,某些自定义模块会试图从原路径加载数据或配置。 解决:导出模型时用torchscript格式,它会把模型结构和权重全部打包进一个.pt文件,不依赖外部配置。我在导出时一般顺手把源码目录统一复制到部署机器上一个固定路径,从根源上把路径不一致问题消灭。如果不想管这些,直接导出成onnx格式,跨平台问题最少。
6.5 训练时CPU和GPU的结果对不上
现象:CPU上跑完一轮验证集mAP是0.8,GPU上同样配置却只有0.6,怀疑人生。 原因:浮点运算的精度、batch size不同导致的BatchNorm统计差异,以及多卡训练时的同步策略——都是常见的差距来源。 解决:验证时固定--batch-size为同一个值,不开--multi-scale;确定用python train.py --device 0单卡训练,避免分布式策略绕晕自己。另外YOLOv5默认训练和验证用的是同样的imgsz,如果你想验证时用更大尺寸,结果会比训练时好——这不是玄学,是正常现象。
7. 让模型从“能跑”到“能用”:部署到树莓派前的量化与验证清单
7.1 导出TorchScript与ONNX:选择适合场景的模型格式
训练结束得到best.pt,这只是第一步。best.pt依赖Python环境和PyTorch框架,不适合直接部署到边缘设备或嵌入式平台。我会按部署目标选择导出格式:
python export.py --weights flower_v1/exp1/weights/best.pt --img 640 --batch 1 --include torchscript onnx这段命令把训练好的权重导出为TorchScript和ONNX两种格式。TorchScript格式能在没有Python环境的情况下用libtorch加载推理,适合C++部署;ONNX格式可以转成TensorRT(NVIDIA GPU加速)、OpenVINO(Intel CPU加速)或NCNN(移动端部署)。参数说明:--img指定测试输入尺寸,必须和训练时的imgsz一致;--batch设为1,表示导出的模型一次性只能推理一张图——如果你想做批量推理可以调大,但边缘设备通常用1就够了。
这里有个实操经验:导出ONNX时如果报opset版本相关错误,在命令里加上--opset 11或--opset 12。ONNX的算子集版本和PyTorch版本有一个映射表,版本太新可能导致某些算子不被目标推理引擎支持,取一个保守版本能减少很多麻烦。
7.2 树莓派5上部署的推理效果:先调量化,再谈优化
树莓派5这类边缘设备算力和内存都有限,直接跑FP32的ONNX模型,推理一张640×640图片可能要几秒。部署前做两件事:第一,用ONNX Runtime的SessionOptions开启图优化;第二,把模型做INT8量化,推理速度能翻一倍以上。
import onnxruntime as ort import numpy as np # 加载ONNX模型,开启CPU图优化 sess_options = ort.SessionOptions() sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL sess = ort.InferenceSession("flower.onnx", sess_options, providers=["CPUExecutionProvider"]) # 构造输入数据,尺寸需与导出时一致 input_data = np.random.rand(1, 3, 640, 640).astype(np.float32) outputs = sess.run(None, {"images": input_data}) print(f"推理输出数量: {len(outputs)}, 每个输出的shape: {[o.shape for o in outputs]}")这段代码的核心是:用ONNX Runtime加载导出的模型,开启全部图优化级别,然后把一张随机构造的640×640图喂进去做前向推理。参数说明:providers参数显式指定CPU执行,避免在树莓派上乱找CUDA——虽然找不到也不会报错,但明确指定能减少初始化的意外;input_data里的名字images是YOLOv5导出ONNX时固定的输入节点名,如果你想确认实际的输入名,用ort.get_available_providers()的方式先查看模型结构。这段跑通后,你才算真正把模型从训练环境里解放出来了。
7.3 验证模型边界:光照、角度和遮挡的干扰测试
模型的普适性不能只看花正面无遮挡的照片。我会专门准备三类测试样本:逆光下拍摄的花卉(高曝光、花瓣发白边缘模糊)、俯视和仰视角度拍摄的(没有标准正视角度的清晰轮廓)、花瓣互相遮挡严重的(只能看到部分花心)。每一类跑一轮推理,统计漏检率和错检率。如果某个类别在逆光下漏检率超过三成,说明模型没有学好该类别在不同光照下的纹理特征——我会回去补数据,而不是调大阈值掩盖问题。
一个我经常用的快速验证技巧:用手机录像一段花坛的视频,抽帧后批量跑推理,然后统计每一帧的检测框数量变化。如果某个目标在连续帧中一会儿被检出、一会儿消失,说明模型对该目标的置信度在阈值附近波动,这时可以适当调低置信度阈值(默认0.25),或者检查训练数据里有没有类似的多帧样本。
给这个标题下的源码项目一个公道评价:它值得做。因为它的核心不是“识别花卉”这件事本身,而是“用Python训练、用Shell编排”这个技术组合的完整闭环。我自己的习惯是跑通一遍之后,一定把训练日志、权重备份目录和参数快照全部归档整理,标上日期——三个月后的你,会感谢现在养成这个习惯的自己。希望帮到你。
本文还有配套的精品资源,点击获取