☰
基于YOLO与DeepSeek的生菜生长周期检测系统实战
2026/10/22 9:38:49 网站建设 项目流程

1. 从“一亩生菜”到四代YOLO:这个项目到底在解决什么问题

坦白说,第一次听到“给生菜做生长周期检测系统”这个需求时,我是愣了一下的。蔬菜大棚里种生菜,工人每天巡一圈就能看出大概长到哪个阶段了,为什么要动用YOLOv8、YOLOv10、YOLOv11、YOLOv12这一整套目标检测模型,再配一个SpringBoot后端和Web界面?等真把这个系统搭完、跑起来,我才意识到这里面藏着两个非常实际的问题:第一,生菜的生长阶段判断其实比想象中主观,同一片叶子,老农户和新工人能给出完全不同的结论;第二,当你要管理的不再是一亩地,而是几十个温室、几千盆水培生菜时,人工巡检的效率根本跟不上。

这个项目本质上做的是这样一件事:用摄像头或手机拍一张生菜图片,后端调用YOLO系列模型识别出图像里的生菜个体,同时判断它当前处于哪个生长阶段(幼苗期、莲座期、结球期或成熟期),然后把检测到的结构化数据喂给DeepSeek这类大模型,自动生成一段“当前这批生菜整体状态如何、下一步该怎么管理”的文字建议。前端是一个前后端分离的Web交互界面,管理者可以在网页上上传图片、查看检测结果、浏览历史趋势。整套系统的核心链路可以概括为:图像输入 → YOLO目标检测 → 阶段分类输出 → SpringBoot服务整合 → DeepSeek文本分析 → Web可视化。

适合看这篇文章的人大体有两类。一类是正在做智慧农业相关选题的学生或工程师,想搞清楚“计算机视觉怎么落地到作物表型分析”这件事的完整技术链路;另一类是已经跑通过YOLO检测,但还没想明白“检测完之后的数据怎么变成业务价值”的人。如果你属于后者,那DeepSeek智能分析那一节值得你多看两遍——它解决的不是“能不能检测出来”,而是“检测出来之后然后呢”这个更现实的问题。

这个系统涉及的技术栈比较杂:前端是Vue系,后端是SpringBoot,AI侧是Python生态的YOLO模型和DeepSeek接口,中间还要跨语言通信。但你别被这个长标题吓到,拆开看其实就是三条线:YOLO负责“看”,SpringBoot负责“接”,DeepSeek负责“说”。下面我按实际开发顺序把每一块掰开讲。

2. 模型吃得饱的目标才做得好:生菜生长周期数据集构建全流程

任何目标检测项目,数据集的优先级都排在模型选型前面。你拿YOLOv8还是YOLOv12去跑,前提是训练数据能真实反映你部署环境里的生菜长相。这个项目里最容易翻车的地方,不是模型跑不起来,而是标注出来的标签根本不符合农学意义上的生长周期划分。

2.1 生长阶段如何拆分标签体系

生菜的生长周期大体可以拆成发芽出苗、幼苗、莲座、结球(针对结球生菜)或成熟(针对散叶生菜)几个阶段。但做视觉检测时不能照搬农学定义,因为模型只能学“图像上的形态差异”,而你定义的每个类别必须对应着肉眼可见的形态特征。

我实际用下来比较稳的标签划分是四类:seedling(幼苗期)、rosette(莲座期)、heading(结球期)、mature(成熟期)。如果场地里种的是散叶生菜,可以把heading换成upright或者直接把成熟期的样本并入mature,关键是类别之间不能出现明显的视觉歧义。比如幼苗期和莲座期早期,如果拍摄距离不固定,模型很容易把“长了几片真叶的小苗”和“已经铺开叶片的莲座期个体”混在一起。这时候要在采集阶段就规定一个统一的拍摄视角和大致高度,让类别差异主要体现在叶片的铺展程度、层数和叶色饱和度上,而不是体现在目标大小上。

有一种做法是额外加一个abnormal类别,用来标记黄叶、枯萎或病斑明显的生菜个体。这样做的好处是,管理者不仅能知道生长进度,还能顺带看出这一批里有没有状态异常的植株。代价是标注工作量会大一些,但如果你后期的DeepSeek分析想输出“可能有病害风险”这类结论,这一步非常值得加。

