☰
基于Python与Shell的YOLOv5花卉识别模型从训练到部署全流程
2026/10/5 11:27:53 网站建设 项目流程

简介:这是一套基于Python与Shell语言实现的YOLOv5花卉识别模型源码,专为计算机视觉入门者及深度学习开发者设计,解决花卉种类自动识别问题,可直接用于目标检测教学或实际分类场景。压缩包共102个文件,大小仅1.19MB,其中包含40个YAML配置、32个Python源文件、5个Shell脚本、11个YAML模板,以及Dockerfile、Jupyter Notebook等辅助内容;配置与脚本分别承担数据集参数定义、模型训练推理和自动化流程管理,结构清晰便于快速上手。已有371人学习下载。通过源码可掌握YOLOv5从环境搭建、数据配置到训练与推理的完整链路,理解PyTorch实现细节,并借助Shell脚本简化批处理与部署操作,适合作为毕业设计、竞赛或学习深度学习目标检测的参考工程。

1. 这个 YOLOv5 花卉识别项目,到底解决什么问题

带“基于 Python 与 Shell 语言的 yolov5 花卉识别模型设计源码”这个标题的项目,拆开看其实就两件事:用 Python 驱动 YOLOv5 完成训练和推理,用 Shell 脚本把环境检查、数据准备、训练入口和批量识别这些“脏活”串成一条不用反复手敲的流水线。花卉识别本身不是新问题,难点在于很多花卉形态接近、拍摄背景杂乱,模型容易把“绣球”和“菊花”混在一起,所以项目真正考验的不是会调用 detect.py,而是怎么把数据集、超参数和部署链路调到一个能稳定复现的效果。适合谁?想自己从零训练一个能用的花卉检测模型的学生、做园林植物数字化采集的工程师,以及需要快速给已有检测系统换“新物种”的算法岗同事。一个反直觉的结论先说在前面:换一版更干净的花卉标注数据,往往比重选一个更大的预训练模型带来的提升更明显。

2. 准备数据集:一朵花一个标签,比换预训练模型更管用

花卉识别模型的精度上限在数据标注阶段就被定死了。YOLOv5 自己带的 COCO 预训练权重不认识“月季”和“蔷薇”的区别,微调时能把相似类别分到多细,取决于你的标注里有没有把“同类花在不同光照、不同角度”的样子都框进去。我一般会在动手训练前把数据集准备当成一个独立工程来推进,而不是随手丢一批图片进训练脚本。

2.1 花卉图片从哪来:自采、公开花卉集与标注规范

三个来源按成本排序:公开花卉数据集只用下载解压,但类别分布和标注框质量不可控;网图爬取覆盖广,但清洗成本高到让你怀疑人生;团队或自己拍摄是质量最可控的,缺点是样本量天然受限。常见做法是“公开集打底 + 自采补缺口”,把公开集里容易混淆的类挑出来重点看,再针对你实际部署场景补拍。

标注规范比标注工具更要先定清楚。我给花卉项目定的边界是:一朵独立完整的花算一个目标,花苞和半开状态如果紧贴主花就并入同一框,不单独标;一簇花在画面里能明显分辨出单朵时逐朵标,分辨不出时按整簇标一个框。这个规则直接决定了模型学到的“花”的语义粒度,宁可少标不要错框,错框比漏标更伤模型。

数据目录按 YOLOv5 约定组织,之后训练参数可以少改两处:

datasets/ └── flower/ ├── images/ │ ├── train/ # 训练集图片,.jpg │ └── val/ # 验证集图片,.jpg └── labels/ ├── train/ # 与图片同名的 .txt 标签 └── val/

Shell 命令在这一步就能派上用场:用find统计图片数量、检查图片与标签是否一一对应,比在 Python 里写一堆 IO 逻辑来得更快,这也是标题里 Shell 语言最常见的落点。

2.2 把 VOC 或 JSON 标注转成 YOLO 格式:转换脚本与参数说明

YOLOv5 的标签格式是每张图片对应一个同名.txt,每一行是class x_center y_center width height,四个坐标全部归一化到 0~1。公开花卉数据集里很大一部分是 VOC 格式的 XML 标注,不转换直接用,train.py 大概率读不到标签。

