19263张市容巡检VOC+YOLO双格式数据集:YOLOv8训练与增强图避坑指南 简介本资源为面向智慧城市市容巡检场景的目标检测数据集适用于街道涂鸦、垃圾堆放、故障路灯等城市管理问题的自动识别任务可服务于算法工程师、高校研究者及安防巡检项目开发者。数据集同时提供Pascal VOC与YOLO两种标注格式包含jpg原图及一一对应的xml与txt标注文件共11个类别覆盖市容异常的多类目标。压缩包内文件总数2000个以1999个xml标注文件和1个说明txt为主整体约978.91MB需注意其中超过一半为四图拼接的增强样本下载前建议核对预览图确认符合需求。目前已有224人学习下载。读者可获得19263张带标注图像及双格式标签直接用于YOLO或VOC框架的训练与验证省去自行采集与标注成本并借助多类别标注快速搭建市容检测基线模型适合作为智慧城市相关课题与工程落地的数据支撑。1. 19263 张市容巡检图这份 VOCYOLO 双格式数据集到底能干什么如果你正在做智慧城市方向的视觉检测尤其是街道涂鸦、垃圾堆积、故障路灯这类市容问题的自动识别那这份 19263 张、11 个类别的数据集大概率能省掉你最头疼的标注环节。它同时提供 Pascal VOC 的 xml 和 YOLO 的 txt 两套标注jpg 图片、xml、txt 数量一一对应都是 19263。拿到手不用自己写转换脚本直接就能喂给 YOLOv5、YOLOv8 这类框架开训。但有一件事必须先说清楚这个数据集里超过一半是增强图片具体做法是 4 张图拼接成一张。这意味着你看到的单张图里可能同时出现四个场景的局部目标尺度、分布和真实单帧巡检画面不完全一样。如果你直接拿它训完就上线模型在真实摄像头单帧上的表现可能和验证集对不上。所以这份资源适合谁适合想快速搭起市容检测 baseline、验证类别体系、跑通训练流程的人不适合想直接拿去做高精度落地的团队除非你先做筛选和再标注。下面我从格式、类别、增强图的处理一路讲到训练和避坑。2. VOC 与 YOLO 双格式拆解11 类标注怎么读、怎么转、怎么筛2.1 两套标注的目录结构与字段对应关系数据集解压后常见做法是看到一个 images 目录和两个标注目录或者图片和标注混放。VOC 格式每张图对应一个 xml里面包含 filename、size、object 列表每个 object 有 name、bndboxxmin、ymin、xmax、ymax。YOLO 格式每张图对应一个 txt每行是class_id x_center y_center width height全部归一化到 0~1。两套标注的类别顺序不一定一致这是第一个要确认的点。VOC 里用的是类别名字符串YOLO 里用的是数字 id。如果你直接拿 YOLO 的 txt 训练但 id 和你的data.yaml里 names 顺序对不上模型学出来的类别就是错的。我一般会先跑一段脚本把两边的类别映射关系打出来。import os import xml.etree.ElementTree as ET from collections import Counter xml_dir annotations_xml txt_dir labels_yolo # 统计 VOC 类别名 voc_names Counter() for f in os.listdir(xml_dir): if not f.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, f)) for obj in tree.findall(object): voc_names[obj.find(name).text] 1 # 统计 YOLO 类别 id yolo_ids Counter() for f in os.listdir(txt_dir): if not f.endswith(.txt): continue with open(os.path.join(txt_dir, f)) as fp: for line in fp: if line.strip(): yolo_ids[int(line.split()[0])] 1 print(VOC 类别分布:, voc_names) print(YOLO id 分布:, yolo_ids)这段脚本的作用是分别统计两套标注里的类别出现次数。VOC 输出的是类别名和对应框数YOLO 输出的是 id 和对应框数。把两个结果对照你就能确认 id 0 对应的是哪个类别名。如果发现某个类别在 VOC 里有几千个框在 YOLO 里对应 id 却只有几百那说明映射错了或者有标注丢失。参数上注意xml 解析时如果遇到 encoding 问题可以加ET.parse(path, parserET.XMLParser(encodingutf-8))。txt 解析时如果某行只有 class_id 没有坐标说明是空标注要跳过。2.2 从 VOC 转 YOLO 的完整脚本与边界处理虽然数据集已经给了 YOLO 格式但你很可能需要自己再转一遍比如过滤掉某些类别、或者把增强图单独拆出来。下面这个转换脚本处理了几个常见边界图片尺寸从 xml 的 size 里读坐标做归一化并裁剪到 [0,1]遇到宽高为 0 的框直接丢弃。import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_txt_path, class_map): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_map: continue cls_id class_map[name] 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 max(0, min(xmin, w)) xmax max(0, min(xmax, w)) ymin max(0, min(ymin, h)) ymax max(0, min(ymax, h)) bw xmax - xmin bh ymax - ymin if bw 1 or bh 1: continue xc (xmin xmax) / 2.0 / w yc (ymin ymax) / 2.0 / h nw bw / w nh bh / h lines.append(f{cls_id} {xc:.6f} {yc:.6f} {nw:.6f} {nh:.6f}) with open(out_txt_path, w) as f: f.write(\n.join(lines)) class_map {garbage: 0, graffiti: 1, lamp_fault: 2} # 按你的类别补全 voc_to_yolo(firc_pic_3231.xml, firc_pic_3231.txt, class_map)逻辑说明先读 xml 的 size 拿到原图宽高再遍历每个 object。class_map 是你自己定义的类别名到 id 的映射不在映射里的类别直接跳过这样可以做类别筛选。坐标裁剪是为了防止标注越界导致归一化后出现负数或大于 1 的值。宽高小于等于 1 像素的框直接丢这种框训练时基本是噪声。参数说明xc、yc是中心点归一化坐标nw、nh是宽高归一化值。保留 6 位小数足够 YOLO 训练用。如果你的类别数不是 11改 class_map 即可。输出路径建议和图片同名放在 labels 目录下。2.3 增强图识别与筛选4 图拼接怎么判断、怎么拆摘要里明确说了超过一半是 4 张图拼接的增强图。这类图的特点是分辨率通常是单图的 2x2 倍画面里有明显的拼接缝或者四个象限内容不连续。你可以在训练前先做一轮筛选把增强图和原生单帧图分开。常见做法是看图片宽高比和分辨率。如果大部分原生图是 1920x1080那增强图可能是 3840x2160 或者类似 2x2 排列的尺寸。另一个方法是看画面内容拼接图在象限边界处通常有突变用简单的边缘检测就能看出竖直和水平的两条强边缘。import cv2 import numpy as np def is_mosaic(img_path, ratio_thresh0.6): img cv2.imread(img_path) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) edges cv2.Canny(gray, 50, 150) h, w edges.shape # 检查中间竖线和横线附近的边缘密度 mid_v edges[:, w//2-2:w//22].mean() mid_h edges[h//2-2:h//22, :].mean() return mid_v ratio_thresh * 255 or mid_h ratio_thresh * 255这段代码判断图片中间是否有强竖直或水平边缘。如果是 4 图拼接中间通常有拼接缝边缘密度会明显偏高。阈值ratio_thresh需要根据你的实际图片调建议先跑几十张看看分布。筛选完之后你可以选择只用原生图训练也可以把增强图单独作为一个子集做数据增强实验。我一般会保留增强图做预训练然后在原生图上做微调这样既能利用大数据量又能让模型适应真实单帧分布。3. 用这份数据集跑 YOLOv8 训练配置、参数与增强图处理策略3.1 data.yaml 怎么写与类别顺序核对YOLOv8 训练需要一份 data.yaml里面指定 train、val 路径和 names 列表。names 的顺序必须和 YOLO txt 里的 class_id 一一对应。如果你不确定顺序回到 2.1 的统计脚本把 YOLO id 分布和 VOC 类别分布对照着看。path: /data/city_clean train: images/train val: images/val nc: 11 names: 0: garbage 1: graffiti 2: lamp_fault 3: road_damage 4: illegal_parking 5: billboard 6: tree_overgrown 7: waterlogging 8: construction_waste 9: stray_animal 10: other上面 names 只是示例实际类别名以你数据集里的为准。nc必须等于 names 长度。train 和 val 路径可以是相对 path 的目录也可以是 txt 文件列表。如果数据量太大建议用 txt 列表方式方便做分层采样。注意如果你的增强图和原生图混在一起建议在 train 里包含增强图val 里只放原生图。这样验证指标更能反映真实场景。做法是把原生图单独复制一份到 val 目录或者写一个 split 脚本按文件名前缀区分。3.2 训练命令与关键超参设置YOLOv8 的训练入口很直接但有几个参数对这份数据集特别重要。因为增强图占一半模型容易过拟合到拼接模式所以mosaic增强可以关掉或者调低概率避免和数据集自带的拼接增强叠加。yolo detect train \ data/data/city_clean/data.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch32 \ workers8 \ device0 \ mosaic0.0 \ mixup0.0 \ degrees0.0 \ translate0.1 \ scale0.3 \ fliplr0.5 \ patience20 \ projectruns/city_clean \ nameexp01参数说明mosaic0.0关掉 YOLO 自带的马赛克增强因为数据集本身已经有拼接图再叠加会让模型更难学。mixup0.0同理。degrees0.0关掉旋转市容检测里目标方向基本固定旋转增强收益不大。translate和scale保留小幅度增加一点泛化。fliplr0.5水平翻转可以保留对涂鸦、垃圾这类目标没有方向敏感性。patience20是早停轮数验证集指标 20 轮不升就停。如果你的显存不够把batch降到 16 或 8同时把imgsz降到 512。workers根据 CPU 核数调一般设成 8 或 16。device0指定第一块 GPU多卡可以用device0,1。3.3 增强图对训练的影响与验证集构造增强图对训练的影响主要体现在两方面。一是正样本密度变高因为一张拼接图里可能有多个类别的多个目标模型学到的上下文更丰富。二是背景分布被改变拼接缝附近会出现不自然的边缘模型可能把拼接缝当成特征。这就是为什么验证集一定要用原生单帧图。我一般会这样做先把所有图片按文件名或分辨率分成两组原生组和增强组。训练集用原生组 增强组验证集只用原生组。如果原生组数量不够可以从增强组里按象限裁出单帧图但要注意裁剪后的标注也要对应变换。import cv2 def split_mosaic(img_path, out_prefix): img cv2.imread(img_path) h, w img.shape[:2] h2, w2 h // 2, w // 2 quadrants { tl: img[:h2, :w2], tr: img[:h2, w2:], bl: img[h2:, :w2], br: img[h2:, w2:], } for name, sub in quadrants.items(): cv2.imwrite(f{out_prefix}_{name}.jpg, sub)这段代码把一张 2x2 拼接图拆成四张单帧图。拆完之后标注也要对应变换原来在拼接图里的框如果中心落在某个象限就减去象限偏移量重新归一化。这一步容易出错建议拆完后用可视化脚本抽查几十张。提示拆图后一定要重新检查标注尤其是跨象限的框直接丢弃比强行保留更安全。4. 避坑与排查标注、类别、增强图里的五个血泪教训4.1 现象训练 loss 正常下降但验证 mAP 极低原因YOLO txt 的 class_id 和 data.yaml 的 names 顺序不一致。模型学的是 id验证时按 names 映射结果全错。解决用 2.1 的统计脚本打印 YOLO id 分布和 VOC 类别分布对照确认每个 id 对应的类别名。改 data.yaml 的 names 顺序或者重新生成 txt。4.2 现象某些类别一个都检测不出来原因该类别在数据集里样本极少或者增强图里被拼接缝遮挡。11 个类别里大概率有长尾分布个别类别可能只有几百个框。解决先统计每个类别的框数对少于 1000 的类别做过采样或者单独增强。训练时可以用cls参数调高分类损失权重比如cls1.5。4.3 现象模型在验证集上表现好但实际摄像头画面漏检严重原因验证集里混入了增强图模型学到了拼接模式真实单帧分布对不上。解决验证集只用原生单帧图。如果原生图不够从增强图里裁象限但裁完要人工抽查标注。4.4 现象训练到一半突然报错提示图片尺寸不一致原因数据集里混了不同分辨率的图片YOLO 默认会 resize但如果某张图宽高比极端resize 后标注框会变形。解决训练前统一检查图片尺寸把极端宽高比的图筛掉。或者用rectTrue做矩形训练减少 padding。4.5 现象xml 解析报错提示 not well-formed原因部分 xml 文件里有非法字符或者编码问题常见于文件名包含特殊符号。解决用ET.XMLParser(encodingutf-8)解析遇到报错的文件单独记录并跳过。如果数量少直接丢弃如果数量多用正则清洗 xml 内容。5. 进阶用法用这份数据集做预训练与类别增量实验这份数据集的价值不只是训一个市容检测模型。19263 张、11 类、双格式很适合做几件事。第一拿它做预训练 backbone然后在你的小规模业务数据上微调。市容场景的纹理、光照、目标尺度分布和很多城市视觉任务接近预训练权重比 COCO 通用权重更贴。第二做类别增量实验先训 6 类再增量到 11 类观察模型对旧类别的遗忘程度。第三做增强图消融分别用原生图、增强图、混合图训练对比验证集指标你能直观看到拼接增强对泛化的影响。我一般会先跑一个 baseline只用原生图YOLOv8s100 epochs记录 mAP50 和 mAP50-95。然后跑混合图版本其他参数不变。如果混合图版本在原生验证集上掉点超过 3 个 mAP就说明增强图带来了分布偏移需要调整采样比例。常见做法是增强图:原生图 1:2 或 1:3而不是 1:1。验证方法上除了看 mAP我还会用混淆矩阵看类别间的误判。市容检测里涂鸦和广告牌、垃圾和建筑废料容易混。如果混淆矩阵里这两对互相误判多说明类别定义有重叠需要合并或者重新标注。yolo detect val \ modelruns/city_clean/exp01/weights/best.pt \ data/data/city_clean/data.yaml \ splitval \ conf0.25 \ iou0.5 \ plotsTrue这条验证命令会输出 mAP、混淆矩阵和 PR 曲线。conf0.25是置信度阈值iou0.5是 NMS 的 IoU 阈值。plotsTrue会生成可视化图表方便你直接看哪些类别弱。从那以后我每次拿到带增强图的数据集都强制先跑一遍原生图验证集确认增强图没有把验证指标带偏再决定训练策略。希望帮到你。本文还有配套的精品资源点击获取