简介:这份资源面向从事目标检测、智慧农业与秸秆禁烧监测的开发者与研究者,提供田间农作物焚烧场景的标注数据集,覆盖火、烟雾、灰烬三类目标,可用于训练烟火识别、焚烧行为检测等模型。压缩包共665个文件,包含221张jpg原图、221个VOC格式xml标注和223个txt文件(含YOLO格式标签),整体约16.33MB,图片清晰且未做数据增强,标签均为矩形框,其中灰烬275框、火143框、烟雾170框,合计588个标注框,可直接用于YOLO与VOC双格式训练流程。目前已有152人学习下载。数据集来自网页爬取后人工标注,场景贴近农村田间实际,适合作为小样本检测任务的基线数据,帮助读者快速验证模型在烟火与灰烬识别上的表现,并可按需扩充或调整标签体系。
1. 221 张灰烬图能训出什么:田间焚烧残留检测的数据集底子
田里烧完秸秆、杂草之后留下的灰烬,在可见光图像里就是一片低对比度的暗斑,跟湿土、阴影、深色地膜极易混淆。我拿到的这个数据集,标题写得很直白:221 张图、3 个标签、同时给 YOLO 和 VOC 两套标注格式。它解决的不是"能不能检测火"这种问题,而是焚烧事后的残留识别——灰烬堆、焚烧痕迹、未燃尽残茬这三类目标。适合谁用?做农业遥感落地、环保巡查无人机、秸秆禁烧事后核查的团队,以及想拿一个小而脏的真实数据集练 YOLO 全流程的人。221 张这个量级,注定它不是拿来刷 SOTA 的,而是拿来验证"这条技术路线在你们场景里到底跑不跑得通"的。先把这个预期立住,后面所有参数和坑才有意义。
这个数据集的真实价值在于"小样本 + 双格式 + 真实田间背景"三件事叠在一起。小样本逼你把数据增强和迁移学习做扎实,双格式让你能在 YOLO 和两阶段检测器之间快速横跳做对比,真实田间背景则意味着光照、土壤湿度、拍摄角度全都不受控。我一般会先花半小时把 221 张图全部翻一遍,统计每类目标的框数分布,再决定后面训练策略。这一步不做,后面调参全是玄学。
2. 拆开压缩包先看什么:VOC 与 YOLO 双格式的结构差异
2.1 两套标注格式到底差在哪
VOC 格式的核心是每张图对应一个 XML 文件,里面用<object>节点描述每个目标,包含<name>、<bndbox>四个坐标(xmin、ymin、xmax、ymax),坐标是绝对像素值。YOLO 格式则是每张图一个 txt,每行一个目标,格式是class_id cx cy w h,后四个都是归一化到 0~1 的相对值,且 cx、cy 是框中心点。这个差异决定了你转换时最容易翻车的地方:归一化除的是原图宽高,一旦某张图尺寸记录错了,框就会整体偏移。
我拿到这个数据集,第一件事是确认目录结构。常见做法是 VOC 放在Annotations/(XML)和JPEGImages/(原图),YOLO 放在images/和labels/。先跑一遍文件数核对:
# 统计各目录文件数,确认 221 张是否对齐 echo "原图:"; ls JPEGImages/ | wc -l echo "VOC标注:"; ls Annotations/ | wc -l echo "YOLO图像:"; ls images/ | wc -l echo "YOLO标签:"; ls labels/ | wc -l四个数字必须一致(或 YOLO 图像与 VOC 原图指向同一批)。只要有一个对不上,说明有图漏标或漏拷,这种脏数据直接进训练会让 loss 曲线出现莫名其妙的尖刺。参数上没什么可调的,这一步就是纯核对。
2.2 三个标签的类别映射必须锁死
3 个标签意味着类别索引只有 0、1、2。VOC 里是字符串类别名,YOLO 里是整数 id,两者必须建立一张固定映射表。我一般会写一个脚本,从所有 XML 里扫出全部<name>去重,确认真的只有 3 类,而不是标注时手滑打出了第 4 种拼写。
import os, glob from xml.etree import ElementTree as ET names = set() for xml in glob.glob("Annotations/*.xml"): root = ET.parse(xml).getroot() for obj in root.findall("object"): names.add(obj.find("name").text.strip()) print("实际类别:", sorted(names)) # 期望只有 3 个 # 固定映射,顺序一旦定下就不能改 classes = sorted(names) name2id = {n: i for i, n in enumerate(classes)} print(name2id)逻辑说明:遍历所有 XML,把<name>收进集合去重。如果打印出来是 3 个,说明标注干净;如果冒出 4 个以上,多半是"灰烬"和"灰烬堆"这种同义不同写,得先人工统一。name2id一旦生成就写进classes.txt,训练、推理、可视化全程共用这一份,避免训练时 id=0 是灰烬、推理时 id=0 变成残茬这种低级事故。
3. 从 VOC 转 YOLO:转换脚本与四个边界坑
3.1 转换脚本怎么写才不出错
虽然数据集号称双格式,但实际用的时候我仍然会自己转一遍,因为别人给的 YOLO 标签未必和你的类别顺序一致。转换的核心就是读 XML、算归一化中心点和宽高、写 txt。
import os, glob from xml.etree import ElementTree as ET from PIL import Image name2id = {"ash": 0, "burn_mark": 1, "stubble": 2} # 按你实际类别改 os.makedirs("labels", exist_ok=True) for xml in glob.glob("Annotations/*.xml"): root = ET.parse(xml).getroot() img_name = root.find("filename").text img_path = os.path.join("JPEGImages", img_name) w, h = Image.open(img_path).size # 用真实图像尺寸,别信 XML 里的 size lines = [] for obj in root.findall("object"): cls = obj.find("name").text.strip() if cls not in name2id: continue bb = obj.find("bndbox") xmin = float(bb.find("xmin").text) ymin = float(bb.find("ymin").text) xmax = float(bb.find("xmax").text) ymax = float(bb.find("ymax").text) # 裁剪越界坐标,防止出现负值或超过1 xmin, xmax = max(0, xmin), min(w, xmax) ymin, ymax = max(0, ymin), min(h, ymax) cx = (xmin + xmax) / 2 / w cy = (ymin + ymax) / 2 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{name2id[cls]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") with open(f"labels/{os.path.splitext(img_name)[0]}.txt", "w") as f: f.write("\n".join(lines))逻辑说明:关键点是用 PIL 读真实图像尺寸,而不是 XML 里<size>节点写的宽高。很多标注工具导出的 size 是旧的,图被裁过之后 size 没更新,直接拿它归一化,框会整体错位。坐标裁剪那两行是后悔药,防止标注时框拖出边界导致归一化后出现负数或大于 1 的值,YOLO 训练时这类值会直接报错或产生 NaN。
3.2 四个必须检查的边界坑
第一个坑:空标签文件。某张图确实没有目标,转换后 txt 是空的,这本身合法,但有些训练脚本会把空文件当损坏数据跳过,导致这张图被静默丢弃。第二个坑:文件名带空格或中文,YOLO 的 dataloader 在某些环境下会读不到。第三个坑:宽高为 0 的退化框,xmin 等于 xmax 时 bw=0,训练时 IoU 计算会出问题。第四个坑:类别顺序不一致,前面已经强调过。
# 快速体检:找出空标签、退化框、异常坐标 for f in labels/*.txt; do [ -s "$f" ] || echo "空标签: $f" awk '{if($4<=0 || $5<=0) print "退化框:", FILENAME, $0}' "$f" done这段体检脚本我每次转换完必跑。空标签要人工确认是"真没目标"还是"漏标";退化框直接删掉那一行;坐标超出 [0,1] 的说明裁剪逻辑没生效,回头查图像尺寸。221 张图跑一遍不到十秒,能省下后面几小时的排查。
4. 221 张图怎么训:YOLOv8 配置与增强参数
4.1 环境与最小训练命令
221 张属于典型小样本,我一般用 YOLOv8n 或 YOLOv8s 起步,预训练权重必须加载,否则从零训基本学不出东西。环境上,Anaconda 建个干净环境,装 ultralytics 即可,CUDA 版本按你显卡来,V100 和消费级卡都能跑。
conda create -n ash python=3.10 -y conda activate ash pip install ultralytics # 最小训练命令 yolo detect train data=ash.yaml model=yolov8n.pt epochs=150 imgsz=640 batch=16ash.yaml里写清train、val路径、nc: 3和names列表。imgsz=640是默认,如果原图分辨率远大于 640,灰烬这种小目标会被缩没,可以考虑imgsz=960,但显存要够。batch=16在 221 张图上意味着一个 epoch 只有约 14 个 iteration,所以epochs要给到 150 以上,否则模型根本没看够数据。
4.2 小样本下增强参数怎么设
小样本的核心矛盾是:增强太弱容易过拟合,增强太强会把灰烬这种低对比度目标彻底糊掉。我一般会关掉或调低几个激进增强。
| 参数 | 默认 | 我的建议 | 原因 |
|---|---|---|---|
| mosaic | 1.0 | 0.5 | 四图拼接对低对比度目标干扰大 |
| hsv_v | 0.4 | 0.3 | 亮度抖动太强,灰烬和阴影更难分 |
| degrees | 0.0 | 5.0 | 田间拍摄有轻微旋转,小幅够用 |
| fliplr | 0.5 | 0.5 | 左右翻转安全,保留 |
| mixup | 0.0 | 0.0 | 小样本上 mixup 收益不稳定,先关 |
yolo detect train data=ash.yaml model=yolov8n.pt epochs=150 imgsz=640 \ mosaic=0.5 hsv_v=0.3 degrees=5.0 mixup=0.0参数说明:mosaic=0.5表示一半概率做四图拼接,降低后模型更容易看到完整单图上下文。hsv_v控制亮度扰动幅度,灰烬本身就是暗目标,扰动过强会让它和湿土混在一起。degrees=5.0给一点旋转不变性。这些值不是标准答案,是我在类似低对比度农业数据上反复试出来的起点,你可以在验证集 mAP 上做小范围网格。
4.3 训练过程盯什么指标
221 张图,验证集可能只有 40 多张,mAP 波动会很大,别被单个 epoch 的数字带偏。我一般盯三件事:train/box_loss是否稳定下降、metrics/mAP50是否在 100 epoch 后趋于平台、以及验证集预测可视化里有没有把阴影误检成灰烬。如果 mAP50 卡在 0.3 以下不动,先别调模型,回去查标签质量——小样本上标签错误的影响被放大得特别明显。
5. 避坑与排查:小样本灰烬检测的五个血泪记录
5.1 现象:mAP 一直上不去,loss 却降得很好
原因:训练集和验证集分布不一致,比如验证集全是阴天图,训练集全是晴天图。221 张图如果按顺序切分,很容易出现这种偏斜。解决:切分前先按拍摄时间或光照条件分层,或者干脆用随机种子多切几次取平均,别用默认的前 80% 后 20%。
5.2 现象:模型把阴影全检成灰烬
原因:灰烬和阴影在灰度上高度重叠,模型学到的是"暗"这个特征而不是"灰烬"的纹理。解决:在增强里加入更强的色彩抖动(hsv_h、hsv_s调高),逼模型关注纹理;同时检查标签里有没有把大片阴影误标成灰烬,这种脏标签会直接教坏模型。
5.3 现象:训练报错 "negative coordinates" 或直接 NaN
原因:VOC 转 YOLO 时没做坐标裁剪,框超出图像边界,归一化后出现负值或大于 1。解决:回到 3.1 的转换脚本,确认max(0, xmin)和min(w, xmax)那两行生效,然后重跑体检脚本。
5.4 现象:推理时框位置整体偏移
原因:转换时用了 XML 里的<size>而不是真实图像尺寸,两者不一致。解决:统一用 PIL 或 OpenCV 读图取尺寸,永远不信标注文件里的 size 字段。
5.5 现象:某个类别几乎检不出来
原因:3 个标签的样本数严重不均衡,比如灰烬 200 个框、残茬只有 15 个框。解决:先统计每类框数,对稀有类做针对性过采样,或者用copy_paste增强把稀有类贴到其他图上。如果某类框数少于 30,基本可以判定这个数据集训不出可用的该类检测器,得先补数据。
6. 把 221 张用到极致:交叉验证与推理阈值微调
小样本数据集最忌讳的就是"训一次看个 mAP 就完事"。我一般会做 5 折交叉验证,把 221 张分成 5 份轮流做验证,这样得到的 mAP 才有统计意义,而不是某一次随机切分的运气。做法上,写个脚本生成 5 份ash_fold{i}.yaml,每份的 train/val 路径不同,然后循环训练。
for i in 0 1 2 3 4; do yolo detect train data=ash_fold$i.yaml model=yolov8n.pt epochs=150 imgsz=640 name=fold$i done跑完 5 折,把每折的metrics/mAP50拉出来看均值和方差。如果方差超过 0.1,说明数据分布太不均匀,得回头做分层切分。这个流程比单次训练多花 5 倍时间,但对 221 张这种量级,是判断"这条路到底行不行"最靠谱的方式。
推理阶段的阈值微调同样关键。灰烬检测里,置信度门限调高会漏掉暗弱目标,调低会引入大量阴影误检。我一般会在验证集上扫一遍 0.1 到 0.5 的阈值,画一条 precision-recall 曲线,取 F1 最高的点。
from ultralytics import YOLO model = YOLO("runs/detect/fold0/weights/best.pt") for conf in [0.1, 0.2, 0.25, 0.3, 0.4, 0.5]: m = model.val(data="ash_fold0.yaml", conf=conf) print(conf, m.box.map50, m.box.mp, m.box.mr)逻辑说明:conf是推理置信度门限,map50是 IoU=0.5 下的平均精度,mp、mr是平均精确率和召回率。灰烬这种目标,我通常会把门限设在 F1 峰值略偏召回的一侧,宁可多检几个可疑区域让人工复核,也别漏掉真正的焚烧点。这个取舍取决于你的下游是自动执法还是人工核查,没有统一答案。
最后说个我自己的习惯:每次拿到新的小样本数据集,我都会先只用 20 张图训一个极简模型,看它能不能在训练集上过拟合到 mAP 接近 1.0。如果连过拟合都做不到,说明数据或流程有硬伤,这时候调任何超参都是浪费时间。221 张不算多,但足够你把从格式转换、增强配置到交叉验证的整条链路走通一遍,这套流程搬到烟草病虫害、鸟类识别这类同样小样本的农业检测任务上,基本可以直接复用。希望帮到你。
本文还有配套的精品资源,点击获取