简介:面向智慧城市市容巡检与自动化治理场景,这套数据集聚焦计算机视觉算法工程师与目标检测研究者的训练需求,覆盖街道涂鸦、暴露垃圾、故障路灯等11类典型目标,提供Pascal VOC与YOLO双格式标注,可直接用于检测模型训练。数据合计19263张JPG图像,每张均配套XML与TXT标注文件,双格式齐全可适配YOLOv5/YOLOv8、SSD、Faster R-CNN等主流框架;压缩包整体约978.91MB,共含2000个文件,其中XML标注为绝对主体(1999个),另附使用说明TXT一份。需要注意,数据集中超过一半为4张拼接而成的增强图片,下载前务必先查看预览图,确认符合需求再使用。目前已有228人浏览学习,适合具备数据预处理经验的中高级CV开发者开展迁移学习与业务预研,可省去自行标注和格式转换环节,加速市容巡检模型原型落地。
1. 智慧城市市容检测:19263张图、11类目标,先别急着训练
做智慧城市巡检的同行应该都有同感:涂鸦、暴露垃圾、故障路灯这类市容问题,看着简单,真让检测模型跑起来却总在奇怪的地方翻车。大白天光线充足时精度尚可,一到夜间路灯亮度不均、垃圾袋半埋在绿化带里,模型就开始摆烂。这套19263张、11个类别的智慧城市街道涂鸦垃圾故障路灯市容检测数据集,VOC和YOLO双格式打包,正好卡在这些场景痛点上。
它的核心价值在于覆盖了真实街道巡检中最让人头疼的几类目标:乱涂乱画的小面积涂鸦、形态千奇百怪的垃圾堆、白天晚上外观差异巨大的故障路灯。对正在做智慧城管、环卫考核、路灯运维算法的团队来说,这是一份可以直接用于训练和验证的底料。但我要先泼一盆冷水:拿到压缩包直接解压丢进训练脚本,大概率第一次跑出来的指标很难看。这不是数据集本身的问题,而是双格式转换、类别映射、样本分布这些前置环节没做好。下文就按我实际处理这类数据集的经验,从格式解析到训练排错,完整过一遍。
2. VOC与YOLO双格式:先读懂标注体系,再谈训练效果
2.1 VOC的XML标注结构:每个字段都有用途
VOC格式源自目标检测领域的经典标注规范,核心是每个图片对应一个同名XML文件。打开任意一个标注文件,你会看到结构大致如下:
<annotation> <folder>JPEGImages</folder> <filename>street_001234.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>graffiti</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>356</xmin> <ymin>412</ymin> <xmax>689</xmax> <ymax>573</ymax> </bndbox> </object> </annotation>这个XML里真正决定检测结果的是<size>和<bndbox>。<size>记录了原始图片的真实宽高,转换YOLO格式时归一化计算全靠它;<bndbox>里的四个值是目标左上角和右下角的像素坐标。<truncated>表示目标是否被图片边缘截断,<difficult>表示目标是否难以辨认。这两项在训练时通常被忽略,但做数据清洗时可以据此筛掉低质量标注。
一个容易踩的细节是<folder>和<filename>字段。有些标注工具导出的XML里,<filename>可能只写文件名不带路径,也可能写了相对路径。如果训练脚本按XML里的 filename 去查找图片,路径对不上就会报找不到文件。我处理这类数据集时的习惯是,先写脚本扫描所有XML的 filename 和实际图片目录,以实际文件为准重建对应关系。
2.2 YOLO的TXT归一化坐标:从像素值到相对值的换算
YOLO格式把每个目标的标注信息放在同名TXT文件中,每一行代表一个目标,格式是:类别ID x_center y_center width height。注意这里四个坐标值全部归一化到0到1之间,不是像素值。转换公式很直接:
# VOC像素坐标转YOLO归一化坐标 x_center = (xmin + xmax) / 2.0 / width y_center = (ymin + ymax) / 2.0 / height box_width = (xmax - xmin) / width box_height = (ymax - ymin) / height这个换算看起来简单,真正容易出错的是类别ID。VOCXML里name是字符串,YOLO格式需要把这个字符串映射成整数ID。VOC默认的类别顺序是 aeroplane, bicycle, ..., 但市容检测有自己的类别集,必须按数据集自带的类别列表文件或文档来映射,不能套用VOC默认顺序。我见过有人直接把 classes.txt 按字母序排,结果graffiti和garbage挤在一起,ID错位导致训练指标完全没法看。
另外注意坐标值域。转换完检查一下所有数值是否在0到1之间,如果出现负数或大于1,说明原始标注框越界了,这类样本要么裁剪图片要么删除标注,不能直接拿去训练。图片宽高信息有的XML里<size>和图片真实尺寸不一致,建议以实际读取图片得到的宽高为准。
2.3 双格式并存的意义:不同训练框架的兼容方案
为什么数据集要同时提供VOC和YOLO两种格式?因为检测框架对标注格式的偏好不同。YOLO系训练用TXT + classes.txt;Faster R-CNN、SSD等基于VOC或COCO格式训练更方便;而一些自动机器学习平台只吃VOC格式。双格式的最大价值在于省去转换的中间步骤,也降低了格式转换出错的概率。
但双格式也带来一个一致性隐患:如果提供方只更新了一种格式而另一种没同步,就会出现同一张图片的XML和TXT标注对不上的情况。拿到手之后第一步不是训练,而是校验两种格式的标注是否一致。校验方法不复杂:写一个脚本同时解析XML和TXT,对比每张图片的目标数量和关键坐标,误差超过一个小阈值就打日志。批量数据转换脚本详见下一章,这里先给出校验思路。
这类问题之所以高发,是因为不少数据集是从网上爬取或由多个标注团队拼接而成,标注规范不统一是常态。把双格式当作可信输入前,先花半小时做一致性校验,比训到一半发现loss异常再回头查要省时得多。
3. 11个类别拆解与训练数据组织:先把验证集划分做好
3.1 类别构成与样本量预估:长尾分布是常态
标题里的11个类别,结合市容检测的常见业务场景,大概率覆盖了类似这样的目标:graffiti(涂鸦)、garbage_bag(垃圾袋)、garbage_heap(垃圾堆)、broken_lamp(故障路灯)、illegal_poster(违规广告)、litter(散落垃圾)、road_damage(道路破损)等。具体类别定义以数据集自带文档为准,但必然包含涂鸦、垃圾和故障路灯这三个主线目标。
19263张图分到11类,平均每类1751张,但实际分布通常不是均匀的。垃圾类由于形态多样、场景频发,样本量往往偏多;故障路灯依赖人工上报和巡检专项,样本量可能偏少。长尾分布直接影响训练时的Loss权重设置和增强策略,所以训练前的第一步不是直接开训,而是先统计各类别样本分布。用Python很容易做到:
# 统计YOLO格式标注的类别分布 from collections import Counter import os label_dir = 'labels/train' class_counter = Counter() for txt_file in os.listdir(label_dir): with open(os.path.join(label_dir, txt_file), 'r') as f: for line in f: class_id = line.split()[0] class_counter[class_id] += 1 print(class_counter.most_common()) # 输出示例:Counter({'3': 4821, '7': 3675, '1': 2904, ...}) # 3和7可能是垃圾相关类别,样本量明显大于1等类别看到长尾分布后,两个选择:一是直接训练,在Loss函数里给少样本类别加权重;二是做类别平衡采样。YOLOv8的class_weights参数可以指定各类别Loss权重,常见做法是给样本少的类别设置更高权重。权重计算公式可以用1 / sqrt(class_count),避免权重过大导致训练震荡。
3.2 划分训练集与验证集:随机划分的隐藏风险
很多数据集自带的划分是随机的,但市容数据有个特点:同一街道、同一时间段拍摄的图片高度相似。如果随机划分,相似的训练图和验证图会同时出现在两边,验证集Loss虚低,部署到新场景精度立刻掉下来。更合理的方式是按拍摄位置或时间分组划分,但数据集如果没提供视频序列信息,退而求其次的做法是保证划分比例合理,并在验证结果上保持审慎。
# 按拍摄时间分组划分,避免数据泄漏 # 假设文件名前缀包含时间戳或街道编号 import os import random from collections import defaultdict image_dir = 'images' groups = defaultdict(list) for img_name in os.listdir(image_dir): # 示例文件名:street_001_20250110_143200.jpg group_key = img_name.split('_')[0] + '_' + img_name.split('_')[1] groups[group_key].append(img_name) train_files, val_files = [], [] for group_key, imgs in groups.items(): random.shuffle(imgs) split_idx = int(len(imgs) * 0.85) train_files.extend(imgs[:split_idx]) val_files.extend(imgs[split_idx:]) print(f'train: {len(train_files)}, val: {len(val_files)}')这段脚本的核心逻辑是按文件名前缀分组,同一条街或同一个拍摄批次的数据只进训练集或验证集,避免相似图片跨集泄漏。实际数据集文件名可能采用不同规则,关键是理解这个分组思路,然后按实际命名调整group_key的提取规则。
3.3 类别不平衡的采样策略
分布统计之后,如果某些类别的样本量低于500张,光靠Loss权重可能不够。我一般会配合采样策略:在训练集上对少样本类别做过采样,即每个epoch遍历图片时,少样本类别的图片以更高概率被选中。YOLO训练可以在Dataset类里实现,或者直接用数据增强顶上去——下一章的增强策略部分会详细说。
4. 七天跑通训练:从环境准备到YOLO格式转换落地
4.1 数据完整性校验:用Python检查图片与标注对应关系
拿到压缩包解压后,第一件事不是打开图片看热闹,而是跑完整性校验。常见的校验项包括:图片是否能正常解码、XML/TXT是否都能找到对应图片、标注框坐标是否超出图片边界、类别ID是否在合法范围内。用一个脚本把这些全部扫一遍:
# 数据完整性校验脚本 import os import cv2 import xml.etree.ElementTree as ET from pathlib import Path img_dir = Path('images') xml_dir = Path('annotations_xml') txt_dir = Path('labels_yolo') valid_img_ext = {'.jpg', '.jpeg', '.png', '.bmp'} problems = [] for img_path in img_dir.iterdir(): if img_path.suffix.lower() not in valid_img_ext: continue img = cv2.imread(str(img_path)) if img is None: problems.append(f'图片无法打开: {img_path.name}') continue h, w = img.shape[:2] xml_path = xml_dir / (img_path.stem + '.xml') txt_path = txt_dir / (img_path.stem + '.txt') if not xml_path.exists(): problems.append(f'缺少XML: {img_path.name}') if txt_path.exists(): with open(txt_path, 'r') as f: lines = f.readlines() for line in lines: parts = line.strip().split() if len(parts) != 5: problems.append(f'TXT格式错误: {img_path.name} - {line.strip()}') continue try: cls_id = int(parts[0]) x_c, y_c, bw, bh = map(float, parts[1:]) except ValueError: problems.append(f'TXT坐标非数值: {img_path.name} - {line.strip()}') continue # 检查归一化坐标是否合理 if not (0 <= x_c <= 1 and 0 <= y_c <= 1 and 0 <= bw <= 1 and 0 <= bh <= 1): problems.append(f'坐标越界: {img_path.name} - {line.strip()}') print(f'共发现 {len(problems)} 个问题') for p in problems[:50]: print(p)这段脚本的作用是快速定位图片缺失、标注缺失、坐标越界这三类最影响训练的问题。实际运行中,图片无法打开通常是因为压缩包解压不完整或文件损坏,坐标越界多见于标注框超出图片范围——这类样本需要裁剪或丢弃,不能直接送进训练。
4.2 构建YOLO训练目录:images与labels的组织方式
YOLO系框架对数据目录有固定要求,以YOLOv8为例,标准的数据集目录结构是:
dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yaml这里train和val的比例,一般按8:2或85:15划分。如果数据集自带划分好的目录就直接用;如果只有一个总目录,需要按3.2节的思路手动划分。注意,YOLO格式的标注文件名必须和图片文件名完全同名(后缀不同),否则训练过程会大量报image ... has no labels警告。
4.3 训练脚本与超参数:首次训练如何选参数
数据集就绪后,首次训练我建议用YOLOv8的默认参数打底,只改epochs、imgsz和batch。19263张图的规模,默认的300个epoch明显偏多,市容检测目标不算特别复杂,150到200个epoch通常已经收敛。输入分辨率方面,原始图片如果是1920x1080,训练分辨率用640或1280都行——640速度快,但小目标比如路灯灯头容易漏检;1280精度高,显存占用也翻倍。我一般先用640跑通流程,再上1280精调。
# 首次训练命令,以YOLOv8为例 yolo task=detect mode=train \ model=yolov8m.pt \ data=data.yaml \ epochs=150 \ imgsz=640 \ batch=16 \ device=0 \ project=runs \ name=street_urban_firstmodel=yolov8m.pt表示用中量级预训练权重做迁移学习初始权重,比从头训练收敛快得多。batch=16需要根据显存调整,显存不够就降到8或4。device=0指定用第一张GPU卡训练。如果训练过程中显存报错(OOM),优先调小batch,其次调小imgsz,不要直接换模型大小。
4.4 首次训练常见症状速查
首次训练经常遇到三类症状:第一类,前几个epoch Loss不降反升,大概率是学习率太大或类别权重设置不当,把lr0从默认0.01降到0.005试试;第二类,验证集mAP一直为0,先查是不是数据划分出了问题,验证集里只有图片没有标注,或者类别ID映射错位;第三类,训练速度远慢于预期,优先检查是否误用了CPU训练,其次看数据加载是否成为瓶颈——把workers参数调到4或8能改善不少。
5. 避坑:市容检测数据集训练与格式转换的五个高发坑
5.1 坑一:类别ID错位导致mAP反常识
现象:训练正常跑完,验证集mAP极高(如0.9+),但推理时发现模型把涂鸦识别成垃圾,把路灯漏掉。
原因:VOC格式的类别是字符串,转YOLO时类别ID映射顺序错误。常见错误是按字母序排列类别,或直接用VOC默认类别顺序,而实际数据集的类别列表不是这个顺序。
解决:训练前反复确认data.yaml里的names列表顺序,与TXT标注里的数字ID一一对应。可以打印一条标注样本人工核对:05_street_0234.txt第一行的class_id是否是graffiti而不是其他类别。
5.2 坑二:图片Exif旋转信息引发的标注错位
现象:用图像查看软件看图片方向正常,但训练时检测框整体偏移,尤其是竖拍的图片偏差明显。
原因:手机或部分街拍设备在保存JPG时会把旋转信息写入Exif,而OpenCV默认读取时不应用Exif旋转。如果标注工具基于旋转后的图像标注,而训练框架读的是原始方向图像,坐标必然错位。1920x1080的横拍图不受影响,但竖拍或带旋转信息的图会出问题。
解决:读书阶段用OpenCV的imread后主动检查Exif的Orientation字段,发现非正常方向就cv2.rotate转正,或者预处理阶段统一重新保存所有图片。这个坑在爬取的街拍数据里尤其高发,处理完最好重新生成标注,确保图片和标注方向一致。
5.3 坑三:空标签文件不删会怎样
现象:训练日志频繁出现WARNING ⚠️ no labels found in ...,且训练速度明显变慢。
原因:部分图片没有标注任何目标,但YOLO格式仍然生成了空的TXT文件。训练时框架会为这些空文件反复扫描查找对应关系,浪费IO和CPU。
解决:校验阶段直接把空的TXT文件删除,同时把对应的图片也从训练集移除,或者保留图片但确认框架支持无标注图片。YOLOv8默认支持无标注图片参与训练(只计算背景损失),但如果比例超过10%,最好手动筛选,避免干扰正样本学习。
5.4 坑四:类别不平衡导致检测头偏向大类别
现象:训练过程中,垃圾类的AP持续走高,涂鸦类AP徘徊在0.15以下,怎么调参都上不去。
原因:垃圾类样本量可能超过涂鸦类数倍,模型把大部分容量用在了垃圾上。单靠固定Loss权重无法解决,因为少量类别在训练后期梯度占比太小。
解决:除了Loss权重外,采用类别感知的数据采样。具体做法是每个epoch从少样本类别的图片里多采几轮,或给少样本类别做离线增强——旋转、缩放、色彩抖动各复制一份进训练集,把涂鸦样本量撑到与垃圾类同数量级。
5.5 坑五:.7z压缩包解压后路径含特殊字符导致读取失败
现象:Linux服务器上解压后训练正常,Windows本地解压后图片路径报错,或者标注文件找不到。
原因:压缩包内文件名可能包含中文字符或在Windows下非法的文件名字符(如冒号、问号)。7z在Windows上解压时自动替换,Linux上不处理,导致两种平台行为不一致。
解决:全部解压后用脚本把所有文件名统一重命名为纯英文小写,路径中不保留空格和特殊符号。同时用7z t your_file.7z先测试压缩包完整性,确认文件数和解压后一致再接训练。
6. 让模型在真实街道场景撑住:增强策略、难例挖掘与部署验证
6.1 复制粘贴增强(Copy-Paste)复用已有标注
市容数据的标注成本很高,尤其涂鸦和垃圾堆这类边界不规则的目标,人工标注一个框要花不少时间。复制粘贴增强可以复用现有标注:从一张图里裁出目标区域,随机旋转缩放后贴到另一张没有该目标的图片上,同时生成新标注。YOLOv8的augment参数配置比较方便,但自定义Copy-Paste需要写一个小脚本离线生成增强图。我一般会把少样本类别数据量先扩充1.5倍再训练,效果比单纯调Loss权重更稳定。
6.2 夜间场景增强:亮度抖动与模糊模拟
故障路灯的白天和夜间外观差异极大,白天就是一根杆子顶个灯罩,夜间发光明显但容易被误检成车灯或店铺招牌。针对路灯类别,我建议单独生成夜间增强版本:对图片做亮度降低0.5倍、高斯噪声增加、对比度拉高,模拟夜间街拍效果。扩充后的路灯样本会让模型学会识别「发光物+灯杆」的组合特征,而不是只靠亮度判断。
6.3 难例挖掘的实用做法
训练完第一个版本后,拿一批没参与训练的真实巡检图片做推理,把置信度在0.3到0.6之间的预测框全部捞出来,人工筛一遍,把确实有目标但模型没识别出来的图片补充进训练集。这个过程迭代两轮,模型在真实场景的mAP通常能再涨3到5个百分点。相比从零标注新数据,难例挖掘用最少的标注量解决了模型最痛的短板。
6.4 从训练到部署的检查清单
最后给出一个部署前的验证清单:第一,在未见过的街道上随机抽300张图,计算mAP和每类别AP,确认没有个别类别严重掉分;第二,用视频流截帧测试推理速度,确认单帧处理时间满足实时性要求;第三,统计误报类型——如果是把黑色垃圾袋误检成故障路灯,说明类别间特征区分度不够,需要补充更典型的负样本。
我自己的习惯是每次拿到新的市容数据集,先花一天做标注校验和类别统计,再花一天跑通基线,剩下的时间全花在难例挖掘和类别平衡上。这套流程虽然听起来不如直接开训刺激,但省下的调试时间远超投入。希望这篇拆解能帮你把这个数据集快速用起来,少走我踩过的弯路,祝训练顺利。
本文还有配套的精品资源,点击获取