ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

YOLOv8目标检测实战:从数据集训练到智能花盆自动灌溉系统

YOLOv8目标检测实战:从数据集训练到智能花盆自动灌溉系统 简介一套基于YOLOv8的阳台花盆自动灌溉监测系统完整工程面向计算机视觉与深度学习方向的毕业设计、课程设计及初学者实践。项目源于个人毕业设计代码已通过运行验证包含模型训练与检测脚本、可视化操作界面、完整数据集和部署说明可输出标签分布、验证集预测、混淆矩阵、F1分数曲线与精确率-召回率曲线等关键图表便于答辩展示和效果复盘。压缩包共8个文件3个Python脚本分别承担训练、视频检测与可视化交互3个pt权重文件用于模型推理2个txt文件提供项目说明与README整体仅15.91MB部署门槛低。目前已有36人学习使用适合希望快速复现智能灌溉目标检测流程、完成课设或毕设实践的读者。1. 从“养死好几盆花”到“阳台自动灌溉”YOLOv8 这个毕设项目到底在解决什么问题养花的人都知道阳台植物的死法多半不是突然发生的而是“干了好几天没人发现”。这个基于 YOLOv8 的智能家居阳台花盆自动灌溉监测系统干的就是一件事用摄像头盯着花盆让模型识别叶片萎蔫、土壤发干这类缺水信号再联动电磁阀自动补水再用可视化界面把检测画面、传感器数值和历史记录摊给你看。它把目标检测、自动控制、界面开发三件事揉进一个能直接运行的项目里源码、数据集、部署教程都齐属于拿过来改一改就能当毕设或课程设计交的典型系统。适合三类人想做 CV 方向毕业设计的学生、家里有花想练 IoT 的爱好者、以及想从“跑通 YOLOv8 检测”走向“检测之后接控制”的入门者。下面按我做同类系统的顺序把数据准备、模型训练、界面联动、部署避坑逐条拆开讲。2. 系统拆解YOLOv8 在花盆监测里到底检测什么、数据从哪来2.1 检测目标不是“有没有花”而是叶片的缺水状态很多人第一次接触这个项目会以为 YOLOv8 在这里是拿来识别“这是什么花”的。其实不是。自动灌溉系统的核心决策依据是“这盆花现在缺不缺水”所以检测目标通常设计成四类healthy_leaf健康叶片、wilted_leaf萎蔫叶片、dry_soil干裂发白的土壤、flowerpot花盆轮廓用来定位是哪一个盆。有的方案还会加一类 empty_pot用来识别花被移走的情况避免对空盆浇水。为什么不当成纯传感器项目来做传感器当然要有但视觉检测的价值在于“提前量”。叶片萎蔫是植物自身的缺水反馈往往比土壤表面干透出现得更早而且一台摄像头能覆盖整个阳台不需要在每个花盆里埋探头。所以这类系统的常见做法是视觉检测做第一道判断土壤湿度传感器做第二道确认两道都满足才触发灌溉。YOLOv8 扮演的就是第一道视觉判断。分类的粒度直接影响后续控制逻辑。如果你只标一类 flower模型只能告诉你“这里有盆花”对灌溉毫无帮助拆成健康/萎蔫两类控制逻辑才能区分“该浇水”和“不用动”。这也是整个数据集标注的核心不是标“花在哪里”而是标“这盆花的状态是什么”。状态粒度切得越细模型训练难度越高但后续代码写起来越简单。2.2 数据集怎么凑自拍照片 公开资料 LabelMe 标注一条线标题里提到项目自带完整数据集这对毕设来说很关键。如果要做自己的版本或者想换成自己阳台的真实场景重训就得自己攒数据。我建议三种来源混用自拍晴天、阴天、傍晚各拍一轮同一盆花分别拍健康态和缺水态、公开植物叶片数据集补充形态多样性、网络图片补充背景多样性。自拍比例要占一半以上因为最终部署场景就是你自己的阳台模型必须见过这个背景。标注工具推荐 LabelMe它能画多边形适合叶片这种不规则轮廓。但 LabelMe 默认导出 JSON而 yolov8 训练需要的是 txt 格式的归一化坐标中间需要一个转换脚本。下面是我常用的转换方式import json import os from pathlib import Path def labelme_to_yolo(json_path, out_dir, class_names): with open(json_path, r, encodingutf-8) as f: data json.load(f) img_w, img_h data[imageWidth], data[imageHeight] txt_name Path(json_path).stem .txt lines [] for shape in data[shapes]: label shape[label] if label not in class_names: continue class_id class_names.index(label) points shape[points] # [[x1,y1],[x2,y2],...] xs [p[0] for p in points] ys [p[1] for p in points] x_min, x_max min(xs), max(xs) y_min, y_max min(ys), max(ys) box_w (x_max - x_min) / img_w box_h (y_max - y_min) / img_h x_center (x_min x_max) / 2.0 / img_w y_center (y_min y_max) / 2.0 / img_h lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(lines) \n) class_names [healthy_leaf, wilted_leaf, dry_soil, flowerpot] for jf in Path(labels_json).glob(*.json): labelme_to_yolo(str(jf), labels_txt, class_names)这段脚本做的事是读取 LabelMe 导出的 JSON把每个多边形标注转成外接矩形再按 YOLO 格式写成归一化坐标的 txt。三个注意点一是 class_names 的顺序必须和后续训练用的 data.yaml 里的 names 顺序完全一致否则类别全部错位训练出来的模型会拿健康叶片当萎蔫二是这里用的是外接矩形而不是多边形坐标对叶片检测精度够用只有做实例分割才需要保留多边形三是同一张图里多个目标就写多行每行一个目标。转换完建议随机抽查 20 张图用可视化脚本把框画回原图检查一遍这一步能拦住大部分错标问题。标注完成后按 8:1:1 划分训练集、验证集、测试集。划分时有个容易犯的错同一盆花的照片要尽量只进同一个集合否则模型相当于“见过答案”验证集指标虚高。2.3 YOLOv8 结构里和本系统最相关的三个部分选 YOLOv8 而不是 YOLOv5对这种项目来说主要是工程理由ultralytics 库把训练、验证、导出、推理全部封装成命令行对毕设来说少写大量底层代码而且它取消了 anchor 预设用 anchor-free 检测头不用针对花盆这种目标反复调 anchor 尺寸。真正影响效果的三个结构部分BackboneCSPDarknet负责提取叶片纹理、土壤颜色这类特征。叶片萎蔫在图像上表现为叶缘卷曲、颜色变暗这些纹理特征主要靠 Backbone 中后段的感受野捕捉。NeckPAN-FPN融合多尺度特征让模型同时看得到大片的健康叶片和小块干裂土壤。阳台摄像头视角下近处花盆占大半画面远处花盆可能只有几十个像素PAN-FPN 对这种尺度差异很关键。Detect Headanchor-free 解耦头分类和回归分支解耦分类分支判断“萎蔫还是健康”回归分支框出目标位置。解耦之后分类精度比 YOLOv5 的耦合头好一些对“健康 vs 萎蔫”这种相似类别尤其有用。这三部分训练时不用手写但理解它们能指导调参。比如发现远处的花盆漏检优先提高输入分辨率而不是改网络结构这是性价比最高的手段。3. 把模型训起来环境、关键参数和损失曲线怎么看3.1 环境配置Ubuntu 20.04 CPU 版能跑但你要知道代价标题强调“简单部署即可运行”通常意味着依赖已经打包好。但如果从零搭环境最常见的检索路径就是 ubuntu20.04 搭建 yolov8 环境 cpu 版本。GPU 版和 CPU 版的代码完全一样差别只在速度上。我的建议是按住下面这套走conda create -n yolov8 python3.9 -y conda activate yolov8 pip install ultralytics pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu说明一下机器有 NVIDIA 显卡比如 GTX 1660 Ti时把最后一行换成pip install torch torchvision让它自动装 CUDA 版。CPU 版不是不能训练是慢——几百张图的小数据集GPU 十几分钟跑完的轮次CPU 可能要两三个小时。我的建议是训练放 GPU推理部署阶段 CPU 也能凑合毕竟推理只是一次前向计算。装完后先验证环境再动手用自带权重跑一张图yolo predict modelyolov8n.pt sourcetest.jpg能输出检测结果说明链路通了。这一步卡住的常见原因只有两个conda 环境没激活导致 import 失败或者 torch 装成 CPU 版但代码里强行指定 cuda 设备报错会直接提示 CUDA not available。这类环境问题占了新手踩坑的一大半别急着怀疑自己的代码。3.2 训练命令与五个必调参数yolov8 训练自己的数据集训练自己数据集的命令入口统一最小可用的训练命令长这样yolo detect train \ dataflowerpot.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ project./runs \ nameflowerpot_train逐个说参数含义data 指向数据集配置文件里面写 train/val 路径和类别名列表model 用 yolov8n.pt 预训练权重做迁移学习绝不从零开始小数据集下收敛速度差距明显epochs 在这个项目里 100 轮足够花盆检测不是高难度任务跑太多轮反而过拟合imgsz 是输入分辨率训练和推理用同一尺寸效果最稳640 是精度和速度的折中batch 受显存限制GTX 1660 Ti 这种 6GB 显存跑 640 建议 16 以内爆显存就降到 8。lr0 是初始学习率数据量小时可以降到 0.005 防止震荡patience 是早停轮数连续 20 轮验证集指标不提升就自动停这是训练时的后悔药防止你人走了它还在空转烧电。data 配置文件里最容易踩坑的是 path 字段path: datasets/flowerpot # 相对路径项目根目录下 train: images/train val: images/val names: 0: healthy_leaf 1: wilted_leaf 2: dry_soil 3: flowerpot很多人写绝对路径比如/home/user/datasets/flowerpot换台机器就崩。我习惯在 yaml 里写相对路径先 cd 到项目根目录再执行训练命令这样整个项目文件夹拷到别的电脑不用改任何路径。这个习惯救了我很多次尤其是毕设答辩前临时换电脑演示的场景。3.3 损失函数曲线怎么画、怎么判断要不要停训练完之后最重要的就是看曲线。ultralytics 会自动在 runs 目录下生成 results.png里面包含 train/val 的 box_loss、cls_loss、dfl_loss 曲线和 precision、recall、mAP 曲线。但很多课程设计或毕设要求自己画图或者想看更细的变化可以读训练日志里的 CSV 自己画import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/flowerpot_train/results.csv) df.columns [c.strip() for c in df.columns] # 列名首尾可能带空格 plt.plot(df[epoch], df[train/cls_loss], labeltrain cls_loss) plt.plot(df[epoch], df[val/cls_loss], labelval cls_loss) plt.xlabel(epoch) plt.ylabel(loss) plt.legend() plt.title(Classification Loss Curve) plt.savefig(cls_loss_curve.png, dpi150)这段代码把每个 epoch 的分类损失画出来。判断标准就两句话train 和 val 的曲线都持续下降并趋于平缓说明模型在正常收敛train 还在降但 val 已经抬头就是过拟合信号。过拟合时优先回退到 val 最低点的权重——ultralytics 每个 epoch 都保存 best.pt直接用 best.pt 做推理不需要重新训练。另外注意 results.csv 的列名可能有空格读进来先 strip 再画不然报 KeyError这种小问题卡住过不少新手。4. 从模型到系统可视化界面、传感器与自动灌溉的联动4.1 可视化界面把检测画面、状态灯和手动按钮放进同一个窗口标题里的“可视化界面”是这个项目区别于纯算法 demo 的关键。常见做法是用 PySide6 做桌面窗口左边放摄像头实时画面画面上叠加着 YOLOv8 的检测框右边放土壤湿度读数、灌溉状态、历史记录表格和手动浇水按钮。选 PySide6 而不是 Tkinter 的理由是 QThread 处理摄像头这种持续 I/O 更顺手。核心线程结构import cv2 from PySide6.QtCore import QThread, Signal from ultralytics import YOLO class CameraThread(QThread): frame_ready Signal(object) # 把检测后的帧发射给主线程 def __init__(self, model_path): super().__init__() self.model YOLO(model_path) self.cap cv2.VideoCapture(0) self.running True def run(self): while self.running: ret, frame self.cap.read() if not ret: continue results self.model(frame, verboseFalse) annotated results[0].plot() self.frame_ready.emit(annotated) self.cap.release()关键设计是摄像头读取和模型推理全放在 QThread 的 run 方法里主线程只负责接收 Signal 去更新 QLabel 的图像。如果反过来把推理放进 UI 线程界面会卡成幻灯片几帧就假死。两个参数细节verboseFalse关掉控制台刷屏否则推理时终端疯狂打印检测日志results[0].plot()返回画好框的 BGR 图像转 QImage 时注意通道顺序RGB 和 BGR 弄反了画面会整体偏蓝。主窗口再用一个 QTimer 定期读传感器数据刷新状态栏和按钮状态间隔 2 秒即可别和推理线程抢 CPU。手动浇水按钮要加确认弹窗防止误触一下就哗哗浇水。4.2 自动灌溉逻辑连续帧确认 湿度传感器双重验证灌溉触发是最容易出事故的部分也是整个系统最像“黑匣子”的地方——你很难肉眼判断模型内部到底基于什么做出了浇水决定。所以控制逻辑要做得可解释、可兜底。我的做法是模型在连续 N 帧里检测到 wilted_leaf 且置信度超过阈值同时土壤湿度传感器读数低于阈值才向继电器发一次开阀信号。核心决策函数import time CONF_THRESH 0.6 REQUIRED_FRAMES 10 # 连续多少帧确认 DRY_SOIL_VALUE 30 # 土壤湿度阈值0~100越低越干 IRRIGATE_SECONDS 8 # 单次浇水时长 MIN_INTERVAL 300 # 两次浇水最小间隔秒 def decide_irrigation(detections, soil_moisture, state): wilted_detected any( d[class] wilted_leaf and d[conf] CONF_THRESH for d in detections ) now time.time() if wilted_detected: state[consecutive] 1 else: state[consecutive] 0 if (state[consecutive] REQUIRED_FRAMES and soil_moisture DRY_SOIL_VALUE and now - state[last_irrigate] MIN_INTERVAL): state[last_irrigate] now state[consecutive] 0 return True return False这个函数里三个参数是调出来的REQUIRED_FRAMES 取 10是因为 5 帧以下容易被叶片晃动误触发20 帧以上又让响应过于迟钝10 是折中DRY_SOIL_VALUE 取决于湿度传感器型号电容式和电阻式的标定值完全不一样必须先拿一杯干土和一杯饱和湿土实测出两个基准值再定阈值MIN_INTERVAL 300 秒是硬保险即使模型持续误报也不会短时间反复浇水把花淹死。state 是一个字典在循环里反复传入传出保存连续帧计数和上次浇水时间戳。继电器驱动这块要提醒树莓派或开发板通过 GPIO 控制继电器模块时代码里记得GPIO.setmode(GPIO.BCM)并且程序退出时清理 GPIO否则继电器会保持吸合状态水就一直流。这条在避坑章节我会再展开。4.3 数据存储SQLite 记录每一次检测和浇水毕设展示时“历史记录”是加分项。同一时刻的检测结果、传感器值、是否浇水建议落一条记录。单用户场景完全不需要 MySQLSQLite 一个文件就够整个项目文件夹拷走时数据库也跟着走。建表和写入示例import sqlite3 from datetime import datetime conn sqlite3.connect(irrigation.db) conn.execute(CREATE TABLE IF NOT EXISTS irrigation_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT, wilted_count INTEGER, soil_moisture REAL, irrigated INTEGER, note TEXT )) def log_event(wilted_count, moisture, irrigated, note): conn.execute( INSERT INTO irrigation_log (timestamp, wilted_count, soil_moisture, irrigated, note) VALUES (?, ?, ?, ?, ?), (datetime.now().isoformat(), wilted_count, moisture, int(irrigated), note) ) conn.commit()写入策略是不每帧写按检测决策结果写即可比如每秒执行一次决策并写一条否则数据库文件几小时就膨胀。查询历史直接 SELECT 按 timestamp 排序界面用 QTableWidget 渲染。一个值得养成的习惯是程序启动时先执行 CREATE TABLE IF NOT EXISTS这样即使数据库文件被误删程序也不会因为缺表直接崩。日志表里加一列 note用来记录“手动浇水”还是“自动浇水”后期统计误浇率全靠这个字段区分。5. 部署与避坑从开发机搬到阳台要过的五道坎5.1 白天训练好的模型晚上在阳台灯下就翻车现象训练时验证集 mAP50 有 0.9搬到阳台实测白天效果不错晚上一开阳台灯萎蔫叶片全部漏检还把窗帘褶皱误检成 dry_soil。原因数据集里绝大多数照片是白天自然光拍的夜晚暖色灯光下色温偏移大模型学到的颜色特征失效。花盆检测对颜色纹理敏感光照迁移性差是 CV 项目落地最常见也最隐蔽的翻车点属于典型的“训练时看不到、部署后才暴露”的问题。解决在数据集里补充傍晚、夜晚开灯、逆光三种场景的照片每类至少占 20%。如果不想重新标注太多图可以对现有图片做色彩增强暖色调偏移、降低亮度、加高斯噪声。ultralytics 默认带 HSV 增强但幅度覆盖不了灯光的剧烈色温偏移最可靠的还是真实补拍。补拍时注意同一盆花在灯光下的萎蔫状态别把健康叶片在暖光下的样子标成健康模型会学偏。5.2 USB 摄像头掉线导致整个程序假死现象程序跑了几个小时后画面冻结在最后一帧界面能点但检测不再更新日志里没有任何报错看起来像死机。原因USB 摄像头的 VideoCapture 在设备断开或系统休眠后不会自动恢复read() 要么一直阻塞要么持续返回 False而线程里没有做退出或重连处理。开发时摄像头一直插着不会暴露问题部署到阳台上走远距离 USB 延长线后供电不足导致掉线的概率大幅上升。解决给摄像头线程加连接状态检查和自动重连。每次 read() 返回 False 时释放 capsleep 2 秒后重新 VideoCapture(0)同时把异常也包进 try。硬件上USB 延长线选粗线径、带供电的型号或者用独立供电的 USB HUB。逻辑上还要加一层空检测保护重连期间没有任何检测结果控制模块不得触发灌溉否则掉线瞬间的空列表会被误判成“没有缺水信号”。5.3 CPU 推理太慢检测帧率只有 2 FPS现象用 CPU 跑 yolov8n640 分辨率下每帧耗时 400 到 500 毫秒画面肉眼可见地卡顿连续帧确认逻辑因为帧率太低十帧确认要等好几秒响应特别迟钝。原因CPU 算神经网络本来就不快640 输入对 CPU 属于偏大的负载而且很多人推理时开着默认的 verbose 打印每帧的检测结果刷屏本身又拖慢一截。GTX 1660 Ti 跑这个模型能到 30 FPS 以上但部署到没有显卡的旧笔记本上就原形毕露。解决三个手段叠加。推理输入尺寸降到 320 或 416牺牲一点小目标精度换速度对阳台这种近距离场景影响不大推理时把 verbose 关掉最有效的是隔帧检测——检测帧率 3 FPS 其实完全够用叶片状态变化是以分钟为单位的没必要 30 FPS 全跑。如果要部署到 rk3588 这类边缘设备导出成 RKNN 格式做硬件加速桌面 CPU 则用 OpenVINO 导出速度能再翻一倍。具体导出命令在最后一章给。5.4 数据集背景太干净换到真实阳台精度骤降现象训练集是在室内纯色背景拍的验证集 mAP 很高拿到真实阳台后误检率飙升花盆边缘、地砖反光、栏杆影子都被框成目标。原因数据分布漂移。训练集背景单一模型偷懒学了“背景区分”而不是“叶片特征”一换环境就露馅。这是数据集质量里最要命的问题因为它很隐蔽——训练曲线一切正常只有部署后才现形。解决采集时让背景尽量贴近真实场景花盆周围放上其他花盆、浇水壶、栏杆等杂物别追求“干净素材”训练时保持 mosaic 增强开启ultralytics 默认开它会把四张图拼在一起强迫模型学目标本身的特征而不是背景再在数据增强里加随机裁剪和旋转降低模型对位置和角度的依赖。这条最难事后补救所以应该一开始就按“脏背景”去采集而不是按“好看”去拍。5.5 继电器误触发把花浇成了水培现象晚上没人时系统自动浇水第二天发现花盆积水土壤湿度传感器读数明明是湿的系统还在浇。查日志发现模型把叶片的夜间阴影误检成萎蔫连续帧确认机制扛不住持续误报。原因两层问题叠加。一是夜间光照下模型误检率高误报持续时间超过连续帧阈值二是继电器用了电平触发模式程序异常退出后 GPIO 输出保持高电平电磁阀一直开着没人管。浇水事故很少是单一原因基本都是“误检 硬保护缺失”同时发生。解决加三道防线。第一道是土壤湿度传感器作为硬保护湿度读数不低于阈值时无论模型怎么报都不浇水这条在 4.2 的决策函数里已经实现第二道是继电器选脉冲触发型号或者程序里做 GPIO 看门狗每隔 5 秒强制输出一次关闭信号即使主逻辑卡死阀门也会被定时器拉回关闭状态第三道是单次浇水时长上限一次浇水不超过 8 秒两次浇水间隔不小于 5 分钟。三层都加上即便某一层失效整体也不会出大事故。水淹阳台这种事出一次就够你长记性的。6. 验收与进阶怎么证明这套系统真的“监测得住、浇得准”毕设答辩和工作汇报时光有界面和演示不够得有数字。我的验收习惯是固定测三个指标指标怎么测合理值检测准确率在验证集上跑 yolo val读 mAP50单一场景 0.85 以上端到端延迟从摄像头取帧到继电器动作的时间3 秒以内误浇率连续运行 7 天非缺水状态下触发浇水的次数占比低于 5%前两个好测mAP50 直接看训练输出端到端延迟在决策函数前后打时间戳累计 100 次取平均。误浇率需要真实跑几天靠 SQLite 日志里 irrigated 字段和手动记录的植物状态对比统计。这个指标最容易被忽略但恰恰是系统可信度的核心——你不能答辩时说“我觉得它挺准的”得有连续几天的无人值守记录作证。验证通过后值得做的第一个进阶是推理加速桌面 CPU 用 OpenVINO 导出yolo export modelbest.pt formatopenvino imgsz640导出后替换模型路径即可代码逻辑不用改CPU 推理速度通常提升 1.5 到 2 倍。跑在 rk3588 场景则走 ONNX 转 RKNN 的量化流程更适配边缘部署。第二个进阶是定时巡检在程序里加一个调度器每天早上 8 点和傍晚 6 点各自动巡检一次并记录状态快照配合 SQLite 日志画出“这周每盆花的健康度变化曲线”这张图拿去做毕设展示很有说服力。再往后可以按花盆编号区分检测框灌溉时只开对应编号的电磁阀实现精准到盆的浇水。我自己做完这套系统后养成的习惯是所有阈值参数置信度、连续帧数、湿度阈值、浇水时长全部抽到独立配置文件里不散落在代码各处调参只改一个文件。另一个习惯是每次改完控制逻辑先不接真实电磁阀用 LED 灯代替阀门跑两天确认触发逻辑稳定了再接水。这套流程帮我挡住了好几次把测试台淹了的风险。希望帮到你。本文还有配套的精品资源点击获取
返回列表