2.2 采集、标注与YOLO格式

数据采集的核心原则只有一个:你的训练集必须长在部署环境里。如果系统打算部署在温室水培架上,那训练图片就应该在类似的水培架上拍,而不是去网上爬一堆地面露天种植的照片。光照条件尤其重要。温室里早晚的斜射光、补光灯的紫红色光、阴天的散射光,都会让生菜叶片的颜色和阴影形态发生很大变化。我自己的做法是分三个时段采集:上午十点前、正午前后、下午四点后,每种光照下都拍一批,数量不用平均,但至少保证每种都有。

标注工具我用的是LabelImg和X-AnyLabeling。LabelImg轻量够用,导出的就是标准的YOLO txt格式,每一行对应一个目标框:类别ID 中心点x坐标(归一化) 中心点y坐标(归一化) 框宽(归一化) 框高(归一化)。实操时要注意三点:

  • 框要紧贴叶片或叶球的最外缘,不要为了省事把花盆、基质、支架包进去。框内背景越多,模型越容易学到“边框周围的环境特征”,而不是生菜本体特征。
  • 叶片交叉遮挡时,只标露出来的可见部分。不要硬脑补出一个完整的圆框,因为你的目标框哪怕只框住半个叶球,对后续阶段判断的影响也不大,模型看的是总体形态分布。
  • 类别ID的对应关系必须单独维护一份classes.txt,和训练配置里的names完全一致。很多人训练时发现模型预测的标签名全乱了,十有八九是这里没对齐。

如果一批图片里生菜数量很多,比如水培架上一排十几棵,建议一张图里尽量保留全部可见个体,不要裁剪成单棵再标。因为模型推理时面对的就是整排整片的环境,训练时让它见过密集场景,检测时它才不会漏检被叶片挡住的目标。

2.3 数据增强与训练划分的务实做法

样本数量上,每个阶段至少600到800张,四个阶段加起来2000到3000张是一个比较稳妥的起步量。如果条件有限,每个阶段压到400张也能跑,但mAP会明显不稳定,尤其是mature和heading这种外观差异本来就小的相邻类别。

增强策略我在这个项目里只用了三种:Mosaic(四张图拼接)、随机色调和亮度扰动、轻微随机旋转和翻转。Mosaic对密集场景的提升很显著,生菜地块经常是一张图里多个目标,Mosaic等于变相增大了单张图的样本密度。亮度扰动是必须的,因为温室不同位置的光照差异比想象中大。翻转要慎用180度和90度,如果图片里能看出种植架的方向信息,过度翻转会让模型学到“反着的生菜”这种没意义的特征。

训练集、验证集、测试集的划分,我习惯按7:2:1。有一个细节:划分时尽量按“拍摄批次”而不是“单张图片”来分。比如同一时段同一位置连拍的一批图,要么全进训练集,要么全进验证集,避免因为同一场景的相似图片被分到训练集和验证集里,导致验证指标虚高。这个坑我踩过,当时mAP到0.95,一换实地照片立刻跌到零点几,问题就出在数据划分时泄露了场景特征。

3. YOLOv8、YOLOv10、YOLOv11、YOLOv12在同一批生菜数据上如何选型

标题把四个版本的YOLO并列写出来,看起来像要做一个全家桶,但实际项目里你只会选一个作为主力模型,可能再留一个作为备选。这里面的选型逻辑比“哪个版本最新就用哪个”要复杂一点,我按自己的实测理解拆开讲。

3.1 四个版本的核心差异速览

这四代模型的差异主要体现在网络结构设计和训练策略上,而不是使用方式上。对做应用层的开发者来说,感知最明显的是:预测速度和精度的权衡、训练框架的兼容性、以及部署时的依赖复杂度。

  • YOLOv8:Ultralytics统一了训练和推理接口,生态最成熟,文档和社区资源最多。C2f模块替代了之前的C3模块,模型表达能力有提升。对于这个生菜项目来说,它是“最不容易出错”的基线选择。
  • YOLOv10:最大的变化是去掉了NMS后处理,用双标签分配策略实现端到端推理。推理过程少了一步后处理,延迟更低,部署更简洁,但代价是需要适配它的预测输出格式,不能直接用v8那套推理代码。
  • YOLOv11:在v8基础上做了结构升级,引入了C3k2模块,小模型参数效率更高。从体验上看,它在相同精度水平下推理速度比v8略快,是一个“加量不加价”的稳定版本。
  • YOLOv12:是目前最新的一个版本,引入注意力机制来增强对大目标的特征提取。注意力机制在复杂背景下的泛化能力确实值得期待,但它的训练和部署生态还不如前三个版本成熟,环境配置和导出环节可能多花时间。

