ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

YOLOv5花卉识别实战:Python训练与Shell编排的完整工程方案

2026/9/24 0:34:51 拓冰建站 浏览量
YOLOv5花卉识别实战:Python训练与Shell编排的完整工程方案 简介这是一份基于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 python3.8 -y conda activate yolov5_flower pip install torch1.12.1 torchvision0.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.exeLinux下按实际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是0tulip是1daisy是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里的后悔药训练跑到一半显存溢出、断电、手动CtrlC中断这种事谁都碰过。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秒拼接后用${}包围才能赋值给变量——注意不要写成TIMESTAMPdate ...或者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 64016GB用--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.8GPU上同样配置却只有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格式可以转成TensorRTNVIDIA GPU加速、OpenVINOIntel 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编排”这个技术组合的完整闭环。我自己的习惯是跑通一遍之后一定把训练日志、权重备份目录和参数快照全部归档整理标上日期——三个月后的你会感谢现在养成这个习惯的自己。希望帮到你。本文还有配套的精品资源点击获取