YOLO11西红柿检测实战:从数据集划分到PyQt可视化全流程解析
发布时间:2026/9/26 7:21:58来源:尧图网络
简介面向Python与深度学习初学者及目标检测实践者这包资源提供基于YOLO11的西红柿检测完整方案涵盖标注数据集、训练脚本、推理脚本与可视化界面可帮助快速掌握YOLO11在农业视觉场景中的落地流程。压缩包共1322个文件约143.79MB包含656张jpg原图、326个txt标签、321个xml标注、3个yaml配置文件、3个Python脚本以及3个pt权重文件同时附有训练日志与results.csv指标记录既可直接调用已训练模型完成识别也可依据流程重新训练。资源已有102人学习脚本分工为数据划分、模型训练和PyQt图形界面检测界面支持加载图片并输出识别结果操作直观。此外包内自带依赖清单与目录说明便于环境配置与二次开发适合作为YOLO系列目标检测入门及西红柿识别任务的参考资源。1. yolo11目标检测做西红柿检测这套源码包到底能跑出什么做农业视觉项目最怕的不是模型选型而是找到一个“数据集、训练脚本、可视化界面都齐了”的现成工程。这套基于 python pytorch 的 YOLO11 西红柿检测资源就是干这个用的它自带标注好的西红柿图像数据集通过01划分数据集.py把图片转成 YOLO 格式的 txt 标注并生成data.yaml02train.py完成训练最后03pyqt.py拉起一个 PyQt5 可视化界面鼠标点一下就完成单张图片的检测与框选展示。适合正在做采摘机器人、果实产量估算、农学类毕业设计目标检测方向的人或者其他目标检测项目想拿“小数据集 完整 pipeline”当模板的开发者。西红柿在拍摄时存在果实重叠、叶片遮挡、光照不均三类干扰恰好能把 yolo11 目标检测的泛化能力逼出来。2. 资源包内部结构tfevents、labels.cache、results.csv 分别是什么2.1 三个 py 文件的分工与 requirement.txt 的定位解压后不要急着跑代码先把目录里的文件分成三类看。第一类是三个 Python 脚本命名已经说明用途01划分数据集.py负责把原始图片整理成 YOLO 训练要的目录结构和标签格式02train.py负责训练03pyqt.py负责加载训练好的权重做可视化识别。第二类是环境文件requirement.txt里面列的是 pytorch、ultralytics、opencv-python、PyQt5 这一串依赖安装顺序建议先装 PyTorch按自己的 CUDA 版本选命令再pip install -r requirement.txt装其余部分。第三类是训练痕迹文件几张 jpg 是样本图events.out.tfevents.1733742028.zzg.7648.0是 TensorBoard 日志labels.cache是标签缓存results.csv是每一轮训练指标。前两个很多人当垃圾文件删掉其实它们是判断训练是否正常的关键证据。2.2 labels.cache 不是垃圾文件标签缓存与失效机制labels.cache这个文件值得单独说因为它最容易引发“改了数据集但训练没变化”的玄学问题。ultralytics 系的目标检测训练流程里第一次读取数据集时会扫描images目录下的所有图片、校验对应labels目录里的 txt 是否存在、标签坐标是否越界把校验结果打包成一个缓存文件。第二次再跑训练时模型直接读缓存不再全量扫描启动速度快很多。import pickle # 读取训练时生成的 labels.cache看看里面到底存了什么 with open(labels.cache, rb) as f: cache pickle.load(f) print(type(cache)) # dict print(cache.keys()) # 通常包含 images、labels 等键 # labels 键对应的值就是每个图片路径对应的标注信息这段代码把缓存文件反序列化出来你就能看到它本质上是一个 Python 字典记录着每一张图片的路径、尺寸、标注框、是否损坏等信息。关键坑在“缓存失效”上如果你在训练之后手动增删了图片或者改了某个 txt 标签labels.cache里的信息还是旧的训练时就会跳过新图片、沿用旧标签。解决方式很直接——每次调整数据集后删掉这个文件再重新训练或者训练指令里加cacheFalse强制不走缓存。我在项目里习惯写个小命令rm -rf labels.cache放在训练脚本开头宁可每次多花十几秒扫描也不赌缓存一定是最新的。2.3 results.csv 与 events.out.tfevents训练结果怎么看results.csv是训练过程的“成绩单”每一行是一个 epoch 的指标。YOLO11 训练时标准列大致是epoch、train/box_loss、train/cls_loss、train/dfl_loss、metrics/precision(B)、metrics/recall(B)、metrics/mAP50(B)、metrics/mAP50-95(B)、val/box_loss、lr/pg0这些。训练完不用等脚本打印直接拿 pandas 读这个文件就能画出曲线。import pandas as pd df pd.read_csv(results.csv) # 列名里可能带空格先清理 df.columns [c.strip() for c in df.columns] # 看最后 5 轮的 mAP50-95 变化 print(df[[epoch, metrics/mAP50(B), metrics/mAP50-95(B)]].tail(5))训练中断后想恢复也要靠这个文件判断之前跑到哪个 epoch。events.out.tfevents.*是 TensorBoard 的事件文件训练代码里一般已经埋了tensorboard回调想可视化的话在项目目录下跑tensorboard --logdir .浏览器打开http://localhost:6006就能看到 loss 曲线和 mAP 曲线。我一般只用它看一个东西train/box_loss和val/box_loss之间的剪刀差——训练 loss 一直降、验证 loss 拐头上升就是过拟合信号这时候再调数据增强比调模型结构管用。3. 把西红柿数据集转成 YOLO 格式01划分数据集.py 的划分逻辑与 data.yaml 生成3.1 YOLO txt 标签格式与数据集目录约定YOLO 系列训练不是直接读 xml 或 json 标注它要求每张图片对应一个同名 txttxt 每行描述一个目标框格式是五个数字类别id x_center y_center width height。注意后四个值全部是归一化坐标也就是用像素值除以图片宽高范围在 0 到 1 之间。这套西红柿数据集里已经有标注文件了但原始标注长什么样不重要重要的是01划分数据集.py会统一转成这种 txt。目录约定建议按照 ultralytics 默认结构来tomato_dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ ├── train.txt ├── val.txt └── data.yamlimages/train放训练图片labels/train放对应 txtval 同理。train.txt和val.txt是图片路径列表训练时按这个名单找图。data.yaml 告诉模型“类别有几个、叫什么、训练集在哪”。3.2 划分脚本的核心逻辑shuffle、比例、路径统一01划分数据集.py做的事可以拆成四步扫描所有图片、打乱顺序、按比例切分训练集和验证集、把切分结果写进 txt。下面这段是这类脚本最常见的最小实现逻辑和资源里的脚本等价你可以打开自己的 01 文件对照着看。import os import random from sklearn.model_selection import train_test_split # 你的数据根目录改成实际路径 root tomato_dataset image_dir os.path.join(root, images_all) # 未划分前的图片总目录 # 1. 收集所有图片路径 all_images [] for img_name in os.listdir(image_dir): if img_name.lower().endswith((.jpg, .jpeg, .png)): all_images.append(os.path.join(image_dir, img_name)) # 2. 切分常见比例是 8:2样本少就 9:1 train_imgs, val_imgs train_test_split( all_images, test_size0.2, random_state42 ) # 3. 写 train.txt / val.txt注意写的是绝对路径 with open(os.path.join(root, train.txt), w) as f: f.write(\n.join(train_imgs)) with open(os.path.join(root, val.txt), w) as f: f.write(\n.join(val_imgs)) print(ftrain: {len(train_imgs)}, val: {len(val_imgs)})train_test_split的test_size0.2就是验证集占 20%西红柿这类单一类别小数据集验证集比例太高会导致 mAP 曲线抖得厉害我一般会压到 15%。random_state42是随机种子固定它保证每次划分结果一致否则跑两次训练数据分布不一样对比实验就不公平。写 txt 时建议写绝对路径YOLO 训练时对相对路径的容错很差报AssertionError的概率很高。3.3 data.yaml、train.txt、val.txt 如何串起来划分脚本生成data.yaml后训练时它就是唯一的“地图”。一个单类别西红柿检测的 data.yaml 长这样# data.yaml train: /absolute/path/to/tomato_dataset/train.txt val: /absolute/path/to/tomato_dataset/val.txt nc: 1 names: [tomato]train和val指向的是 txt 文件本身而不是图片目录。这是很多新手写 yaml 最容易搞错的地方——写成train: /path/to/images/train会让模型认为每个子目录是一类训练直接报错。nc是类别数量西红柿只有一类就写 1names用列表按 id 顺序排列第 0 个就是tomato。如果以后想加“未成熟西红柿”这个类就改成nc: 2、names: [tomato, green_tomato]同时所有标签 txt 里的类别 id 要对应修改。3.4 从 VOC xml 或杂乱文件夹迁移时怎么改这套资源自带的数据集已经组织好了但很多人手头是另一批数据比如从网上下的是 VOC 格式 xml 标注或者图片散落在一个大文件夹里没分类。这时候 01 脚本不能直接跑要加一步格式转换。常见做法是用xml.etree.ElementTree解析 xml把bndbox里的xmin, ymin, xmax, ymax换算成 YOLO 需要的中心点加宽高公式# VOC 坐标转 YOLO 坐标 x_center ((xmin xmax) / 2) / img_width y_center ((ymin ymax) / 2) / img_height width (xmax - xmin) / img_width height (ymax - ymin) / img_height换算完的四个值必须都在 0 到 1 之间超出说明标注框越界了训练时会被过滤掉直接表现就是“训练集图片数量对得上但有效标注少了一半”。我踩过一次这种坑一批西红柿图片是从高处俯拍的部分果实被叶片完全遮住标注员把遮挡区域也框进去导致 width 或 height 接近 1归一化后越界。解决办法不是删图而是跑一遍标签校验脚本打印出所有越界标注的文件名一个个手动修。这种脏数据在labels.cache里其实已经标出来了但只有用前面那段 pickle 脚本才能看到UI 界面上不显示。4. 训练自己的西红柿检测模型02train.py 的参数设置与日志解读4.1 训练启动前要确认的四个环节跑02train.py之前先按顺序确认四件事能省下半天排错时间。第一确认data.yaml里的路径是绝对路径且真实存在train.txt里第一行路径用cat train.txt | head -1看一眼能访问到再继续。第二确认显卡可用命令行跑python -c import torch; print(torch.cuda.is_available())输出True才走 GPU输出False说明装的 pytorch 是 CPU 版得按 CUDA 版本重装。第三确认labels.cache已经删除尤其在你动过数据集之后。第四看一眼requirement.txt里 ultralytics 的版本YOLO11 是较新的模型太老的 ultralytics 根本读不到yolo11n.pt这个权重文件。4.2 训练参数怎么给epochs、batch、imgsz、device 的选择02train.py内部通常就是一行 ultralytics 的训练调用核心参数全部是 API 的参数。常见写法如下from ultralytics import YOLO model YOLO(yolo11n.pt) # 从预训练权重开始收敛快 model.train( datatomato_dataset/data.yaml, epochs100, # 训练轮数 batch16, # 每批图片数 imgsz640, # 输入尺寸 device0, # 0 表示第一张显卡CPU 训练写 cpu workers4, # 数据加载线程数 patience20, # mAP 连续 20 轮不涨就早停 optimizerAdamW, # 优化器 lr00.001, # 初始学习率 seed42, # 固定随机种子 cacheFalse, # 强制不走 labels.cache )参数关键点逐个说。epochs不要迷信越大越好西红柿检测场景下 100 轮足够配合patience20早停机制实际可能 50 轮就停了这样能省至少一半时间。batch纯看显存16G 显存跑 yolo11n 可以给 328G 就给 8 或 16爆显存时报错信息是CUDA out of memory下面的避坑章细说。imgsz640是性价比最高的输入尺寸西红柿这种中等大小的目标 640 完全够想要更高精度可以试 960但训练时间会多一半以上推理速度也会下降。lr00.001是 AdamW 下比较稳的初始值SGD 的话建议0.01起步。4.3 训练日志与 results.csv正常收敛长什么样训练开始后终端会每轮打印一张表格包含box_loss、cls_loss、dfl_loss、precision、recall、mAP50、mAP50-95这些列。西红柿检测场景正常的收敛曲线应该是这样的前 10 轮box_loss从 0.1 量级快速掉到 0.04 左右mAP50 从 0.2 涨到 0.7中间 20 到 60 轮进入平台期mAP50 在 0.85 上下波动如果后段出现训练 loss 继续降、验证 loss 反弹说明过拟合回退到验证 loss 最低的那个 epoch 用它的权重。比较隐蔽的问题出现在“收敛太快”上。西红柿数据集如果只有几百张图、背景又比较单一比如都是大棚里拍的前 10 轮 mAP50 可能直接冲到 0.95看起来很爽但换一批光照不同的图片立刻掉到 0.5 以下。判断模型是不是“背题”了最直接的办法就是看results.csv里训练集和验证集的 mAP 差距验证集 mAP 比训练集低 10 个点以上基本可以断定过拟合了。这时候优先做两件事一是增加mosaic0.5之类的数据增强参数二是提升val集图片的场景多样性而不是继续加 epochs。4.4 中断训练、改参数继续跑的后悔药训练到一半老板说“效果不错换个方式再跑”或者机房断电训练中断这两件事都有后悔药。ultralytics 的train()支持断点续训先找到上次训练保存的last.pt权重然后加载它继续跑。model YOLO(runs/detect/train3/weights/last.pt) model.train( datatomato_dataset/data.yaml, epochs100, # 这里的 epochs 是总轮数不是剩余轮数 resumeTrue, # 自动读取 last.pt 对应训练状态 )resumeTrue会自动续上之前的状态、学习率调度和优化器参数不用手动指定起始 epoch。需要注意的是epochs要填总轮数代码会自己跳过已完成的轮数。想改数据增强或者学习率再训练同样是加载last.pt但不加resumeTrue相当于基于已有权重继续微调。从这里开始你的results.csv会接着追加新训练结果原文件会保留第一段曲线对比着看就能发现哪次参数更有效。我习惯把每一次实验的results.csv拷贝一份带时间戳的副本比如results_20250116_epoch50.csv等项目做完回看这些文件就是完整的调参轨迹。5. 三个 py 文件连跑的常见问题排查从 labels.cache 到 PyQt 界面的坑5.1 现象01 脚本跑完train.txt 和 val.txt 是空文件原因基本只有一个源图片目录路径写错了。01 脚本里通常硬编码了一个图片根目录比如source_images或者raw_data如果你的数据集解压后目录名对不上os.listdir扫不到任何图片train_test_split拿到空列表写出的 txt 自然为零字节。还有一个隐蔽情况是图片扩展名大小写混用集合里写了.jpg但目录里全是.JPGWindows 下能扫到、Linux 下全部漏掉。解决在扫描图片那段代码后面加一行打印print(len(all_images))先确认扫到了几张图再往下走。如果路径没问题但数字是 0改成遍历所有文件再按扩展名过滤的写法for f in os.listdir(img_dir): if os.path.isfile(os.path.join(img_dir, f)) and f.split(.)[-1].lower() in [jpg, jpeg, png]: all_images.append(os.path.join(img_dir, f))5.2 现象02train.py 一启动就报 CUDA out of memory这是 yolo11 训练最常见问题原因不一定真是显存不够更多是batch和workers设置不合理。yolo11n 在 640 分辨率下显存占用约 4GB 每 16 张图8GB 显存跑batch16已经到极限了。另一个被忽略的因素是workers线程数开太多会额外占显存做数据预处理。解决先把batch降到 8workers降到 2能跑通再逐步加。如果必须要大 batch在 train 参数里加device0指定单卡避免 ultralytics 默认尝试多卡并行导致显存翻倍。还有一招是开启梯度累积ultralytics 支持batch-1自动按显存选择最大 batch虽然会慢一点但至少不会中途崩掉。5.3 现象训练到一半 loss 变成 NaN然后 mAP 全变 0训练 loss 变 NaN 的原因比较集中学习率太大、标签里有空 txt 或全零坐标、数据里有损坏图片。西红柿数据集里偶尔会有完全被遮挡的果实标注员可能只点了几个像素归一化后 x_center 和 width 都很小接近 0这类极端的框容易在损失计算时产生数值溢出。解决先删掉labels.cache重新扫描一遍看有没有图片找不到标签的警告再检查labels目录下 txt 文件是否有空文件find labels/ -size 0 | head直接列出来。学习率方面lr0从0.001降到0.0005重跑一次。如果前 10 轮就 NaN九成是数据问题不是参数问题。5.4 现象03pyqt.py 加载图片后程序直接卡死或闪退PyQt 界面卡死大概率不是检测模型的问题而是图片路径问题。Windows 下如果图片路径包含中文比如C:\Users\张三\Desktop\tomato.jpgOpenCV 的imread会读不到图像返回一个空对象后续画框操作直接报空指针错。另一类是图片分辨率过大读取后没做缩放直接送进模型推理耗时几十秒界面自然像“死机”了。解决代码里读取图片后、推理前强制做一次尺寸规整。用统一转换路径的方式规避中文问题import cv2 import numpy as np def read_image(path): # 用 numpy 读字节流绕开 cv2.imread 的中文路径问题 data np.fromfile(path, dtypenp.uint8) img cv2.imdecode(data, cv2.IMREAD_COLOR) return img img read_image(你的图片路径.jpg) img cv2.resize(img, (640, 640)) # 与训练 imgsz 保持一致界面里再兜底加一层判断if img is None: return弹个对话框提示用户换张图而不是让程序闪退。5.5 现象检测框太多误检把叶子和土壤也框出来了这个现象只发生在推理阶段训练指标一切正常。原因不是模型坏了而是推理时的置信度阈值太低。YOLO 默认conf0.25西红柿在自然光下和叶片颜色接近时模型会给出大量 0.1 到 0.3 的预测框默认阈值全被当成目标了。解决跑推理时手动调高置信度。面对大棚场景我用conf0.35起步叶片遮挡多再提到0.45。想保留低置信度框但控制数量就同时调iou0.5做更激进的 NMS 合并。03pyqt.py 里model.predict(sourceimg, conf0.4, iou0.5)改成这两个值误检会明显下降代价是极个别被严重遮挡的西红柿会漏检这属于权衡没有两全的参数组合。6. 03pyqt.py 的进阶技巧把单图检测升级成批量推理与 CSV 导出PyQt 界面点一张图测一张验证模型没问题但真要统计一棚西红柿的产量这样效率太低。我拿到这套资源后的第一步改动就是给 03pyqt.py 加一个批量处理入口选一个文件夹遍历所有图片跑推理把每张图的检测框数量、平均置信度、所有框坐标写进 CSV。这样不仅能算出整批图片的果实总数还能顺带验证模型的泛化能力——如果某个批次的平均置信度明显低于其他批次说明这批图片的拍摄条件跟训练集差异大。核心改动是在检测按钮的槽函数后面加一个批量分支import os import csv import time from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) def batch_inference(folder_path, csv_path): rows [] for img_name in os.listdir(folder_path): if not img_name.lower().endswith((.jpg, .png, .jpeg)): continue img_path os.path.join(folder_path, img_name) t0 time.time() results model.predict( sourceimg_path, conf0.4, iou0.5, imgsz640, verboseFalse ) infer_ms (time.time() - t0) * 1000 r results[0] boxes r.boxes.xyxy.cpu().numpy() # 左上右下坐标 confs r.boxes.conf.cpu().numpy() # 置信度 rows.append([ img_name, len(boxes), round(float(confs.mean()), 4) if len(confs) else 0, round(infer_ms, 1) ]) with open(csv_path, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([图片名, 检测框数, 平均置信度, 推理耗时ms]) writer.writerows(rows)这里有个细节值得注意boxes.xyxy返回的是 CPU 张量要先转 numpy 再取数直接对 CUDA 张量做循环会拖着数据在 GPU 和 CPU 之间反复搬运批量跑的时候会慢得明显。另外每张图都单独调一次predict是有开销的更快的做法是把整个文件夹路径直接传给model.predict(sourcefolder_path)让 ultralytics 内部批量推理但对 PyQt 界面来说逐张调用更便于更新进度条两难之下我选逐张处理因为一套西红柿图也就几百张时间差在可接受范围内。跑完批量推理后用time.time()包住推理那一段还能顺手验证自己的硬件水平yolo11n 在 1080Ti 上跑 640 分辨率大概每张 25 到 35ms在纯 CPU 上大概是 800 到 1500ms。如果发现 CPU 推理慢到不可用说明这份资源更适合“先离线训练、再在带显卡的机器上做识别”的部署路径不要幻想笔记本 CPU 能实时跑视频流。从那以后我每次拿到目标检测项目都强制自己先跑一遍批量推理并导出 CSV而不是在界面上点十张图凭肉眼判断“效果不错”。检测框数量分布、置信度分布、单张耗时这三个数字比截图更能说明模型真实水平。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网