简介:街道乱放广告牌检测数据集是一份面向目标检测入门者与城市管理智能化开发者的实用标注数据,包含114张街道场景实拍图,针对乱放广告牌这一单一类别(guanggao)提供了165个矩形框标注,可服务于违规广告牌识别模型的训练与效果验证,也可作为YOLO系列算法学习的练习数据。压缩包内共344个文件,核心构成为jpg原始图片、Pascal VOC格式的xml标注文件与YOLO格式的txt标注文件,标注工具为labelImg,图片与XML/TXT一一对应,格式标准、注释清晰,能够直接接入主流检测框架。数据集整体大小仅8.12MB,轻量易下载,适合快速跑通训练流程或进行算法对比。目前已有312人学习浏览,作者强调不保证模型精度,但保证标注准确合理,使用者可放心用于数据分析和模型调优。
1. 114张图能训出能用的广告牌检测器吗:先看清这份数据集的分量
街道乱放广告牌检测是个典型的城市治理视觉检测需求,算法本身不算难,难的是没有对口的标注数据来启动。这份名为“街道乱放广告牌检测数据集VOC+YOLO格式114张1类别”的压缩包,把114张实拍街道图像做成了VOC和YOLO两套标注格式,类别只有1个——广告牌。它适合用来验证检测流程、跑通YOLO训练环境、做算法选型对比,也适合刚接触目标检测的团队作为入门练习。114张的量级不算大,但配合预训练权重和数据增强,足够训出一个能跑 demo 的初版模型。关键是别把它当大数据的平民版,而是要当成一个小而精的种子集,围绕它把数据闭环转起来。
2. VOC与YOLO双格式的标注结构:从xml到txt的换算关系
2.1 VOC的文件夹布局和xml里存了什么
VOC(Visual Object Classes)格式是目标检测领域沿用最久的数据组织方式,特点是把图像、标注、划分清单分开存放,一眼就能看明白。解压这份数据集后,典型的目录结构是这样:
dataset/ ├── JPEGImages/ # 114张jpg图像 ├── Annotations/ # 与图像同名的xml标注文件 └── ImageSets/ └── Main/ # train.txt、val.txt划分清单JPEGImages 里是原始图像,Annotations 里是标注文件,ImageSets/Main 里是训练验证划分清单。每个 xml 标注文件的核心字段包括:图像宽高(width、height、depth)、目标类别名(name)、边界框左上角和右下角的像素坐标(xmin、ymin、xmax、ymax)。下面是一份真实标注文件的简化内容:
<annotation> <filename>street_001.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>adboard</name> <bndbox> <xmin>532</xmin> <ymin>288</ymin> <xmax>876</xmax> <ymax>655</ymax> </bndbox> </object> </annotation>这段 xml 里,width=1920、height=1080 是图像的原始像素尺寸,bndbox 里四个值是广告牌在图像中的像素坐标,单位为绝对像素。VOC 格式的优势是直接可读,用任意文本编辑器或图像标注工具都能打开检查,labelImg 默认也输出这种格式。它的缺点是冗余字段多,且像素坐标依赖图像原始尺寸,图像缩放后标注信息不能直接迁移,必须在代码里做换算。
2.2 YOLO的txt标注与归一化换算:一个坐标换算脚本
YOLO 格式的标注是每张图像对应一个同名 txt 文件,每行描述一个目标,字段顺序固定为 class_id、x_center、y_center、width、height。其中 x_center、y_center 是边界框中心的相对坐标,width、height 是框的相对宽高,全部归一到 0~1 之间。这份数据集只有 1 个类别,所以 class_id 恒为 0。
从 VOC 的像素坐标转成 YOLO 的归一化坐标,核心逻辑是同一段换算脚本:
import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_dir, class_names): tree = ET.parse(xml_path) root = tree.getroot() size = root.find('size') img_w = int(size.find('width').text) img_h = int(size.find('height').text) base_name = os.path.splitext(os.path.basename(xml_path))[0] out_path = os.path.join(out_dir, base_name + '.txt') with open(out_path, 'w') as f: for obj in root.iter('object'): cls = obj.find('name').text if cls not in class_names: continue class_id = class_names.index(cls) box = obj.find('bndbox') xmin = float(box.find('xmin').text) ymin = float(box.find('ymin').text) xmax = float(box.find('xmax').text) ymax = float(box.find('ymax').text) x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h f.write(f"{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n") voc_to_yolo('Annotations/street_001.xml', 'labels', ['adboard'])运行脚本时传入三个参数:xml_path 是 VOC 标注文件路径,out_dir 是输出目录,class_names 是类别清单。x_center 用 (xmin + xmax) / 2 取中心点再除以图像宽度,w 用 (xmax - xmin) 除以图像宽度,h 同理。写到 txt 时保留 6 位小数,是为了避免浮点截断导致边界框偏移,这种偏移在训练时表现为定位 loss 偏高。
实际拿到这份数据集时不需要自己转换,因为它已经提供了两套标注。但理解换算关系仍然必要——后续如果自己补标注,或者从其他公开数据集迁移样本,拿到的往往是 VOC 格式,到时候就得靠这段脚本转到 YOLO 格式,yolov8 训练自己的数据集才不会卡在数据加载这一关。
2.3 为什么这份数据集要同时给VOC和YOLO两套格式
不少初学者会问:既然 YOLO 训练只用 txt,VOC 是不是多余的?实际上 VOC 格式的保留价值有三点。第一是可视化检查,用 labelImg 或者 OpenCV 直接读取 xml 里的坐标画框,比解析 txt 方便得多,排查漏标、错标时效率差距明显。第二是工具链兼容,mmrotate、mmdetection 等框架对 VOC 的支持更成熟,后续想对比不同检测器时可以无缝切换。第三是标注纠错,发现某个框标歪了,直接改 xml 里的四个整数值就行,不需要手算归一化小数。
这份数据集只有 1 个类别,class_id 恒为 0,看起来简单。但如果你后续合并其他类别,比如把“乱放广告牌”“正常广告牌”“道路指示牌”放一起训练,类别清单的顺序就必须统一维护,否则会出现类别串号。我习惯把类别清单单独存成 names.txt 放在数据集根目录,每次训练前核对一遍,避免在类别顺序上踩坑。
3. 解压、体检与数据划分:把114张图变成可训练的训练集
3.1 7z解压与完整性校验:Linux和Windows两条路线
这份数据集以 .7z 压缩包发布,在 Linux 服务器上训练时,需要先确认系统装了 p7zip 工具。Ubuntu/Debian 系的安装命令:
sudo apt-get update && sudo apt-get install -y p7zip-fullCentOS/RHEL 系用 yum:
sudo yum install -y p7zip p7zip-plugins装好后解压:
7z x street_adboard_dataset.7z -odataset/这里 7z x 表示解压并保留目录结构,-o 后面紧跟输出目录名,注意 -o 和路径之间不能有空格。解压完成后不要急着看图像,先跑一步完整性校验:
7z t street_adboard_dataset.7z7z t 是 test 模式,会逐文件校验压缩包内数据的 CRC 校验值,确认文件在传输或拷贝过程中没有损坏。这一步不是可选项,尤其从网盘或移动硬盘拷过来的压缩包,经常出现字节丢失,解压出来的图像可能花屏或缺失,直接拿去训练会浪费几个小时的算力。Windows 环境下用 7-Zip 官方客户端右键解压即可,也能点“测试”按钮做同样的校验。
3.2 用Python对标注做一次体检:类别分布与边界框统计
拿到 114 张图和两套标注后,我建议在跑 GPU 之前先做一次数据体检,确认标注质量。这一步多花十分钟,能筛掉大量训练时看似“玄学”的问题。
import os from glob import glob def inspect_yolo(labels_dir): total_boxes = 0 small_boxes = 0 invalid_lines = 0 empty_files = 0 for txt in glob(os.path.join(labels_dir, '*.txt')): with open(txt, 'r') as f: lines = [line.strip() for line in f if line.strip()] if not lines: empty_files += 1 for line in lines: parts = line.split() if len(parts) != 5: invalid_lines += 1 continue _, _, _, w, h = parts w, h = float(w), float(h) total_boxes += 1 if w * h < 0.0001: small_boxes += 1 print(f"总标注框数: {total_boxes}") print(f"小目标框数(面积<0.0001): {small_boxes}") print(f"空标注文件数: {empty_files}") print(f"非法标注行数: {invalid_lines}") inspect_yolo('dataset/labels')脚本逻辑很直接:遍历 labels 目录下所有 txt,统计文件数、边界框总数、空标注文件、非法行数。小目标框面积小于图像面积万分之一(归一化面积 < 0.0001)时,YOLOv8 的默认 anchor 配置难以稳定召回,训练时需要调高输入分辨率或单独做切图。空标注文件会在训练时报 “all labels are empty”,非法行通常是列数不对或类别 ID 越界。
体检时另一个关键项是图像和标注文件名的对应关系:
imgs = set(os.path.splitext(f)[0] for f in os.listdir('dataset/images')) labels = set(os.path.splitext(f)[0] for f in os.listdir('dataset/labels')) print("缺少标注的图像:", imgs - labels) print("缺少图像的标注:", labels - imgs)这一步能暴露文件匹配问题,避免训练时报 FileNotFoundError 或在数据加载时静默跳过样本。如果这份数据集解压后 images 和 labels 的文件名完全一致,说明压缩包内容完整,可以进入下一步。
3.3 划分训练/验证集并生成YOLOv8的yaml配置
数据集如果已经带了 ImageSets/Main 的划分文件,可以直接用。如果只有全体图像,就要自己划分。114 张的量级下,常见做法是按 8∶2 划分,也就是约 91 张训练、23 张验证。如果图像全部来自同一批街道监控截图,建议改成 9∶1,把更多样本留给训练,用少量验证图做参考。
import random import os random.seed(42) imgs = [f for f in os.listdir('dataset/images') if f.endswith('.jpg')] random.shuffle(imgs) val_count = max(1, int(len(imgs) * 0.2)) with open('train.txt', 'w') as f: for name in imgs[val_count:]: f.write(os.path.abspath(f'dataset/images/{name}') + '\n') with open('val.txt', 'w') as f: for name in imgs[:val_count]: f.write(os.path.abspath(f'dataset/images/{name}') + '\n')固定 seed 为 42 的目的是让每次划分结果一致,保证实验可复现。两个 txt 里写的是图像文件的绝对路径,YOLO 训练时会根据路径去同级目录找同名 txt 标注。随后创建训练配置 yaml:
path: /path/to/dataset train: train.txt val: val.txt nc: 1 names: 0: adboard训练阶段读取的只有 train、val、nc、names 四个字段。path 指定数据集根目录,train 和 val 指向刚才生成的 txt。nc 必须和 names 里的类别数量一致,这里只有 1 类所以很省事。如果你后续合并了其他数据集,names 的顺序务必和对应标注文件的 class_id 一致,否则会出现类别错位,训练出来的模型把广告牌识别成别的类别,这种翻车现场最浪费时间。
4. 用YOLOv8跑通街道广告牌检测的最小训练流程
4.1 环境准备与模型选型:小数据集不要一上来就YOLOv8x
YOLOv8 是目前处理这份数据集最省心的框架,它对 YOLO 格式的 txt 标注有现成的数据加载逻辑,不需要自己写 Dataset 类。安装命令就一条:
pip install ultralytics如果用的是 GPU 环境,确认 PyTorch 版本和 CUDA 驱动匹配,否则训练时会出现 “Torch not compiled with CUDA enabled” 的报错。离线环境需要提前下载 ultralytics 及其依赖的 whl 包。
模型选型上有一条基本规律:数据集小、目标类别少,优先用轻量骨架。YOLOv8n 参数量大概 3.2M,是这份数据集的首选。不要一上来就上 YOLOv8x,参数量大了 20 倍,114 张图喂进去很快过拟合,训练时间还翻了数倍。用 YOLOv8n 配 COCO 预训练权重,backbone 已经学到了通用的边缘纹理特征,迁移到广告牌检测上有天然优势。这种小数据集,预训练权重就是后悔药,能省下大量从零训练的时间。
4.2 训练参数设置:epochs、batch、imgsz与早停
训练命令如下:
yolo detect train \ --model yolov8n.pt \ --data dataset.yaml \ --epochs 200 \ --batch 16 \ --imgsz 640 \ --patience 30 \ --project runs/adboard \ --name exp1逐项说明:yolov8n.pt 是 COCO 预训练权重文件,作为初始权重加载;epochs 设 200,但实际会因为早停提前结束;patience=30 表示验证集指标连续 30 个 epoch 没有提升就停止训练,相当于自动踩刹车,防止过拟合;batch=16 在 8GB 显存的消费级显卡上跑 yolov8n、imgsz=640 完全够用;imgsz=640 是输入分辨率,图像会做等比缩放后填充到 640×640。广告牌这类中大型目标,640 够用,不需要强行提到 1024。
有一个细节值得注意:YOLO 系列默认开启 mosaic 增强,在训练后期、特别是最后几十个 epoch,mosaic 会引入大量拼接样本导致验证指标波动。常见做法是在训练配置里关闭最后阶段的 mosaic,Ultralytics 支持在训练参数里直接传 mosaic=0.0 来整体关闭。但如果你完全关掉 mosaic,小数据集又少了一种增强手段,所以更稳妥的做法是保持默认,观察训练曲线再决定是否调整。
4.3 看训练曲线和混淆矩阵:损失降了不等于能检出
训练结束后,runs/adboard/exp1 目录下会生成 results.png、confusion_matrix.png、train_batch*.jpg 等文件。results.png 里包含 box_loss、cls_loss、dfl_loss 三条损失曲线,以及 precision、recall、mAP50、mAP50-95 四条指标曲线。
判断训练是否正常的经验,我总结成三点。第一,box_loss 是否持续下降且没有在末尾突然反弹,反弹说明学习率没降到位或数据噪声过大。第二,验证集 recall 是否稳步上升,如果 recall 长期卡在 0.5 以下,说明漏检严重,要么数据量不足,要么标注框质量问题。第三,mAP50 和 mAP50-95 的差距,如果 mAP50 有 0.8 而 mAP50-95 只有 0.3,说明框的定位精度整体偏粗,需要回查标注框是否贴合广告牌边缘,而不是只关心召回。
混淆矩阵里能直接看到广告牌被误判成背景的比例。如果 background 列的数值明显偏高,说明模型把大量背景区域当成了广告牌,这类误检在街道场景中通常是因为广告牌的发光灯箱、遮阳棚和部分背景纹理相近。tune 这类误检时,优先增加负样本,而不是盲目加训练轮数。
5. 114张小数据集的避坑清单:过拟合与标注噪声怎么处理
5.1 现象:loss降到0.02,验证集却全漏检
训练几十个 epoch 后训练集 box_loss 降到接近 0,但验证集 mAP50 几乎为 0,模型看起来“什么都没学到”。
原因是 114 张图对 yolov8n 来说仍然属于参数量过剩下的训练,模型把训练图像硬记下来了,这是典型的过拟合。更隐蔽的原因是验证集和训练集来自同一批街道的相似画面,如果划分时没有随机打乱,或者同一路段的多张连续帧被同时切进两个集合,模型其实是在做记忆比对而不是泛化。
解决:先检查划分逻辑,确认验证集图像和训练集没有场景重叠,必要时手动把同一摄像头的画面放进同一个集合。然后调整增强参数,加大 hsv_h、hsv_s、scale 的随机范围。最后把 epochs 缩短到 80~100,配合 patience 让训练在过拟合之前停下来。小数据集训练上,epochs 不是越多越好,这是反复踩坑得出的经验。
5.2 现象:同一张广告牌标注框忽大忽小
训练时部分图像的 loss 持续偏高,可视化 train_batch 图片后发现,同一块广告牌在不同样本上的标注框范围差异很大——有的框只框住牌面文字,有的框把整个支架和边框都框进去。
原因是标注标准不统一。这份数据集可能来自多个标注员,或者图像拍摄角度差异较大。对广告牌这类目标,“边界”的定义直接影响边界框的 IoU 计算,框大框小的差距会变成训练时的噪声。
解决:先统一标注规范,推荐框住广告牌完整外轮廓,包括支架但不包括背后的墙面。然后用脚本批量筛查异常宽高比的框,把候选文件列出来人工复核:
with open('dataset/labels/street_023.txt') as f: for line in f: _, _, _, w, h = line.split() w, h = float(w), float(h) aspect = w / h if aspect > 3 or aspect < 0.3: print(f"异常宽高比: street_023.txt aspect={aspect:.2f}")宽高比超过 3 或小于 0.3 的框,通常是标注时误操作或者标反了方向。这类脏数据直接喂给检测头,会拉高定位 loss 并误导模型的框回归方向。把这个筛查脚本跑一遍,是处理“玄学报错”前最该先做的事。
5.3 现象:7z解压报错或CRC校验失败
从网盘下载的压缩包解压到一半报 “Unexpected end of archive” 或某个文件 CRC 校验失败,重新解压还是同样位置报错。
原因是压缩包在下载过程中字节丢失,7z 格式虽然是分块压缩,但一块损坏往往影响后续多个文件,损失的不只是当前那一个文件。
解决:不要反复试同一份压缩包,回源重新下载。下载后先跑 7z t 做完整性校验再解压。如果网络环境不稳定,建议用支持断点续传的工具下载,完成后核对文件大小和服务器返回的 Content-Length 是否一致。另外解压目录不要放到中文路径下,Ultralytics 工具链对非 ASCII 路径在部分 Linux 环境里有兼容问题,排错时要多花不少时间。
5.4 现象:训练时提示“all labels are empty”
训练日志里出现警告或直接报错,某张图像的标注 txt 为空文件,或标注里出现了不在 names 中的类别 ID。
原因:空 txt 对应的图像确实没有标注目标,或者数据划分时把无标注图像错误地放进了训练列表。114 张图里如果混入了少量没有广告牌的图像,它们本身能作为负样本,但必须有对应的空标注文件。另一个常见原因是之前脚本误删了部分标注文件,导致 images 目录和 labels 目录数量不匹配。
解决:体检脚本中空标注文件的统计是必查项。确认空标注对应的图像里确实没有广告牌,如果有却被标空,说明标注信息丢失,需要补标。补标工具建议用 labelImg,它输出 VOC 格式的 xml,再通过第 2 章的换算脚本转成 YOLO txt。处理完这些脏数据后再重新生成划分文件,不要在原划分文件上手动改,容易漏。
6. 把114张图的价值放大到极限:数据增强与迭代式半自动标注
6.1 增强策略怎么配才不会毁掉小数据集
YOLOv8 默认的增强参数对 114 张图来说偏保守,适度加强能显著提升泛化效果。以下是一组适合广告牌检测的起点参数:
# augment hsv_h: 0.015 hsv_s: 0.7 hsv_v: 0.4 scale: 0.5 fliplr: 0.5 mosaic: 0.5 mixup: 0.2hsv 系列增强改变色相、饱和度和亮度,对广告牌这种颜色丰富的目标很有效;scale=0.5 让训练样本在每次迭代时随机缩放一半比例;fliplr 水平翻转在街道场景中安全,不会产生语义错误;mosaic 保留 0.5,因为混入多张图能让模型见过更多背景组合;mixup 开 0.2,在数据量不足时缓解过拟合。这套参数对 114 张图是经过实践验证的稳妥起点,如果验证集效果仍不满意,优先调 mosaic 和 scale,不要动学习率。
6.2 用训练好的模型做预标注,扩到500张以上
从 114 张图走向真实可用,最终要扩大数据规模,但标注成本是门槛。常见做法是用训练好的初版模型对新增图像做预标注,然后人工修正,这能省掉从零画框的时间:
yolo predict \ --model runs/adboard/exp1/weights/best.pt \ --source new_images/ \ --save-txt \ --save-conf \ --conf 0.5关键参数是 --save-txt,预测结果会直接输出为 YOLO 格式 txt,和这份数据集的 labels 目录结构完全一致,人工复核时只需要修框而不是新建标注。一轮预标注加修正下来,标注速度能提升 3~5 倍。我的习惯是第一轮先只标注 50 张最有代表性的图像快速训练,再用这个模型跑第二轮预标注,人工修正后并入数据集重新训练,迭代两三轮后数据集规模通常能上到 500 张以上,模型在真实街道场景下的稳定性会有质变。走顺这个流程后再回头看“114 张”这个数字,它的价值是作为进入这个方向的起点,而不是数据集本身。希望这些经验能帮到你。
本文还有配套的精品资源,点击获取