对一个生菜生长周期检测任务来说,背景不算复杂(要么是温室基质,要么是水培架),目标不算太小(单棵生菜在正常拍摄距离下至少占几十个像素),所以这四代模型的差距不会像在人像分割、自动驾驶那种复杂任务上那么悬殊。这反而是个好事,意味着你可以优先选工程上最好部署的那个,而不是纠结模型理论精度高了那零点几个点。

3.2 实测思路与结果解读

我当时拿同一批生菜数据集,分别用v8s、v10s、v11s、v12s训练了300轮,对比了三个指标:mAP@0.5、mAP@0.5:0.95、单张推理耗时。

模型版本mAP@0.5参考值mAP@0.5:0.95参考值单张推理耗时参考(GPU)部署难度
YOLOv8s0.9420.7863.8ms最简单
YOLOv10s0.9380.7793.2ms较简单,需适配输出格式
YOLOv11s0.9470.7913.5ms简单
YOLOv12s0.9510.8034.1ms较复杂,依赖环境需额外配置

这个结果只对“生菜数据集”这一个任务有参考意义,别把它当成四个模型的绝对性能排序。变化的幅度其实很小,尤其是mAP@0.5都到了0.94以上,说明这个任务本身的难度没有拉开代际差距。真正拉开体验差距的是部署阶段:YOLOv8训练完直接导出ONNX就能交给后端调用,YOLOv12在模型导出和目标格式解析上就要多花一些力气。

3.3 我的选择建议和理由

如果这个项目要在一个月内交付、并且后续维护的人不固定,我的建议很直接:使用YOLOv8s或YOLOv11s作为默认模型。理由是它的训练、导出、推理路径最短,遇到问题能搜到的解决方案最多。我最终在本地验证环境里选了YOLOv11s做主力,YOLOv8s做备用。

YOLOv10和YOLOv12可以保留在项目的对比实验记录里,作为“未来优化路径”写进文档:如果后续现场反馈说检测速度不够,换YOLOv10s试试;如果反馈说小目标漏检严重,换YOLOv12m跑一轮。这样既不会让项目被模型版本绑架,又能给后续优化留出抓手。

还有一个容易被忽略的点是模型大小。生菜检测的部署环境大概率是现场的工控机或普通服务器,不一定有高端GPU。s型号参数量小、显存占用低,CPU也能勉强跑推理,这对于农业现场非常关键。别一上来就试x型号,精度提升有限,推理延迟和显存压力却会成倍增加。

4. SpringBoot与Python推理服务:前后端分离架构里的关键桥接

标题里同时出现了SpringBoot和YOLO,很多人第一反应是“能不能在Java里直接调YOLO模型推理”。技术上是可能的,通过Java版本的ONNX Runtime加载导出的模型文件,但工程实践上我强烈不建议第一次做这个项目的时候就选这条路。原因后面展开,先说推荐方案。

4.1 为什么不把YOLO直接跑在Java进程里

YOLO模型的前处理和后处理远比“输入图片、输出结果”这几个字复杂。输入前要做letterbox(等比缩放补边),输出后要做置信度过滤、类别映射,YOLOv10还要额外解析无NMS的输出结构。这些逻辑在Python生态里都有现成实现,换成Java重写一遍,等于自己造一遍轮子,而且造出来的轮子大概率不如原版圆。

我当时采用的架构是Python推理服务 + SpringBoot后端服务,两者通过HTTP接口通信。Python侧用FastAPI起了两个接口:/detect接收图片,返回JSON格式的检测结果;/health做健康检查。SpringBoot后端收到前端上传的图片后,先调用Python服务拿检测结果,再决定要不要调用DeepSeek接口生成文本分析。

