简介:面向智慧城市街道市容检测的目标检测数据集,包含19263张街道场景图片,覆盖涂鸦、垃圾堆放、故障路灯等11个常见问题类别,适合用于城市管理智能化算法研发、相关课题研究与竞赛实践。数据采用VOC与YOLO两种标注格式,分别提供xml和txt标注文件,可直接接入目标检测框架训练。压缩包共2000个文件,以xml标注、txt说明为主要文件类型,整体约978.91MB。需要特别留意,数据集中超过一半为拼接增强图片(四张拼成一张),下载前务必查看预览图片确认是否符合需求。目前已有224人浏览学习。除标注文件外,包内还附有使用前必读说明txt,帮助快速理解目录结构与格式约定,降低上手成本;标注与图片一一对应,可直接用于训练验证环节的自动化处理。
1. 智慧城市街道涂鸦垃圾故障路灯市容检测数据集:到手先别急着训练,先搞懂这批数据怎么用
智慧城市市容巡检这类项目,算法侧最缺的不是模型结构,而是带标注的现场数据。智慧城市街道涂鸦垃圾故障路灯市容检测数据集VOC+YOLO格式19263张11类别.7z,是我见过比较少见的中文场景市容检测数据集,覆盖涂鸦、垃圾、故障路灯等11个市容问题类别,同时给了VOC和YOLO两套标注格式,训练前不需要再做标注格式转换。19263张图对市容场景来说不算小,关键是图片来源复杂——白天、夜间、不同天气、不同街道背景都有涉及,这决定了它能直接用于城市管理部门的巡检模型训练。
这个数据集适合谁用?一类是接政府市容项目的算法工程师,需要快速验证YOLO系列在涂鸦、垃圾、路灯故障上的检测效果;另一类是智慧城市集成商的技术负责人,拿到数据集后要评估是否需要补拍数据、是否需要重新划分训练验证集。先说结论:数据集质量整体不错,但VOC转YOLO、train/val划分、类别权重这几个环节都有暗坑,直接拿默认配置训练会翻车。下面按我自己的处理流程,从解压到训练到排查,一步步拆开讲。
2. 解压与目录结构:.7z格式先过解压关,再看VOC和YOLO两套标注的差异
2.1 Linux和Windows下解压7z的正确姿势
这个数据集打包成.7z格式,解压工具选择决定了后续体验。Linux服务器上先确认有没有装p7zip,Ubuntu/Debian系用apt装,CentOS/RHEL系用yum,装完再用7z命令解压:
# Ubuntu/Debian sudo apt update && sudo apt install -y p7zip-full p7zip-rar # CentOS/RHEL sudo yum install -y p7zip p7zip-plugins # 解压到指定目录,-o后面没有空格,这是p7zip的老规矩 7z x 智慧城市街道涂鸦垃圾故障路灯市容检测数据集VOC+YOLO格式19263张11类别.7z -o./street_dataset/逻辑说明:p7zip-full提供了7z和7za命令,p7zip-rar是用来解压rar的,如果确信用不到rar可以只装前者。解压参数-o指定输出目录,注意-o和目录路径之间不能有空格,写错了p7zip会直接报错。
如果压缩包有密码(数据集发布方有时会做加密),用-p参数带密码解压:
7z x archive.7z -o./street_dataset/ -p你的密码 -yWindows环境就更简单,装个7-Zip,右键菜单直接解压。但有个细节:Windows下解压出来的文件路径如果带中文或特殊字符,传到Linux训练服务器时容易碰到编码问题,建议在Windows上先解压,再打包成tar.gz或zip后传输,不要直接传.7z到服务器再解压。我遇到过数据集解压后图片路径里有非法字符导致ImageFolder读图失败的情况,最后在Linux上重新解压一遍才解决。
2.2 目录结构长什么样:JPEGImages、Annotations、labels三件套
解压后先不要急着训练,花两分钟理清目录结构。标准VOC+YOLO双格式数据集一般长这样:
street_dataset/ ├── JPEGImages/ # 原始图片,19263张,jpg格式 ├── Annotations/ # VOC格式标注,XML文件,每张图对应一个 ├── labels/ # YOLO格式标注,txt文件,每张图对应一个 ├── classes.txt # 类别清单,11行,顺序就是YOLO类别编号 ├── train.txt # 训练集图片路径列表 ├── val.txt # 验证集图片路径列表 ├── test.txt # 测试集图片路径列表(可能没有) └── data.yaml # YOLO训练配置(有的数据集会给)提示:如果解压后发现没有data.yaml,或者train.txt/val.txt是空的,就自己按8:2或9:1划分,后面第4章会专门写划分脚本。
先看classes.txt的内容,11个类别名称按行排列,YOLO标注txt里的第一个数字就是类别索引,对应classes.txt的行号(从0开始)。这里特别提醒:类别顺序一旦固定就不要再变,不然后面训练出的模型预测结果全乱。
2.3 VOC XML标注的字段含义与常见缺失项
VOC格式的XML标注是理解整个数据集的基础。
import xml.etree.ElementTree as ET tree = ET.parse('street_dataset/Annotations/street_000001.xml') root = tree.getroot() # 图片文件名和尺寸 print("filename:", root.find('filename').text) size = root.find('size') print("width:", size.find('width').text, "height:", size.find('height').text) # 遍历所有目标框 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) print(f"class={name}, bbox=({xmin},{ymin})-({xmax},{ymax})")这个脚本能快速抽查少量标注文件,看类别名是否和classes.txt一致、坐标是否有明显越界。VOC标注里常见的坑有三个:一是folder字段可能为空或指向旧目录名;二是有些标注的filename和实际文件名大小写不一致,转YOLO格式时按filename找图片会找不到;三是bndbox坐标可能超出图片宽高——采集工具或标注平台bug会导致这个现象,训练前必须清洗掉,否则YOLO训练时会因为坐标归一化后出现大于1的值而报错。
2.4 YOLO txt标注的绝对坐标与归一化坐标差别
YOLO格式的标注文件是纯文本txt文件,每行内容为:类别索引 x_center y_center width height,这四组数值全部是相对于图片宽高的归一化坐标。用Python可以直接验证:
with open('street_dataset/labels/street_000001.txt', 'r') as f: lines = f.readlines() for line in lines: parts = line.strip().split() cls_id = int(parts[0]) x_center, y_center, w, h = map(float, parts[1:]) print(f"cls={cls_id}, x_center={x_center:.4f}, y_center={y_center:.4f}, " f"w={w:.4f}, h={h:.4f}")检查要点:坐标值是否都在(0,1)区间内。如果出现负值或大于1的值,说明原标注坐标越界,或转格式时除以的分母用错了——比如用了缩略图的宽高而不是原图宽高。这个问题后面在训练时会表现为 损失不降或AP为0,尤其难排查。
YOLO格式和VOC格式的本质区别是:VOC是绝对像素坐标,YOLO是归一化中心点坐标。从VOC转YOLO只需要一步简单的数学换算:x_center = (xmin + xmax) / 2 / width,width = (xmax - xmin) / width。但要真正转得可靠,还要处理类别映射、异常坐标、图片缺失等一系列边界情况,下一章专门写脚本。
3. VOC转YOLO格式:转换脚本与四个边界坑
3.1 为什么要保留两套格式,直接只用YOLO行不行
拿到这个数据集后,最省事的选择是直接只用它的YOLO格式标注。但问题在于:你无法保证原始YOLO标注没经过有损转换。市面上的数据集发布流程经常是:先用LabelImg或Roboflow标成VOC格式,再跑脚本转YOLO,转的过程中类别顺序、坐标归一化任何一步出错,生成的txt就会有问题。所以我的习惯是:先用VOC标注做基准,自己写脚本重新转一遍YOLO格式,生成之后再做抽样比对。这个步骤半小时能完成,能省下后续训练排查的半天时间。
VOC和YOLO双格式的真正价值在于:VOC格式适合做二次人工标注修正——XML可读性强,LabelImg直接打开,人工检查语义直观;YOLO格式适合直接喂给Ultralytics训练框架。当你后续要补充标注新数据时,一定是在VOC或COCO格式上做增量标注,再统一转成YOLO。这是行业里比较稳妥的工作流。
3.2 转换脚本:带类别校验和越界坐标清理
下面这个脚本是我处理这类数据集时的标准做法,处理了类别映射、坐标越界、图片缺失三个问题:
import os import glob import xml.etree.ElementTree as ET from PIL import Image # ========== 配置区 ========== VOC_DIR = 'street_dataset/Annotations' # VOC XML目录 JPEG_DIR = 'street_dataset/JPEGImages' # 原图目录 YOLO_DIR = 'street_dataset/labels_yolo' # YOLO txt输出目录 CLASSES_FILE = 'street_dataset/classes.txt' # 类别清单 # 读取类别清单,顺序就是YOLO编号 with open(CLASSES_FILE, 'r') as f: class_list = [line.strip() for line in f.readlines() if line.strip()] print(f"共{len(class_list)}个类别: {class_list}") # 创建一张图,用于判断XML里图片尺寸是否和真实图片一致 os.makedirs(YOLO_DIR, exist_ok=True) def convert_xml_to_yolo(xml_path): """ 把单个VOC XML转成YOLO txt """ tree = ET.parse(xml_path) root = tree.getroot() # 从XML里读文件名和图片尺寸 filename = root.find('filename').text size = root.find('size') img_width = int(size.find('width').text) img_height = int(size.find('height').text) # 校验:真实图片是否存在 img_path = os.path.join(JPEG_DIR, filename) if not os.path.exists(img_path): print(f"警告: 图片不存在 {img_path}, 跳过 {xml_path}") return # 校验:XML里写的图片尺寸和真实图片是否一致 try: with Image.open(img_path) as img: real_w, real_h = img.size if real_w != img_width or real_h != img_height: print(f"警告: 尺寸不一致 {filename} XML:{img_width}x{img_height} 实际:{real_w}x{real_h}") img_width, img_height = real_w, real_h except Exception as e: print(f"警告: 无法读取图片 {filename}: {e}") return txt_lines = [] for obj in root.findall('object'): name = obj.find('name').text if name not in class_list: print(f"警告: 类别'{name}'不在classes.txt中, 跳过") continue cls_id = class_list.index(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) # 坐标越界处理:超出部分截断到边界 xmin = max(0.0, min(xmin, img_width - 1)) xmax = max(0.0, min(xmax, img_width - 1)) ymin = max(0.0, min(ymin, img_height - 1)) ymax = max(0.0, min(ymax, img_height - 1)) # 过滤掉宽度或高度接近0的无效框 if xmax - xmin < 1 or ymax - ymin < 1: print(f"警告: {filename} 有一个无效框, 已跳过") continue # VOC像素坐标 -> YOLO归一化中心点坐标 x_center = (xmin + xmax) / 2.0 / img_width y_center = (ymin + ymax) / 2.0 / img_height box_w = (xmax - xmin) / img_width box_h = (ymax - ymin) / img_height txt_lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}") # 写出YOLO txt文件,文件名和图片同名 yolo_path = os.path.join(YOLO_DIR, os.path.splitext(filename)[0] + '.txt') with open(yolo_path, 'w') as f: f.write('\n'.join(txt_lines)) # 批量转换 xml_files = glob.glob(os.path.join(VOC_DIR, '*.xml')) for xml_file in xml_files: convert_xml_to_yolo(xml_file) print(f"转换完成,共处理{len(xml_files)}个XML文件")逻辑说明:这个脚本的核心不只是格式转换,而是加入了三道校验关卡——图片是否存在、XML尺寸与真实图片是否一致、类别名是否在CLASSES文件里。第二道校验尤其关键:有些数据集的XML尺寸是从标注工具缓存里读出来的,和实际图片尺寸不一致(常见于图片被缩放处理后标注没更新),直接转会导致归一化坐标系统性偏移。
参数说明:CLASSES_FILE路径指向数据集自带的classes.txt,读取时按行分割并去掉空行,保持顺序不变。JPEG_DIR如果在Windows上解压,路径分隔符用反斜杠也没关系,Python的os.path.join会自动处理。越界坐标用max/min截断到边界,消除训练时的隐患。
3.3 转换后的五步验证流程
转完不能直接开训,要花十分钟做验证。我的验证流程是:
第一步:统计生成的txt文件数量,应该和XML数量一致,少一个就排查一个。 第二步:统计所有txt文件中的总标注框数量,和VOC XML中的object总数对比,如果数量差异明显,说明有无效框被过滤掉或被跳过了。 第三步:用Python遍历所有txt文件,检查有没有空文件(即某张图没有任何标注)。YOLO训练时空标注文件会触发警告,但不影响训练;如果数据集本身允许存在负样本图(即没有目标物的纯背景图),这反而是正常的。 第四步:随机抽取5个txt文件,手工打开对应图片,把中心点坐标乘回图片宽高,在图上画框验证。
import random import numpy as np from PIL import Image, ImageDraw yolo_dir = 'street_dataset/labels_yolo' label_files = os.listdir(yolo_dir) sample = random.sample(label_files, 5) for lf in sample: img_name = os.path.splitext(lf)[0] + '.jpg' img_path = os.path.join('street_dataset/JPEGImages', img_name) img = Image.open(img_path) draw = ImageDraw.Draw(img) w, h = img.size with open(os.path.join(yolo_dir, lf), 'r') as f: for line in f: parts = line.strip().split() cls_id, xc, yc, bw, bh = int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) xmin = int((xc - bw / 2) * w) ymin = int((yc - bh / 2) * h) xmax = int((xc + bw / 2) * w) ymax = int((yc + bh / 2) * h) draw.rectangle([xmin, ymin, xmax, ymax], outline='red', width=2) img.save(f'check_{lf.replace(".txt", ".jpg")}')逻辑说明:这步是把YOLO的归一化坐标还原成像素坐标,画在图片上,人工看框是否贴合目标。如果画出来的框明显偏移或框错了目标,回归原始XML检查,定位是原始标注问题还是转换脚本问题。
第五步:统计每个类别的标注框数量分布。市容数据集的类别不平衡通常很严重——垃圾的框可能上万,故障路灯可能只有几百。这个统计结果直接决定第4章训练参数怎么设。
3.4 转换时最容易踩的一个坑:类别顺序错位
这个坑我见过不止一次。数据集发布方提供的classes.txt顺序是graffiti, garbage, street_lamp...,你自己写脚本时嫌名字太长,重新排了一个顺序,或者从网上找了一个类别顺序和你想象不同的版本,结果模型训练完预测时,把涂鸦识别成垃圾、把路灯故障识别成涂鸦。这类错误在测试集上AP看着还行,但类别对应关系完全对不上,到了现场一用就是事故。
解决办法就是转换脚本里坚持从classes.txt读取顺序,不做任何手动排序。如果确实需要自定义类别顺序,那就先改classes.txt,再重跑转换脚本,绝不在训练yaml里单独改names顺序。训练时用的yaml文件里names列表必须和txt标注第一列的编号完全一致,这个原则值得刻在工位上。
4. 用YOLOv8训练市容检测模型:数据划分、yaml配置和参数调优
4.1 数据集划分:按类别分层抽样,别用随机划分
这个数据集如果自带了train.txt和val.txt,先确认划分比例和类别分布是否合理。如果没有,建议不要用random.shuffle全局打乱后直接按比例切,因为市容场景类别极其不均衡,随机划分容易让某个类别的全部样本都进训练集或验证集,最后混淆矩阵和mAP完全失真。
建议按类别做分层抽样划分,核心思路是:先统计每个类别的所有图片编号,然后按类别分别划分,再合并成训练集和验证集,确保每个类别在训练集和验证集中的比例接近整体比例。
import random from collections import defaultdict from pathlib import Path # 建立 类别 -> 图片路径列表 的映射 # 注意:一张图可能有多个类别,所以一张图可能出现在多个类别列表里 cls_to_imgs = defaultdict(set) with open('street_dataset/classes.txt') as f: class_names = [line.strip() for line in f if line.strip()] for cls_id, cls_name in enumerate(class_names): label_dir = Path('street_dataset/labels_yolo') for txt_file in label_dir.glob('*.txt'): with open(txt_file, 'r') as f: for line in f: line_cls = int(line.strip().split()[0]) if line_cls == cls_id: cls_to_imgs[cls_name].add(txt_file.stem) break # 按类别分开 shuffle 再划 8:2 train_imgs, val_imgs = set(), set() for cls_name, img_names in cls_to_imgs.items(): img_list = list(img_names) random.shuffle(img_list) n_val = int(len(img_list) * 0.2) val_imgs.update(img_list[:n_val]) train_imgs.update(img_list[n_val:]) # 写出 yolo 需要的 train.txt / val.txt with open('street_dataset/train.txt', 'w') as f: for img in sorted(train_imgs): f.write(f'street_dataset/JPEGImages/{img}.jpg\n') with open('street_dataset/val.txt', 'w') as f: for img in sorted(val_imgs): f.write(f'street_dataset/JPEGImages/{img}.jpg\n') print(f"训练集: {len(train_imgs)} 张, 验证集: {len(val_imgs)} 张, 重合: {len(train_imgs & val_imgs)}")逻辑说明:这里按图片维度划分,一张图可能属于多个类别,所以在合并时用set.update去重。重合部分必须为0,如果train和val有交叉,验证结果就失去了意义,这是低级错误但要单独检查。
提示:划分完之后,建议打印每个类别的train/val数量对比,确认没有出现某个类别在验证集中只有个位数样本的情况。像“故障路灯”这类少数类,验证集样本太少会导致mAP抖动剧烈,此时应加大该类别在验证集中的比例,而不是硬按20%切。
4.2 data.yaml配置:路径、类别名和注意事项
ultralytics框架训练时通过一个yaml文件告诉模型数据在哪里。写法如下:
# street_dataset/data.yaml path: /absolute/path/to/street_dataset # 改成你的实际绝对路径 train: train.txt # 训练集图片路径列表 val: val.txt # 验证集图片路径列表 names: 0: graffiti 1: garbage 2: street_lamp_fault 3: xxx # 按classes.txt实际顺序写全 4: xxx 5: xxx 6: xxx 7: xxx 8: xxx 9: xxx 10: xxx注意:path建议用绝对路径,因为ultralytics在训练时如果换了工作目录,相对路径会导致读不到图片。names必须和classes.txt里的顺序一一对应,一个位置都不能错。如果数据集自带了data.yaml,也要打开核对路径是否指向正确的目录。
常见错误是把names只写11个类别名而不写索引,ultralytics还吃这一套;但如果索引从1开始而不是0,类别编号会错一位。检查方法:训练日志里会有Dataset 'xxx' with 11 classes的提示,然后把训练数据加载的类别名打出来对照一遍。
4.3 选择模型规模:从YOLOv8s起步,v8n做对照
市容检测场景的目标物有几个特点:涂鸦字体大小差异极大,从整墙涂鸦到小范围喷漆;垃圾包括饮料瓶、塑料袋、纸屑等,很多属于小目标;故障路灯则是大面积目标但训练样本通常偏少。综合考虑推理设备和精度诉求,我一般从YOLOv8s起步,显存低于8G就换yolov8n。
yolo detect train \ data=street_dataset/data.yaml \ model=yolov8s.pt \ epochs=150 \ imgsz=1280 \ batch=16 \ device=0 \ workers=8 \ patience=30 \ project=runs_detect \ name=street_yolov8s训练前先本地能访问外网就把预训练权重下载到~/.cache/ultralytics/assets/,或者手动下载后放到本地路径指定model=./yolov8s.pt。离线环境下载预训练权重的方法是:在有网的机器上下载,拷贝到目标机器上,直接把model参数指向该文件路径。
逻辑说明:一步步拆解这个训练命令。imgsz=1280是当前比较关键的一个决定,市容数据集默认的640分辨率会丢失大量小目标细节,尤其在涂鸦检测上,1280能保留更多纹理信息。代价是显存占用变大,batch=16大概需要12-16G显存;8G显存建议imgsz=960 batch=8或imgsz=640 batch=16。patience=30是早停机制,如果验证集mAP连续30个epoch不涨就提前停,可以省时间。
4.4 针对市容场景的数据增强参数调整
YOLOv8默认的增强参数适合通用检测,但市容场景要专门调几项。核心原因是:涂鸦和垃圾的外观形态高度多变,常规的mosaic增强对小目标反而有害——四张图拼在一起后,原有的小目标变得更小,甚至缩到几个像素,网络根本学不到特征。
# street_aug.yaml 数据增强覆盖配置 mosaic: 0.3 # 默认1.0,市容场景降到0.3避免小目标被拼接后更小 mixup: 0.2 # 默认0,垃圾场景mixup能增加背景多样性,但不开太高 scale: 0.5 # 默认0.9,缩太狠会让小目标消失 hsv_h: 0.01 # 色相微调,市容场景色调本来就复杂,不宜大 hsv_s: 0.5 # 饱和度增强,用于模拟不同光线条件 fliplr: 0.5 # 水平翻转保持默认 degrees: 0.0 # 旋转关闭,涂鸦和路灯旋转后语义会变奇怪 translate: 0.1 # 轻微平移模拟拍摄偏移训练时在命令里追加cfg=street_aug.yaml即可,ultralytics会覆盖默认增强参数。
参数说明:mosaic: 0.3——保留少量mosaic来增加背景多样性,但不让拼接把小目标过度缩小。degrees: 0.0——涂鸦方向一般是固定的,路灯也是垂直的,旋转增强会生成大量不合理样本,财报曲线看着提升了,实际部署时误检更多。fliplr: 0.5——水平翻转对市容目标基本没有语义破坏,可以保留。
4.5 类别不平衡处理:先看类别分布再决定要不要动loss
前文说过要先统计每个类别的框数量,这个统计直接决定是否需要调loss权重。如果11个类别里最少的类别样本数不足最多的5%,就需要针对性处理。
# 快速统计每个类别的标注框数量 python -c " from collections import Counter import glob cnt = Counter() for txt in glob.glob('street_dataset/labels_yolo/*.txt'): with open(txt) as f: for line in f: cnt[int(line.split()[0])] += 1 print(sorted(cnt.items())) "如果少数类(比如故障路灯)的框数明显偏少,常用的处理手段是:对包含少数类样本的图片做过采样——把这几百张图在训练时重复读取。在ultralytics里可以用repeat参数实现:训练命令加repeat=True,框架会对包含少数类的样本做2倍重复采样。如果这还不够,就考虑把样本非常少的类别合并成一个大类(比如把“路灯不亮”和“路灯破损”合并成“路灯故障”),但这个决定需要回到业务侧确认类别含义是否允许合并。我在实际项目中很少调loss权重,原因是用不好会破坏原本正常类别的精度;优先做数据层面的过采样和重采样。
4.6 训练过程中的监控:损失曲线和bn崩溃
训练开始后不要放着不管。重点关注:训练损失是否在稳定下降、验证集mAP是否逐渐上升、loss曲线有没有突然变成NaN。这里特别说一下“yolo训练中bn崩溃”这个现象——表现为训练的前几十轮一切正常,某一步后loss突变为NaN,继续训练也不恢复。
bn崩溃的常见原因:学习率设置偏高,配合小batch size导致BN统计量震荡;另一种可能是数据里有纯色图片或大面积过曝/过暗的异常图,喂进去后输出feature map方差爆炸。遇到这种情况,第一反应不是换网络,而是先用下面这段脚本清洗异常图片:
# 用PIL筛查全黑/全白/异常低方差图片 python -c " from PIL import Image import glob import numpy as np for img_path in glob.glob('street_dataset/JPEGImages/*.jpg'): img = np.array(Image.open(img_path).convert('L')) if img.std() < 5: print(f'低方差图片: {img_path} std={img.std():.2f}') "低方差图片会干扰BN统计,建议直接从数据集里剔除。如果清洗后仍有崩溃,尝试把学习率从默认0.01降到0.005,并开启weight_decay=0.0005。
5. 避坑指南:市容数据集训练与部署的6条血泪经验
5.1 .7z解压报密码错误但密码明明正确
现象:用7z x解压带密码的.7z时,提示“Wrong password”或CRC校验失败,反复确认密码没错。
原因:常见有两个。第一,p7zip版本太老,对7-Zip新版加密算法的支持不完整,高版本7-Zip创建的带加密包头文件在老版p7zip下会报密码错误;第二,密码包含特殊字符时,Shell做了转义,传进7z的密码和实际不符。
解决:Linux上升级p7zip到16.02以上,要么直接用7-Zip 21.0以上版本在Windows解压后重新打包。命令行解压时建议用-p'密码'把密码用单引号包起来,避免特殊字符被Shell吃掉。如果密码真的忘了,.7z基本上没有后悔药,这是压缩格式的安全特性,直接找发布方要密码吧。
5.2 classes.txt顺序和yaml的names顺序不一致,预测结果全错位
现象:训练过程正常,mAP曲线正常,但用模型跑一张图,明明地面是垃圾,预测出来是涂鸦;故障路灯全部预测成正常路灯。
原因:VOC转YOLO时用的类别顺序表和训练时data.yaml的names顺序不一致。YOLO txt文件第一列的数字不是类别名,而是一个索引,索引到类别名之间靠的就是这套顺序。两个文件一旦错位,索引含义就变了,模型学到的和推理时对不上。
解决:统一以classes.txt为唯一基准。转换脚本读取它,data.yaml的names用手工逐行核对。训完之后再跑一次yolo predict model=best.pt source=test.jpg,看打印出来的类别名是否与预期一致,不一致立即停。
5.3 混淆矩阵行和列总和对不上
现象:训练完成后打开混淆矩阵,发现某一类的行数不等于该类别的真实样本数,列也一样,总和奇怪。
原因:YOLO的混淆矩阵是按“样本数”计算的,但标注中的目标框数量和实际目标实例数量不一定相同;另外验证集划分不均衡,比如某类样本在验证集中只有几十张,混淆矩阵统计会出现较大波动。还有一个常见情况:一张图里有多个目标框,混淆矩阵统计的行列值是总样本数的倍数,看起来像对不上。
解决:不要纠结于混淆矩阵横纵轴总和的绝对相等,应该看每类被正确识别的比例是否合理。如果某一类mAP有90+但混淆矩阵里大量被分到背景类(background),说明存在大量漏检——验证锚定阈值和置信度阈值不一致导致的。此时用yolo val输出带conf_thres=0.1的指标文件进一步分析。
5.4 涂鸦小目标全漏检,大目标检测倒是还行
现象:整墙涂鸦能框出来,小块喷漆、小广告、饮料瓶这类小目标在640分辨率下几乎全漏。
原因:输入分辨率不够,小目标在多次下采样后特征图上的像素太少,特征信息在深层特征图里已经完全丢失。即便YOLOv8有P2检测头,640分辨率下也救不回10x10像素的目标。
解决:三个手段按优先级用。第一,训练时imgsz=1280或1536(8G显存扛不住就做梯度累积),把更多小目标细节留在特征图里。第二,推理阶段做切片推理(SAHI思路),把大图切成512或640的块,分别检测再拼接结果,能有效找回大量小目标。第三,如果部署设备性能有限不能加大分辨率,那就接受“只检大目标”的定位,调整业务预期——巡检车跑一圈先抓大堆垃圾和明显涂鸦,小目标交给人工复核。
5.5 夜间路灯故障样本太少,白天模型在夜间全面翻车
现象:用数据集的白天样本训出的模型,夜间场景mAP暴跌,故障路灯完全大概率漏检。
原因:市容数据集里夜间图片占比通常偏低,路灯故障这类目标本身数量少,而且夜间图像整体亮度低、噪声大,白天学到的特征在夜间分布完全不同。
解决:第一,训练时把hsv_s和hsv_v增强开大,模拟不同亮度条件,让模型对亮度变化更鲁棒。第二,用Albumentations里的RandomBrightnessContrast做夜间模拟增强,把部分白天图压暗后参与训练。第三,最根本的办法是去现场补拍夜间数据,几百张夜间故障路灯图比几千张白天图对模型的夜间鲁棒性提升更大。这个教训来自我实际项目:一开始光靠算法调参折腾了两天,收益很小;后面安排采集车夜间跑了两晚补了800张图,夜间mAP从0.15直接跳到0.68。
5.6 训练到一半loss变NaN,检查顺序有讲究
现象:第80轮训练loss骤降后突然变为NaN,验证mAP从此停在某个值不动。
原因:按经验由高到低排查——学习率过高导致梯度爆炸;数据集中存在全黑/全白/损坏图片;BN统计量在某个batch上崩掉;训练进程被中断后状态没保存好。
解决:第一步先清洗数据,用前文的低方差筛查脚本剔除异常图。第二步降低学习率,比如从0.01降到0.005。第三步把batch size调大一点点,减少随机噪声。如果前两步都做了还有问题,检查是不是输入分辨率太大显存不够导致OOM后被信号杀死——此时日志里会有CUDA out of memory的记录,用batch=4加accumulate=8来做梯度累积。
6. 验证模型效果和迭代闭环:mAP够用不等于能上线,还要跑通现场的误检反馈
训练完不要只盯着验证集mAP就宣布模型可用。mAP是一个均值指标,它掩盖了每类之间的巨大差距,尤其是市容场景类别不平衡严重时,均值很漂亮但少数类的实际问题可能很大。我的做法是三个专项验证:第一,按类别单独看AP值,低于0.5的类别一定要单独开会讨论——是样本太少、还是样本质量差、还是这个类别本身区分度太低(比如垃圾和落叶在外观上确实接近);第二,白天/夜间分组看验证集表现,按图片加载时间或文件名前缀分组统计mAP,差异超过50%就要考虑补数据;第三,找现场视频或实拍照片做压力测试,用训练过程中没见过的新街道新场景图片,看模型会不会产生大量误检——涂鸦模型最常见的误检是把有纹理的墙面、带纹路的瓷砖误认为涂鸦。
验证阶段跑通了之后,还要设计一个迭代闭环。市容检测和工业质检不一样,没有一个封闭的“合格/不合格”标准,数据分布是随时间漂移的——夏天和冬天的垃圾形态不一样,新交付的智慧城市项目里街道风格各不相同。所以我把这个数据集当起点而不是终点:第一轮训练后,把模型部署到现场跑一段时间,每天收集误检和漏检的图片,每周做一次增量标注并补充训练。项目中用到的半自动标注工具采用初筛:先用当前模型对采集的新图做预标注,人工只修正边界框,标注成本能压到原来的三分之一。增量训练时不要从头训练,用model=street_yolov8s.pt继续喂新数据,学习率降到0.001,epochs设30左右。
另外,部署侧有两个容易被忽略的细节。第一,推理时置信度阈值不要用默认的0.25,建议调到0.3-0.4,市容场景的门店招牌、配电箱等背景纹理和涂鸦非常像,低阈值会大量误报;漏检导致的额外一次人工巡查成本远低于误报导致频繁处置,这个平衡各团队要自己拿捏。第二,批量推理时数据加载工具如ultralytics的source参数可以传一个视频目录或摄像头URL,但要提前确认现场相机的曝光参数——夜间巡检车相机快门、ISO设置不稳定,图像亮度波动大会直接影响模型表现。我一般在部署脚本里加入一个即时帧统计功能,每500帧算一下平均亮度,亮度低于阈值就直接切换到夜间增强分支。
这个数据集本身的价值,我说句实话,不在于有19263张图,也不在于同时给了VOC和YOLO双格式。它的真正价值是一套覆盖了白天夜间、多种街道场景、11类市容问题的中文语境基准。在此基础上往前推进,比从零开始标注要快两周以上。我的个人习惯是:每拿到一个新数据集,第一件事永远是清洗和统计,不急着训练。类别顺序、坐标越界、train/val交叉,这三个问题任何一次翻车都要花一天时间去排查,远远超过做前面这些验证的半小时。希望这些经验能帮你顺利跑通这个方向。
本文还有配套的精品资源,点击获取