简介:面向智慧交通场景的城市道路打场晒粮检测数据集说明文档,定位服务于目标检测算法研究及智慧城管、交通违规行为识别等应用场景,帮助开发者快速掌握符合YOLO/VOC训练要求的标注数据组织方式。资源以单个docx文档形式打包,共1个文件,资源包大小约2.32MB,文档内含数据集格式说明、标注统计与类别定义,便于对照使用。文档明确说明数据集包含1065张jpg图片,对应1065个xml标注文件和1065个txt标注文件,标注类别为shailiang(晒粮),共1245个矩形框,所有标注均通过labelImg工具完成,类别单一、规则明确,可直接迁移至目标检测项目。资源适合具备一定深度学习基础、需要道路场景数据集或格式转换参考的学习者,已有63人学习浏览。借助这份说明可快速理解数据集的标注格式与统计信息,避免自行整理格式,节省前期准备时间。
1. 打场晒粮检测:一份 1065 张图、1245 个框的单类别数据集
道路打场晒粮检测是智慧交通里一个非常具体、又特别容易被通用目标检测框架带偏的需求:夏秋两季,国省道和乡镇公路上经常出现整片占道晾晒的粮食,车辆压上去轮胎附着力骤降,急刹直接侧滑。这种场景靠路政巡逻发现太慢,靠监控墙人眼盯也不现实,真正能落地的做法是给路口和卡口摄像头配一个实时检测模型。这份数据集就是为这个场景准备的——1065 张真实道路场景 jpg 图片,Pascal VOC 与 YOLO 双格式标注,单一类别 shailiang,共 1245 个矩形框。适合已经在跑目标检测、想拿真实占道晒粮数据训练识别模型的从业者,也适合刚接触 YOLO 的初学者:数据是干净的,格式是标准的,拿它把从数据集到权重文件的完整流程跑通,比对着 COCO 空想靠谱得多。
2. VOC 与 YOLO 双格式:文件结构、标签字段与标注规则
这份数据集最实在的地方是同时给了两种标注格式:每个 jpg 图片对应一个 VOC 的 xml 文件和一个 YOLO 的 txt 文件,不需要自己拿 labelImg 重新画一遍。先把三种文件的对应关系讲清楚,后面训练才不会被路径和格式问题卡住。
2.1 xml 与 txt 是怎么一一对应的
Pascal VOC 的 xml 保存的是绝对坐标和类别名,适合人读,也适合做数据可视化检查;YOLO 的 txt 保存的是归一化的中心点坐标和宽高,适合直接喂给深度学习框架。两者描述的是同一个框,只是表达方式不同。
一份 VOC xml 里真正影响训练的字段并不多,关键就这几个:filename(图片文件名)、size下的width/height/depth(图片宽高和通道数,归一化时要用)、object下的name和bndbox(矩形框四角坐标)。对应关系如下表:
| xml 字段 | 含义 | YOLO txt 对应 |
|---|---|---|
| filename | 图片文件名 | 无(由图片文件名关联) |
| size/width, size/height | 图片宽高(像素) | 归一化分母 |
| object/name | 类别名 shailiang | class id 0 |
| bndbox/xmin, ymin | 框左上角坐标 | 计算 x_center, y_center 用 |
| bndbox/xmax, ymax | 框右下角坐标 | 计算 box_w, box_h 用 |
YOLO 格式的每行是五个数字:class_id x_center y_center width height。以一张 1920×1080 的图为例,如果 xml 里是xmin=500, ymin=300, xmax=900, ymax=600,那么 YOLO txt 里对应这行:
0 0.364583 0.416667 0.208333 0.277778怎么来的:x_center=(500+900)/2/1920≈0.3646,y_center=(300+600)/2/1080≈0.4167,width=(900-500)/1920≈0.2083,height=(600-300)/1080≈0.2778。四个值全部归一化到 0~1,和图片原始分辨率解耦,模型训练时不管输入 640 还是 1280,标签都不用改。这也是 YOLO 系算法只认 txt、不认 xml 的原因——少一层运行时解析,数据加载更快。
需要留意的是,这份数据集的 txt 只有样本标签,没有额外提供 train.txt、val.txt 这类划分列表,后面要自己做训练集和验证集划分,后面第 4 章会给出脚本。1045 这个偏差我确认了一下,数量确实是 1065 张图、1065 个 xml、1065 个 txt,一一对应,没有缺漏。
2.2 labelImg 的标注规则与类别名细节
数据集的标注工具是 labelImg,这个工具默认保存的就是 VOC 格式的 xml,很多初学者拿它标注后会再问“怎么导出 YOLO 格式”,作者应该是标注完成后做了格式转换,把两个格式都保留了下来。这类双格式资源的好处是:你用 detectron2、mmdetection 之类吃 VOC 的框架可以直接用 xml,你用 YOLOv5/YOLOv8 可以直接读 txt,不需要在数据准备阶段做格式转换。
标注规则也简单:对每个目标画矩形框,类别只有shailiang一个。这里有一个新手特别容易踩的细节——类别名不是中文,也不是drying、grain这类英文,而是汉语拼音shailiang,在 YOLO 的 data.yaml 里写类别名时必须严格对应这个字符串,如果你的训练脚本里写的是names: ['sun_dried_grain'],模型也能训练,但后续推理结果里类别显示的就是你自定义的名字,和这份数据集的原始标注对不上,做验证集评估时标签映射会乱。
另外,这份数据集平均每张图只有 1.17 个框(1245 框 ÷ 1065 张图),说明大多数图是单目标场景,少数图里有多个晒粮区域。这样分布的好处是类别单一、背景相对可控,训练收敛快;代价是如果你想做密集小目标检测,这个数据集的框密度不够,需要额外补充多目标场景。
2.3 用脚本核对标签和框数
拿到任何数据集,我习惯先跑一个统计脚本,确认三件事:xml 和图片能不能对上、所有类别是不是只有预期的那几个、每张图的框数分布是否正常。这个习惯帮我避过不少坑。下面这个脚本可以直接用:
import os import glob import xml.etree.ElementTree as ET xml_dir = "Annotations" # xml 文件夹路径 jpg_dir = "JPEGImages" # jpg 文件夹路径 xml_files = sorted(glob.glob(os.path.join(xml_dir, "*.xml"))) jpg_files = sorted(glob.glob(os.path.join(jpg_dir, "*.jpg"))) print("xml 数量:", len(xml_files)) print("jpg 数量:", len(jpg_files)) class_counter = {} box_total = 0 missing_jpg = [] for xml_path in xml_files: tree = ET.parse(xml_path) root = tree.getroot() filename = root.findtext("filename") img_path = os.path.join(jpg_dir, filename) if not os.path.exists(img_path): missing_jpg.append(filename) for obj in root.iter("object"): name = obj.findtext("name") class_counter[name] = class_counter.get(name, 0) + 1 box_total += 1 print("类别统计:", class_counter) print("总框数:", box_total) print("缺少对应 jpg 的 xml:", missing_jpg[:10])脚本逻辑:先分别列出 xml 和 jpg 文件,检查数量是否一致;然后逐个解析 xml,核对 filename 对应的 jpg 是否存在;最后统计每个类别的框数和总框数。如果你跑出来的class_counter是{'shailiang': 1245}、总框数 1245,说明标签文件是完整的,可以进入下一步。如果框数比 1245 少,优先怀疑 xml 文件损坏或者复制时丢文件,不要直接开始训练。
提示:import 路径和文件夹名按你本地的实际目录改。Windows 下路径分隔符用
\\或直接用正斜杠,Python 都能处理。
3. 这份数据集能做什么:打场晒粮检测的选型理由与边界
知道格式怎么读之后,更重要的问题是:这个东西到底能解决什么问题?选择目标检测而不是图像分割、选择单类别而不是多类别,背后都有实际原因。这一章把选型理由和数据边界讲透,你拿到手之后才不会用错场景。
3.1 为什么打场晒粮适合用目标检测,而不是分割或分类
打场晒粮在画面里是“一片区域”,很多人第一反应是语义分割,觉得把粮食区域像素级抠出来更精确。但真去落地智慧交通场景,需求方要的是两件事:一是有没有晒粮,二是在画面什么位置。目标检测的矩形框完全满足这两个需求,而且标注成本远低于分割——画一个框几秒钟,画分割掩膜要几十秒。这份数据集全部用矩形框标注,就是奔着工程落地去的。
分类模型为什么也不合适?因为分类只回答“这张图里有没有”,不回答“在哪”。在监控场景里,一张路口的图可能有三个方向都有晒粮,分类模型只能报一个“有”,算法后续联动路政调度时不知道具体要看哪个区域。检测框可以直接输出像素坐标,换算成实际位置后能联动球机变焦抓拍,这在实际项目里是刚需。
从算法角度看,单类别检测的训练目标也简单:YOLO 的损失函数主要由分类损失、置信度损失和框回归损失三部分构成,单类别时分类损失区分“有目标/无目标”,置信度损失负责筛掉背景,框回归损失负责把框收紧。三个目标互不打架,收敛快,对小数据集特别友好。像这份数据集只有 1065 张、1245 个框,如果做成多类别(晒粮、收堆、车辆、行人),平均每类只有两三百个框,检测头很难学出区分度。单类别是这类“路面异常状态识别”场景的常见做法,不是偷懒,是样本量和任务复杂度匹配后的最优解。
3.2 数据分布的边界:哪些场景管用,哪些会翻车
说完了优势,泼一盆冷水:这份数据集不是万能的,它的边界很明显。第一,图片来自城市道路和国省道场景,核心是“道路上出现成片晾晒物”这个特征,对乡村小路、田间地头的场景覆盖有限;第二,全部是白天光照下的样本,没提夜晚、雨天、雾天的情况,监控摄像头在夜间从红外模式切回彩色模式的瞬间、大雾天气下画面发白,模型的检出率大概率会掉;第三,1 类目标只表示“晒粮”,不区分“正在摊晒的散粮”和“已经收堆装袋的粮堆”,实际项目中,路政部门可能两者都要管——收堆占道同样影响交通,但处置优先级不同。
这些不算数据集的缺陷,而是单类别、单一场景数据集的固有边界。你需要做的是在训练前想清楚部署条件:如果你的摄像头点位在白天能见度好的国省道,这个数据集的效果会不错;如果你要做 24 小时全天候监控,就得额外补充夜间数据,或者用数据增强模拟光照变化,但增强只是治标,真实夜视样本才是治本。
3.3 与通用车辆数据集(如 BDD100K)的互补关系
做智慧交通方向的人应该都知道 BDD100K 这类大型车辆检测数据集,里面有 car、bus、truck、person 等常见类别,但几乎没有“路面占道物”这个类别。原因是公开数据集的目标是“识别交通参与者”,而不是“识别交通障碍状态”。而打场晒粮恰恰是后者的典型代表——它不具备车辆的固定形态,颜色从金黄到灰褐不等,边界不规则,还常常和路面标线重叠,通用目标检测模型见到这种目标容易漏检。
这份数据集的价值在于补上了这个空缺。你在自己的项目里完全可以把它当作“基础类别”来用:先把 shailiang 单独训练一个检测头,再和 BDD100K 训练的车辆检测模型做结果融合,一个模型管交通参与者,一个模型管路面异常状态。这也是实际工程里常见的“小模型组合”思路——不是所有任务都必须塞进一个大模型里。
用这份数据集训练还有一个附加收益:它是单类别,画风干净,适合用来系统学习 YOLO 的训练流程。很多人学 YOLO 入门时拿 COCO 80 类数据集,训练慢、参数多、效果还不直观;换成这个单类别数据集,类名就是一个shailiang,配置简单,训练时间短,迭代一次只要十几分钟,适合做消融实验理解学习率、batch size 和增强参数的影响。网上很多“yolov8 训练自己的数据集”教程,本质上就是这套流程:准备 data.yaml、划分数据集、跑训练脚本、看验证结果。
4. 用 YOLO 训练自己的打场晒粮检测器:目录调整、data.yaml 与参数设置
拿到这份数据集之后,下一步就是训练。很多人直接跑 yolov8 训练命令发现报错,不是因为模型有问题,而是目录结构不符合 YOLO 的约定。这一章把标准流程走一遍,从目录布局到划分脚本,再到关键参数调节,按步骤操作就能跑通。
4.1 目录结构整理与 data.yaml 配置
YOLO 系列训练时默认从images/train、images/val读图,从labels/train、labels/val读对应的 txt 标签。这份数据集原始目录是 jpg、xml、txt 平铺或者放在一起的,需要重新组织成如下结构:
dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yaml也就是把图片和标签按 train/val 分成两份,注意 images 和 labels 下的子目录名必须完全一致,YOLO 靠“同名不同目录”来找对应标签:images/train/0001.jpg对应labels/train/0001.txt。目录名不一致是新手最容易翻车的地方,比如图片在train_images、标签在train_labels,训练时模型会报image ... not exists之类的问题。
然后写 data.yaml:
path: /home/user/dataset train: images/train val: images/val nc: 1 names: ['shailiang']path是数据集根目录的绝对路径;train和val是相对path的图片目录路径,不需要写images/train/*.jpg,框架会自动拼接;nc是类别数,单类别写1;names列表顺序要和标签文件里的 class id 对应,这份数据集只有shailiang,所以names的第一个元素必须是它,class id 0 才能正确映射上。如果你的 txt 标签里出现1、2这样的 id,说明数据集不是单类别,需要重新看标注。
4.2 划分训练集与验证集
接下来做样本划分。这份数据集没有提供现成的 train.txt/val.txt,可以用下面这段脚本按比例随机分:
#! /bin/bash IMG_DIR="dataset/images/all" LABEL_DIR="dataset/labels/all" TRAIN_RATIO=0.9 mkdir -p dataset/images/train dataset/images/val mkdir -p dataset/labels/train dataset/labels/val for img in "$IMG_DIR"/*.jpg; do base=$(basename "$img" .jpg) label="$LABEL_DIR/$base.txt" if [ ! -f "$label" ]; then echo "缺少标签文件: $label" continue fi if [ "$(echo $RANDOM % 100)" -lt "$(echo $TRAIN_RATIO * 100 | bc)" ]; then cp "$img" "dataset/images/train/" cp "$label" "dataset/labels/train/" else cp "$img" "dataset/images/val/" cp "$label" "dataset/labels/val/" fi done echo "train 图片数量: $(ls dataset/images/train | wc -l)" echo "val 图片数量: $(ls dataset/images/val | wc -l)"脚本逻辑:遍历全部 jpg,取同名 txt 检查是否存在,然后用$RANDOM取模判断分到 train 还是 val,最后复制过去并打印两边的数量。这里用了cp而不是mv,保留原始文件,方便后续重新划分;等确认划分合理后,可以手动删掉原始全量目录省空间。TRAIN_RATIO是训练集占比,我一般设 0.9,1065 张图分下来大约 958 张训练、107 张验证,验证集虽然小但足够看趋势。如果你的机器显存小、想多留点数据训练,可以调到 0.95,但验证集少于 50 张时评估指标的波动会很大,我不建议再低了。
提示:
bc在部分精简系统里没装,如果你执行时报bc: command not found,直接把TRAIN_RATIO那一行改成固定值if [ $((RANDOM % 100)) -lt 90 ]; then就行。
4.3 训练参数怎么设:从 imgsz 到增强项
目录和 yaml 都就绪后,训练命令本身不复杂。我用 YOLOv8 举例,训练脚本长这样:
from ultralytics import YOLO model = YOLO("yolov8n.pt") # 从预训练权重开始,收敛更快 model.train( data="dataset/data.yaml", epochs=100, imgsz=640, batch=16, lr0=0.01, mosaic=0.5, fliplr=0.5, cache=True, )几个参数的实际含义和为什么这么设,逐个说清楚。
imgsz=640是训练输入分辨率。打场晒粮在画面里通常偏大(占道路宽度的一大片),640 足够让模型看清纹理特征;如果你发现小目标多、框只有几十个像素,再上 1280,但显存占用会涨约 4 倍,我的经验是 640 起步,效果不足再升。
mosaic=0.5是马赛克增强的概率,把 4 张图拼成 1 张训练,能让模型看到更多背景组合。这份数据集单目标居多,拼图能模拟出“同一画面多个晒粮区”的情况,有助于提高多目标场景的召回率,但拼图也可能把晒粮区域裁掉一半,所以我不建议开到 1.0,0.5 是平衡值。
fliplr=0.5是水平翻转增强,对道路交通场景非常安全——左右翻转不改变“晒粮区域”的语义,但要注意的是如果你的后续应用里包含了“根据位置联动球机转向”的逻辑,训练时翻转会让模型对左右位置的敏感度下降,位置输出仍然准确,只是模型不再把“靠右停车带”当作强先验,这不算问题。
batch=16在 1065 张图的数据集上,大约 60 步一个 epoch,100 个 epoch 也就 6000 步,普通单卡十几分钟能跑完。如果显存报错,先把 batch 降到 8,再把cache=True去掉——cache是把图片预加载进内存,能显著提速,但 1065 张 1080p 的图大约占 3~4 GB 内存,小内存机器容易扛不住。
训练完成后看两个指标:val/box_loss是否持续下降、metrics/precision(B)和metrics/recall(B)是否都在 0.9 以上。单类别数据集上如果这两个指标低于 0.85,优先怀疑是标签和图片没对齐,其次再考虑调超参。
5. 避坑指南:拿到这份数据集后最常踩的 5 个坑
数据格式干净不代表训练过程顺利,以下 5 个坑是我在类似数据集上真实踩过的,每一条都按“现象 → 原因 → 解决”写清楚,希望能帮你少走弯路。
5.1 文件与格式层面的坑
坑一:训练时报 “No labels found in train set” 或 “Label not found”,但 labels 目录里明明有 txt。
现象:启动训练后第一轮输出就报错,提示找不到标签文件。
原因:大概率是你的图片和标签分到了不同子目录,或者目录名大小写不一致(例如Labels和labels)。YOLO 在 Linux 下严格区分大小写,Windows 下不区分,代码换到服务器上跑就露馅。另一个高发原因是这份数据集的 txt 文件是 UTF-8 编码,文件里末尾可能有空行,个别解析版本会把空行当作一条无效标签直接忽略。
解决:第一步检查目录树,确保 images 和 labels 的唯一差别是顶层目录名;第二步用命令file labels/train/0001.txt看编码,不是 UTF-8 就转码;第三步打开一个 txt 看内容是否只有一行且五个数字。这三个都没问题,才轮到模型配置层面的排查。
坑二:xml 里记录的是绝对路径,换机器后可视化脚本找不到原图。
现象:写脚本读 xml 做可视化检查时,filename字段或path字段指向本机绝对路径(比如/home/user/Downloads/xxx.jpg),换到服务器后这些路径全部失效,脚本直接报文件不存在。
原因:labelImg 在标注时会自动记录图片路径,如果当时打开图片用的就是绝对路径,xml 里存的就是绝对路径。这份数据集转成 YOLO 格式后,txt 和 jpg 是平铺的,不依赖路径解析,所以训练不受影响;但如果你想用 xml 做数据检查或者用 VOC 格式训练,就会撞上路径失效。
解决:不要直接依赖 xml 里的path,而是以filename为准,在脚本里自己拼一个图片目录前缀。脚本改成img_path = os.path.join(jpg_dir, filename),和我在 2.3 节给的一样,永远不要把 xml 里的路径当真实路径用。
坑三:用 OpenCV 把图片统一转成 PNG 或灰度图后,训练效果暴跌。
现象:为了统一输入格式,用脚本把所有 jpg 转成 PNG,或者转成灰度图,训练完 mAP 下降明显。
原因:转成 PNG 本身不丢信息,但很多人顺手把图片从 BGR 转成了灰度(单通道),模型输入通道数变成 1 或者被框架复制成 3 通道,颜色纹理信息丢了。打场晒粮的识别特征里,粮食的金黄色和道路水泥灰的对比是关键线索,灰度图下两者都变成灰阶,非常容易被当成路面背景。这份数据集的 jpg 本身就是标准三通道,没必要转换。
解决:保持原图格式不转码;如果一定要转,用cv2.imread后注意通道顺序,BGR 转 RGB 再存,不要灰化。
5.2 训练与效果层面的坑
坑四:训练集里几乎全是“正样本”,模型学不到“负面场景”。
现象:模型训练完在验证集上 precision 很高,但接到真实摄像头后疯狂误报,把树叶阴影、路面反光也判成晒粮。
原因:这份数据集的目标是检测晒粮,每张图里都有至少一个框,等于所有样本都是正样本。模型从来没有见过“没有晒粮但长得很像晒粮”的负样本,决策边界自然收不紧。阴影、积水反光、深色路面修补痕迹都容易触发误报。
解决:训练完成后不要急着部署,收集 100~200 张完全没有晒粮的道路图,作为负样本加入训练集,标签为空 txt。YOLO 支持负样本,一个空 txt 就代表这张图没有目标。加入负样本后重新训练,precision 会明显回升。这也是拿到任何单类别检测数据集后都要做的第一步补充。
坑五:盲目加大 imgsz 到 1280,显存直接爆掉,或者训练速度慢到无法接受。
现象:听说高分辨率对小目标友好,直接把imgsz设成 1280,batch 设 16,结果显存溢出(CUDA out of memory)。
原因:分辨率从 640 提升到 1280,特征图面积是原来的 4 倍,计算量和显存占用也接近 4 倍,batch 不变的情况下必然爆显存。打场晒粮目标普遍是大面积区域,不是小目标,640 的输入完全够用,盲目提分辨率是拿短板换长板,没必要。
解决:先用 640 训练并评估,如果确实有零散小框漏检,优先尝试在train参数里加hsv_h=0.015, hsv_s=0.7, hsv_v=0.4这类颜色增强,模拟光照变化,比提分辨率更划算。实在要提升精度再考虑 1024 分辨率,同时把 batch 降到 8,并加cache=True减少 IO 压力。
6. 验证与进阶:从单图推理到第二个类别扩展
训练完别急着收工,先做一轮单图验证,确认模型输出真的符合预期。用训练生成的best.pt,跑一段最直接的推理脚本:
from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") results = model.predict("test_road.jpg", conf=0.25, iou=0.45, imgsz=640) for r in results: for box in r.boxes: cls = int(box.cls[0]) conf = float(box.conf[0]) xyxy = box.xyxy[0].tolist() print(f"类别 id={cls} 置信度={conf:.2f} 框坐标={xyxy}")conf=0.25是置信度阈值,低于这个值的框会被过滤。打场晒粮区域边缘通常模糊,框的置信度不会像车辆检测那么高,0.25 左右比较合适;如果你部署的场景对误报零容忍(比如联动自动执法),往上调到 0.4,但召回会下降。iou=0.45是 NMS 阈值,单类别且目标互不重叠的图用默认值就好。输出列表里cls=0对应shailiang,置信度正常应当超过 0.5。
验证通过之后,我建议你再做一件事:单类别模型往往在“第一版 demo”里够用,但真实路政场景里,“正在晒粮”和“已收堆但占道”是两种不同优先级的告警。给这份数据集扩展第二个类别的思路是:先用现有模型对新增图片做粗标注,导出带框的图片,再人工把“已收堆”的框挑出来改成shoudan或dui的类别,最后用 labelImg 修正边界。这样比从零标注省一半时间。改完类别后,把data.yaml改成nc: 2,names: ['shailiang', 'dui'],重新训练即可。已有权重里 class id 0 的参数可以保留,相当于迁移学习只多学了一个头,收敛速度很快。
我的习惯是:每拿到一份数据集,先跑统计脚本确认框数,再单图推理验证模型输出,最后才敢喂给训练脚本。这三个步骤全部走一遍,基本能过滤掉 80% 的格式坑和标注坑。这份数据集本身是靠谱的,剩下的就看你怎么用它——先把 640 分辨率、单类别、0.9 划分这套组合跑通,再根据自己的场景逐步加负样本、加类别,打场晒粮检测这个事就能真正落地。希望帮到你。
本文还有配套的精品资源,点击获取