这个方案的开销是增加了一次内网HTTP调用,但换来的是模型迭代成本大幅下降。以后想换YOLOv12或者加一个新的检测类别,只需要重新训练模型并更新Python服务,SpringBoot那侧一行代码都不用改。对农业现场这种“模型会持续迭代”的场景,这个优势非常实用。

4.2 后端接口设计与异步处理

SpringBoot这边的接口设计我分成三层:

  • 上传层:接收前端传来的图片,校验格式和大小,保存到本地或对象存储,生成任务ID。
  • 调度层:调用Python推理服务,记录检测结果的原始JSON。
  • 业务层:根据检测结果计算阶段分布、置信度均值、单株生菜数量等统计量,写入MySQL,同时判断是否需要触发DeepSeek分析。

下面是一个简化的detect接口核心逻辑示例,方便你理解调用路径:

@RestController @RequestMapping("/api/growth") public class GrowthController { private final RestTemplate restTemplate; private final GrowthReportService reportService; public GrowthController( RestTemplate restTemplate, GrowthReportService reportService ) { this.restTemplate = restTemplate; this.reportService = reportService; } @PostMapping("/detect") public Result<DetectResponse> detect( @RequestParam("file") MultipartFile file ) { String imageUrl = fileService.save(file); String yoloResult = callYoloService(imageUrl); DetectResult result = jsonMapper.parse(yoloResult); reportService.saveDetectRecord(imageUrl, result); return Result.success( DetectResponse.of(result) ); } private String callYoloService(String imageUrl) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); Map<String, Object> body = Map.of( "image_url", imageUrl, "conf_threshold", 0.45 ); HttpEntity<Map<String, Object>> request = new HttpEntity<>(body, headers); ResponseEntity<String> response = restTemplate.postForEntity( YOLO_SERVICE_URL + "/detect", request, String.class ); return response.getBody(); } }

这里第一个要注意的坑就是别用同步调用处理长耗时任务。YOLO推理加上DeepSeek生成报告,整个链路可能耗时2到5秒。如果前端一直同步等待,用户会以为页面卡死了。我的做法是:纯检测走同步,响应控制在500毫秒内;带DeepSeek分析的完整报告走异步任务,先返回“分析中”状态,前端轮询任务结果。后面Web交互界面也要按这个节奏设计。

另一个细节是图片传输格式。我踩过的坑是——前端传Base64字符串,后端转存时忘记处理数据前缀,导致图片文件损坏。统一的规范是:前端用multipart/form-data上传原始文件流,后端保存后返回一个URL;Python服务直接通过URL或文件流读取图片。尽量避免在JSON里传大段Base64,既浪费带宽又增加解析出错概率。

4.3 Web交互界面的可视化呈现

前端页面我用的是Vue 3配合ECharts。核心页面有三块:检测页、历史趋势页、批次管理页。

  • 检测页:左侧是图片上传区,支持拖拽上传和点击选择;右侧是检测结果展示区,用Canvas在原图上绘制目标框,每个框上用标签显示阶段名称和置信度。检测框的颜色按阶段区分,比如幼苗期用绿色、莲座期用黄色、结球期用蓝色、成熟期用橙色,这样管理者一眼扫过去就能了解整片生菜的成熟度分布。
  • 历史趋势页:展示过去一段时间内各阶段占比的变化曲线。比如连续两周每天上传一次整排生菜的俯视图,系统就能生成一条“莲座期占比下降、结球期占比上升”的曲线。这个功能如果不做DeepSeek,光靠图表就已经能提供不少决策价值。
  • 批次管理页:把不同种植批次分开管理。每批生菜有独立的检测记录和报告,不会互相干扰。这个模块对实际生产管理很重要,因为不同批次的播种时间不同,生长阶段自然不同,混在一起统计会得到毫无意义的平均值。

前端通过Axios调用SpringBoot接口,因为前后端域名不同,记得在SpringBoot里配置CORS。实战里经常出现的跨域问题,解决方案很简单,但一旦漏配,所有请求都会在浏览器控制台报红色错误,排查起来又容易怀疑人生。

5. DeepSeek智能分析:从检测框到人能看懂的生长报告

YOLO模型输出的只是坐标框和类别ID,比如“检测到5个heading、3个rosette”。这种数据对后端程序很友好,但直接丢给用户,绝大多数人看不明白。DeepSeek在这个项目里扮演的角色就是“翻译官”:把结构化检测数据翻译成一段自然的、有管理价值的文字建议。

