简介这套基于YOLO11的西红柿检测资源以Python和PyTorch为技术栈面向目标检测初学者及农产品视觉应用开发者解决西红柿图像定位与识别问题。压缩包内容丰富共1322个文件、约143.79MB覆盖从数据整理到模型部署的全链路其中包含656张JPG真实样本、326个TXT标签、321个XML标注配套YAML配置、Python脚本和预训练PT模型。内置数据集划分脚本01划分数据集.py可将原始图像转为YOLO格式并生成train.txt、val.txt和data.yaml02train.py负责启动训练03pyqt.py提供PyQt可视化界面支持加载图片后一键检测同时带有依赖列表与运行说明环境搭建更为顺畅。此外包内还包括训练输出事件文件与结果指标便于分析模型收敛情况。总体代码结构清晰适合在此基础上二次修改与扩展。目前已有102人学习适合希望以真实数据集快速上手YOLO11的开发者。1. 西红柿检测为什么要换yolo11成熟期采收窗口背后的漏检账我在温室里做过西红柿目标检测算法落地不是贴一张图画几个框那种demo而是给采摘机器人和产量估测系统数清楚“这一垄到底有多少颗能被采的果”。用yolov8做的时候演示集上挺好看一到成熟期采收窗口就现原形果实重叠、叶片挡掉半边、青果和叶子颜色搅在一起漏检率一上来后台统计数字就崩。换到yolo11之后配合一套整理干净的数据集才把这个问题按住。这个标题里真正值钱的不是那行训练代码而是“含数据集.zip”这几个字——数据怎么解压、怎么转格式、怎么划分直接决定你后面三分之二的成败。这篇笔记就按我自己的落地路径写适合温室管理、采摘机器人、果实计数这类做视觉的工程师照着复现。2. 先伺候数据解压与目录规划、VOC转YOLO、按垄划分数据集2.1 拿到zip先别急着训练目录结构和中文路径是第一个坑常见做法是先从zip开始。我用Linux和Windows都跑过踩得最狠的一个坑是路径数据集压缩包解压到桌面上外层文件夹叫“我的数据集”里面图片文件名带中文或者空格yolo11加载的时候直接报 dataset not found或者找到了图但标签对不上。所以第一步是解压到一个纯英文、无空格的路径下先把目录结构看明白。unzip -q tomato_dataset.zip -d tomato cd tomato find . -maxdepth 2 -type d逻辑说明-q是安静模式解压时不在终端刷一大片文件名-d tomato指定解压目标目录。第二行进入目录第三行只看两层子目录是为了快速确认数据集的总体布局一般会是一个images文件夹配一个annotations文件夹images下按train/val/test分好annotations里可能是VOC的xml也可能是COCO的json。参数说明Windows下如果已经解压到中文路径最简单的处理是剪切到D:\tomato这种纯英文盘符路径下。很多人忽略这一点yaml里写路径时用了反斜杠ultralytics在Windows下解析时也容易出问题我一般统一写成绝对路径且用正斜杠比如D:/tomato/images/train。2.2 写一次性的VOC转YOLO脚本把坐标算清楚yolo11训练用的是txt标签格式每行是类别id、中心点x、中心点y、框宽、框高全部对图片宽高做归一化。如果zip里给的是VOC xml就要做一次转换。这个脚本我现在还留着每次接新数据集都改改用。import os import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path, out_path, class_map): tree ET.parse(xml_path) root tree.getroot() w float(root.find(size/width).text) h float(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_map: continue box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) bw x2 - x1 bh y2 - y1 if bw 0 or bh 0: continue # 异常框直接跳过防止训练时loss变NaN cx (x1 x2) / 2 / w cy (y1 y2) / 2 / h bw_n bw / w bh_n bh / h lines.append(f{class_map[name]} {cx:.6f} {cy:.6f} {bw_n:.6f} {bh_n:.6f}) if lines: Path(out_path).parent.mkdir(parentsTrue, exist_okTrue) Path(out_path).write_text(\n.join(lines), encodingutf-8) class_map {tomato_ripe: 0, tomato_green: 1} xml_dir tomato/annotations/train txt_dir tomato/labels/train for xml_file in os.listdir(xml_dir): if xml_file.endswith(.xml): voc_to_yolo( os.path.join(xml_dir, xml_file), os.path.join(txt_dir, xml_file.replace(.xml, .txt)), class_map )逻辑说明这个脚本把VOC的绝对坐标转成yolo需要的相对坐标同时过滤掉宽高为0或负数的异常框。if bw 0 or bh 0: continue这行是我后来加的因为真实数据集里偶尔会出现xmin xmax的脏标注不处理轻则损失异常值重则loss飘成NaN。参数说明class_map的类别顺序必须跟后续data.yaml里的names完全一致第一个类别是tomato_ripe红果第二个是tomato_green青果。如果你做的是单纯产量计数不区分成熟度那就只留一个类别tomato映射成0其余代码不用改。2.3 按“垄”划分数据集不是按图片随机切很多教程教你把图片随机分成train/val这个做法在照片类数据集上没问题但温室视频抽帧数据集上会翻车。因为同一串西红柿会出现在连续几十帧里随机划分后同一颗果实的微变版本既在训练集又在验证集mAP虚高得离谱真机一跑立刻现原形。我一般会先看文件名有没有规律比如camera1_row3_20240701_140000_001.jpg这种camera1_row3就是物理位置。按位置分组整组划入训练或验证才能模拟“模型没见过这垄西红柿”的真实场景。这个步骤没有通用脚本我通常是先把文件名里的垄号提出来再按垄分配import os from collections import defaultdict img_dir tomato/images/train grouped defaultdict(list) for name in os.listdir(img_dir): row_id name.split(row)[1].split(_)[0] # 从文件名里提取垄号 grouped[row_id].append(name) train_groups [g for i, g in enumerate(grouped.values()) if i % 5 ! 0] val_groups [g for i, g in enumerate(grouped.values()) if i % 5 0]逻辑说明这里用“每隔5组取1组做验证”的思路保证验证集覆盖多垄又不会和训练集重叠。如果原始文件名没有分组信息那就只能按拍摄时间戳分段把前80%时间段做训练、后20%做验证。参数说明i % 5 ! 0表示每组数据有80%概率进训练集这个比例可以根据总垄数调整。垄数少就改成i % 4多就i % 8总目标是让验证集至少包含两整垄的影像。2.4 数据增强不要预先存文件让yolo11在训练时按帧生成我看到很多新手会把增强图预先存到硬盘上跑出几千张重复图片再进训练。这个做法最明显的害处是增强图之间高度相关一不小心就和验证集撞在一起数据泄漏后模型表现和真实场景脱节。yolo11自带的光学增强已经很强建议让它在训练阶段动态生成。# data_augment.yaml mosaic: 0.8 # 概率从默认的1.0降到0.8温室单果太小马赛克太狠反而学不到 hsv_h: 0.02 # 色相偏移小一点避免红果变成橙果 hsv_s: 0.6 # 饱和度拉大增强对温室光照变化的适应 close_mosaic: 12 # 最后12个epoch关闭马赛克增强让模型稳定下来逻辑说明温室场景的光照特点是中午过曝、早晨和傍晚偏暗、灯照时偏暖。hsv_s拉大能让模型不那么依赖饱和色hsv_h别给太大否则红果的色相漂移模型会把青果和红果混淆。close_mosaic是ultralytics提供的参数最后若干轮不吃马赛克样例回归到全尺寸目标上精调边界框。参数说明mosaic: 0.8的比例是给单类别小目标场景用的如果你的果实很大、一帧里只有三五个可以默认1.0。如果数据集本身只有500张我建议mosaic: 0.9以上因为马赛克能大幅扩充小目标的上下文多样性。3. 训练yolo11模型选型、数据yaml、四参数配方3.1 选yolo11n还是yolo11m先想清楚部署在哪yolo11的模型家族从n到x是体积和精度的权衡。做西红柿检测前先回答一个问题模型跑在哪如果是温室现场的嵌入式盒子比如RK3588或者K230这类带NPU的板子首选yolo11n量化后跑起来流畅。如果是后端服务器用yolo11m起步就好。别一上来就yolo11x训练慢、部署难收益在果实检测这个任务上并不明显。我用一个简单表格给你参考模型推理速度精度表现适用部署位置yolo11n最快小目标略弱RK3588 / 手机端yolo11m中等小目标均衡服务器 / 边缘盒子yolo11x最慢最好离线处理参数说明如果你的摄像头距离西红柿比较远单颗果实只占几十个像素n模型会吃力。此时宁可把输入分辨率提到960也不要贸然换x模型——它对算力的要求是指数级上升的。3.2 data.yaml写法路径、类别名、训练验证划分yolo11训练前需要一份数据集配置文件。它负责告诉框架图片在哪、标签在哪、类别叫什么。我一般把它放在数据集的根目录下跟data同级。# tomato/data.yaml path: D:/tomato train: images/train val: images/val names: 0: tomato_ripe 1: tomato_green逻辑说明path是数据集根路径train和val都是相对于根路径的相对路径。ultralytics在读取时会自动拼接。重点在于names的顺序和标签txt里的类别id一一对应顺序错了训练不会报错但推理结果会张冠李戴。参数说明如果你用两个类别那么标签txt里第一列只能是0或1。如果你的目标是产量计数而不是成熟度分类建议先只放一个类别tomato单类别模型的漏检率通常比双类别更低因为模型不需要在“红果/青果”之间做决策。3.3 训练超参配方mosaic、close_mosaic、imgsz、epochs怎么给训练命令写出来不算完真正影响结果的是那四个参数。这一版配方是我在几百张番茄图上跑过的稳定组合适合中低算力的单卡环境。yolo train modelyolo11m.pt datatomato/data.yaml \ epochs120 imgsz640 batch16 \ mosaic0.8 close_mosaic12 \ lr00.005 patience20逻辑说明modelyolo11m.pt会从ultralytics下载COCO预训练权重在COCO上见过球类和小目标的模型迁移到西红柿上有明显优势。epochs120在温室数据下是够的如果数据集只有二三百张建议加到150轮。imgsz640是最稳的起点它能平衡显存占用和检测精度。参数说明mosaic0.8前面说过了。close_mosaic12表示最后12个epoch停止马赛克增强此时模型从粗特征回归到细框精调这个参数能明显改善边界框的定位精度。lr00.005是针对迁移学习的如果换yolo11n学习率可以降到0.002防止小模型震荡。batch16按显存来如果GPU在12GB以下降到8也不要硬扛。3.4 少量数据时的另一条退路两段式训练如果你的数据集只有几百张且确实还没法补充标注不要把所有epoch都从头到尾跑。我建议两段式迁移学习先用冻结骨干的方式跑基础轮数再解冻全量微调。这条路径这几天我用过多次效果比直接全量微调稳定。yolo train modelyolo11m.pt datatomato/data.yaml \ epochs30 imgsz640 batch16 freeze10 yolo train modelruns/detect/train/weights/last.pt datatomato/data.yaml \ epochs90 imgsz640 batch16 \ mosaic0.8 close_mosaic12 lr00.003逻辑说明第一段freeze10把yolo11m骨干网络的前10层冻结住只训练检测头。这么做的好处是COCO预训练骨干已经能提取很好的纹理和边缘特征温室数据量太少时动骨干反而把已有特征覆盖掉了。第二段用第一次训练得到的last.pt做全量微调把骨干和检测头一起调整到西红柿的分布上。参数说明freeze10是控制冻结层数的参数对yolo11m来说冻结前10层是骨干的大部分。如果数据集在1000张以上可以直接跳过两段式。注意第二段的lr00.003要明显小于第一段微调阶段学习率过大容易把已经学好的检测头洗坏。4. 评估与部署从PR曲线到RK3588边缘盒子的完整链路4.1 验证阶段先别急着看mAPPR曲线和混淆矩阵更暴露问题验证命令本身很简单yolo val modelruns/detect/train/weights/best.pt datatomato/data.yaml逻辑说明yolo11验证阶段会自动计算mAP50、mAP50-95、精确率和召回率。但我在西红柿这类的单场景任务向来先打开results.png和confusion_matrix.png。看两点一是红果类别的P-R曲线是不是在召回率0.8附近出现明显拐点如果是说明模型漏检集中在遮挡果实上二是混淆矩阵里青果和红果之间有没有大面积互相误认。参数说明验证输出在runs/detect/val目录下PR_curve.png是核心输出。如果你发现mAP50在0.9以上但错检集中在“叶片被识别成青果”那问题不在数据量而在负样本帧缺失这个问题下一章专门讲。4.2 用一段推理脚本把现场效果的置信度分布打印出来验证集mAP只是纸上成绩真机部署前的最后一关是带阈值做推理。温室现场背景复杂我会先写一段脚本把结果落成JSON逐帧看置信度分布。from ultralytics import YOLO import json model YOLO(runs/detect/train/weights/best.pt) results model.predict( sourcelive_row1.mp4, conf0.45, iou0.5, imgsz640, saveTrue ) out [] for r in results: boxes r.boxes.xyxy.cpu().numpy().tolist() confs r.boxes.conf.cpu().numpy().tolist() clss r.boxes.cls.cpu().numpy().tolist() out.append({boxes: boxes, confs: confs, clss: clss}) with open(detect_results.json, w, encodingutf-8) as f: json.dump(out, f)逻辑说明conf0.45是置信度阈值低于这个分数的框直接丢弃。iou0.5是NMS的IoU阈值它的作用是把同一个西红柿上的多个重叠框合并成一个。对西红柿这类簇状生长的目标两个相邻果实框的重叠度经常超过0.5NMS阈值设太低会把两颗果并成一颗设太高又保留一堆重叠框。参数说明现场使用建议conf在0.45~0.55之间调。置信度调低漏检少了但叶片误检会多调高则反过来。iou我一般固定在0.5如果你发现同一个果实被反复框住说明重叠框太多把NMS阈值往0.4方向调。4.3 边缘盒子的转换链路pt转ONNX再转RKNN校准集别有玄学温室现场的农产品检测设备大多跑在带NPU的边缘盒子上最常见的是RK3588。这条链路就是yolo11训练出的pt权重导出为ONNX再用rknn-toolkit2转换为RKNN格式。转换本身不难难点在量化。yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640 opset12 # 拿到best.onnx后用rknn-toolkit2转成rknn量化方式选PTQ逻辑说明第一行把pt转成ONNX是部署的通用中间格式。第二行描述的是后续通用转换流程用RKNN工具加载ONNX做INT8量化输出rknn模型。这个转换过程为了减少模型体积会把权重从FP16压到INT8精度损失是必然的操作得好能控制在三个点以内。参数说明PTQ量化需要校准集这是整个部署链路里最容易被忽视的环节。校准集不是拿训练集增强图去凑数而是从现场采样的原始帧它们必须覆盖逆光、阴影、果实成熟度临界态这些极端场景。我一般从现场视频里抽500张画面做校准效果远胜于用1000张训练图。4.4 真机推理时letterbox的尺寸对齐别改错边缘盒子推理时输入图像会先被缩放到训练时的imgsz然后做letterbox也就是在上下侧补灰边保持宽高比。很多人部署时忘了统一这个行为训练是640部署时为了省算力改成512模型看的目标尺度完全变了小番茄直接消失。参数说明把ONNX或RKNN转换时的输出尺寸锁定在训练尺寸上不要省这几个毫秒。真机摄像头如果俯视角度大单颗果实偏小我建议直接imgsz960训练并在部署端保持960。这个改动对边缘盒子推理速度的影响在可控范围内但对小目标精度的提升是实打实的。5. 避坑清单西红柿检测最常见的五个现场故障5.1 训练mAP高得吓人温室里一数就翻车现象验证集mAP50到了0.95开开心心部署到温室结果统计出来的果实数量和人工清点差10%以上。原因这是典型的数据泄漏。上一章说的按帧随机划分同一串西红柿分别在训练和验证集里出现过“同一颗果实不同角度的照片”模型等于提前背过答案。解决把数据集按物理位置分组比如同一垄、同一相机视角作为一个整体划分。如果原始数据没有位置标签那就按拍摄时间分段划分让验证集完全是模型没见过的植株。改完划分后重训mAP可能掉到0.88但那个0.88才是真本事。5.2 青果被叶子误报成西红柿现象成熟红果检测不错但大量青果和枯叶被框出来后台误检率飙升产量估测直接不可信。原因青果和绿叶在HSV空间里的色相范围高度重叠模型学到的主要是“圆形的绿色物体”。如果你的类别定义里包含绿色未熟果正样本不足时它就会把叶片边缘的圆形区域也纳入置信范围。解决两个手段一起用。第一追加标注那些“长得像果实的叶片”作为负样本帧直接放原图不加框让模型在训练时看到“这个区域没有目标”第二把conf阈值从0.3提高到0.5现场跑一遍挑出置信度在0.4~0.5之间的误检补充进数据集后重训一轮。5.3 训练Loss中途变成NaN训练进度条全废现象训练跑到第20轮loss突然变成nan之后所有指标都归零。原因第一是标签里有宽高为0的框前面转换脚本里if bw 0 or bh 0: continue就是为了挡这个问题第二是学习率设太高小模型配大学习率时梯度爆炸。如果你换过lr00.01基本就是踩这个。解决先跑一轮数据校验检查labels文件夹里所有txt是否有0宽0高或坐标超出1的行。数据没问题就把lr0降到0.002批大小降到8重新启动训练。曾经有人问我能不能改BN动量来救那是把训练拉向更不稳的方向别这么干。5.4 mAP50很高但mAP50-95始终差一截现象mAP50在0.9左右mAP50-95只有0.45怎么加epoch都升不动。原因两个方向。一是标注框边界不一致同一个果实有人画紧贴轮廓有人多框了一圈茎模型在回归框时学到的标准是模糊的二是小目标占比高IoU阈值变严格后小目标的框稍微偏移一点就被判负。解决统一标注规范为“只包住果肉部分不包含果蒂和柄”并选同一名标注员重校一遍验证集。mAP50-95还有第二个增强手段把输入分辨率从640换到960小目标在更高分辨率下对应更多像素回归精度会明显改善。5.5 pt模型好好的转成RKNN后漏检翻倍现象在GPU服务器上跑ONNX没问题量化成RKNN部署到板子上小番茄大面积漏掉。原因量化校准集没有覆盖现场的“暗光、过曝、远处小目标”等尾部样本。RKNN做PTQ量化时权重阈值是根据校准集的激活分布决定的校准集都是亮堂堂的训练图模型对暗光帧的激活值落到量化范围外直接被削成0。解决从真实温室录制一整天视频从中抽取包含早中晚光照、逆光、远端小果的500~1000帧作为校准集。重新转换后对比量化前后在同一段现场视频上的mAP差值应该控制在2~3个点以内。有人追问“校准集要不要标注”答案是不需要量化只看特征的数值分布标注信息不参与这个过程。6. 漏检回放验证法把模型现场表现变成可回归的每日习惯模型部署后不是结束而是开始。我自己的习惯是至少每周做一次“漏检回放”录一段温室巡检视频让模型输出检测结果但把所有置信度低于0.6的框和没有任何框的历史帧单独存到一个文件夹里这个操作能快速暴露模型的盲区。回放脚本不长核心是输出一张csv表每行记录帧号、类别、置信度和该帧是否有低置信度目标from ultralytics import YOLO import csv model YOLO(deploy/best_rk3588.rknn) cap cv2.VideoCapture(weekly_row2.mp4) with open(suspect_frames.csv, w, newline) as f: writer csv.writer(f) writer.writerow([frame_id, class, conf, x, y, w, h]) frame_id 0 while True: ret, frame cap.read() if not ret: break if frame_id % 5 0: res model.predict(frame, conf0.3, imgsz640)[0] for box in res.boxes: conf float(box.conf[0]) if conf 0.6: # 只看低置信度框这些是潜在漏检 x, y, w, h [float(v) for v in box.xywh[0]] writer.writerow([frame_id, int(box.cls[0]), conf, x, y, w, h]) frame_id 1逻辑说明这里用conf0.3做一个低阈值推理然后把所有低于0.6的框记录成可疑样本。不要嫌低阈值会产生大量误检在这个场景里误检的价值远低于漏检因为误检最多浪费你几秒钟漏检则是模型能力的缺口。攒一周的可疑帧后人工过一遍把真正漏掉的果实挑出来补标下一轮训练就多了几百个有效样本。这个回放过程最值钱的一点是它把“今天模型好不好”从一个玄学问题变成了可查的csv和数据量。我做过最狠的一次是连续三周积累漏检帧用不到1000张补充样本重训现场漏检率直接砍了一半。后来我养成了一个习惯每周固定半小时做这件事比任何调参都管用。从yolo11模型选型、数据整理到边缘部署这条链路的每一步都在为这个回放循环服务——你能记录盲区就能定向补数据能补数据模型就会持续变好。希望帮到你。本文还有配套的精品资源点击获取