简介:本资源为面向智慧城市市容巡检场景的目标检测数据集,适用于街道涂鸦、垃圾堆放、故障路灯等城市管理问题的自动识别任务,可服务于算法工程师、高校研究者及安防巡检项目开发者。数据集同时提供Pascal VOC与YOLO两种标注格式,包含jpg原图及一一对应的xml与txt标注文件,共11个类别,覆盖市容异常的多类目标。压缩包内文件总数2000个,以1999个xml标注文件和1个说明txt为主,整体约978.91MB,需注意其中超过一半为四图拼接的增强样本,下载前建议核对预览图确认符合需求。目前已有224人学习下载。读者可获得19263张带标注图像及双格式标签,直接用于YOLO或VOC框架的训练与验证,省去自行采集与标注成本,并借助多类别标注快速搭建市容检测基线模型,适合作为智慧城市相关课题与工程落地的数据支撑。
1. 19263 张市容巡检图:这份 VOC+YOLO 双格式数据集到底能干什么
如果你正在做智慧城市方向的视觉检测,尤其是街道涂鸦、垃圾堆积、故障路灯这类市容问题的自动识别,那这份 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、bndbox(xmin、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, parser=ET.XMLParser(encoding="utf-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_thresh=0.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//2+2].mean() mid_h = edges[h//2-2:h//2+2, :].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 \ model=yolov8s.pt \ epochs=100 \ imgsz=640 \ batch=32 \ workers=8 \ device=0 \ mosaic=0.0 \ mixup=0.0 \ degrees=0.0 \ translate=0.1 \ scale=0.3 \ fliplr=0.5 \ patience=20 \ project=runs/city_clean \ name=exp01参数说明:mosaic=0.0关掉 YOLO 自带的马赛克增强,因为数据集本身已经有拼接图,再叠加会让模型更难学。mixup=0.0同理。degrees=0.0关掉旋转,市容检测里目标方向基本固定,旋转增强收益不大。translate和scale保留小幅度,增加一点泛化。fliplr=0.5水平翻转可以保留,对涂鸦、垃圾这类目标没有方向敏感性。patience=20是早停轮数,验证集指标 20 轮不升就停。
如果你的显存不够,把batch降到 16 或 8,同时把imgsz降到 512。workers根据 CPU 核数调,一般设成 8 或 16。device=0指定第一块 GPU,多卡可以用device=0,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参数调高分类损失权重,比如cls=1.5。
4.3 现象:模型在验证集上表现好,但实际摄像头画面漏检严重
原因:验证集里混入了增强图,模型学到了拼接模式,真实单帧分布对不上。
解决:验证集只用原生单帧图。如果原生图不够,从增强图里裁象限,但裁完要人工抽查标注。
4.4 现象:训练到一半突然报错,提示图片尺寸不一致
原因:数据集里混了不同分辨率的图片,YOLO 默认会 resize,但如果某张图宽高比极端,resize 后标注框会变形。
解决:训练前统一检查图片尺寸,把极端宽高比的图筛掉。或者用rect=True做矩形训练,减少 padding。
4.5 现象:xml 解析报错,提示 not well-formed
原因:部分 xml 文件里有非法字符或者编码问题,常见于文件名包含特殊符号。
解决:用ET.XMLParser(encoding="utf-8")解析,遇到报错的文件单独记录并跳过。如果数量少,直接丢弃;如果数量多,用正则清洗 xml 内容。
5. 进阶用法:用这份数据集做预训练与类别增量实验
这份数据集的价值不只是训一个市容检测模型。19263 张、11 类、双格式,很适合做几件事。第一,拿它做预训练 backbone,然后在你的小规模业务数据上微调。市容场景的纹理、光照、目标尺度分布和很多城市视觉任务接近,预训练权重比 COCO 通用权重更贴。第二,做类别增量实验:先训 6 类,再增量到 11 类,观察模型对旧类别的遗忘程度。第三,做增强图消融:分别用原生图、增强图、混合图训练,对比验证集指标,你能直观看到拼接增强对泛化的影响。
我一般会先跑一个 baseline:只用原生图,YOLOv8s,100 epochs,记录 mAP50 和 mAP50-95。然后跑混合图版本,其他参数不变。如果混合图版本在原生验证集上掉点超过 3 个 mAP,就说明增强图带来了分布偏移,需要调整采样比例。常见做法是增强图:原生图 = 1:2 或 1:3,而不是 1:1。
验证方法上,除了看 mAP,我还会用混淆矩阵看类别间的误判。市容检测里涂鸦和广告牌、垃圾和建筑废料容易混。如果混淆矩阵里这两对互相误判多,说明类别定义有重叠,需要合并或者重新标注。
yolo detect val \ model=runs/city_clean/exp01/weights/best.pt \ data=/data/city_clean/data.yaml \ split=val \ conf=0.25 \ iou=0.5 \ plots=True这条验证命令会输出 mAP、混淆矩阵和 PR 曲线。conf=0.25是置信度阈值,iou=0.5是 NMS 的 IoU 阈值。plots=True会生成可视化图表,方便你直接看哪些类别弱。
从那以后我每次拿到带增强图的数据集,都强制先跑一遍原生图验证集,确认增强图没有把验证指标带偏,再决定训练策略。希望帮到你。
本文还有配套的精品资源,点击获取