5.1 检测结果如何变得有温度

DeepSeek分析模块的输入我设计成三部分:检测结果的统计摘要、历史数据的趋势摘要、固定的任务指令。统计摘要包括:本次检测到各阶段生菜的株数、总株数、平均置信度、各阶段占比。趋势摘要则从数据库里读取过去几次检测记录,比如“前一次检测rosette占比60%,本次下降到45%,heading占比从20%上升到35%”。

有了这三部分输入,DeepSeek能输出类似下面这样一段话:

当前该批次生菜整体处于从莲座期向结球期过渡的阶段,大部分植株已开始形成松散叶球,预计5到7天后可进入集中结球期。与上次检测相比,莲座期占比下降14个百分点,结球期占比上升12个百分点,生长速度处于正常偏快区间。建议适当控制夜间温度在12至15摄氏度,避免结球过快导致叶球松散;水肥方面可暂不追加氮肥,保持当前磷钾比例。

这段输出并没有做什么复杂的模拟计算,它只是把检测结果和农学经验规则做了一个自然语言组织。但正是这一步,让系统的输出从“程序员的JSON”变成了“管理者能直接执行的工作指令”。

5.2 Prompt设计示例与上下文拼接

DeepSeek调用我走的是标准的大模型API接口,核心在于Prompt模板的设计。下面是我调整过几轮后比较稳定的一个模板结构:

你是一个生菜种植管理助手。下面是一次视觉检测的结果汇总: 【本次检测数据】 总株数: 58 幼苗期(seedling): 6株, 占10.3% 莲座期(rosette): 18株, 占31.0% 结球期(heading): 24株, 占41.4% 成熟期(mature): 10株, 占17.2% 平均置信度: 0.91 【历史趋势】 上一次检测: 莲座期48%, 结球期32%, 成熟期8% 上两次检测: 莲座期61%, 结球期22%, 成熟率5% 【你的任务】 结合生菜生长规律,输出一段不超过150字的管理建议,要求: 1. 先判断当前整体处于哪个生长阶段 2. 分析生长速度是否正常 3. 给出调整温度、光照、水肥的明确建议 4. 避免输出空泛的套话,直接给出可执行结论

有一个细节值得注意:大模型分析的质量完全取决于你给它的数据精度。如果YOLO检测本身置信度很低、或者类别判断经常出错,那DeepSeek再会组织语言也会输出误导性的建议。所以在上游检测结果的封装阶段,我会做一个过滤——置信度低于0.6的检测框不计入统计;某张图里同一个类别检测数量异常少或异常多时,标记为“该图检测结果可信度较低”,并在报告中提示用户复核。

5.3 降级方案与成本控制

任何大模型接口都可能有服务不稳定或响应超时的时候,农业现场又不能因为AI服务挂了就停摆。我做的降级方案很简单:DeepSeek调用设置三秒超时,超时或返回异常时,走本地模板生成报告——就是把阶段占比数据套进几个写死的模板里,生成类似“当前结球期占比42%,较上次上升10个百分点”这种描述性报告。虽然不比DeepSeek的分析有洞察力,但至少不会让前端空着。

成本控制上,我建议控制Prompt输出长度,设置max_tokens为200左右就足够生成一段精炼的管理建议。增加一个开关按钮,让用户选择“仅检测”还是“检测+分析”。对只需要看数据的场景,不必每次都调大模型,能省不少接口费用。

6. 落地过程中最容易被忽略的五类问题

整个系统从模型训练到Web界面跑通,中间会遇到不少“文档里没有但实际必踩”的坑。我按问题出现的频率和破坏力排个序,选五类最典型的说。

6.1 模型层面的误检和漏检

生菜叶片的光泽感很强,尤其是水培环境下叶片表面经常有一层水膜,在灯照下会形成高光反光。高光区域在图像上表现为亮斑,YOLO有时候会把这种亮斑识别成独立目标。解决办法有两个:训练数据里加入一些带水珠、带高光的负样本图片,这些图片里不含生菜,但背景光照和正样本类似,模型会学会“亮斑不等于目标”;另外把置信度阈值从默认的0.25调到0.4到0.45,也能过滤掉一部分低分误检。

