简介这是一份面向农业人工智能与粮食仓储管理的谷物害虫目标检测数据集适合目标检测算法研究者、农业监测系统开发者以及相关专业学生使用。数据集聚焦单一“害虫”类别收录687张真实谷物环境下的高清图片每张均带有精确的YOLO格式边界框标注便于直接训练模型实现害虫自动识别与粮食状态实时监控。资源共1376个文件主要由687个jpg图像和对应687个txt标签文件构成另附1个yaml配置文件和1个docx说明文档压缩包整体仅22.76MB目录结构清晰、易于加载。兼容YOLOv5、YOLOv8等主流深度学习框架可直接用于模型训练、算法评估与调优也可作为智慧农业、仓储害虫防治等课题的基准数据集。已有230人学习下载无论是想要快速上手目标检测应用还是开展农业AI交叉研究都能从中获得实战价值。1. 谷物害虫目标检测数据集.zip打开它之前先想清楚用它来干嘛谷物害虫目标检测数据集.zip 这类压缩包最常见的下场是被人一拿到就 unzip 丢进训练脚本。我的建议是反过来先花半小时把它当作一份原料做体检图片完整不完整、标注和图片是否一一对应、类别文件里的类名能不能和标注内容对得上。它的本质是把仓储粮堆环境下的害虫图像和标注文件打包在一起图片教模型认识虫子的真实外观标注文件给出每个目标的边界框和类别。这份数据包能帮你在没有现场采集条件时先把目标检测的完整流程跑通拿到一个可信的基线模型再做现场增量微调。适合做粮仓虫情监测、农产品质检、储粮智能管理的人也适合毕业设计选择目标检测方向的学生拿来练手。指望不拍一张现场照片直接上产线大概率会翻车。2. Linux 下解压 zip 与数据体检先花半小时排除坏图和脏标注这种数据 zip 的解压从来不是一条 unzip 命令就完事。我在 Linux 上处理过的数据包里文件名乱码、路径穿越、坏图片、标注缺失全部遇过现在一律把它当工业原料来验收先列目录再查图片最后核对标注和图片的对应关系。这三步做完后面转格式、训练、调参才会顺。2.1 解压前先用 unzip -l 看清楚目录结构避开乱码和伪加密我一般会先在数据包同目录里执行unzip -l 谷物害虫目标检测数据集.zip | head -80-l参数只列出压缩包内的文件清单不真正落盘所以即使压缩包里存在异常路径或伪加密文件也不会在磁盘上产生任何副作用。输出包含四列文件长度、日期、文件名、压缩率。重点看两件事一是目录形态图片是放在images/下还是JPEGImages/下标注是annotations/还是labels/有没有独立的classes.txt二是文件名是否乱码。如果列表里出现了大片?????或者“锟斤拷”这类字符说明 zip 是从 Windows 环境打的文件名以 GBK 编码写入而 Linux 的 unzip 默认按 CP437 去解释中文字符一进去就变样。这种包直接用 unzip 解文件名对不上之后的脚本会连锁炸掉。碰到这种情况我直接用 Python 提取import zipfile from pathlib import Path src Path(谷物害虫目标检测数据集.zip) dst Path(data) with zipfile.ZipFile(src) as zf: for info in zf.infolist(): # zipfile 默认拿到的 filename 是按 cp437 解码的尝试还原成 GBK 中文 try: name info.filename.encode(cp437).decode(gbk) except UnicodeDecodeError: name info.filename if .. in name or name.startswith(/): print(跳过危险路径:, info.filename) continue target dst / name if info.is_dir(): target.mkdir(parentsTrue, exist_okTrue) else: target.parent.mkdir(parentsTrue, exist_okTrue) with zf.open(info) as fin, open(target, wb) as fout: fout.write(fin.read())这段脚本需要解释清楚info.filename是 zip 归档内的原始文件名字符串因为 Python 的 zipfile 遵循 ZIP 规范把它当作 CP437 解码所以用encode(cp437)还原成原始字节再用decode(gbk)重新解码为中文。如果压缩包本身用的是 UTF-8 文件名这个流程会抛UnicodeDecodeError我在异常分支里保留了原文件名。脚本里的..和/判断是安全兜底防止压缩包里的路径穿越把文件释放到上层目录数据集几百 MB 到几 GB 不等犯不上为省这几行检查去冒风险。顺带提一句如果在命令行执行unzip时提示要密码先不要急着认定它加密了用zipinfo -v看一眼加密标志位很可能是伪加密。伪加密不影响 Python zipfile 提取具体怎么看放到后面的避坑章节展开。2.2 图片完整性快检用 PIL 扫一遍别让坏图毁掉整个训练解压完先别急着看标注把图片全部扫一遍。很多新手在训练时遇到的CUDA error: an illegal memory access或者中途卡死真正原因不是模型而是某张图片是损坏文件读取后返回空数组数据加载器在放大变换时直接炸掉。我的检查脚本非常短from pathlib import Path from PIL import Image img_dir Path(data/images) quarantine_dir Path(quarantine) quarantine_dir.mkdir(exist_okTrue) for p in sorted(img_dir.rglob(*)): if p.suffix.lower() not in {.jpg, .jpeg, .png, .bmp, .webp}: continue try: with Image.open(p) as im: im.verify() except Exception as exc: print(f坏图: {p} - {exc}) p.rename(quarantine_dir / p.name)rglob(*)会递归遍历所有子目录suffix用来过滤常见图片扩展名。im.verify()做的事情是读取文件头部的格式信息并检查关键结构不会把整张图解码到内存所以几千张大图跑起来也不会有太大压力。被 verify 检查过的图片在 YOLO 的数据读取阶段基本不会崩。这里有个细节坏图我选择先移动到quarantine/而不是直接删。因为损坏原因可能是打包时网络传输中断也可能是源文件本身就有问题。直接删掉再后悔就晚了隔离到一边还能回头确认。还有一种情况是扩展名正常但实际不是图片的文件比如把一张 BMP 改名成.jpgverify 会报cannot identify image file同样会被隔离。这种文件通常要直接删留着没有任何用处。2.3 标注文件对齐检查找出有图无标、有标无图、空标注图片全绿之后再核对标注和图片的对应关系。谷物害虫数据集的理想状态是每张图都有同名标注文件但实际打包时经常出现缺漏。我的检查方法是把两边的主文件名取出来求差集import xml.etree.ElementTree as ET from pathlib import Path ann_dir Path(data/annotations) img_dir Path(data/images) img_exts {.jpg, .jpeg, .png, .bmp, .webp} img_stems {p.stem for p in img_dir.rglob(*) if p.suffix.lower() in img_exts} ann_stems {p.stem for p in ann_dir.glob(*.xml)} print(有图无标:, sorted(img_stems - ann_stems)) print(有标无图:, sorted(ann_stems - img_stems))这段脚本用 Python 集合运算求差集逻辑上很直白。有图无标里的图片有可能是作者有意保留的负样本也就是画面里没有害虫用于告诉模型这些区域是背景但更多时候就是漏标。如果这些图片占比超过 5%我建议直接从训练集里剔除否则模型会把一部分害虫当背景学习损失函数还学得很开心。还要再查一层看看同名的 XML 里是否真的存在 objectempty_ann [] for stem in sorted(img_stems ann_stems): root ET.parse(ann_dir / f{stem}.xml).getroot() if len(root.findall(object)) 0: empty_ann.append(stem) print(空标注数量:, len(empty_ann)) print(empty_ann[:20])空标注和负样本是两回事负样本是作者故意没标注空标注则是标注文件损坏或内容丢失。前者可以控制比例后保留后者应该直接删掉。做完这几步数据包才算到达能用来训练的状态。别觉得这二十分钟白费带着坏图脏标去训练后面排查问题的时间几十倍都打不住。3. 把标注转成 YOLO 格式VOC XML 归一化、类别映射与数据集划分数据包干净了接下来要解决格式问题。YOLO 训练不吃 XML 也不吃 COCO JSON它只认每张图对应的同名.txt文件每行五个数字类别 id、归一化目标中心 x、中心 y、宽度、高度。转换工作里最常见的坑是坐标写反、类别顺序错位、XML 缺少图片尺寸信息。3.1 先判断标注格式VOC XML、COCO JSON 还是 YOLO txt拿到压缩包的标注目录我习惯先随机打开一个文件看结构而不是上来就写转换脚本。三种格式的区分非常明显格式文件形态判断特征Pascal VOC每张图对应一个.xml根节点是annotation内部有object、bndboxCOCO JSON整个标注集放在一个或多个.json包含images、annotations数组使用category_idYOLO txt每张图对应一个.txt每行五个数字第一列是整数类别 id后面四列是小数VOC XML 是 LabelImg 这类常见标注工具的默认导出格式公共数据集里出现频率最高。COCO JSON 多出现在做实例分割的数据集里因为它的segmentation字段能存多边形点。如果你的压缩包打开直接就是.txt那就跳过转换但要确认这些 txt 是否已经归一化过有些老数据集的 txt 存的是绝对像素坐标这种必须重算。如果包里附带一个classes.txt那它就是类别顺序的唯一权威。后面所有脚本的类别列表都要从它读不要自己重新敲一遍。3.2 VOC XML 转 YOLO 的 Python 脚本与坐标计算逻辑我常用的转换脚本是这样它对单个 XML 的处理函数拆出来方便单独调试import xml.etree.ElementTree as ET from pathlib import Path ANN_DIR Path(annotations) IMG_DIR Path(images) LABEL_DIR Path(labels) LABEL_DIR.mkdir(exist_okTrue) # 以 classes.txt 为准构建类别列表顺序不能改 CLASSES Path(classes.txt).read_text().strip().splitlines() def convert_voc_to_yolo(ann_path): root ET.parse(ann_path).getroot() size root.find(size) if size is None or int(size.find(width).text) 0: return None # 缺 size 或宽高为 0交给调用方兜底 width float(size.find(width).text) height float(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in CLASSES: raise ValueError(f{ann_path.name}: 类名 {name} 不在 classes.txt 里) cls_id CLASSES.index(name) bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # YOLO 需要目标中心坐标和宽高并且全部除以图片尺寸归一化 cx (xmin xmax) / 2.0 / width cy (ymin ymax) / 2.0 / height bw (xmax - xmin) / width bh (ymax - ymin) / height lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) return lines for ann_path in ANN_DIR.glob(*.xml): lines convert_voc_to_yolo(ann_path) if lines is None: print(f跳过 {ann_path.name}: 缺 size需要从图片读宽高) continue out_path LABEL_DIR / f{ann_path.stem}.txt out_path.write_text(\n.join(lines) \n)几个参数要说明。CLASSES直接读classes.txt保证顺序和文件一致。size.find(width).text提供的是 XML 里记录的图片宽度cx的分子是(xmin xmax) / 2也就是边界框中心像素坐标再除以width把它映射到 0 到 1 区间。宽度bw用xmax - xmin除以图片宽度高度同理。最终输出的浮点数保留六位小数足够YOLO 不需要更高精度。这里必须提醒一个常见边界条件很多数据集的 XML 里size字段写的是 0 或者缺失这时直接拿宽高做除法会得到无穷大。我的处理是返回 None 并打印跳过然后在第二遍单独用Image.open读取真实分辨率来补转。不要把这些 XML 直接删掉否则会丢一批标注。我还会在脚本里加一个校验如果bw或bh小于等于 0说明 XML 的 xmin/xmax 写反了需要打印出来人工复核。坐标写反不会让训练直接报错但它的损失函数会一直震荡最后表现为掉点很难诊断。提示标注文件里的类别名字符串和 classes.txt 不一致时转换脚本会直接抛错。先把错误信息读完整再检查是数据集本身有问题还是 classes.txt 少了行不要硬改脚本去凑。3.3 类别顺序是“地基”classes.txt 与 cls_id 必须一一对应YOLO txt 里保存的只是整数类别 id类别名存放在 data.yaml 的 names 字段里。假如转换脚本的类别顺序和训练配置里的 names 顺序不一致模型学到的内容全部错位它可能真的学到了谷象的特征但输出 id 对应到 names 上被你叫成了书虱。这种错最阴险因为 loss 照常下降mAP 也好看只有把预测框画在图上才会露出马脚。所以我的建议是转换脚本里不写死CLASSES [a, b, c]永远用Path(classes.txt).read_text().strip().splitlines()读文件。训练时的 data.yaml 也由同一个文件生成这样双端都锚定在同一个源头上避免复制出三份不一样的类别顺序。换机器、换人接手时只要 classes.txt 还在就能复现整个管道。3.4 数据集划分按类别分层抽样别让某个害虫类别从验证集里蒸发在划分 train 和 val 之前先统计每个类别的样本数。谷物害虫数据集十有八九是不平衡的优势种类可能占据六成以上稀有种类只有几十个。如果简单地把图片文件随机打散稀有种类的样本很容易全落在训练集里验证集这个类 mAP 直接是 0你还会误以为是模型没学会。我一般用按类别分层抽样的逻辑来划分import random from pathlib import Path from collections import defaultdict random.seed(42) img_paths sorted(Path(images).glob(*)) labels_map {} for img_path in img_paths: txt_path Path(labels) / f{img_path.stem}.txt classes [] if txt_path.exists(): for line in txt_path.read_text().strip().splitlines(): if line.strip(): classes.append(int(line.split()[0])) labels_map[img_path.stem] classes val_stems set() for cls_id in range(len(CLASSES)): stems [stem for stem, classes in labels_map.items() if cls_id in classes] if len(stems) 5: val_stems.update(random.sample(stems, kint(len(stems) * 0.2)))这段代码的逻辑是对每个类别单独采样 20% 的图进入验证集然后取并集。同一张图如果含有多个类别可能被多次采样用set去重后作为真正的 val 集合。对于完全没有标注的负样本图单独随机抽 20% 进 val确保验证集里也有背景样例。划分后建议输出一份val_stems.txt保存下来。原因有两个一是以后换模型重训时要保持相同的验证集结果才有可比性二是当模型表现异常时可以回查验证集里是否包含某个特定场景的图片。没有这份清单等于模型每次考试的试卷都不一样成绩好坏就成玄学了。4. 用 YOLOv8 训练自己的数据集data.yaml、模型选型与三个必调参数标注转成 YOLO 格式并完成划分后下一步是把数据集喂给模型。现在最常见的做法是使用 Ultralytics 的 YOLOv8 系列配置少、文档全、训练命令简洁。但配置少不等于可以乱配data.yaml 里的路径和类别数第一次训练选哪个规格的模型都会直接影响结果和训练时长。4.1 data.yaml 怎么写路径、类别数与 names 对应新建一个data/grain_pest.yaml内容大概长这样# data/grain_pest.yaml path: /home/user/grain_pest_dataset train: images/train val: images/val nc: 3 names: 0: rice_weevil 1: sawtoothed_grain_beetle 2: booklousepath是数据集根目录的绝对路径train和val是相对path的子目录最终指向的目录必须真实存在。nc是类别数量必须等于names的长度。names的 key 从 0 开始顺序必须和上一章转换时用的 classes.txt 完全一致。很多第一次用 YOLO 的人会把path写成./data但当前工作目录不在数据集根目录导致训练报错找不到图片。我的习惯是写绝对路径然后在训练脚本里用环境变量替换路径前缀这样换机器时不用改 yaml 文件内容。如果目录结构保持images/train和labels/train的并列关系yaml 里甚至连标签路径都不用写YOLO 会自动去同名 labels 目录找标签。一个值得检查的地方是images/train里的图片数量和labels/train里的 txt 数量应一致差太多说明上一章划分脚本漏复制了一部分文件。我每次写完 yaml 都会跑一句确认ls images/train | wc -l ls labels/train | wc -l两个数字对不上就先别训练回去补文件。4.2 模型选型先从 nano 起步不要一上来就放大模型YOLOv8 按参数量分为 n、s、m、l、x 五个规格nano 最小x 最大。谷物害虫目标在画面里通常属于中偏小目标但多数数据集原始图片本身不大用 nano 和用 small 的差距没有那么显著。我建议第一次先把 nano 跑通拿到 baselines之后再逐步放大。放大模型前先看两个东西验证集 mAP 是否还有提升空间以及推理速度是否能接受。第一次训练命令我一般是这样yolo detect train \ datadata/grain_pest.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ workers8 \ cacheTruemodelyolov8n.pt是官方发布的 COCO 预训练权重用它初始化模型迁移学习可以大大缩短收敛时间。epochs100是上限配合早停机制实际可能用不到这么多。imgsz640是输入网络的图片短边尺寸图片会被等比缩放再填充。batch16是每次迭代的样本数显存不够就降到 8显存大可以提到 32。workers8是数据加载线程数一般设为 CPU 核心数的一半即可。cacheTrue表示把图片加载进内存省去训练时反复读盘几 GB 的数据集完全放得下。注意第一次跑别急着调损失函数的权重也别动任何数据增强参数。YOLO 的默认增强组合在绝大多数害虫数据上表现够用先把默认配置跑通再按验证结果做增量改动。4.3 三个必调参数imgsz、batch 与 cache 的取舍这三个参数对训练结果影响最直接我把常见取值整理成表格参数作用谷物害虫数据集的建议imgsz输入网络的图片尺寸640 起步小目标密集或原图很大时提到 960/1280batch每轮迭代送入的样本数显存不够就 8够用就 16/32过大会让收敛不稳定cache是否把图片缓存进内存小数据集用 True图片超过几万张或单图很大时改 Falseimgsz对害虫检测的影响比很多人以为的大。害虫体积小在 640 的输入尺寸下可能只有十几个像素特征很弱。如果你想把手上的模型刷到更高的 mAP把imgsz提到 1280 往往比换更大的模型更有效代价是训练和推理时间翻倍。谷物害虫这类任务一般在 960 附近能取到精度和速度的平衡点。batch太大有时会让模型在前期震荡得厉害尤其是类别不平衡显著的数据集。我踩过的做法是先用 16 跑 20 个 epoch如果 loss 曲线像锯齿一样尖锐就降到 8 再试。cacheTrue在数据量只有两三千张时能明显加速但如果图片分辨率很高、内存本就不宽裕反而可能 OOM需要改成 False。4.4 正式训练前先跑冒烟测试5 个 epoch 确认流程通我每次都不会直接拿 100 个 epoch 去跑而是先用最小代价验证流程yolo detect train \ datadata/grain_pest.yaml \ modelyolov8n.pt \ epochs5 \ imgsz320imgsz320是刻意把分辨率压低让每个 epoch 的时间缩短到一分钟以内epochs5只是让前向和反向传播完整跑几轮。这一步能暴露绝大部分问题data.yaml 路径写错、标签读不到、图片解码失败、显存不足。跑完之后看一下输出日志里的 box_loss 和 cls_loss如果两者都没有出现 NaN就可以放心开正式训练。冒烟测试里 loss 不降是正常的5 个 epoch 本来就学不出东西如果 loss 直接冲高到几十说明标签和图片没有对齐回去查上一章的对齐检查脚本不要浪费时间调参。5. 避坑从 zip 到模型最常见的 5 个翻车现场数据准备和训练流程走通之后真正折磨人的是各种隐藏问题。这里把我在谷物害虫这一类数据集上反复踩过的 5 个坑整理出来每条都按现象、原因、解决的顺序讲清楚希望能帮你省掉几个晚上的排查时间。5.1 训练中途卡死或报数据读取失败坏图和重复文件名的锅现象训练在加载数据的阶段就卡住或者运行到中途抛image not found甚至直接造成显存访问异常。出现这类问题时退出码指向的往往不是真正的原因只是崩溃前最后一个操作。原因常见情况有两种。第一数据包里某张图片扩展名正常但文件损坏OpenCV 读取后返回空对象数据加载器在缩放或归一化时无法处理第二由于文件名编码问题同一张图片在 Linux 下出现了两个相似文件名其中一个有标注另一个没有导致标签和图片错位。解决回到第 2 章的体检脚本先做图片完整性扫描再把重复文件名找出来。我习惯在解压后用这个命令检查重复find data/images -type f | sed s/.*\/// | sort | uniq -d如果输出有内容就重命名冲突文件统一改成小写字母、数字和下划线组合避免空格和中文。重新整理后再跑冒烟测试崩溃基本消失。5.2 验证集 mAP 高得离谱现场预测却一团糟val 泄漏现象训练过程中 val mAP 一路飙到接近 1validation 曲线非常完美但是拿现场照片测试时漏检严重检测框位置也飘。原因这个现象十有八九是验证集泄漏val 里混入了训练集的图片。常见于没有固定随机种子就划分数据集或者作者给的 zip 里本来就有重复图片还有一种是只复制了图片到 val 目录但 labels 也一并复制过去了等于把标准答案交给了模型。解决划分脚本里固定random.seed(42)并把划分出的 val 文件名清单写入文本留着事后核查。再跑一次预测时从 val 目录随机挑几张图用yolo predict生成结果同时打印图片路径人工确认它们在训练过程中没出现过。如果 mAP 依旧高得异常就检查原始压缩包里是否混有网络上的公开图片。5.3 小目标害虫漏检成片imgsz 和原图分辨率不匹配现象模型对画面里的大目标表现正常但散布在角落里的书虱、谷象基本检不出来可视化结果里小目标区域一片空白。原因高分辨率原图被缩放成 640x640 后较小的害虫只占十几个像素卷积特征在多次下采样后彻底丢失。这不是模型不够大也不是训练轮数不够而是输入分辨率没保住小目标的尺度。解决如果训练资源允许直接把imgsz提到 1280召回通常会有肉眼可见的提升。另一个常见做法是图片切片把原图切成 640x640 的块再按切割位置重新计算边界框坐标。切片需要注意边框重叠否则虫子被切开标签成两半。这个操作建议在训练脚本外单独完成不要和 Mosaic 增强叠加否则增强后一张图里会出现大量被切断的目标。5.4 zip 解压报密码错误或文件名乱码伪加密与中文编码双坑现象在 Linux 下用 unzip 解压时提示需要密码但压缩包来源说明里并没有提供密码或者解压成功但文件名全是乱码导致标注脚本找不到匹配文件。原因前一种是 zip 伪加密也就是压缩包内的通用标志位里被误设了加密位实际数据并没有加密。这种情况常见于某些 Windows 压缩工具生成的包。后一种则是文件名的编码问题ZIP 规范里没有一个明确字段记录文件名编码Windows 中文环境生成的 GBK 编码文件名在 Linux 的 unzip 下会被当成其他字符集解码。解决先用zipinfo -v查看加密信息判断它是真加密还是伪加密。如果只是通用标志位异常用 Python 的zipfile模块提取完全不受影响因为它走的是中央目录结构不依赖那些标志位。文件名乱码则用第 2.1 节里的 Python 脚本按cp437 - gbk路径还原。zipinfo -v 谷物害虫目标检测数据集.zip | grep -i encryption如果确认是真加密只能找数据提供方要密码。网上那些声称能移除 zip 密码的工具基本都不可信别在这上面浪费时间和安全风险。5.5 标签越界和漏标只有画出来才看得出可视化自检现象训练损失正常下降验证 mAP 也能看但把模型输出的框画在图片上发现有些框整体偏移或者多个框叠在同一个害虫身上。原因很多问题不是模型学得不好而是标注本身就错了。比如 XML 坐标超出了图片边界归一化后出现大于 1 或小于 0 的数值或者图里明明有虫子却漏了标。这种脏标签不会让训练崩溃只会让模型在错误样本上学到噪声。解决在训练前多执行一步可视化自检。我从 labels 里随机抽几十张图把边界框画回原图人工过一遍import cv2 from pathlib import Path img_dir Path(images) label_dir Path(labels) out_dir Path(check) out_dir.mkdir(exist_okTrue) for img_path in sorted(img_dir.glob(*.jpg))[:80]: img cv2.imread(str(img_path)) if img is None: continue h, w img.shape[:2] label_path label_dir / f{img_path.stem}.txt if not label_path.exists(): continue for line in label_path.read_text().strip().splitlines(): cls_id, cx, cy, bw, bh map(float, line.split()) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, str(int(cls_id)), (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imwrite(fcheck/{img_path.name}, img)这段代码把归一化坐标乘回图片宽高得到像素坐标并画框。如果矩形明显超出图片边缘说明标签越界如果框和虫子对不上位置说明标注有系统性偏差。画框检查是目标检测任务里成本最低的纠错手段训练前花十分钟看一遍能省掉训练后好几天。6. 交付前最后一步用混淆矩阵和一次真实推理验证模型真能用训练完成后runs/detect/train/weights/best.pt就是当前效果最好的权重。先不要急着接进检测系统做两个动作看混淆矩阵跑一次真实预测。混淆矩阵保存在runs/detect/train/confusion_matrix.png从中能直观看到哪些类别互相搞混。如果谷象和书虱之间频繁互串说明它们外形相似或者标注样本太少这时去调置信度阈值是没用的要回到类别设计本身考虑合并相似类别或补充训练样本。第二步是拿训练集之外的照片做预测。如果手头没有现场照片就从一个独立的测试目录里抽图yolo predict modelruns/detect/train/weights/best.pt \ sourcesamples/现场样本.jpg \ conf0.25 \ saveTrue观察两点。第一每个框里的目标是否被正确分类如果错误集中出现在某两个相邻类别大概率是标注或类别顺序问题第二图像上有没有大量重复框两个框几乎重合地框住同一个害虫说明后处理失效这时可以尝试提高iou阈值或降低类别数来缓解。预测结果里的图片会保存到runs/detect/predict/直接把图和框画在一起比看数值指标直观得多。我现在的习惯是每次训练前把classes.txt、data.yaml、转换脚本、划分脚本四个文件一起提交进仓库并让 data.yaml 里的类别列表从 classes.txt 自动生成。这样换一台机器也能复现训练不会出现“本地能跑、换机器就崩”的情况。模型权重不是最值钱的最值钱的是数据和标注处理的整个流程流程清晰了换一个数据集也能快速跑起来。上面这些坑哪一个单独拿出来都不复杂但连起来就会让一个目标检测项目从“看着正常”变成“反复翻车”。希望我的这些处理习惯对你有用。本文还有配套的精品资源点击获取
