资讯动态

基于YOLO与SpringBoot的生菜生长周期检测系统开发实战

发布时间:2026/10/22 9:36:34 来源:尧图企业网站定制
搞农业视觉检测的朋友应该都有同感生菜生长周期判断这种活儿看着简单真做起来却特别烦人。不同品种、不同光照、不同湿度下同一阶段的生菜叶片形态差异很大人工巡检一天跑几趟大棚耗时耗力判断标准还不统一。这篇文章把我近期做完的一套“生菜生长周期检测系统”全过程拆开讲一遍包含YOLOv8/YOLOv10/YOLOv11/YOLOv12四个版本的对比实测、SpringBoot后端搭建、前后端分离交互界面、以及接入DeepSeek做语义化分析的经验。这套方案不只能用来数生菜抽掉数据集和业务层之后直接改造成其他作物检测或者工业质检场景也没问题整个思路值得参考。1. 项目概述与需求拆解1.1 生菜生长周期检测到底要检测什么先明确一个容易搞混的概念这里说的不是“检测生菜有没有病”而是把生菜从播种到采收分成若干个可识别的阶段用目标检测模型去判断每一株生菜当前处于哪个阶段。我在实际项目中把生菜生长期粗略划分为四类幼苗期子叶展开真叶尚未形成、快速生长期叶片数量增多但未抱球、结球期叶片向内合拢开始形成叶球、成熟期叶球紧实可采收。这四个阶段在图像上存在比较明显的形态差异但也有过渡模糊的情况比如结球期和成熟期之间往往只有两三天的窗口比较难区分。所以单纯用普通分类网络去做效果不稳定而目标检测模型可以在识别阶段的同时框出每株生菜的位置这样后端能同时拿到“在哪里”和“长到哪一步”两个维度的信息。系统的使用场景也不只是拍一张图判断一次而是需要长期跟踪同一片区域的生长趋势。因此后端里必须能记录每次识别的时间戳、坐标信息和阶段结果再通过统计图表展示整个生长周期的变化曲线。这一块需求往往比模型训练本身更烦琐因为涉及数据库设计、接口约定和前端可视化是一个完整的工程而非一个单点算法。1.2 为什么选择YOLO系列而非其他算法当前的目标检测方案有两大流派两阶段的R-CNN系列和单阶段的YOLO系列。两阶段检测精度虽然在个别数据集上略高但速度慢、部署重不适合需要频繁推理且硬件条件有限的农业场景。YOLO系列的检测头结构决定了它在保证精度的同时能把推理速度做到实时级别更适合部署在园区边缘设备上。从工程角度讲YOLO还有一个巨大的生态优势标注工具、训练脚本、导出格式、推理SDK整套链路已经非常成熟。我带过的项目里实习生接手YOLO训练流程基本一周内就能跑通但换成R-CNN或者自定义Transformer检测头光环境配置就够折腾一两个星期。因此在农业视觉这类对算法创新要求不高、对交付效率要求很高的场景中YOLO系列几乎是无脑首选。另外有一个很实际的原因YOLOv8到YOLOv12的迭代过程中模型的导出格式一直保持兼容训练好的权重可以通过ONNX、TensorRT等中间格式在不同框架之间转换。这意味着即使后续想在边缘设备上部署也不需要把整个训练流程推倒重来只要额外做一次模型导出和精度校验即可。2. 数据集准备与模型训练对比2.1 数据采集、清洗与标注流程我使用的是“公开数据集自采图像”混合的方式。公开数据集能提供大量不同光照、不同角度、不同生长环境的生菜图像提升模型的泛化能力自采图像则贴合实际项目所在园区的种植模式减少场景迁移的偏差。自采图像用手机和大棚内固定摄像头分时段拍摄覆盖早晨、正午、黄昏三种光照条件尽量把阴影、反光、遮阳网造成的色偏都包含进去。采集完成后不是直接标注先做一轮手工清洗。大约有10%左右的图像存在严重模糊、叶片被大面积遮挡、或者多株生菜挤在一起导致边界完全无法确认的情况这类数据我选择删掉一部分另一部分作为难例样本留作后续测试集。清洗完成后用标注工具逐张框出每株生菜的边界框并打上阶段标签。我的标注格式采用YOLO的txt格式类别按0到3编号边框坐标归一化。这里有个经验教训标注标准必须在最开始就固定下来否则后期返工成本极高。比如“结球期”到底是从叶片开始闭合就算还是叶球直径达到某个程度才算项目组内部要提前对齐。我当时花了整整两天重新标注了一批错标的图像就是因为前期标准写得含糊。建议把每个阶段的判图示例打印出来贴在标注人员能看到的地方。数据增强方面我用了比较克制的方案轻微旋转、水平翻转、亮度扰动、对比度扰动。没有用太激进的马赛克增强和随机拼贴因为生菜的形态特征较强过强的增强反而会让模型学到不真实的纹理。最终数据集总量在12000张左右按8:1:1划分训练、验证、测试集。2.2 YOLOv8/v10/v11/v12的训练配置对比四个版本的YOLO模型我都实际跑了一遍训练而不是只看公开的benchmark。因为不同版本的模型结构差异会影响在自定义数据集上的收敛表现尤其是YOLOv10和YOLOv11在训练时的超参敏感性不太一样。先说YOLOv8。这个版本最成熟社区资料最多遇到问题基本都能搜到解决方案。它的训练成本较低默认超参就能在生菜数据集上达到不错的效果。我用YOLOv8m输入尺寸640训练150个epochbatch size根据显存调到16最终mAP50在0.91左右mAP50-95大概在0.73附近。YOLOv10最大的特点是使用了无NMS非极大值抑制的端到端训练方式推理时少一步后处理速度更快。但在我的数据集上它需要把训练epoch加大到200才能达到和YOLOv8相近的精度前100个epoch的收敛速度明显偏慢。这个特性在算力紧张的项目里不太友好。YOLOv11在CSPLayer和检测头上做了进一步优化官方定位是精度和速度平衡最好的一版。实测下来它的收敛速度确实比YOLOv10快mAP50-95能到0.75左右。YOLOv12则引入了区域注意力机制在中小目标检测上有优势。但必须说清楚R-AL-Neck这种结构让它的计算量比前几代高不少在只有一块消费级显卡的环境下训练时间明显增加推理速度也慢了一截。最终权衡后我的系统默认使用YOLOv11m因为它在推理速度、精度、显存占用这三项之间最平衡。下表是我在固定数据集和固定硬件条件下记录的四个版本的一些关键对比数据。由于训练本身存在随机性数值仅供参考目标读者后面自己复现时应该以自己机器上的实测为准。模型版本输入尺寸mAP50mAP50-95单张推理耗时(ms)显存占用(MB)输出权重大小(MB)YOLOv8m6400.910.7312.3198049YOLOv10m6400.890.718.6193047YOLOv11m6400.920.759.2195051YOLOv12m6400.910.7415.72430582.3 训练参数选择思路和关键源码很多初学者会把训练参数直接抄官方的默认配置这没有问题但要知道每个参数到底在干什么。我这次训练的核心参数如下epoch150batch-size16img-size640优化器使用SGD而不是AdamW初始学习率0.01使用余弦退火调度。SGD在检测任务中的泛化能力通常比Adam系列更稳尤其是数据量不大时不容易过拟合。标签平滑我开了0.05目的是让模型对结球期和成熟期这类边界模糊的类别不要输出过于自信的概率。实际效果是验证集上的mAP虽然变化不大但推理阶段的置信度分数分布更合理阈值筛选时更不容易误判。训练命令本身并不复杂关键是在训练过程中盯着日志里的验证集指标变化。我习惯每20个epoch停下来手动推理几张难度较大的测试图肉眼看边界框的贴合程度因为有时mAP上涨但某个特定阶段的框忽大忽小这种问题纯看指标发现不了。如果读者的显存比较小可以把img-size降到480batch-size降到8虽然精度会有轻微下降但对生菜这种大目标来说影响不明显。另外强烈建议开启AMP混合精度训练能省30%以上的显存速度也有明显提升几乎不损失精度。3. SpringBoot后端与前后端分离实现3.1 系统模块划分与数据库设计整个系统的后端基于Spring Boot搭建Java版本使用17Spring Boot版本使用3.x。后端主要拆成四个模块用户与权限模块、图像上传与任务调度模块、YOLO推理服务调用模块、检测结果分析与统计模块。四个模块之间通过接口通信避免互相依赖过深。数据库选用了MySQL主要存储用户信息、检测记录、生长阶段字典、地块信息等。检测记录表是关键设计时预留了image_url、model_version、box_coordinates、stage_result、confidence、analysis_result这几个字段。现在看当时的设计还好把box_coordinates单独拆成了JSON字段否则后面扩展前端可视化时又要改表结构。也建议加上create_time和deleted标记方便后续做时间线查询和逻辑删除。图像存储方面我选了本地文件系统加Nginx静态映射的方式没有引入OSS对象存储。因为项目数据量还没到需要大规模存储的程度本地存储成本更低也更可控。如果后续接入多个园区再把文件访问层抽象出来替换即可。3.2 YOLO推理服务如何与SpringBoot协作这是整个项目最容易踩坑的地方。YOLO模型是Python生态的工具SpringBoot是Java生态的后端框架两者不能直接互相调用。业界常见有三种方案。第一种是把YOLO模型导出成ONNX格式在Java端用ONNX Runtime加载并推理。这种方案部署最简单不需要额外起Python服务但后处理逻辑要自己写一旦模型升级或换成其他算法结构Java端的兼容代码也要跟着改。第二种方案是单独部署一个Python推理服务用Flask或FastAPI封装一个HTTP接口SpringBoot通过RestTemplate或WebClient去调用。这种方案结构清晰模型训练和推理逻辑都在Python端维护Java端只需要关心业务编排。缺点是网络通信有额外开销如果频繁调用要控制好超时和连接池配置。第三种方案是引入消息队列比如RabbitMQ或Kafka。图像上传后SpringBoot把任务发到队列Python推理服务消费队列并执行推理再把结果写回数据库。这种方案最适合高并发场景但架构复杂度明显增加。我最终选择的是第二种方案用FastAPI封装推理服务因为要接入DeepSeek分析需要在Python端灵活做结果后处理。FastAPI的异步特性加上Uvicorn的多worker模式就能满足当前并发需求完全不需要引入消息队列增加维护成本。# Python 端 YOLO 推理服务核心逻辑 from fastapi import FastAPI, File, UploadFile import json import numpy as np from PIL import Image app FastAPI() # 全局加载模型避免每次请求都初始化 model None app.on_event(startup) async def load_model(): global model # 实际项目中用 ultralytics 加载这里省略具体导入 model load_yolo_model(best.pt) app.post(/api/detect) async def detect(file: UploadFile File(...)): image Image.open(file.file) results model.predict(image, imgsz640, conf0.25, iou0.45) detections [] for box in results[0].boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) x1, y1, x2, y2 map(float, box.xyxy[0]) detections.append({ class_id: cls_id, confidence: round(conf, 4), bbox: [x1, y1, x2, y2] }) return {code: 0, data: detections}SpringBoot端使用并发调用时要注意FastAPI默认是同步阻塞的如果里面用了同步的model.predict并发上来后会卡在CPU推理上。我后来把detect函数改成了def而非async def让FastAPI自动放到线程池中处理问题解决。3.3 前端页面与交互流程前端采用了前后端分离架构技术栈选择Vue3和Element Plus构建工具用Vite。页面主要包含三块图片上传并展示检测结果的主界面、历史记录查询页面、生长周期统计面板。开发前后端分离项目时最烦的是跨域问题。前端的请求地址是http://localhost:5173后端接口是http://localhost:8080浏览器默认会拦截跨域请求。我在SpringBoot里写了一个全局CORS配置类允许指定域名跨域访问并注意让前端携带凭证信息。开发环境用Vite的proxy代理也可以解决但我更推荐后端直接配置CORS因为生产环境部署时往往还有一层Nginx提前把CORS处理好能少一道折腾。前后端交互的完整流程可以概括为用户在前端页面上传图片前端把图片以二进制文件形式POST给SpringBoot接口SpringBoot先把文件保存到本地并生成访问URL然后调用Python推理服务拿到检测框坐标和阶段分类后端把检测结果和图片URL一同保存到数据库再返回给前端前端拿到结果后在Canvas画布上把边界框和标签绘制出来并根据阶段类型显示不同的颜色。调参时前后端联调往往比模型训练还耗时。当时我踩过最典型的坑是前端拿到的检测框坐标和原图尺寸对不上。原因是Python端返回的边界框坐标是相对于输入图像尺寸的而前端展示的图片发生在缩放后的容器里如果直接绘制会错位。最终方案是前端读取图片的原始宽高再和Canvas呈现的宽高做比例换算才修正了这个对齐问题。4. DeepSeek智能分析与结果解读4.1 大模型接入的必要性很多工程背景的开发者会问已经有YOLO输出的检测结果了为什么还要额外接大模型做分析直接返回“类别3、置信度0.87”不就行了实际上农业经营者看到的界面不能是冷冰冰的数字。他们想知道的是“这株菜还能不能继续长”“现在浇水施肥是否合理”“再过几天可以采收”。这类决策性建议需要结合阶段识别结果、置信度、时间序列等多维度信息来生成传统规则引擎可以写但逻辑非常僵硬换一个品种又得重新梳理规则。接入DeepSeek这类大模型之后系统可以把原本需要人工归纳的农业种植经验自动转化为结构化建议。比如检测结果显示一株生菜处于结球期且置信度超过0.9大模型结合预设的知识库就能分析出“当前进入快速结球阶段请保持土壤湿度适当补充磷钾肥预计5-7天后进入成熟期”这样用户看到的是一个能指导生产的自然语言报告而不是技术字段。需要注意的是大模型不能替代目标检测模型。YOLO提供的是可量化的视觉事实大模型提供的是基于事实的分析与归纳。两者分工明确绝不能混在一起做端到端判断否则出现“幻觉”时会很难排查。4.2 API接口对接与Prompt设计对接DeepSeek时我采用后端异步调用的方式SpringBoot收到YOLO识别结果后把数据组装成一段结构化文本通过HTTP调用DeepSeek的开放接口拿到分析文本后再保存到数据库。整个调用过程封装在独立的Service类中方便后续替换成其他大模型。Prompt设计是这个环节的重中之重。我第一版写的提示词比较简陋只把检测结果拼进去结果模型输出的内容经常出现冗长废话。后来参考了几轮实践把Prompt拆成了三部分任务角色描述、输入数据格式说明、输出格式约束。这样系统可以稳定输出JSON结构方便前端直接渲染。# Python 端构造 Prompt 的示例逻辑 def build_prompt(detections, crop_stagelettuce): stage_map {0: 幼苗期, 1: 快速生长期, 2: 结球期, 3: 成熟期} stage_desc [] for item in detections: stage_desc.append(f{stage_map[item[class_id]]}置信度{item[confidence]}) prompt f 你是一位农业种植顾问请根据以下生菜生长周期检测结果为用户提供简明、可执行的种植建议。 检测结果{stage_desc} 要求 1. 总结当前生菜的主要生长阶段。 2. 给出在这个阶段需要注意的管理措施。 3. 如果存在多个阶段按比例说明不同阶段的大致分布。 4. 输出格式必须是JSON包含summary、suggestions、highlight三个字段。 return prompt实际使用时还要注意几个细节。大模型接口的响应时间通常不短我遇到最快也要2秒到3秒有些场景下更久。因此不要把大模型调用放在同步的图片识别链路里否则前端会一直转圈等待。我的做法是识别请求先返回检测结果前端立刻展示同时后端异步调用大模型分析完成后再通过浏览器轮询或WebSocket推送给前端。这样既保证了界面响应速度又不影响分析的完整性。另外大模型接口存在失败风险网络抖动、服务端限流、返回内容格式异常都有可能发生。我增加了重试机制和降级方案如果连续调用3次都失败就回退到本地规则模板根据阶段识别结果生成一句带基础信息的管理建议。这样即使大模型服务不可用系统也不会完全瘫痪。4.3 成本控制与缓存优化接入大模型服务后会发现一个现实问题钱烧得比想象快。每张图片识别一次就调用一次大模型单纯检测任务还好但如果用户反复查看历史记录后端就会反复调用白花冤枉钱。我为大模型分析结果加了缓存。第一次识别完成后把大模型生成的文本和JSON结构化结果一并存到数据库的analysis_result字段中同时用本地缓存组件做一层短期缓存。后续用户查看同一条记录时直接读取缓存内容不再重复调用。如果同地块的多次检测间隔较短还可以做去重比如用户连续上传10张同一区域的照片其中8张检测结果高度相似可以直接复用第一次的分析结果。这个逻辑放在SpringBoot的Service层判断根据地块ID和检测时间差做一个阈值筛选。5. 部署、测试与常见问题排查5.1 本地联调与生产环境部署整套系统在开发环境跑通后我部署在一台Linux服务器上。服务器配置为8核CPU、32GB内存、一张消费级显卡显存12GB。这个配置跑推理还可以但训练就在本地工作站完成服务器只负责推理和业务。前后端分离的部署方式并不复杂。前端构建后生成dist目录用Nginx托管后端SpringBoot打成jar包直接运行Python推理服务用Uvicorn启动监听默认端口。Nginx里需要配置两条反向代理规则/api/开头的请求转发到SpringBoot进程/predict/开头的请求转发到Python推理服务端口。同时给前端静态资源设置合理的缓存策略图片上传目录也要映射到Nginx的静态目录中否则前端无法展示历史图片。启动顺序也有讲究。我习惯先启动Python推理服务再启动SpringBoot最后启动Nginx。因为SpringBoot启动时会做一次健康检查尝试调用Python推理服务的/health接口如果没启动会打出告警日志虽然不会导致进程退出但看日志时容易产生误解。5.2 性能瓶颈与并发优化系统刚联调完时我压测发现一个问题多个用户同时上传图片时推理时延明显上升。原因很简单Python推理服务默认只启动了一个worker多张图片排队等着GPU推理。解决思路其实常见FastAPI配合Uvicorn启动多个worker即可每个worker加载一份模型副本。虽然显存占用成倍增加但8GB至12GB的显存足够承载2到3个worker。如果显存有限比起多worker还有一个方案是模型推理请求排队而不是让每个请求都独立加载模型。实测下来显存不足时并发请求会产生OOM错误进程直接崩溃非常难受。建议设置显存监控脚本超出阈值时自动拒绝新请求并返回“系统繁忙”的提示同时利用SpringBoot端的信号量控制对Python服务的并发调用数。除此之外上传的图片大小也需要限制。普通手机拍摄的生菜照片动辄5MB以上直接传到后端做Base64编码会占用大量带宽和内存。我在前端做了压缩图片长边超过1600像素就先等比缩放质量调整为0.8再上传。这样识别准确率几乎不受影响但传输和推理速度都能快很多。5.3 常见问题速查表整理一下整个开发过程中遇到频率最高的几个问题后续读者如果复现这套系统大概率也会撞上。问题现象可能原因解决方案前端图片检测框坐标错乱未按原图尺寸和展示尺寸比例换算用原生图片宽高与Canvas显示宽高做坐标映射Python推理接口超时模型加载或推理耗时过长调整SpringBoot的RestTemplate超时时间启用FastAPI线程池大模型返回内容解析失败返回的JSON里混了额外说明文本Prompt中强制要求只输出纯JSON并在解析时做正则剔除GPU显存不足导致推理崩溃并发请求过多或图片过大增加图片压缩限制并发数必要时降级到CPU推理中文乱码数据库连接未指定UTF-8MySQL连接串加useUnicodetruecharacterEncodingUTF-8训练时loss震荡严重学习率过高或batch size过小降低初始学习率适当增大batch size开启混合精度结球期和成熟期经常混淆训练数据中两阶段边界样本不足补充过渡期样本调整标签平滑参数适当增加置信度阈值排查问题的顺序也有讲究。看到前端白屏先看浏览器控制台的网络请求是哪些接口报了500或404看到接口报错先看后端日志看到Python服务日志有异常先确认是否因为模型权重路径写错。不要一上来就怀疑数据库或环境问题大部分报错还是集中在接口调用和数据格式上。6. 后续可以继续扩展的方向这套系统做到当前阶段已经能满足基本的生菜生长周期检测和智能分析需求。但基于这次开发的经验我认为有三个方向值得继续深入。第一是模型层面的持续迭代。YOLOv12虽然在我的数据集中速度不够理想但如果后续更换更强力的硬件它在中目标检测上的表现可能会反超YOLOv11。同时可以尝试把四个版本的输出结果做模型集成用加权投票提升最终阶段判断的稳定性不过要对推理时延增加有心理准备。第二是时间序列分析。现在每次识别是独立事件后端只保存了历史记录没有自动挖掘生长曲线的变化趋势。如果基于同一株生菜的多日检测数据训练一个轻量的时序模型就能提前预警“生长停滞”或“快速成熟”等异常情况比单次识别结果更有农业决策价值。第三是移动端适配。当前的Web界面在手机浏览器上能用但体验一般。生菜种植大棚里管理人员多数使用手机拍照记录如果做成微信小程序或App用起来会更顺手。前端Vue技术栈可以直接迁移到小程序端后端接口基本不用改动。我自己在实际操作中的体会是这类农业视觉项目真正的难点不在算法创新而在工程整合能力。YOLO训练再厉害也得和SpringBoot接口、前端图表、大模型分析串在一起才能变成生产力。数据标注标准、接口字段约定、异常降级策略这些看起来不起眼的细节才是决定项目能不能稳定跑起来的核心。踩过几次坑之后我现在做任何系统第一件事都会先把接口文档的字段类型固定下来再让前后端并行开发。这一点建议直接照抄能少走很多弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