import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, out_label_path, class_list): tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in class_list: continue cls_id = class_list.index(name) box = obj.find("bndbox") x_min = float(box.find("xmin").text) y_min = float(box.find("ymin").text) x_max = float(box.find("xmax").text) y_max = float(box.find("ymax").text) x_center = (x_min + x_max) / 2.0 / img_w y_center = (y_min + y_max) / 2.0 / img_h w = (x_max - x_min) / img_w h = (y_max - y_min) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") with open(out_label_path, "w", encoding="utf-8") as f: f.write("\n".join(lines)) if __name__ == "__main__": classes = ["daisy", "dandelion", "rose", "sunflower", "tulip"] xml_dir = "annotations/" out_dir = "labels/" os.makedirs(out_dir, exist_ok=True) for xml_name in os.listdir(xml_dir): if not xml_name.endswith(".xml"): continue stem = xml_name.replace(".xml", "") voc_to_yolo(os.path.join(xml_dir, xml_name), os.path.join(out_dir, stem + ".txt"), classes)

这段脚本的逻辑核心是先取原图宽高,再把绝对坐标换算成归一化坐标。参数说明里有两个关键点:classes的列表顺序就是训练时类别 id 的顺序,中途增删类别会导致已有训练权重分类头失效,必须重新训练;坐标保留 6 位小数在大多数场景下够用,但如果你做的是密集小目标花卉检测,建议保留到 8 位,减少归一化误差累加。

2.3 train/val 划分:用固定随机种子保证可复现

花卉数据里常见一个坑:按文件顺序前 80% 做 train、后 20% 做 val,结果某一种花全在验证集里,训练时模型根本没学过这一类。我习惯按类别做分层划分,保证每个类别在 train 和 val 里的比例一致。

import os import random from collections import defaultdict random.seed(42) # 固定随机种子,划分结果可复现 label_dir = "labels/train_val_all/" train_out = "train.txt" val_out = "val.txt" val_ratio = 0.2 class_to_files = defaultdict(list) for label_name in os.listdir(label_dir): if not label_name.endswith(".txt"): continue label_path = os.path.join(label_dir, label_name) with open(label_path, "r") as f: first_line = f.readline().strip() if not first_line: continue cls_id = first_line.split()[0] class_to_files[cls_id].append(label_name.replace(".txt", "")) # 图片文件名(不含后缀) train_lines, val_lines = [], [] for cls_id, stems in class_to_files.items(): random.shuffle(stems) split_idx = int(len(stems) * (1 - val_ratio)) for stem in stems[:split_idx]: train_lines.append(f"/absolute/path/to/images/{stem}.jpg") for stem in stems[split_idx:]: val_lines.append(f"/absolute/path/to/images/{stem}.jpg") with open(train_out, "w") as f: f.write("\n".join(train_lines)) with open(val_out, "w") as f: f.write("\n".join(val_lines))

这段代码的关键不是随机本身,而是“按类别”这三个字。class_to_files把同属一个类别的样本聚在一起,再各自按比例切分,从机制上避免了某一类整组失踪。训练时在 data 配置文件的train和val字段填这两个 txt 的路径即可。val_ratio对小型花卉数据集按 0.2 起步;如果总样本量不到 500 张,建议用 0.15,给训练多留一点数据。

3. 用 Python 和 Shell 把训练链路搭起来

数据集就位后,下一步是把 YOLOv5 的训练调用、超参传递和环境准备串成一条命令。这一步是 Shell 语言在项目里的核心价值:它不是一个“可选装饰”,而是把训练从“一行命令”变成“一套可复用的流程”。

3.1 环境与依赖:Python 版本先定住,torch 按设备选

YOLOv5 对 Python 版本的要求并不苛刻,但如果你环境里同时有 3.7 和 3.12,建议用虚拟环境固定在 3.8~3.10 之间,这个区间对着 torch 的官方兼容表基本无坑。安装依赖的基本原则是分两步:先装 torch 系列,再装 requirements.txt 里的其余包。顺序反了可能出现 numpy 版本冲突,因为 torch 的预编译包自带 numpy 约束,后装的 numpy 可能被强制回退。

python -m venv .venv source .venv/bin/activate pip install --upgrade pip # 先装 torch 和 torchvision,版本以你的 CUDA 版本为准 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install -r yolov5/requirements.txt

参数说明里有两点要讲透:--index-url指向 pytorch 官方 wheel 源,是为了让 torch 和 torchvision 的版本与 CUDA 库精确匹配,不要用默认 PyPI 源装 torch,装出来的多半是 CPU 版或版本错配。如果你没有独立显卡,去掉--index-url让 pip 安装 CPU 版 torch 反而更小更快,训练慢一点但不至于跑不起来。这一步和“python 安装 numpy 库”等热搜词关联的场景都在这里,解决的是同一个问题:环境装不对,后面全白搭。

