资讯详情

城市道路井盖破损丢失检测:VOC-1377数据集训练YOLO全流程

📅 2026/10/11 11:18:35 | 华诺云谱 👁 阅读
城市道路井盖破损丢失检测:VOC-1377数据集训练YOLO全流程
简介本资源为城市道路井盖破损丢失目标检测数据集面向从事智慧城市、道路巡检与市政设施安全检测的算法工程师、研究生及竞赛选手可用于训练和验证井盖异常状态识别模型。数据集采用Pascal VOC格式仅包含jpg图片与对应xml标注文件共1377张图像、1377个标注文件标注类别4类jg井盖、jg_ps井盖破损、jg_ds井盖丢失、jg_nd井盖挪动各类别框数分别为872、623、177、50主要覆盖圆形与方形井盖场景。压缩包内共2000个文件以xml标注与jpg图像为主另含1个说明txt整体约211.93MB使用labelImg按矩形框规则标注。目前已有1637人学习下载适合直接用于目标检测模型训练、类别分布分析与数据增强实验帮助读者快速搭建井盖缺陷检测流程并验证算法效果。1. 城市道路井盖破损丢失检测1377 张 VOC 标注数据能跑出什么结果市政巡检车每天跑两百公里回传的影像里真正需要立刻处置的井盖问题可能只有个位数。人工逐帧翻看一天下来眼睛先报废。把这件事交给目标检测模型前提是你得先有一批标好的井盖数据——城市道路井盖破损丢失目标检测数据集 VOC-1377 张就是为这个场景准备的一份可直接投入训练的标注集。它用 Pascal VOC 格式组织覆盖破损、丢失等典型病害类别适合做市政巡检、自动驾驶感知冗余、智慧城市视频分析这几类落地。1377 张不算大但胜在场景聚焦、标注格式通用拿来跑通 YOLO 系列或 Faster R-CNN 的完整链路足够。下面按「数据长什么样 → 怎么转格式 → 怎么训 → 坑在哪」的顺序拆开讲。2. 先看清 VOC-1377 的结构目录、标注与类别分布2.1 VOC 目录长什么样每部分管什么拿到一份 VOC 格式数据集第一件事不是急着写训练脚本而是把目录结构摸清楚。标准 Pascal VOC 的布局是固定的井盖这份数据也遵循同一套约定VOCdevkit/ └── VOC2007/ ├── Annotations/ # 每张图对应一个 XML 标注文件 ├── ImageSets/ │ └── Main/ # train.txt / val.txt / trainval.txt / test.txt ├── JPEGImages/ # 原始图片jpg 格式 └── SegmentationClass/ # 分割任务才用检测任务可忽略Annotations里每个 XML 对应JPEGImages里同名的一张图文件名一一对应这是 VOC 最核心的约定。ImageSets/Main下的 txt 文件只存文件名不带扩展名每行一个决定哪些图进训练、哪些进验证。很多人第一次用 VOC 会犯的错是直接拿JPEGImages全量去训结果验证集里混进了训练图指标虚高上线就翻车。一份典型的井盖标注 XML 内容如下重点看object节点annotation folderJPEGImages/folder filenamemanhole_0001.jpg/filename size width1920/width height1080/height depth3/depth /size object namebroken/name !-- 类别名破损 -- poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin842/xmin ymin615/ymin xmax978/xmax ymax742/ymax /bndbox /object /annotationname是类别字符串bndbox是左上角和右下角坐标difficult标记难样本VOC 评估时会单独处理truncated表示目标是否被截断。井盖这类目标经常被车辆、路面积水遮挡difficult和truncated的标注质量直接决定模型在真实场景的召回。2.2 类别分布与 1377 张的规模判断1377 张图如果按 8:2 切训练验证训练集约 1100 张、验证集约 277 张。这个量级对单类别或双类别检测是够用的但如果类别数超过 4 类每类可能只剩两三百张小类别容易欠拟合。井盖场景常见类别是「破损」「丢失」「正常」三类正常类有时不标只标异常具体以你拿到的 XML 里name实际取值为准。判断数据够不够有个简单办法统计每个类别的实例数而不是图片数。一张图里可能有 3 个井盖实例数才是检测模型真正学的东西。用下面这段脚本快速统计import os import xml.etree.ElementTree as ET from collections import Counter anno_dir VOCdevkit/VOC2007/Annotations cls_counter Counter() img_with_obj 0 for xml_file in os.listdir(anno_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(anno_dir, xml_file)) objs tree.findall(object) if objs: img_with_obj 1 for obj in objs: cls_counter[obj.find(name).text] 1 print(含目标图片数:, img_with_obj) for k, v in cls_counter.items(): print(f类别 {k}: {v} 个实例)这段脚本遍历所有 XML用Counter累计每个类别出现的次数。img_with_obj统计至少有一个目标的图片数——如果它远小于 1377说明有大量空图背景图空图对降低误检有帮助但占比过高会拖慢收敛。一般建议空图不超过 10%。跑完你会对这份数据的真实可用量心里有数再决定要不要做数据增强。提示统计实例数时顺手看一下每类的最小实例数低于 200 的类别要重点做增强或考虑合并类别。3. 把 VOC 转成 YOLO 格式转换脚本与四个边界坑3.1 为什么训练前几乎都要转格式VOC 用 XML 存绝对坐标YOLO 用 txt 存归一化的中心点加宽高两者不是简单换个后缀。现在主流的 ultralytics YOLO 系列v5/v8/v11训练时读的是 YOLO 格式所以转格式是绕不开的一步。转换的核心是三件事坐标从「左上右下」变「中心宽高」、从绝对像素变 0~1 归一化、类别名从字符串变整数索引。归一化公式要记牢x_center (xmin xmax) / 2 / widthy_center (ymin ymax) / 2 / heightw (xmax - xmin) / widthh (ymax - ymin) / height。分母是图片自身的宽高不是固定值这点搞错会导致框整体偏移。3.2 一份能直接跑的转换脚本import os import xml.etree.ElementTree as ET import shutil # 类别名到索引的映射顺序一旦定下就不要改 CLASSES [broken, lost, normal] cls2id {c: i for i, c in enumerate(CLASSES)} voc_root VOCdevkit/VOC2007 out_root datasets/manhole img_out os.path.join(out_root, images) lbl_out os.path.join(out_root, labels) os.makedirs(img_out, exist_okTrue) os.makedirs(lbl_out, exist_okTrue) def convert(xml_path, img_w, img_h): tree ET.parse(xml_path) lines [] for obj in tree.findall(object): name obj.find(name).text.strip() if name not in cls2id: continue # 跳过未定义类别避免索引错乱 bnd obj.find(bndbox) xmin float(bnd.find(xmin).text) ymin float(bnd.find(ymin).text) xmax float(bnd.find(xmax).text) ymax float(bnd.find(ymax).text) # 边界裁剪防止标注越界产生非法坐标 xmin, xmax max(0, xmin), min(img_w, xmax) ymin, ymax max(0, ymin), min(img_h, ymax) if xmax xmin or ymax ymin: continue # 宽高非正丢弃 xc (xmin xmax) / 2 / img_w yc (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls2id[name]} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}) return lines for xml_file in os.listdir(os.path.join(voc_root, Annotations)): if not xml_file.endswith(.xml): continue stem xml_file[:-4] xml_path os.path.join(voc_root, Annotations, xml_file) tree ET.parse(xml_path) size tree.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines convert(xml_path, img_w, img_h) if not lines: continue # 无有效目标不生成空标签 with open(os.path.join(lbl_out, stem .txt), w) as f: f.write(\n.join(lines)) src_img os.path.join(voc_root, JPEGImages, stem .jpg) if os.path.exists(src_img): shutil.copy(src_img, os.path.join(img_out, stem .jpg))脚本逻辑分四步读 XML、按类别映射取索引、坐标归一化、写 txt 并拷贝图片。CLASSES的顺序就是训练时names的顺序必须和后续data.yaml完全一致否则模型学到的类别会张冠李戴。坐标裁剪那两行是血泪经验——真实标注里偶尔有框超出图片边界的不裁会产生大于 1 的归一化值训练时直接报错或静默学歪。3.3 转换后必须做的三项校验转完不要直接开训先校验。第一图片和标签数量是否一一对应images和labels目录下同名文件数应该相等无目标的图不生成标签所以标签数可能略少。第二随机抽 5 张做可视化把归一化坐标还原成像素框画到图上肉眼看框是否贴合井盖。第三检查有没有坐标值等于 0 或 1 的极端框这类框往往是标注错误。import cv2 def draw_check(img_path, lbl_path): img cv2.imread(img_path) h, w img.shape[:2] with open(lbl_path) as f: for line in f: cid, xc, yc, bw, bh map(float, line.split()) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(check.jpg, img)可视化是转换环节的后悔药花五分钟抽检能省下几小时排查训练不收敛的时间。4. 用 YOLOv8 在 VOC-1377 上跑通训练配置、参数与验证4.1 data.yaml 怎么写路径和类别别写错YOLO 训练靠一份data.yaml描述数据位置和类别。井盖数据转换后配置如下path: datasets/manhole # 数据集根目录 train: images # 相对 path 的训练图目录 val: images # 验证图目录正式训练应换成单独的 val 目录 names: 0: broken 1: lost 2: normalpath是根train和val是相对路径。上面把val也指向images只是为了先跑通流程正式训练必须按 8:2 切出独立验证集否则评估指标没有意义。切分时用ImageSets/Main里现成的 txt 最省事或者自己按文件名随机划分并写两个 txt。4.2 训练命令与关键参数怎么调环境装好 ultralytics 后一条命令启动yolo detect train \ datadatasets/manhole/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/manhole \ nameexp1参数逐个说清楚。modelyolov8n.pt用 nano 版预训练权重1377 张的小数据集从预训练微调比从头训收敛快得多nano 版在巡检这种对速度敏感的场景也够用。imgsz640是输入分辨率井盖在 1920 图里占比不大如果小目标漏检严重可以提到 960 或 1280代价是显存和耗时上升。batch16按显存调8G 显存跑 640 分辨率一般能到 16。lr00.01是初始学习率小数据集可以降到 0.005 减少震荡。patience20表示 20 轮指标不升就早停防止过拟合。训练过程中重点盯三个指标box_loss是否稳定下降、mAP50是否爬升、mAP50-95是否同步改善。如果box_loss下降但mAP不动多半是验证集和训练集分布不一致或标注有问题。4.3 推理与效果验证训完用验证集跑一遍看每类的 precision、recall 和 mAPyolo detect val \ modelruns/manhole/exp1/weights/best.pt \ datadatasets/manhole/data.yaml \ imgsz640输出会按类别列出指标。井盖场景里「丢失」类通常召回高、精度也高因为井盖缺失后是个明显的黑洞「破损」类容易和路面坑洼、阴影混淆精度偏低是常态需要靠更多负样本和难例挖掘来压误检。单张图推理yolo detect predict \ modelruns/manhole/exp1/weights/best.pt \ sourcetest_images/ \ conf0.4 \ saveTrueconf0.4是置信度阈值巡检场景宁可多报也别漏报可以降到 0.25 提高召回代价是误检增多最终阈值要结合人工复核成本来定。5. 训练井盖检测模型最容易踩的五个坑5.1 验证集混进训练图mAP 虚高现象验证 mAP 到 0.95 以上上线后实际漏检严重。原因val路径指向了训练图目录或者切分时没去重同一张图既在训练又在验证。解决切分后核对两个 txt 的文件名交集必须为空用set(train) set(val)检查非空就重新切。5.2 类别索引和 names 顺序不一致现象模型把「破损」识别成「丢失」混淆矩阵里两类互相串。原因转换脚本里CLASSES的顺序和data.yaml里names的顺序对不上。解决把类别映射写成一个共享的常量文件转换和训练都从它读杜绝两处手写。5.3 小目标漏检井盖在远处只剩几十像素现象近处井盖检出正常远处小井盖几乎全漏。原因imgsz640下原图 1920 缩到 640远处井盖可能只剩十几像素特征太弱。解决提高输入分辨率到 960 或 1280或者用切片推理把大图切成小块分别检测再合并代价是推理变慢。5.4 标注框越界导致训练报错或学歪现象训练启动即报坐标非法或 loss 出现 NaN。原因XML 里xmax超过图片宽度归一化后大于 1。解决转换脚本里做边界裁剪见 3.2 的max(0, xmin)那几行并在转换后统计有没有值大于 1 的坐标。5.5 背景图过多拖慢收敛现象训练前期 loss 下降很慢模型迟迟学不到目标。原因1377 张里混入大量无井盖的纯路面图正负样本失衡。解决统计空图占比超过 15% 就下采样一部分保留少量背景图用于压误检即可。6. 让 1377 张数据发挥更大价值增强策略与难例回流数据量固定时提升效果靠两件事增强和难例回流。增强方面井盖场景最有效的是随机透视变换和亮度扰动——巡检车视角多变、光照从正午到夜间差异极大这两类增强能显著提升泛化。YOLO 内置的mosaic和mixup对小数据集帮助明显但mixup在类别少时容易产生不自然的样本建议mixup0.1保守开。旋转增强要慎用井盖是圆形旋转对形状没影响但会破坏「井盖与路面」的上下文关系收益有限。难例回流是我自己跑巡检项目养成的习惯模型上线后把误检和漏检的图定期捞回来人工补标后加入训练集重新微调。1377 张是起点不是终点跑一轮实际道路数据后往往能攒出几百张高价值难例加进去后 mAP 能再涨几个点。具体做法是推理时开save_txtTrue保存预测结果和人工复核结果比对差异大的图就是难例候选。验证增强是否有效别只看 mAP 涨没涨要看验证集里「破损」类的 recall 有没有提升——井盖破损的漏检代价远高于误检一个没发现的破损井盖可能引发事故。我一般会把conf阈值调到 0.25 跑一遍召回再调到 0.5 跑一遍精度两个数都记下来根据实际巡检的人力复核能力选平衡点。这套流程跑下来1377 张 VOC 数据足够撑起一个能用的井盖检测模型剩下的就是持续喂难例、迭代权重。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