漏检集中在密集种植场景。水培架上一排生菜首尾相连,叶片互相遮挡,靠边的几棵经常被当作背景。对这个问题,数据增强阶段我把Mosaic开启比例提高了一些,同时增加了密集场景的标注数量,模型对被遮挡目标的容忍度会有明显改善。如果你不想重新训练,可以在后处理阶段做一次简单的重叠框合并,也能救回一部分被漏检的目标。

6.2 前后端接口联调时的“坑”

这个项目跨了Python和Java两个技术栈,接口联调时最常见的坑是字段命名不一致。比如Python侧返回的字段是class_name,Java侧解析时用了className,两边都没错,但就是接不上。我的习惯是接口文档第一版就定死字段名,全用下划线风格,然后在SpringBoot的DTO里加@JsonProperty注解做映射,而不是各写各的字段再联调时对暗号。

另一个坑是图片URL的访问权限。如果SpringBoot保存了图片,但Python服务所在的机器访问不到这个URL(比如两张网卡、判断彼此网络不可达),推理服务就永远超时。开发环境里最简单的方式是让Python服务接收图片的二进制内容,而不是URL,绕开共享存储的依赖。部署到同一台服务器或者同一个内网环境后,再改回URL模式。

6.3 数据记录与统计口径的统一

这个坑属于业务层面的。数据库里记录检测结果时,要明确一个统计口径:如果一张图里检测到40个目标,其中3个置信度低于0.5,这3个算不算有效目标?如果不做过滤,后续所有统计都会包含这部分噪声。我建议在入库前就做一次置信度过滤,并单独保存过滤规则版本,这样以后规则变了也能追溯历史数据为什么和当前统计数不同。

生菜个体还会在两次拍摄之间被移走或补种,导致历史趋势曲线出现“成熟期数量下降但总株数没变”这种异常。严格的做法是记录每一株生菜的位置信息,跨批次比对时做空间匹配,但这对于大多数场景来说过于复杂。我采用的是简化方案:前端趋势图同时展示“总株数”曲线,让管理者自己判断那根线条的波动是真实生长导致的还是种植操作导致的。

6.4 部署环境差异导致的推理崩溃

训练模型时用的是高性能GPU,部署到现场工控机以后可能变成CPU推理甚至低端GPU推理。最直接的后果是推理时间从几十毫秒变成几百毫秒甚至几秒。如果你的前端同步等待检测结果,这个体验会非常糟糕。

我踩过一次很深的坑:在现场机器的CPU上,YOLOv8s单张推理耗时要1.8秒,理由是摄像头抓拍的图片分辨率太高(4000×3000),前处理缩放就占了大半时间。后来在Python服务里加了图片长边压缩到1280像素的逻辑,推理时间降到了0.6秒左右。这个优化很简单效果却很明显。压缩图片对检测精度的损失可以忽略不计,因为生菜目标本身足够大,不需要保留4000万像素的细节。

6.5 DeepSeek分析结果的“幻觉”问题

大模型生成的管理建议偶尔会出现数据对不上的情况,比如明明检测结果里mature占比是0,它却在报告里说“部分植株已成熟可采收”。原因是Prompt里的数字太多,模型在组织语言时把百分比记串了。我解决这个问题的方式是加一条硬性指令:报告中的所有百分比必须与输入数据完全一致,如果输入数据为0则不得提“部分”。这类指令虽然不能100%消除幻觉,但出现的频率会明显下降。

更稳妥的做法是让DeepSeek只生成结论性建议,严格不生成数据描述。数据描述部分由后端代码用模板拼接,保证永远准确。比如模板先写死“结球期占比42%,较上次上升10个百分点”,DeepSeek只负责给出温度、光照、水肥的管理建议。这样做以后,报告里再也没有出现过数据矛盾的情况。

就我个人体会而言,这个项目最耗时的部分不是写SpringBoot代码,也不是训练模型,而是把数据口径、图像拍摄规范和置信度阈值这些“业务规则”定清楚。技术链路其实都是成熟组件——YOLO负责看,SpringBoot负责管,DeepSeek负责说,真正决定系统好不好用的,是你看完检测结果之后准备怎么处理那棵生菜。

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

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

立即咨询