资讯动态

智慧城市市容检测数据集VOC转YOLO与YOLOv8训练避坑指南

发布时间:2026/10/2 2:42:16 来源:尧图企业网站定制
简介面向智慧城市街道市容检测的目标检测数据集包含19263张街道场景图片覆盖涂鸦、垃圾堆放、故障路灯等11个常见问题类别适合用于城市管理智能化算法研发、相关课题研究与竞赛实践。数据采用VOC与YOLO两种标注格式分别提供xml和txt标注文件可直接接入目标检测框架训练。压缩包共2000个文件以xml标注、txt说明为主要文件类型整体约978.91MB。需要特别留意数据集中超过一半为拼接增强图片四张拼成一张下载前务必查看预览图片确认是否符合需求。目前已有224人浏览学习。除标注文件外包内还附有使用前必读说明txt帮助快速理解目录结构与格式约定降低上手成本标注与图片一一对应可直接用于训练验证环节的自动化处理。1. 智慧城市街道涂鸦垃圾故障路灯市容检测数据集到手先别急着训练先搞懂这批数据怎么用智慧城市市容巡检这类项目算法侧最缺的不是模型结构而是带标注的现场数据。智慧城市街道涂鸦垃圾故障路灯市容检测数据集VOCYOLO格式19263张11类别.7z是我见过比较少见的中文场景市容检测数据集覆盖涂鸦、垃圾、故障路灯等11个市容问题类别同时给了VOC和YOLO两套标注格式训练前不需要再做标注格式转换。19263张图对市容场景来说不算小关键是图片来源复杂——白天、夜间、不同天气、不同街道背景都有涉及这决定了它能直接用于城市管理部门的巡检模型训练。这个数据集适合谁用一类是接政府市容项目的算法工程师需要快速验证YOLO系列在涂鸦、垃圾、路灯故障上的检测效果另一类是智慧城市集成商的技术负责人拿到数据集后要评估是否需要补拍数据、是否需要重新划分训练验证集。先说结论数据集质量整体不错但VOC转YOLO、train/val划分、类别权重这几个环节都有暗坑直接拿默认配置训练会翻车。下面按我自己的处理流程从解压到训练到排查一步步拆开讲。2. 解压与目录结构.7z格式先过解压关再看VOC和YOLO两套标注的差异2.1 Linux和Windows下解压7z的正确姿势这个数据集打包成.7z格式解压工具选择决定了后续体验。Linux服务器上先确认有没有装p7zipUbuntu/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 智慧城市街道涂鸦垃圾故障路灯市容检测数据集VOCYOLO格式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三件套解压后先不要急着训练花两分钟理清目录结构。标准VOCYOLO双格式数据集一般长这样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(fclass{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(fcls{cls_id}, x_center{x_center:.4f}, y_center{y_center:.4f}, fw{w:.4f}, h{h:.4f})检查要点坐标值是否都在(0,1)区间内。如果出现负值或大于1的值说明原标注坐标越界或转格式时除以的分母用错了——比如用了缩略图的宽高而不是原图宽高。这个问题后面在训练时会表现为 损失不降或AP为0尤其难排查。YOLO格式和VOC格式的本质区别是VOC是绝对像素坐标YOLO是归一化中心点坐标。从VOC转YOLO只需要一步简单的数学换算x_center (xmin xmax) / 2 / widthwidth (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_okTrue) 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], outlinered, width2) img.save(fcheck_{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(fstreet_dataset/JPEGImages/{img}.jpg\n) with open(street_dataset/val.txt, w) as f: for img in sorted(val_imgs): f.write(fstreet_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 \ datastreet_dataset/data.yaml \ modelyolov8s.pt \ epochs150 \ imgsz1280 \ batch16 \ device0 \ workers8 \ patience30 \ projectruns_detect \ namestreet_yolov8s训练前先本地能访问外网就把预训练权重下载到~/.cache/ultralytics/assets/或者手动下载后放到本地路径指定model./yolov8s.pt。离线环境下载预训练权重的方法是在有网的机器上下载拷贝到目标机器上直接把model参数指向该文件路径。逻辑说明一步步拆解这个训练命令。imgsz1280是当前比较关键的一个决定市容数据集默认的640分辨率会丢失大量小目标细节尤其在涂鸦检测上1280能保留更多纹理信息。代价是显存占用变大batch16大概需要12-16G显存8G显存建议imgsz960 batch8或imgsz640 batch16。patience30是早停机制如果验证集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 # 轻微平移模拟拍摄偏移训练时在命令里追加cfgstreet_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参数实现训练命令加repeatTrue框架会对包含少数类的样本做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_decay0.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 modelbest.pt sourcetest.jpg看打印出来的类别名是否与预期一致不一致立即停。5.3 混淆矩阵行和列总和对不上现象训练完成后打开混淆矩阵发现某一类的行数不等于该类别的真实样本数列也一样总和奇怪。原因YOLO的混淆矩阵是按“样本数”计算的但标注中的目标框数量和实际目标实例数量不一定相同另外验证集划分不均衡比如某类样本在验证集中只有几十张混淆矩阵统计会出现较大波动。还有一个常见情况一张图里有多个目标框混淆矩阵统计的行列值是总样本数的倍数看起来像对不上。解决不要纠结于混淆矩阵横纵轴总和的绝对相等应该看每类被正确识别的比例是否合理。如果某一类mAP有90但混淆矩阵里大量被分到背景类background说明存在大量漏检——验证锚定阈值和置信度阈值不一致导致的。此时用yolo val输出带conf_thres0.1的指标文件进一步分析。5.4 涂鸦小目标全漏检大目标检测倒是还行现象整墙涂鸦能框出来小块喷漆、小广告、饮料瓶这类小目标在640分辨率下几乎全漏。原因输入分辨率不够小目标在多次下采样后特征图上的像素太少特征信息在深层特征图里已经完全丢失。即便YOLOv8有P2检测头640分辨率下也救不回10x10像素的目标。解决三个手段按优先级用。第一训练时imgsz1280或15368G显存扛不住就做梯度累积把更多小目标细节留在特征图里。第二推理阶段做切片推理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的记录用batch4加accumulate8来做梯度累积。6. 验证模型效果和迭代闭环mAP够用不等于能上线还要跑通现场的误检反馈训练完不要只盯着验证集mAP就宣布模型可用。mAP是一个均值指标它掩盖了每类之间的巨大差距尤其是市容场景类别不平衡严重时均值很漂亮但少数类的实际问题可能很大。我的做法是三个专项验证第一按类别单独看AP值低于0.5的类别一定要单独开会讨论——是样本太少、还是样本质量差、还是这个类别本身区分度太低比如垃圾和落叶在外观上确实接近第二白天/夜间分组看验证集表现按图片加载时间或文件名前缀分组统计mAP差异超过50%就要考虑补数据第三找现场视频或实拍照片做压力测试用训练过程中没见过的新街道新场景图片看模型会不会产生大量误检——涂鸦模型最常见的误检是把有纹理的墙面、带纹路的瓷砖误认为涂鸦。验证阶段跑通了之后还要设计一个迭代闭环。市容检测和工业质检不一样没有一个封闭的“合格/不合格”标准数据分布是随时间漂移的——夏天和冬天的垃圾形态不一样新交付的智慧城市项目里街道风格各不相同。所以我把这个数据集当起点而不是终点第一轮训练后把模型部署到现场跑一段时间每天收集误检和漏检的图片每周做一次增量标注并补充训练。项目中用到的半自动标注工具采用初筛先用当前模型对采集的新图做预标注人工只修正边界框标注成本能压到原来的三分之一。增量训练时不要从头训练用modelstreet_yolov8s.pt继续喂新数据学习率降到0.001epochs设30左右。另外部署侧有两个容易被忽略的细节。第一推理时置信度阈值不要用默认的0.25建议调到0.3-0.4市容场景的门店招牌、配电箱等背景纹理和涂鸦非常像低阈值会大量误报漏检导致的额外一次人工巡查成本远低于误报导致频繁处置这个平衡各团队要自己拿捏。第二批量推理时数据加载工具如ultralytics的source参数可以传一个视频目录或摄像头URL但要提前确认现场相机的曝光参数——夜间巡检车相机快门、ISO设置不稳定图像亮度波动大会直接影响模型表现。我一般在部署脚本里加入一个即时帧统计功能每500帧算一下平均亮度亮度低于阈值就直接切换到夜间增强分支。这个数据集本身的价值我说句实话不在于有19263张图也不在于同时给了VOC和YOLO双格式。它的真正价值是一套覆盖了白天夜间、多种街道场景、11类市容问题的中文语境基准。在此基础上往前推进比从零开始标注要快两周以上。我的个人习惯是每拿到一个新数据集第一件事永远是清洗和统计不急着训练。类别顺序、坐标越界、train/val交叉这三个问题任何一次翻车都要花一天时间去排查远远超过做前面这些验证的半小时。希望这些经验能帮你顺利跑通这个方向。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