基于YOLOv11的草莓成熟度识别检测系统:从数据集到部署全流程解析
做草莓成熟度识别这个项目起因其实挺实在的——一个做果园的朋友跟我抱怨采摘高峰期人手完全不够用十几个工人连轴转还是赶不上成熟的速度而且“成熟度”这东西全靠肉眼判断每个人标准都不一样同一批果子里经常混进七八分熟甚至过熟的装箱发货后客诉不断。当时我就在想能不能用视觉方案把“成熟度判断”这件事标准化、自动化至少在分拣环节能省掉一大半人力成本。于是就有了这套基于YOLOv11的草莓成熟度识别检测系统包含完整的数据集标注流程、模型训练、UI界面、登录注册功能以及可直接运行的Python源码和训练好的模型权重。这篇文章就把整个项目的完整链路拆开讲清楚包括为什么选YOLOv11、数据集怎么构建和标注、训练参数怎么调、模型如何评估和导出以及UI界面和登录注册模块是怎么集成出来的。最后再分享几个我在实际测试中踩过的坑和对应的解决思路。不管是打算从零跑通一个YOLO检测项目的新手还是想把这个项目改造成果园分拣工具的开发者这篇内容应该都能给你一些参考。1. 为什么是草莓成熟度识别为什么是YOLOv111.1 成熟度检测的落地场景远比想象中多很多人看到“草莓成熟度识别”第一反应是农业科研项目离实际生活很远。但实际上这个需求在产业链里有好几个明确的痛点场景。第一个场景是采摘前的园区巡检。草莓是典型的阶梯式成熟作物同一垄地里的果子成熟时间能差出两三天。靠人一垄垄蹲下去翻叶子找红果效率极低而且漏采率不低。如果有一个移动端检测设备对着草莓田扫一遍系统能实时标出哪些区域的成熟果密度足够值得安排采摘采摘计划的制定就能从“经验驱动”变成“数据驱动”。第二个场景是分拣车间的成熟度分级。很多规模化草莓基地的出货标准是“同一批次成熟度一致”但现在大量靠人工肉眼分选不同人对“八分熟”和“九分熟”的界定完全不同。视觉模型则稳定得多——同一个模型、同一套参数几千个果子的分级标准完全一致这就解决了“标准漂移”问题。第三个场景是电商发货的质检环节。线上销售的草莓对成熟度要求极高太生口感酸硬太熟运输过程中容易烂。传统人工抽检效率低而视觉检测可以做传送带上的全检不合格的直接在分拣环节剔除。这些场景共同指向一个结论成熟度检测不是伪需求而是真实存在、且有付费意愿的工程问题。1.2 YOLOv11的核心改进不是简单换个版本号选YOLOv11之前我其实也对比过YOLOv8和YOLOv10。先说结论YOLOv8是成熟稳重的选择但YOLOv11在特征提取结构上做了一个比较关键的调整——把原来的C3/C2f模块换成了C3k2同时在网络深层引入了C2PSA模块。C3k2的本质是CSP瓶颈结构的变体区别在于它用了两个更小的卷积核在保持感受野的前提下降低了计算量。这对草莓检测实际意义很大因为草莓果实密集、边缘不规则浅层特征需要捕捉轮廓和纹理细节C3k2在浅层保留细粒度信息的能力比之前的C2f更强。C2PSA则是把注意力机制放进了深层特征提取处。草莓检测一个常见的难题是果实颜色和背景比如红色果子和暗色土壤、枯叶容易混淆深层引入注意力后模型能更聚焦在“果实区域”而抑制背景干扰这对提升小目标和遮挡场景下的检测率很有效。另外YOLOv11在训练策略上也有变化比如默认的anchor-free检测头做了优化对重叠目标的回归更稳定。实际测试下来同样条件下YOLOv11的mAP50比v8能高1-2个百分点推理速度基本持平。选它最大的理由其实是在精度和速度的平衡点附近它比上一代更靠前。1.3 项目整体架构模型 界面 数据闭环这个项目的定位是“可直接运行的完整系统”不是只有模型的demo。整体架构分成四层第一层是数据层包含草莓成熟度数据集标注格式为YOLO格式的txt文件按类别分为未熟、半熟、成熟、过熟四个等级。第二层是模型层基于YOLOv11训练权重负责对输入图像进行推理输出每个果实的边界框和类别。第三层是逻辑层用Python实现图像预处理、结果解析、统计汇总等核心逻辑。第四层是交互层基于PyQt5开发了登录注册界面和主检测界面让不懂技术的人也能直接用鼠标操作完成检测。这个架构的好处是每一层都可以独立替换。数据集不够就扩充数据模型精度不够就换更大的预训练权重界面不好用就单独改UI逻辑不会牵一发而动全身。2. 数据集决定模型上限的关键一步2.1 成熟度分级标准怎么定才不会让模型困惑做检测项目最忌讳的是类别定义模糊。一开始我也走过弯路一开始只分“熟了”和“没熟”两类训练完发现模型在七八分熟的果子上反复横跳。后来查资料加上自己观察把成熟度重新定义为四个等级每个等级都有明确的视觉特征依据类别视觉特征判断依据未熟果皮绿色或浅白色果实较硬几乎无红色成分果籽颜色偏浅半熟粉红色为主局部带白或绿红色面积约占果面的30-60%果蒂附近偏白成熟鲜红色覆盖大部分果面红色面积超过80%果蒂边缘有少量白色过渡过熟深红色或暗红色果皮发软红色面积接近100%部分出现水渍状或光泽减弱这四类边界清晰模型容易学。需要注意的一点是类别不能纯靠“颜色深浅”来划因为不同品种的草莓基调色就不一样。如果数据集里混了多个品种红颜草莓的“成熟”和章姬草莓的“成熟”颜色差异很大容易被模型学成“按品种分类”而不是“按成熟度分类”。所以数据采集时最好聚焦单一品种或者按品种分别训练。2.2 数据采集光照、角度、遮挡三大变量的处理采集图像时最常见的问题是“看起来拍了很多张但真正有效的没多少”。因为草莓检测的场景变量很大如果数据太单一模型训练完了换个光照环境直接崩。我的经验是采集时要刻意覆盖几个维度光照条件晴天自然光、阴天散射光、大棚薄膜透射光、人工补光灯下至少四种环境各拍一部分。不要全用手机在晴天中午拍的图——那种图颜色饱和、曝光均匀模型学完换到早晨或傍晚光线下马上失灵。拍摄角度俯拍果实正上方采摘机器人的视角、侧拍贴近水平线人工手持设备视角、以及斜45度。不同角度下果实的形状和遮挡关系完全不同只用一个角度训练会导致泛化能力很差。距离与密度单果特写、两三颗果子的中景、一整垄多果的大景。远距离小果是检测难点特意加大远景占比能逼着模型去学小目标特征。遮挡情况草莓结果位置低经常被叶子遮挡一半果实之间还会互相重叠。如果训练集里全是“干净”的暴露果实到了真实场景必然大量漏检。我建议遮挡样本占训练集20-30%。补充一个采集技巧同一个果实可以隔两三天再去拍一组刚好覆盖它从半熟到成熟的几个状态。用这个方法扩数据不需要刻意找不同植株效率高得多。2.3 标注细节YOLO格式不复杂的几个要点数据标注用的是LabelImg开源免费安装方便。YOLO格式的标注文件是txt文件每行内容为class_id x_center y_center width height注意四个坐标值都是归一化的除以图片宽高后的比例数值范围在0到1之间。标注工具会自动计算但要知道原理方便排查问题。标注时有两个操作规范值得强调边界框要紧贴果实主体。草莓果实的形状是圆锥形边界框要包住整个果实但不要把果蒂和叶子框进去太多。如果框得过大模型会把叶子特征学进来推理时容易误检。合适做法是框住果实的最宽处和最尖端上下左右各留2-3%的余量。被遮挡严重的果实要不要标我的原则是遮挡面积小于30%的正常标注大于50%的不标中间的酌情。因为完全不标会让模型在那些位置产生“背景空白”标了又可能引入大量噪声。30%-50%之间的样本如果边界大致可辨就标上让模型学会“看到半个果子也能认出来”。标注完还要做一步“可视化检查”——用标注工具重新加载图片把矩形框画出来一张张过一遍。这一步不能省我第一轮标注完了有差不多5%的标签框偏移肉眼不检查直接训练损失曲线抖得没法看。2.4 数据增强参数与划分比例数据增强是白嫖样本量的常规手段。YOLOv11训练时内置了Mosaic增强、随机仿射变换、色彩空间调整等策略这些都在ultralytics的配置里默认开启基本够用。但有个参数要根据草莓的特点调整hsv_r和hsv_s。草莓成熟度识别非常依赖颜色信息默认的色调偏移设置可能会让成熟和过熟这两类的色差被拉平导致分类混乱。建议把hsv_h从默认值稍微调低hsv_s可以稍微调高让模型更敏锐地捕捉红色饱和度的差异。数据集划分上我按7:2:1拆成训练集、验证集和测试集。这里有个容易踩的坑如果同一株草莓的不同果实出现在训练集和验证集里模型实际上是在“背答案”验证集分数虚高。理想做法是按植株或按垄划分让同一植株的照片尽量落在同一个集合里这才是真正考验模型泛化能力的划分方式。3. 从环境搭建到模型训练让YOLOv11真正跑起来3.1 环境配置里的几个关键版本环境配置是这个项目卡住新手的第一道坎。我用的是Python 3.10 PyTorch 2.1 CUDA 11.8组合这套组合在Windows和Linux上都验证过能稳定跑通。推荐用conda建虚拟环境不要直接在全局环境里装conda create -n yolo11 python3.10 conda activate yolo11 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics pip install pyqt5 opencv-python pillow这里有个容易踩的坑ultralytics库对PyTorch版本有最低要求如果装了PyTorch 1.x版本导入时直接报错。另外opencv-python和opencv-contrib-python不要同时装两个包互相覆盖会导致cv2调用异常。检测显卡是否可用用下面命令python -c import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))如果输出是False说明CUDA版本和PyTorch对不上CPU也能跑但训练速度会慢几十倍250张图的一个训练轮次要跑十几分钟根本没法迭代。3.2 数据集目录结构与配置文件项目目录结构直接决定训练命令能否跑通建议按ultralytics的标准布局StrawberryRipeness/ ├── images/ │ ├── train/ # 训练图片 │ ├── val/ # 验证图片 │ └── test/ # 测试图片 └── labels/ ├── train/ # 对应的txt标注 ├── val/ └── test/注意images和labels两个目录必须平行放在同一父目录下文件名要一一对应。图片是001.jpg标注就必须是001.txt大小写也要一致否则训练时找不到标签直接跳过。配置文件data.yaml这样写path: D:/YOLO/strawberry/StrawberryRipeness train: images/train val: images/val test: images/test nc: 4 names: 0: unripe 1: semi_ripe 2: ripe 3: overripe这里的path路径建议写绝对路径尤其做UI系统时项目会被整体打包移动用相对路径经常因为当前工作目录不对导致找不到数据。nc是类别数names列表的顺序必须和标注文件中的class_id一一对应顺序错了整个模型就废了。3.3 训练参数的选择逻辑与启动命令训练参数的设置逻辑是这样的先用小模型快速验证数据没毛病再用大模型跑最终效果。第一轮用yolo11n.pt预训练权重跑50个epoch此时批大小可以给16输入分辨率用640。如果loss曲线能正常下降、验证集mAP能稳定上涨说明数据和配置没问题。这个时候观察一下各类别的分类情况如果某两个类别一直混淆回到上一步标注阶段去补样本或修正标注而不是急着调参。正式训练的启动命令yolo train datastrawberry.yaml modelyolo11s.pt epochs200 imgsz640 batch16 device0 patience30几个关键参数的解释epochs设200但配合patience30做早停——验证集mAP连续30轮不涨就自动停省时间。batch设16取决于显存大小8G显存跑16没问题如果显存不足报CUDA out of memory就降到8或4。imgsz用640这是精度和速度的平衡点想要更高精度可以提到960但显存占用和训练时间都会明显上升。有个参数很容易被忽略但在这个项目里很关键如果想用预训练权重迁移学习模型会自动冻结backbone之外的所有层在自定义数据集上重新训练。这没有问题。但在迭代调参时要注意默认ultralytics会下载预训练权重到本地缓存如果你的网络环境访问外网受限会卡在下载阶段解决方案是手动下载yolo11s.pt文件放到项目目录并用modelyolo11s.pt参数指定绝对路径。3.4 训练过程中的监视与过拟合判断训练开始后在控制台会吐出类似这样的日志Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 1/200 3.21G 1.088 1.045 1.126 24 640 15/200 4.87G 0.821 0.694 0.958 36 640我一般不看单个epoch的绝对数值也不看训练集loss重点盯验证集指标的变化趋势。如果train_loss持续下降但val_metrics不再上涨甚至掉头往下就说明开始过拟合了。这时候即使patience没触发也可以手动停掉回退到最优的那个checkpoint。训练结束后runs/detect/train目录下会自动生成weights/best.pt和weights/last.pt。last.pt是最后一个epoch的权重如果训练结束时已经过拟合last.pt质量可能比best.pt差很多所以做UI系统时默认加载的必须是best.pt别搞混了。4. 训练不是终点模型评估、导出与部署4.1 评估指标怎么读以及要不要信mAP训练完第一件事不是急着接UI而是跑一遍验证集用指标确认模型真实水平。yolo val modelruns/detect/train/weights/best.pt datastrawberry.yaml输出里重点看六个数值mAP50、mAP50-95、precision、recall以及每个类别的单独mAP50。mAP50是IoU阈值0.5下的平均精度它对定位精度的要求相对宽松只要框大致框中了就算对。mAP50-95则苛刻得多它取IoU从0.5到0.95每隔0.05的均值。在草莓这个场景里分拣机器人需要精准定位果实来做采摘所以mAP50-95不能太低我训练出来的模型这一项大概在0.62左右属于比较理想的范围。看单类别的mAP更关键。如果总mAP挺高但某一类特别低比如overripe只有0.5而其他三类都是0.85说明过熟草莓的样本量不足或样本颜色特征被其他类别干扰。这时候去补过熟类别的数据是最有效的方案。4.2 模型导出从PyTorch到ONNX再到部署格式训练好的best.pt是PyTorch权重格式直接在Python里调PyTorch推理没问题。但要做UI系统或部署到生产环境最好导出成ONNX格式这样不依赖PyTorch环境、推理速度更快、还能进一步转成其他框架的模型。导出命令yolo export modelruns/detect/train/weights/best.pt formatonnx dynamicTrue imgsz640导出后可以用onnxruntime直接跑推理import onnxruntime as ort import cv2 import numpy as np session ort.InferenceSession(best.onnx) input_name session.get_inputs()[0].name img cv2.imread(test.jpg) img cv2.resize(img, (640, 640)) img img[:, :, ::-1].transpose(2, 0, 1) # BGR转RGB并调整通道顺序 img np.ascontiguousarray(img, dtypenp.float32) img / 255.0 img np.expand_dims(img, axis0) outputs session.run(None, {input_name: img})需要排除一个常见问题不要试图直接对图片做cv2.resize然后送入模型就完事YOLO的输入需要归一化到0-1的float32格式同时图片通道顺序要转成RGB否则推理结果会出现大量错误检测框。4.3 边界场景实测一个不漏的完整测试清单模型好不好不能只看数据集测试集真实场景过一遍才有说服力。我整理了一个测试清单每项都实际跑过强光直射下草莓表面反光严重检测框是否会抖动傍晚弱光整体亮度偏低是否漏检雨滴附着在果实表面边缘纹理被破坏分类是否正确果实被叶子挡住大半模型能否依据可见部分保持检测三四个果实紧挨在一起nms后处理能否分离出正确数量的框实测下来强光和弱光都对结果有影响但都在可接受范围。最大的问题出现在“果实紧挨在一起”的场景后处理经常漏掉重叠严重的果实。这一点后面专门讲优化方案。5. 系统集成UI界面、登录注册与检测流程的完整闭环5.1 用PyQt5搭界面还是用Web为什么选桌面端做UI方案时我认真比较过PyQt桌面应用和Flask Web方案。结论是选PyQt5更适合这个场景。原因有三点第一果园分拣环境通常网络不稳定本地桌面应用不需要依赖服务器断网也能正常使用。第二PyQt5的界面交互响应速度快摄像头实时检测每一帧都能立即显示Web方案受限于浏览器和网络传输延迟体感明显卡顿。第三部署简单——直接把整个项目文件夹拷到目标电脑装好Python环境就能跑不需要运维知识。不过PyQt5的开发体验相比Web确实繁琐一些布局用代码写、调试要反复运行速度慢。这里有个实用建议先用Qt Designer拖界面再转换成py代码比纯代码手写布局省一半时间。5.2 登录注册界面不只是装饰是真能用的身份验证登录注册这块很多人觉得是凑功能但我做成了真正能用的身份验证模块。用户信息存在本地SQLite数据库文件中用Python内置的sqlite3操作不需要单独装数据库服务。核心逻辑分两部分密码存储不能存明文。用hashlib的sha256加上随机盐值做哈希import hashlib import os def hash_password(password: str, salt: str None) - str: salt salt or os.urandom(16).hex() hash_obj hashlib.sha256() hash_obj.update((salt password).encode(utf-8)) return f{salt}${hash_obj.hexdigest()}每次注册新用户时生成一个新盐登录时用数据库里存的盐重新计算哈希比对字符串是否一致。这样即使数据库文件被拷走也无法直接看到明文密码。注册时检查用户名是否已存在登录时区分“用户名不存在”和“密码错误”两种提示避免信息泄露。界面逻辑很简单启动程序先弹出登录窗口输入用户名密码校验成功后关闭登录窗口、打开主界面。主界面的“退出登录”按钮反向操作回到登录窗口。为了不增加复杂度界面切换用了窗口的show和close方法而不是复杂的页面栈管理。5.3 主检测界面流程清晰、反馈直接主界面布局我按照“左侧操作、中间预览、右侧结果”的三栏设计左侧放按钮选择图片、开始检测、保存结果、摄像头模式。中间是图片预览区检测前显示原图检测后显示带矩形框和类别标签的标注图。右侧是结果统计区用表格列出当前图片里每个类别的果实数量并计算成熟度占比。检测逻辑核心代码from ultralytics import YOLO model YOLO(best.pt) def detect_image(image_path): results model.predict( sourceimage_path, conf0.25, iou0.45, imgsz640, verboseFalse ) result results[0] boxes result.boxes.xyxy.cpu().numpy() cls_ids result.boxes.cls.cpu().numpy().astype(int) confs result.boxes.conf.cpu().numpy() return boxes, cls_ids, confsconf参数设0.25是经验值。设太高比如0.5会漏掉一些被遮挡但确实是果实的检测框设太低比如0.1会冒出大量假阳框。iou设0.45用于重叠框的抑制草莓重叠场景下调到0.4效果更好能减少相邻果实的“粘连”。可视化部分直接把边界框画在图上for box, cls_id, conf in zip(boxes, cls_ids, confs): color (0, 255, 0) # 按类别分配不同颜色 cv2.rectangle(img, (int(box[0]), int(box[1])), (int(box[2]), int(box[3])), color, 2) label f{class_names[cls_id]} {conf:.2f} cv2.putText(img, label, (int(box[0]), int(box[1]) - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)5.4 摄像头实时检测三行核心代码摄像头模式本质上就是不断“截帧—推理—显示”的过程。用QTimer定时器每100毫秒触发一次def update_frame(self): ret, frame self.cap.read() if not ret: return results model.predict(sourceframe, conf0.25, iou0.45) annotated results[0].plot() # ultralytics自带可视化 qt_img self.convert_cv_to_qpixmap(annotated) self.label_video.setPixmap(qt_img)这里的convert_cv_to_qpixmap是把OpenCV的BGR图像转成QImage再转成QPixmap给QLabel显示注意也要先转RGB。100毫秒间隔相当于10FPS的刷新率在CPU上也能跑得动如果需要更高的流畅度可以把imgsz降到480或者改用更小的yolo11n模型。6. 实测中的坑与优化方向不是调完就能上生产6.1 重叠果实漏检最影响体验的难点这个坑我在测试阶段反复遇到也是整个项目里最有代表性的一类问题。草莓果实坐果位置密集经常三四颗果实挤在一起边界互相重叠YOLO的NMS非极大值抑制后处理会把重叠区域的检测框合并成一个或干脆丢掉一个导致漏检。排查后发现根因有两个层面。第一层是模型本身重叠目标在特征图上对应的是一个局部区域如果没有大感受野支撑模型很难把两个紧挨的目标各自回归出独立的框。第二层是后处理默认NMS阈值0.45对高密度场景偏严格稍微重叠就抑制掉一个框。解决方向我试过两个一是训练时把Mosaic增强强度调高让模型在训练中更多看到拼接重叠的样本二是推理时把conf从0.25降到0.2同时把iou调到0.4让后处理更宽松一些。对轻中度重叠的场景效果改善明显但极度重叠果实互相遮挡超过60%时仍然存在漏检。如果要彻底解决需要在数据层面补充大量极端重叠的标注样本让模型专门学习这种模式。6.2 过拟合问题训练集精度虚高带来的假象第二类常见问题是过拟合。有一次训练完看结果训练集mAP50到了0.98验证集只有0.74差距明显。这种模型拿到真实场景里表现还要再打折。排查发现是训练数据里同类别的图像高度相似——同一批草莓在不同角度拍了十几张导致模型记住了这批果子的纹理细节而不是学类别的通用特征。解决办法是在数据采集阶段就控制重复度同一个果实最多保留2-3张差异明显的照片训练时增强锚定参数hsv_h到0.015左右通过色彩抖动破坏模型对固定颜色的依赖学习率从默认的0.01稍微调低到0.005让模型更新更平缓减少对训练集噪声的过拟合。改进后验证集mAP50稳定在了0.83左右。6.3 推理速度优化CPU部署的几个实用手段如果目标机器没有独立显卡CPU上跑模型推理的速度会直接影响使用体验。CPU推理我从三个方向做了优化第一是模型换小。同一个量级下yolo11n比yolo11s快大约一倍精度损失大约3-5个百分点。在成熟度分级这个场景里如果环境光照相对稳定小模型的精度损失完全可以接受。第二是导出ONNX后用onnxruntime推理比原生的PyTorch eager模式快30-50%。openvino格式还可以更快但配置成本高一些适合在Intel CPU上部署的场景。第三是降低输入分辨率到480。草莓果实不算特别小480分辨率下中近景的检测精度损失很小单张推理时间从大约80ms降到45ms左右。对于摄像头实时模式这个提升非常可观。6.4 后续演进从检测到自动化分拣目前的系统定位是“人工辅助工具”也就是人看屏幕决定怎么分拣。再往下走的话一个自然的方向是和分拣执行机构对接检测结果输出每个果实的坐标和类别机械臂根据这些数据做自动化抓取和分类。另一个方向是在移动端平台部署——把模型转成ncnn格式跑在嵌入式设备上做成手持巡检终端这样农户在田垄间就能完成成熟度巡检不需要扛一台电脑。这两个方向都建立在现有检测核心不变的基础之上扩展空间还挺大的。我个人的体会是这类视觉检测项目的真正难点往往不在模型本身而在数据质量、系统集成和真实场景适配。跑通一个demo只需要几个小时但把它变成一个稳定可用、别人能直接操作的工具每一步都需要大量细节打磨。这个草莓成熟度检测项目目前已经能在本机稳定运行也完成了真实果园图片的批量测试后续我打算继续完善重叠果实的处理逻辑并把导出格式往嵌入式方向优化。如果你也在做类似的农业视觉项目欢迎在实际测试中发现新的问题毕竟这类场景里的坑远比想象中要多。