资讯动态

水泥泵车目标检测数据集:VOC格式解析与YOLO训练实战

发布时间:2026/10/1 4:53:06 来源:尧图企业网站定制
简介VOC格式工程车辆目标检测数据集集中关注水泥泵车单一类别收录了604张已标注好的JPEG图像面向需要训练检测模型的算法工程师、高校学生以及竞赛参与者可省去自行采集和标注工程车辆数据的繁琐流程。压缩包共1209个文件包含604张jpg原图和604份配套xml标注另有一个txt说明文档整体大小仅49.38MB属于轻量级数据集。标注全过程使用labelImg工具以矩形框形式标出每一辆水泥泵车的位置共产生626个标注框标注类别统一为shuinibengche便于集中优化单一类别检测精度所有图片均经过MD5去重部分截自视频的画面虽看似相近但重复项已剔除干净。数据集未附带YOLO等额外格式仅保留标准的VOC jpgxml结构可直接应用于SSD、Faster R-CNN等框架适合做工程车辆识别基线实验或入门练习目前已有331人学习/下载。数据集只保证标注准确、合理不承诺模型训练效果介意视频截取画面的用户请先确认再下载。1. 水泥泵车目标检测数据集VOC格式下的真实工地样本做工地视觉方案的人一定有个共同体验路上跑的小轿车、行人、红绿灯公开数据集一抓一大把但一到工程车辆尤其是水泥泵车这种长臂展、多关节、箱体反光的特种车能找到的标注样本少得可怜。这份604张jpg加604个xml的Pascal VOC格式数据集专门解决这个缺口。类别只有shuinibengche一个标注框总数626个全部是labelImg画的矩形框。对目标检测入门者来说它是不需要出采集设备的第一个练手数据对有迁移学习需求的工程师来说它是工地场景微调前最合适的预训练补充。它的价值不在多而在单一类别、标注统一、格式干净拿来即可进入训练流程。2. 拆开看VOC工程结构jpg和xml如何配对与解析2.1 文件组织的核心规则同名配对拿到资源后第一件事不是看图而是理解它的组织方式。这个数据集只有jpg图片和对应xml文件没有分割txt也没有yolo格式的txt。文件名形如firc_snbc_389.jpg和firc_snbc_389.xml前缀firc加序列号是labelImg默认保存规则生成的没有额外的人工改名。这里最关键的约定是xml文件名必须与图片完全同名仅后缀不同。任何目标检测训练框架读取VOC格式时都是通过这个同名关系去定位标注文件的。jpg和xml各604个一一对应。标注工具用的是labelImg在保存时选择Pascal VOC格式就会自动生成包含图像尺寸、目标类别、矩形框坐标的xml。每张图对应一个xmlxml内可能包含多个object节点也就意味着单张图片里可能有多个水泥泵车框。文件项数量说明jpg图片604视频截帧经MD5去重表面可能有相似帧xml标注604labelImg生成的Pascal VOC格式标注类别1shuinibengche标注框总数626平均每张约1.04个框2.2 从xml中读出关键字段拿到别人标注好的数据第一步永远是验证标注质量而不是直接开训。用Python的xml.etree.ElementTree可以快速遍历所有xml并提取框坐标。以下这段脚本是检查标注完整性的常用做法import xml.etree.ElementTree as ET def parse_voc_xml(xml_path): tree ET.parse(xml_path) root tree.getroot() # filename是xml里记录的图片名用于和实际文件比对 filename root.find(filename).text # size节点记录的是标注时的图像宽高转换yolo格式时要用 size_node root.find(size) img_w int(size_node.find(width).text) img_h int(size_node.find(height).text) objects [] for obj in root.findall(object): name obj.find(name).text bndbox obj.find(bndbox) xmin int(bndbox.find(xmin).text) ymin int(bndbox.find(ymin).text) xmax int(bndbox.find(xmax).text) ymax int(bndbox.find(ymax).text) objects.append({name: name, bbox: [xmin, ymin, xmax, ymax]}) return filename, img_w, img_h, objects这段代码做的事很简单解析xml里的filename、图像尺寸和所有目标框坐标。我在拿到任何VOC格式数据集时都会先跑一遍统计每个xml里读取到的坐标是否在合理范围。需要注意xml里的坐标是像素值单位取决于标注时的原图分辨率后续转YOLO格式做归一化时必须用xml里记录的图像尺寸做分母而不是用cv2重新读取的尺寸因为截图标注后图片可能被压缩过。2.3 626个框分布的含义604张图里有626个框说明多数图片是单目标。单类别数据集在训练时有个优势——类别不均衡问题不存在loss曲线更容易收敛但也有个隐性问题一张图只有一个框时模型的感受野会固化到“整图一个目标”的模式。如果后续要检测密集场景这个数据集的分布与之差异较大需要在更复杂的场景数据上继续微调。标注框由labelImg手绘存在极少数框边缘贴目标过紧或框到背景的情况这是视频截帧数据常见的问题。我建议训练前不手动修框而是依靠数据增强的随机裁剪、mosaic等操作来抵消标注上的小误差。后续在YOLO训练阶段加入适量增强即可。3. 把VOC转成YOLO格式转换脚本与边界处理3.1 为什么要转换数据集只提供了xml标注而YOLO系列训练管道要求的标签是每张图一个txt内容为class_id center_x center_y width height并且所有坐标都归一化到0到1之间。这是两种完全不同的坐标体系VOC记录的是矩形左上角和右下角的绝对像素值YOLO记录的是中心点和宽高的相对比例。很多新手直接拿VOC xml丢进YOLO训练报错就是因为忘了这一步。转换时需要处理三个细节类别到id的映射、像素坐标除以图像宽高得到归一化值、以及越界坐标的裁剪。3.2 转换脚本import os import xml.etree.ElementTree as ET from PIL import Image # 本数据集只有一个类别固定映射到0 CLASS_MAP {shuinibengche: 0} def convert_voc_to_yolo(voc_root, output_root): os.makedirs(output_root, exist_okTrue) for xml_name in os.listdir(voc_root): if not xml_name.endswith(.xml): continue xml_path os.path.join(voc_root, xml_name) img_name xml_name.replace(.xml, .jpg) img_path os.path.join(voc_root, img_name) if not os.path.exists(img_path): print(f[skip] {img_name} 没有对应图片) continue tree ET.parse(xml_path) root tree.getroot() # 这里用PIL读尺寸而不是用xml里的size字段 # 原因受EXIF旋转影响的jpgPIL读到的尺寸才与实际显示一致 with Image.open(img_path) as img: img_w, img_h img.size out_lines [] for obj in root.findall(object): name obj.find(name).text if name not in CLASS_MAP: continue cls_id CLASS_MAP[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) # 归一化前先裁剪越界坐标防止中心点超出图像范围 x_min_c max(min(xmin / img_w, 1.0), 0.0) y_min_c max(min(ymin / img_h, 1.0), 0.0) x_max_c max(min(xmax / img_w, 1.0), 0.0) y_max_c max(min(ymax / img_h, 1.0), 0.0) center_x (x_min_c x_max_c) / 2 center_y (y_min_c y_max_c) / 2 box_w x_max_c - x_min_c box_h y_max_c - y_min_c # 过滤掉坐标异常导致的空框 if box_w 0 or box_h 0: continue out_lines.append( f{cls_id} {center_x:.6f} {center_y:.6f} {box_w:.6f} {box_h:.6f} ) out_txt os.path.join(output_root, img_name.replace(.jpg, .txt)) with open(out_txt, w) as f: f.write(\n.join(out_lines)) if not out_lines: print(f[warn] {img_name} 没有有效标注)脚本里每行txt的格式是类别id 中心x 中心y 宽 高全部保留6位小数足够YOLO使用。类别id从0开始本数据集只有shuinibengche一个类别因此恒为0。{:.6f}控制浮点精度既保证归一化精度又避免文件冗余。运行前确认voc_root路径下有jpg和xml两种文件且文件名完全同名。3.3 转换后必须做可视化验证转换脚本跑完不等于数据没问题。坐标粘贴错误、宽高反了、归一化混用尺寸这类错误在代码里看不出来但画到图上立刻暴露。下面这段代码读取转换后的txt把矩形框画回原图import cv2 def draw_yolo_label(img_path, txt_path, out_path): img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_id, cx, cy, bw, bh parts cx, cy, bw, bh map(float, (cx, cy, bw, bh)) # 归一化坐标乘以图像尺寸恢复成像素值 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, fcls{cls_id}, (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) cv2.imwrite(out_path, img)恢复像素坐标时宽和高必须使用cv2读取的shape值不要使用txt里反推的数值。我在实际项目中抽了20张这种验证图逐个看框是否包住泵车主体。发现疑似框偏移时回到原xml核对原始坐标判断是转换脚本的问题还是标注本身的问题。这一步虽然费几分钟但能为后续训练节省大量排错时间。4. 训练集和验证集划分数据切分策略与YOLO训练参数4.1 随机划分在这里不合适604张图看着不多很多人的做法是random.shuffle之后按9比1切分。这种纯随机切分对拍摄视频截帧的数据集是有隐患的视频相邻帧背景高度相似如果相似帧一部分进了训练集另一部分进了验证集验证指标会虚高模型实际部署到新场景时表现明显变差。我建议先按文件名前缀分组再在组内按比例抽取验证集。这个数据集的文件名都是firc_snbc_xxx.jpg前缀相同代表来自同一个视频源按分组划分可以把同源帧尽量放到同一侧。import os import random random.seed(42) voc_root labels_all val_ratio 0.1 xml_files sorted(f for f in os.listdir(voc_root) if f.endswith(.xml)) # 这里假设文件名的第一段是视频来源标识 by_group {} for xml_name in xml_files: group xml_name.split(_)[0] # firc by_group.setdefault(group, []).append(xml_name) train_names [] val_names [] for group, names in by_group.items(): random.shuffle(names) val_count max(1, int(len(names) * val_ratio)) val_names names[:val_count] train_names names[val_count:]这段脚本按组划分后验证集和训练集之间的画面相似度大幅降低。划分完成后把训练集和验证集的txt分别移动到images/train和images/val对应的labels目录。如果后续想扩大训练数据可以直接用这个脚本重新划分并加随机种子保持结果可复现。4.2 data.yaml与模型选择YOLOv8训练自己的数据集是最常见的做法需要准备一个yaml文件描述数据路径和类别名。这里的类别名必须和xml里完全一致shuinibengche一个字母都不能错否则标签全部失配。path: yolo_dataset train: images/train val: images/val names: 0: shuinibengche路径可以使用绝对路径也可以使用相对path后的子路径。names里的0对应转换脚本里的类别id0。YOLO训练命令如下yolo detect train \ modelyolov8n.pt \ datadata.yaml \ epochs100 \ batch16 \ imgsz640 \ patience20 \ verboseTrue参数说明modelyolov8n.pt是预训练权重604张图这个量级不适合直接从头训练用COCO预训练权重做迁移学习收敛快得多imgsz640是默认分辨率适用于大多数GPUbatch16在8G显存左右的卡上基本能跑如果显存不足降到8patience20表示20轮验证集指标不提升就提前终止能避免无效训练时间。单类别且只有626个框的任务100轮足够了过拟合风险远大于欠拟合风险。4.3 数据增强参数怎么调单类别小数据集最容易出现的就是过拟合。关闭mosaic是常见的错误做法反而应该开启mosaic和mixup来扩大有效样本量。YOLOv8默认增强已经是合理的需要修改的是几个极值参数。在YOLOv8中没有直接命令关闭增强可以在训练脚本里配置。如果使用Ultralytics的Python APIfrom ultralytics import YOLO model YOLO(yolov8n.pt) model.train( datadata.yaml, epochs100, batch16, imgsz640, hsv_h0.015, # 色调增强幅度保持默认即可 hsv_s0.7, # 饱和度增强视频截图颜色偏灰加大饱和扰动有好处 hsv_v0.4, # 亮度增强泵车在不同光照下差异大 degrees10, # 小角度旋转不要超过15度否则框和语义会对不上 translate0.1, # 平移扰动 scale0.5, # 尺度扰动 fliplr0.5, # 水平翻转 mosaic1.0 # mosaic开启 )这些参数需要说明视频截帧的图片色彩单一hsv_s设到0.7可以让模型不去依赖颜色泵车本身是长条形目标degrees旋转不要超过10度防止长边旋转后漏出大块背景mosaic保持1.0因为它能合成多张图片把单目标场景变成多目标混合场景缓解“一张图一个框”导致的感受野固化问题。5. 避坑排查数据集使用中反复出现的五个问题5.1 图片计数为604但xml解析时部分文件坐标越界现象转换YOLO格式后训练日志里出现大量WARNING: corrupted image或坐标警告部分训练样本被跳过。原因视频截帧后经MD5去重但去重不清理标注框在图像边缘的极端情况。labelImg手动标注时矩形框四个顶点可能被拖到画布边缘之外导致xml里记录的xmax或ymax大于图像实际宽高。解决在转换脚本中对所有坐标做clip代码第3.2节里max(min(..., 1.0), 0.0)就是在做这件事。做完clip后重新统计一下有多少框被裁剪过如果占比超过5%建议逐张人工复查。5.2 训练正常收敛但验证集mAP只有0.5左右现象训练loss下降顺畅保存的best.pt在验证集上mAP50只有0.5上下部署到现场视频里误检一堆。原因训练集和验证集划分方式不对。直接shuffle会把高度相似的视频相邻帧拆到两个集合里模型实际没有学到泛化特征只记住背景到了新场景就翻车。解决按4.1节的分组分帧逻辑重新划分保证验证集和训练集没有同源帧。重新训练后mAP会先下降那才是真实水平。5.3 类别名写成cement_pump模型从头到尾不检测现象训练完成后测试单张图完全没有框输出但训练日志正常。原因这份数据集的类别名是shuinibengche不是工程英文名。很多人习惯性映射成自己熟悉的英文类别导致类别字典和标注label对不上。YOLO训练时所有标签按id读取但如果data.yaml里的names顺序与txt里的id不对应预测时显示的名称和实际上根本不是同一个学习目标。解决转换脚本里的CLASS_MAP严格遵守数据集原始的shuinibengche不要自己改名字。data.yaml中的names列表也保持同一个值。5.4 数据增强过度训练出来对水泥泵车吊臂的检测不稳定现象模型能框住泵车主体但吊臂和泵管经常漏检尤其当泵车斜向停放时。原因单框标注范围大吊臂和车身是同一个框模型学到的语义是“整片区域的组合”。增强参数中degrees过大时长条形的泵车旋转后目标形状变化剧烈模型被迫学习旋转后的非典型形态。解决把degrees降到5scale保持0.5以内关掉mosaic里的大面积裁剪。增加一个专门的难例集手动框出吊臂单独作为补充训练样本。5.5 部分xml文件缺失导致训练集数量不足现象训练开始前检查数据发现jpg有604个但对应xml只有600左右训练时报警missing label。原因下载解压过程中个别xml文件丢失或者文件系统复制时跳过同名文件。数据集源文件本身是完整的问题出在传输环节。解决先用脚本比对jpg和xml的名字列表找出缺失项for f in *.jpg; do xml${f%.jpg}.xml if [ ! -f $xml ]; then echo missing: $f fi done跑完确认缺失数量是0再进入下一步。如果确实缺了宁可少喂一张图也不要让train阶段出现无标签样本因为YOLO会把无标签图片当成纯背景参与训练污染loss。6. 验证模型是否真的学到了水泥泵车特征三步走6.1 第一步先做30张图的过拟合测试很多人拿到数据集直接训100轮训完发现效果差再回头排查数据浪费时间。我拿到这份数据集后做的第一件事是从训练集随机抽30张图组成mini数据集强制跑50轮观察loss是否降到接近0。过拟合测试的目的是验证代码链路通畅图片读取、标签配对、数据增强、梯度回传任何环节有错都会在过拟合测试中暴露。如果30张图loss都不降先修bug不需要考虑调参。mkdir -p mini/images mini/labels # 从训练集随机抽30张拷贝图片和同名txt到mini目录 python -c import shutil, random random.seed(0) names random.sample(open(train_list.txt).read().splitlines(), 30) for n in names: shutil.copy(fimages/train/{n}.jpg, mini/images/) shutil.copy(flabels/train/{n}.txt, mini/labels/) 6.2 第二步用验证集做置信度阈值曲线模型能收敛后用yolo predict或model.val()跑一遍验证集保存PR曲线数据。单类别模型主要看两个指标mAP50和mAP50-95。604张图的数据量下mAP50在0.8附近是合理预期如果明显低于0.6优先怀疑划分导致的信息泄漏而不是模型能力。部署时把置信度阈值从默认的0.25提高到0.45能显著减少背景误检这类单类别场景不需要用低阈值去捞回目标。yolo detect val \ modelbest.pt \ datadata.yaml \ conf0.25 \ iou0.5在工地场景部署时我习惯把conf调到0.4到0.5之间根据现场误报率再微调。6.3 第三步换自己的现场样本做盲测验证集指标再高也只代表这个数据集内部的分布。真正的验收是拿手机到施工现场拍一段水泥泵车作业视频跑推理并统计帧级误检。视频样本与训练数据的差距主要在两个维度光照方向、泵车工作姿态。这台数据集里的截帧以侧方和斜侧方视角为主如果现场需要俯视视角检测模型效果大概率打折这时用该数据集做初始训练再采集俯视角的少量样本做微调比从零标注省两到三周时间。从那以后我每次拿到外部数据集都强制走一遍“解析验证、格式转换、可视化抽检、过拟合测试”这四步再谈正式训练。一个人踩过的坑太多就会明白数据工程这个环节省掉的每一个步骤最后都会在训练效果上连本带利还回来。这份水泥泵车数据集本身不承诺训练精度但它的标注质量和VOC格式的完整性足够支撑一轮有说服力的训练流程验证。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