打场晒粮检测数据集:VOC转YOLO流程与避坑实战 简介面向智慧交通与城市道路治理场景的目标检测数据集围绕“打场晒粮”这一特定违规/隐患行为构建适合用于训练车辆或道路监控场景下的识别模型也可为相关科研课题提供数据基准。数据集包含1065张JPEG图片每张均配有Pascal VOC格式的XML标注文件和YOLO格式的TXT标注文件统一使用labelImg标注工具对目标绘制矩形框标注类别仅设“shailiang”一类全部标注框合计1245个类别与框数信息清晰格式规范、数量充足可直接用于YOLO系列或VOC格式模型的训练、验证与评估。资源压缩包体积仅2.32MB内含1个docx说明文档无额外冗余文件便于快速下载与解压查看。目前已有63人浏览学习适合计算机视觉初学者、算法工程师及智慧城市项目开发者作为数据补充或基准测试使用。需注意数据集仅提供准确标注的图片与标签文件不包含训练好的模型或权重也不对模型精度作任何保证请按需取用。1. 打场晒粮检测数据集道路违占场景下的1065张专项样本秋收季节农村穿村公路和城郊道路上经常铺满晾晒的粮食车辆经过时打滑、紧急避让事故率明显上升。智慧交通摄像头拍到了画面但如果只靠人工盯屏几十上百路视频根本盯不过来。要做自动识别“打场晒粮”的检测模型第一步就得有高质量的标注数据。这份VOCYOLO双格式、1065张、1类别的检测数据集正是为解决“摄像头看见但系统不认识”这个断点而准备的。它的价值不在数量大而在场景聚焦全部样本都围绕道路摊铺粮食这一种目标标注格式覆盖了从人工标注到模型训练的常用链路。适合算法工程师做智慧交通场景验证也适合刚入门目标检测的开发者用来熟悉VOC和YOLO两种数据体系之间的流转。2. 打场晒粮样本从哪里来VOC标注流程与场景采集规范2.1 为什么先标VOC、再转YOLO给数据留一条后路打场晒粮检测数据集最常见的交付格式是VOC和YOLO两种并存。VOC格式用XML文件记录每一张图的尺寸、目标类别和边界框坐标坐标是真实的像素值人眼可以直接读、可以直接画出来检查YOLO格式用一行归一化的txt表示一个目标。我的习惯是不管最终训练用什么框架标注阶段一律先做VOC训练前再写脚本转YOLO。理由有三。第一labelImg这类经典标注工具原生输出VOC XML不需要额外插件安装完就能干活。第二XML里保留了图像尺寸、目标坐标、difficult等附加信息后面做数据清洗时能追溯哪张图哪个框有问题一目了然。第三万一之后不打算用YOLO而改用Faster R-CNN或SSDVOC格式可以直接被torchvision、mmdetection读取不需要重新标一遍。很多第一次做数据集的人上手就手写YOLO的txt坐标写错一点点肉眼根本发现不了等训练时损失不收敛才回头查数据非常被动。先标VOC、再转YOLO看似多了一步实际上给整个数据链路留了后悔药。2.2 图像采集与筛选覆盖时段、机位和负样本打场晒粮检测的特殊之处在于目标和道路背景高度相似。粮食摊铺在沥青路面上颜色从金黄到土黄不等边界往往不是锐利的而且会被来往车辆碾压、被风吹散形态变化很大。采集样本时不能随便抓几张图就标注我一般按三个维度控制数据分布。第一个是时段维度。要覆盖上午十点前低角度阳光、正午强光、傍晚逆光以及路灯开启后的夜间画面。白天和夜间的粮食外观差异非常大只采白天会导致夜间漏检只采晴天会导致阴天场景失灵。第二个是机位维度。同时截取高位监控俯拍和低机位枪机平视视角因为不同路口的摄像头安装高度差别很大模型只在一种视角上训练换一个路口就失灵。第三个是干扰维度。保留一部分没有晒粮但路面纹理复杂的负样本让模型学会区分“干燥路面”和“摊铺粮食”不然雨天路面积水、路面裂缝都会被当成目标。具体操作上常见做法是从监控录像里按固定间隔抽帧。用ffmpeg从视频里每隔10帧取1帧或者用播放器逐帧截图。抽帧之后做一次初筛把重复度极高的连续帧、严重过曝和模糊帧剔除。像素上尽量保留1280×720以上分辨率因为晒粮区域在画面里往往占比较大低分辨率虽能看清但对后续小目标检测没有帮助。负样本不需要多占总量10%左右即可它们的作用是给训练提供“背景类别”的对比避免模型把一切黄色区域都当成粮食。2.3 用labelImg完成VOC标注XML里的关键字段与标框边界标注工具用labelImg最省事。常见安装方式是pip安装或conda安装启动时把图片目录和类别文件一起传进去。预定义类别文件一行一个类别名打场晒粮这个数据集只有1个类别文件里写一行即可。标注操作本身不复杂但要约定标框规则这是数据质量的分水岭。我的规则是连片粮食摊铺区域算一个目标框中间被农机、车辆或明显路肩分隔开的断开区域另起一个框如果一整片粮食占据画面超过一半就拆成两个或三个框而不是硬套一个大框。原因在于目标检测训练时过大的锚框对特征图分辨率要求高训练阶段如果框占比超过预期损失函数容易不稳定推理时也容易出现同一个目标被重复检测的问题。保存XML后建议立刻打开一两个XML检查size和bndbox字段是否正常避免标注工具闪退导致写到一半的文件损坏。关于XML字段重点看这几个folder和filename对应图片所在目录和文件名size节点下width、height、depth对应图像宽、高、通道数object节点下name是类别名bndbox节点下xmin、ymin、xmax、ymax是边框的左上角和右下角像素坐标。如果某个目标的xmin比xmax还大或者在图像范围之外这份标注在转YOLO时就会出问题所以我一般会在转格式前先写一个校验脚本。校验至少要做三件事检查坐标是否越界、检查框宽高是否为0、检查图片能否正常解码。这一步花十分钟能省下训练时排查NaN的一整天。字段路径含义常见异常size/width图像宽度像素值与图片实际分辨率不一致size/height图像高度像素值竖图和横图搞混object/name目标类别名类别名大小写不一致bndbox/xmin框左上角x负数或超出图像宽度bndbox/ymax框右下角y小于ymin坐标倒挂3. 把VOC转成YOLO格式归一化坐标、数据集划分与可视化复查3.1 YOLO标注为什么用归一化中心坐标YOLO系列模型读取的标注格式是每行“类别id 中心点x 中心点y 框宽 框高”四个坐标值都除以图像宽高做了归一化。这样做的好处是图像输入训练网络时会被缩放到固定尺寸比如640×640归一化坐标不依赖原始分辨率模型缩放图像时标注不需要跟着变。对应的坐标取值范围是0到1之间中心点cx、cy是边界框中心相对图像宽高的比例w、h是边界框宽度和高度相对图像宽高的比例。初学者最容易犯的错误是写成像素坐标。VOC里的bndbox是像素值直接原样写进YOLO txt会导致归一化后的值远大于1模型训练时完全不收敛。另一个常见错误是中心点坐标算错中心点的x是(xminxmax)/2不是(xmax-xmin)/2后者是框的宽度。每次看到有人把w写成中心点坐标就知道又是从别的项目里复制代码没看公式。转格式这事不复杂但公式错一个符号后期排查成本极高。3.2 VOC转YOLO脚本逐字段解析与越界修正下面这个脚本是我常用的VOC转YOLO版本完整处理了xml读取、坐标归一化和越界修正三个环节。拷贝到项目里改一下目录路径就能直接跑。import os import xml.etree.ElementTree as ET from tqdm import tqdm # 需要修改的路径 voc_dir path/to/Annotations # VOC XML 所在目录 yolo_labels_dir path/to/labels # YOLO txt 输出目录 class_name threshing # 打场晒粮的类别名与训练配置保持一致 os.makedirs(yolo_labels_dir, exist_okTrue) for xml_file in tqdm(os.listdir(voc_dir)): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(voc_dir, xml_file)) root tree.getroot() img_width int(root.find(size/width).text) img_height int(root.find(size/height).text) out_lines [] for obj in root.findall(object): name obj.find(name).text if name ! class_name: continue xmin int(obj.find(bndbox/xmin).text) ymin int(obj.find(bndbox/ymin).text) xmax int(obj.find(bndbox/xmax).text) ymax int(obj.find(bndbox/ymax).text) # 越界修正标注时手抖画到图像外这里强制拉回边界内 xmin max(0, xmin) ymin max(0, ymin) xmax min(img_width - 1, xmax) ymax min(img_height - 1, ymax) # 归一化中心坐标与宽高 x_center ((xmin xmax) / 2) / img_width y_center ((ymin ymax) / 2) / img_height w (xmax - xmin) / img_width h (ymax - ymin) / img_height out_lines.append(f0 {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n) if out_lines: out_txt os.path.join(yolo_labels_dir, xml_file.replace(.xml, .txt)) with open(out_txt, w) as f: f.writelines(out_lines)这段代码的逻辑很直接遍历Annotations目录下所有XML解析出图像宽高遍历所有object节点筛选出目标类别取bndbox四个坐标做clip修正后归一化最后写成与图片同名的txt。这里类别id直接写死为0因为打场晒粮数据集只有1个类别如果以后扩展多类别需要维护一个类别名到id的映射字典不能再用固定值。参数上有两个值得注意的地方。img_width和img_height来自XML而不是真实图片好处是能暴露标注和图片不一致的问题坏处是如果XML本身写错转换结果也会错。所以我在转换后会跑一遍可视化复查见3.4节。min和max的clip操作不是可有可无的标注框超出图片边界时YOLO训练阶段会计算出不合理的anchor匹配轻则警告重则loss变成NaN。输出目录与图片目录最好放在同级YOLO训练时data配置文件需要指定图片目录和标签目录多数框架要求同一前缀的图片和标签在各自目录里一一对应。建议目录结构按“images/xxx.jpg labels/xxx.txt”组织训练配置里只需要写这两个根路径。3.3 数据集划分按摄像头来源分组别让验证集虚高划分数据集时有两种常见做法随机切分和按来源分组切分。1065张的规模其实不大如果直接调用random.shuffle后按比例切成train/val/test看起来很简单但打场晒粮样本大量来自同一监控录像的连续抽帧相邻帧之间只有少量位移把它们随机打散到三个集合里训练集和验证集会出现高度相似的图片验证分数会虚高到了真实路口就翻车。我一般会按图像文件名里带的前缀分组。比如命名规则是cam01_000123.jpgcam01是摄像头编号000123是帧序号。先按摄像头编号分成组再把组按8:1:1比例划分成train、val、test这样同一台摄像头的画面只会出现在一个集合里。下面这段脚本可以完成分组切分。import os import random from collections import defaultdict random.seed(42) # 固定随机种子保证换机器可复现 image_dir path/to/images image_files [f for f in os.listdir(image_dir) if f.lower().endswith((.jpg, .jpeg, .png))] # 按文件名前缀分组命名约定如 cam01_000123.jpg groups defaultdict(list) for f in image_files: prefix f.rsplit(_, 1)[0] groups[prefix].append(f) group_names list(groups.keys()) random.shuffle(group_names) n_train int(len(group_names) * 0.8) n_val int(len(group_names) * 0.1) train_groups set(group_names[:n_train]) val_groups set(group_names[n_train:n_train n_val]) test_groups set(group_names[n_train n_val:]) def write_split(out_path, selected_groups): with open(out_path, w) as f: for g in selected_groups: for img in sorted(groups[g]): f.write(os.path.splitext(img)[0] \n) write_split(train.txt, train_groups) write_split(val.txt, val_groups) write_split(test.txt, test_groups)这段脚本的关键在于先按前缀聚合再打乱组顺序而不是打乱单张图片。这样同一摄像头的连续帧必然落在同一个集合里验证集不会因为和训练集高度相似而虚高。random.seed(42)是复现实验的关键参数换机器跑同样的代码能得到同样的划分否则今天调A参数、明天调B参数对比结果时根本分不清是模型改进还是数据划分变了。比例上如果数据量小于300张可以把test砍掉只保留train和val用交叉验证替代。train.txt、val.txt、test.txt里只是文件名列表真正的训练还需要在data配置里指定路径。很多新手把image路径和label路径写错训练时报unable to load image这时候检查YOLO的data yaml里path、train、val、nc、names五个字段是否正确。打场晒粮是单类别nc1names列表里只有打场晒粮一个名字顺序要和标注时保持一致。3.4 训练前用可视化脚本复查标注把txt画回图上转完格式直接开训是翻车高发点。我自己习惯先把标注画回图像上抽查30张左右这一步能同时发现三类问题坐标归一化取反导致框画在错误方向、某些目标漏标或错标成别的物体、图片颜色异常或模糊。下面这段代码可以批量生成检查图。import cv2 import os image_dir path/to/images label_dir path/to/labels check_dir path/to/check os.makedirs(check_dir, exist_okTrue) for txt_file in os.listdir(label_dir): if not txt_file.endswith(.txt): continue img_name txt_file.replace(.txt, .jpg) img_path os.path.join(image_dir, img_name) img cv2.imread(img_path) if img is None: continue h, w img.shape[:2] with open(os.path.join(label_dir, txt_file)) as f: for line in f: cls, 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, 0, 255), 2) cv2.imwrite(os.path.join(check_dir, img_name), img)循环逻辑不复杂读取图片宽高读取txt里的归一化坐标换算成像素坐标后用opencv画红色矩形保存到check目录。换算时要注意txt里的坐标全部是0到1的浮点数必须乘上当前图片的实际宽高而且要减半才能得到左上角坐标。直接拿中心点当左上角画框会让框偏移半个身位这种低级错误用可视化一眼就能看出来。抽查建议是不要只抽前几十张要按摄像头分组抽样每个组抽2到3张覆盖白天、傍晚、夜间和逆光。检查标准是框紧贴粮食摊铺区域边缘没有把护栏、路肩、树影大片框进去。如果看到某些框明显大一圈回到VOC阶段重新标注不要指望训练时损失函数帮你纠偏标注错的东西模型只会学得更错。4. 打场晒粮检测的避坑实录数据质量与训练翻车的五个排查点4.1 现象把农机具、路面裂缝误检成打场晒粮模型在验证集上表现不错但一跑真实视频收割机的割台、路面的裂缝、甚至土黄色的水渍都被框成打场晒粮。原因是标注阶段把背景纹理也包进了边界框。很多标注新手为了让框“好看”把粮食边缘外几像素的沥青路面也纳进来模型学到的其实是“黄色块粗糙纹理”的组合特征而不是粮食本身。解决方法是收紧bbox到肉眼可见的粮食摊铺边缘宁可让框紧贴粮食也不要多包背景。同时增加负样本把农机作业、空载货车停靠、干燥路面等场景各抽几张放进训练集让模型见过“不是粮食但长得像粮食”的东西。负样本不需要多占总量10%左右就能明显压住误检。4.2 现象夜间与逆光场景明显漏检白天检测一切正常一到傍晚逆光或者夜间路灯下目标直接丢了。原因是采集时段太单一只选了上午10点到下午3点阳光强烈时的画面。粮食在逆光下和沥青路面的对比度极低夜间路灯下颜色偏暗偏灰与白天样本的分布差异很大模型自然认不出来。解决方法是补采傍晚低角度阳光和路灯开启后的样本至少覆盖一天中5个时段。如果重新采集成本高先用数据增强顶一顶在HSV空间里把黄色粮食的饱和度、明度做扰动而不是直接全套用随机亮度。增强能缓解分布偏移但不能替代真实场景样本最终还是要补采夜间数据。4.3 现象验证集mAP很高但实拍视频效果差训练时验证集mAP能到90以上把模型接到实际路口录像上漏检和误报都很多。最常见的翻车原因是数据划分时没有分组同一摄像头同一段录像的连续帧同时出现在训练集和验证集导致验证集和训练集图像高度相似mAP虚高。我之前遇到过mAP 92的模型上线后连续漏检了半小时查到最后就是划分脚本用了随机打乱单帧而不是按摄像头分组。解决方法是严格按3.3节的方式划分数据并按摄像头来源留出盲测集也就是完全不参与训练和验证的那一路画面。模型上线前先跑一段未参与训练的路口录像用误报数和漏报数/小时来评估而不是只看mAP。4.4 现象YOLO训练loss出现NaNloss曲线乱跳训练到第几轮loss突然变成NaN或者从一开始就在几个值之间剧烈跳动不下降。70%的情况出在数据上标注框越界没被修正、XML里width或height记录为0、图片文件损坏无法解码、txt里出现空文件。解决方法是转换脚本里保留越界修正逻辑训练前跑一遍图片解码校验。下面这段代码把无法正常解码的图片单独列出来。import cv2 import os bad_images [] for f in os.listdir(path/to/images): img cv2.imread(os.path.join(path/to/images, f)) if img is None or img.size 0: bad_images.append(f) if bad_images: print(bad images:, bad_images) else: print(all images ok)这个脚本不要只在转换后跑一次把数据集拷贝到另一台机器上训练时也要跑因为文件拷贝过程中可能损坏尤其是通过移动硬盘或网络传输时。空标签同样要排查某个txt文件大小为0会让训练时batch内出现无目标样本轻则影响损失计算重则直接报错。训练初期如果loss曲线异常先把数据和标签都过一遍再去找模型结构的问题。4.5 现象训练轮数加大后验证loss先降后升随着epoch增加训练loss持续下降但验证loss过了某个点之后不降反升说明模型开始过拟合把训练集里的背景纹理也记下来了。1065张样本对深度学习模型来说不算充裕尤其是单类别单场景模型很容易把路面颜色分布也一起学进去。解决方法是两条路并用加载COCO预训练权重做迁移学习而不是从零训练随机初始化权重训练时设置早停策略patience设置在50左右验证指标连续50轮不更新就停。数据增强方面不要把mosaic开满mosaic概率建议降到0.5。打场晒粮在画面里往往是大目标mosaic会把图片裁成四块拼接目标主体容易被截断模型反而学不到完整的粮食摊铺形态。5. 1065张样本的增效空间数据增强、迁移学习与实拍闭环验证5.1 增强策略调参比堆数据更重要1065张的规模决定了不能搞复杂增强。我的常用配置是hsv_h0.015、hsv_s0.5、hsv_v0.4三个参数分别控制色调、饱和度和明度的扰动范围。打场晒粮目标的颜色集中在金黄到土黄区间饱和度扰动稍微大一点没关系但色调扰动不能太大不然粮食变成绿色模型就学歪了。垂直翻转flipud建议关掉监控画面是正常的道路视角翻转后语义变得不自然。随机旋转角度控制在±10度以内大角度旋转会让粮食摊铺区域变形严重。5.2 迁移学习与损失函数调整让小数据集快速收敛加载COCO预训练权重做迁移学习比随机初始化权重收敛速度快得多。常见做法是先用预训练权重从头训练整个网络loss不再下降后解冻全部层用更低的学习率微调。打场晒粮是大目标默认的anchor配置可以保留但如果发现小目标漏检可以手动把anchor尺寸调小。损失函数方面默认的CIoU loss在这个场景下够用不需要额外改。真正影响收敛的是学习率初始学习率0.01对预训练权重来说偏大我一般从0.001开始batch size在16到32之间。5.3 实拍验证闭环导出onnx或tensorrt后跑盲测训练完成后不要直接上墙把模型导出为onnx格式再转成对应硬件平台的推理格式用一段完全没参与训练的路口录像做盲测。评估指标不是mAP而是单位时间内的误报数和漏报数。我自己做安防项目时吃过验证集mAP高、上墙就翻车的亏后来坚持按摄像头来源分组切数据并保留一路盲测录像后才敢交给客户。这趟流程走下来VOC转YOLO、分组划分、可视化复查、迁移学习微调每一步都有明确的检查点数据集的1065张才能发挥出真实价值。希望帮到你。本文还有配套的精品资源点击获取