3.2 训练参数怎么定:花卉场景下的超参数基线

YOLOv5 的train.py参数很多,但花卉识别这个任务真正要调的只有四五个。我给出一个经过验证的起点组合,再讲每个参数背后的道理。

参数建议值(小数据集优先)参数含义与选择理由
--epochs100小数据集 50 轮欠拟合,200 轮以上容易过拟合;100 轮是验证模型潜力最经济的区间
--batch-size16显存不够时优先减半,比直接降分辨率保留的信息更多
--imgsz640YOLOv5 默认训练尺寸,适配合大多数公开花卉图的分辨率
--workers4CPU 核心数的一半以上有时反而拖慢,4 是稳妥起点
--cache不指定小数据集没必要全部缓存进内存,浪费 RAM
--patience50验证指标 50 轮不提升就早停,省时间

--imgsz 640是一个值得较真的点:如果你的花卉图片里单朵花占画面比例很小,模型学的是“花丛”而不是“花”;这种情况下把--imgsz提到 960,或者对图片做滑窗切块再训练,效果往往比加大模型深度更直接。超参数调整的原则是小步试:先固定其他参数,只动--imgsz或--batch-size一个变量,否则多个变量同时变化时你根本不知道是哪个改动起的作用。

3.3 Shell 入口脚本:一条命令完成环境检查与启动训练

直接敲python train.py --data ...的问题在于:换一台机器、隔一个月回来跑,你可能忘了该激活哪个环境、数据集软链接有没有建好。把这些检查写进 Shell 脚本,训练就从“碰运气”变成“走流程”。

#!/bin/bash # run_train.sh set -e # 任何一步失败立即退出,避免带着错误往下跑 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" # 1. 激活虚拟环境(常见做法:脚本里显式指定 python 路径更稳) VENV_PATH="$SCRIPT_DIR/.venv/bin/python" if [ ! -x "$VENV_PATH" ]; then echo "[ERROR] 虚拟环境不存在,请先执行环境安装步骤" exit 1 fi # 2. 数据集软链接:存在且指向有效才继续 DATA_LINK="datasets/flower" if [ ! -d "$DATA_LINK/images/train" ]; then echo "[ERROR] 数据集目录不完整,检查 $DATA_LINK" exit 1 fi # 3. 启动训练,日志同时输出到终端和文件 $VENV_PATH train.py \ --data data/flowers.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --patience 50 \ 2>&1 | tee train_$(date +%Y%m%d_%H%M).log echo "[INFO] 训练结束,日志已保存"

脚本里两个容易被新手忽略的设计:set -e保证脚本在任何一步失败时立刻终止,否则数据集路径写错时脚本会一路空跑;BASH_SOURCE[0]让脚本可以在任意目录下被调用,自动切到脚本所在目录,避免“在别处执行时找不到相对路径”的经典问题。tee把训练输出同时送到终端和日志文件,后台跑长训练时随时可以tail -f看进度。这一步就是 Shell 语言在项目里不可替代的位置:它不是语言层面的炫技,而是把 Python 训练变成可审计、可重跑的流程。

4. 避坑:数据与 Shell 阶段最常见的翻车现场

这个项目最耗时间的往往不在训练本身,而是数据路径、标注格式和 Shell 环境这三类问题反复出现。下面五条是从实际踩坑里沉淀出来的,每条按“现象 → 原因 → 解决”展开。

4.1 训练时提示“No labels found in ...”

现象:训练脚本明明启动了,但前几个 epoch 的 box 损失不降,日志里还出现找不到标签的警告。

原因:最常见的是 labels 目录下的.txt文件名和 images 目录下的.jpg文件名不对应;其次是标签文件内容为空,YOLOv5 会跳过空标签。

解决:用一段短 Shell 命令做交叉核对,比肉眼挨个看快得多:

cd datasets/flower for img in images/train/*.jpg; do base=$(basename "$img" .jpg) if [ ! -f "labels/train/$base.txt" ]; then echo "缺少标签: $base" fi done

这段脚本遍历每张训练图片,检查同名标签是否存在,只输出缺失项。跑完再统计空标签:find labels/train -name "*.txt" -size 0,把空文件找出来重新标注或删除图片,两个动作做完再重启训练。

4.2 Shell 脚本在 Windows 上写好,传到 Linux 上就报错

现象:run_train.sh执行时报$'\r': command not found,或者脚本不按预期换行执行。

原因:Windows 编辑器默认用 CRLF(\r\n)换行,Linux 只认 LF;多出来的\r被当成命令的一部分。

解决:用sed批量转换,一行命令永久解决:

sed -i 's/\r$//' run_train.sh

这个命令把行尾的\r替换为空,-i直接写回原文件。之后在 Windows 上继续编辑,建议在编辑器里把行尾格式固定为 LF,否则每次传文件都要重复这步。

4.3 训练时 CUDA out of memory

现象:--batch 16在 8G 显存的显卡上报 OOM,弹出一大段 PyTorch 的显存栈信息。

原因:花卉数据里常有大尺寸图片,--imgsz 640时模型输入不是瓶颈,瓶颈在 batch 过大导致激活值缓存撑爆显存;也有可能是--workers 4的预加载把内存占满后间接拖慢显存释放。

解决:优先把 batch 降到 8 或 4,不是降--imgsz。显存不够的本质是数据并行太多,降 batch 是保精度最温和的手段;如果 batch 降到 2 还是不够,再考虑--imgsz 416起步。另一种方案是启用梯度累积,但 YOLOv5 的--nbs参数用来模拟更大的 batch,不是人人清楚,我一般建议小显存用户直接降 batch 更省心。

4.4 自定义数据集类别数没改,训练后全部分类到 COCO 类别

现象:模型训练完成,验证时预测框的位置全对,但类别标签全是人、车、狗等 COCO 类别。

原因:data/flowers.yaml里的nc(类别数)还写着 80,或者没写names列表。YOLOv5 加载自定义数据时如果nc不是 80,会自动重建分类头;但如果你沿用默认的coco128.yaml改了个文件名,忘记改nc和names,模型就会用 COCO 的语义去分类。

解决:检查数据配置文件里的三处字段:nc必须与你的花卉类别数一致;names按训练时的类别顺序列出来;train和val指向实际数据集路径。改完删掉runs/train/下的旧实验目录再重启训练,避免加载到缓存里的旧配置。

4.5 远程训练断连,训练进度没保存

现象:用 SSH 在服务器上启动训练,本地网络断开后远程进程被终止,前几十个 epoch 白跑。

原因:没有用后台会话工具,进程随 SSH 会话退出而被系统清理。

解决:让训练跑在screen或tmux会话里,断开重连后继续看日志。下面是用tmux挂训练的最简做法:

tmux new -s train_session bash run_train.sh # 按 Ctrl+b 再按 d 脱离会话,SSH 断开也不影响 # 重连后执行:tmux attach -t train_session

养成开训练就先进 tmux 的习惯后,网络波动基本不再影响实验进度。

5. 部署与验证:从 pt 模型到 Shell 批量推理

训练产出的best.pt只代表“模型在验证集上表现不错”,离真正能拿去识别一文件夹照片还有两步:模型格式转换和批量推理封装。这一章把这两步走完,你就拥有了一个可以脱离训练环境独立工作的花卉识别工具链。

5.1 导出模型:pt 转 ONNX 的参数取舍

需要转换格式的原因很简单:best.pt只能在 PyTorch 环境下跑,而实际部署目标往往是 RK 系列板子、昇腾设备或纯 CPU 服务器,这些环境不一定装得下 PyTorch,但普遍能跑 ONNX。我在嵌入式设备上做花卉识别时常用到rk3568这类平台,转换链路的起点都是 ONNX。

python export.py \ --weights runs/train/exp/weights/best.pt \ --include onnx \ --imgsz 640 \ --batch 1 \ --opset 12 \ --simplify

参数里值得说明的是--opset 12:这个算子集版本在 RKNN 和 OpenVINO 工具链里兼容性较好。--simplify会调用 onnx-simplifier 做图优化,去掉冗余节点,对嵌入式部署的推理速度有明显帮助。--batch 1是固定动态批次的坑:如果你想导出后支持动态输入尺寸,需要加--dynamic;但嵌入端推理我建议固定--imgsz 640,动态尺寸在 RKNN 转换时反而容易出问题。

5.2 Shell 批量推理:for 循环调用 detect 并整理结果

部署场景里最常见的诉求不是单张图片,而是“把这个目录下两百张花卉照片全部识别并把结果汇总成一张表”。这时候 Shell 的 for 循环配合 Python 推理脚本,正好发挥各自的长处。

#!/bin/bash # run_batch_infer.sh INPUT_DIR="$1" OUTPUT_DIR="results_$(date +%Y%m%d)" mkdir -p "$OUTPUT_DIR" for img in "$INPUT_DIR"/*.jpg; do base=$(basename "$img" .jpg) python detect.py \ --weights best.pt \ --source "$img" \ --project "$OUTPUT_DIR" \ --name "$base" \ --exist-ok \ --conf-thres 0.4 \ --iou-thres 0.45 \ --save-txt \ --save-conf done echo "[INFO] 推理完成,结果目录: $OUTPUT_DIR"

这个脚本的逻辑是逐张调用 detect.py 并把结果独立保存到以图片名命名的子目录里。为什么不用--source 整个目录一次跑完?因为单张调用时如果某一张图格式损坏,后面的图还能继续跑;一次跑整个目录时一张坏图会中断整个批次,排错成本高。--save-txt会输出每个检测框的类别、坐标和置信度,--save-conf把置信度写进 txt 而不是只画在图上,之后做指标统计直接解析 txt 即可。

5.3 后处理参数:置信度阈值和 NMS 阈值在花卉场景怎么调

“yolov5 后处理”在花卉识别里主要是两个阈值,参数含义不同,调整方向也不同。

参数默认值花卉场景建议判断依据
--conf-thres0.250.35~0.45conf 偏低会有大量误检;调高到 0.5 后漏检明显增加,就说明模型本身对该类置信度就低
--iou-thres0.450.4~0.5密集花朵相互遮挡时,iou 阈值高了会把两朵相邻花合并成一个框;低了会出现一朵花上叠三四个框

调阈值的血泪经验是:先看 mAP 再调 conf。mAP 高但实际场景误检多,说明验证集和部署场景分布有偏移,单纯调高 conf 治标不治本;mAP 本身就低,调阈值救不回来,得回炉训练。iou-thres在目标密集场景更值得花时间,花卉比一般物体更常见重叠遮挡,可以把 0.4、0.45、0.5 三档各跑一遍同一批测试图,用肉眼数一下漏检和重复框的数量再定。

6. 进阶:用一次多折验证和混淆矩阵确认模型真的能落地

训练完一个模型,最容易产生的错觉是“验证集 mAP 到 0.9,项目稳了”。实际部署时你会发现,验证集图片和训练集出自同一批数据分布,模型记住场景的能力比想象中强得多。我现在的习惯是:不管训练效果多好,都要额外做两件验证——多折交叉验证和野外新环境测试。

多折验证的做法是把数据分成 5 折,轮流用 4 折训练、1 折验证,最后把 5 次验证结果平均。这个操作在你的数据集规模只有几百张时尤其关键,它能暴露“模型只是记住了训练集里某个拍摄场景”的隐患,还能顺便给出一个更诚实的精度区间——比如 5 折的 mAP50 分别是 0.87、0.83、0.79、0.85、0.81,你就能知道浮动范围,而不是只盯着一次实验里的 0.87。

混淆矩阵是另一个不可替代的视角。跑一次python val.py --data data/flowers.yaml --weights best.pt --conf-thres 0.4 --iou-thres 0.45 --save-conf,然后去看runs/val/exp/confusion_matrix.png这张图。重点不是看对角线上的数字,而是找哪些类互相串:菊花和向日葵串,说明两者花盘形态相似;绣球和丁香串,说明模型在学“一团花的整体形状”而不是“花瓣纹理”。看到哪两类串,就去数据里补哪两类的边界样本,这个方向的收益比继续加 epochs 大得多。

最后一件事:真正拿到现场拍摄的一张新花照片,让它走进一个没在训练集里出现过的背景。花卉项目最常见的翻车就是训练集全是纯色背景的标本照,部署到花园里背景变成草地和栅栏,模型精度断崖式下降。我现在做花卉项目收尾前,都会强制自己留 20 张“野外照片”做冒烟测试,这 20 张不参与训练、不参与验证,只在最后推理时跑一遍,肉眼确认每朵花的框位和类别。

这套“多折验证 + 混淆矩阵 + 新场景冒烟测试”的组合,能在模型交付前把大多数隐性坑提前翻出来。我曾经在一个灌木花卉项目里为了赶进度跳过了多折验证,结果模型在第二周新采集的数据上 mAP 掉了将近 10 个点,从那次之后我就坚持所有模型上线前至少跑两折验证,这类翻车只值一次学费。希望这些流程和坑位能帮你把这个方向的模型做得更稳一点,也希望你的花卉识别项目不只在训练集上好看,而是在真实花园里也能认得出每一朵花。

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

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

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

立即咨询