简介:目标检测模型的落地效果,很大程度上取决于训练数据的质量与标注规范。在垂直场景中,数据工程往往比模型调参更关键。本文从数据集构建的基础概念出发,解析了从图像采集、清洗、预处理到COCO JSON标注格式的完整链路,并深入探讨了错误标注如何导致训练集loss异常下降等常见问题。同时,结合COCO预训练权重迁移学习的原理,展示了基于YOLOv8训练广告牌识别模型的高效配置与实践经验。通过合理的场景覆盖、精确的标注规范和科学的训练集划分,单类目标检测在商场、建筑、道路等真实环境中可达到mAP50超0.9的稳定效果,为户外广告监测、智能巡检等应用提供了可复用的技术方案。无论是数据工程入门还是目标检测实战,本文的链路与避坑指南都极具参考价值。 先交代一下背景。我平时主要做目标检测方向的项目落地,去年接了一个户外广告监测的需求,甲方要求对商场外墙、建筑立面、道路两侧的广告牌做到自动识别和定位。吭哧吭哧跑了一个多月,数据、标注、训练、调优全走了一遍,最后沉淀出了一个8157张图片的广告牌识别数据集,其中7137张作为训练集,分辨率统一处理到640x640,标注格式用的是COCO JSON。这篇就把整个数据集从采集、清洗、标注到训练落地的完整链路拆开讲一遍,包括踩过的坑和能直接抄的配置,给正在做类似数据工程或者想用目标检测做广告牌识别的朋友参考。
1. 项目整体设计与数据集构成
1.1 为什么要单独做一个广告牌识别数据集
广告牌识别这个任务,表面上看就是一个单类目标检测,但真正做起来会发现市面上几乎没有现成的、能直接用的数据集。公开数据集里要么是通用物体(COCO、VOC),里面广告牌只是零星出现,样本量少得可怜;要么是街景语义分割数据集(比如Cityscapes),标注粒度是像素级,做检测还得自己转换和处理。更麻烦的是广告牌本身的外观差异极大——商场外墙的巨型LED屏、写字楼的灯箱字牌、路边的小型指示牌、公交站台的广告灯箱,形态、尺寸、光照条件完全不是一个量级,通用模型在这种数据上几乎必翻车。
所以这个项目做数据集的时候,核心思路不是"凑够8000张图"就完事,而是按照场景维度去构建样本分布。商场的、建筑的、道路边的,三大类场景按差不多 4:3:3 的比例采集,确保模型见到的广告牌是多样化的,而不是只认识某一类特定场景下的广告牌。分类能识别这些广告牌,本质上靠的是数据分布足够贴近真实世界。
1.2 数据集规模与文件构成
这个数据集最终是8157张图片,划分情况如下:
| 项目 | 数量 |
|---|---|
| 总图片数 | 8157张 |
| 训练集 | 7137张 |
| 剩余用于验证/测试 | 1020张 |
| 图像分辨率 | 640x640像素 |
| 标注格式 | COCO JSON |
| 目标类别 | 广告牌(单类) |
训练集占比大约87.5%,这个比例在单类检测任务里是合理的。如果是多类别或者类别间样本不均衡,训练集比例可能需要重新调整,但对于单类目标,训练集多一点,模型见过的场景就多一点,泛化会更好。
COCO JSON格式的组织方式,一句话总结就是:一个json文件里包含了所有图片的路径信息、尺寸信息、标注框信息、类别映射信息。Ultralytics YOLO系列、Detectron2、MMDetection这些主流框架都支持COCO格式,后续做训练、评估、部署的生态非常顺。具体字段结构下面专门讲。
2. 数据采集与图像预处理的完整链路
2.1 采集场景规划:为什么必须覆盖商场、建筑、道路三类
广告牌识别的难点不在"识别"本身,而在"广告牌的定义边界"和"场景多样性"。同样是广告牌,商场外立面的广告牌往往是巨幅灯箱或者LED屏,面积大、亮度高,但存在反光和视角畸变;写字楼和公寓楼外墙的广告牌通常是条幅或者亚克力字牌,形态细长,尺度变化大;道路两侧的广告牌更复杂,有高炮广告(离地面很高的大牌子)、路灯杆挂旗、公交站台灯箱,尺寸从几十厘米到几十米都有。
采集的时候我按三个维度做了规划:
- 场景维度:商场、商圈、写字楼群、住宅底商、城市主干道、高速出入口、高架桥周边、公交站台
- 天气/时段维度:白天顺光、白天逆光、阴天、黄昏、夜间(灯光点亮状态)
- 拍摄角度维度:平视、仰视、俯视、正对、侧对
为什么要刻意做这个矩阵?因为我踩过只收集"好看的广告牌照片"的坑。第一次做小规模测试的时候,训练集里全是商场顺光正拍的照片,模型看起来效果不错,但一换到夜间或者逆光场景,AP直接掉了二十多个点,就是典型的场景过拟合。
实际采集来源包括自己拍摄、街景截图、合作方提供的历史巡查照片,以及一些公开来源的图像。合计筛选了接近2万张原始图片,最终保留8157张,剔除比例接近60%。
2.2 图像清洗与预处理
原图五花八门,分辨率从几百到几千都有,拍摄设备从手机到监控摄像头都有。所以清洗和预处理是决定数据集质量的关键一步,我按顺序做了这几件事:
- 第一轮:去重。用感知哈希算法(pHash)计算图片相似度,配合人工抽检,把同一广告牌的连续帧、相似角度的重复图剔除,避免标注样本重复性太高。
- 第二轮:清晰度筛选。直接用Laplacian算子计算图像方差,方差低于阈值的模糊图直接淘汰。广告牌本身在画面里可能是远距离、小目标,如果原图就模糊,标注出来也是噪声样本。
- 第三轮:遮挡度评估。广告牌被树木、电线杆、车辆遮挡超过50%的图片,原则性剔除。当然,轻微遮挡是保留的,否则模型学不会抗遮挡能力。
- 第四轮:分辨率统一。统一缩放到640x640。注意这里不是简单地resize,而是采用letterbox(保持长宽比填充)或者中心裁剪方式。广告牌是细长形状的居多,如果直接拉伸到640x640,广告牌的比例会变形,模型学到的形状特征就歪了。实际用的是letterbox,在缩放后的图片四周补灰边,确保广告牌比例不变。
640x640这个分辨率的选择也做一个说明:这是YOLO系列模型最常用的输入尺寸,兼容性最好。和训练显存的平衡也相关,如果改成1280x1280,精度会提升但显存消耗会翻好几倍,训练成本划不来。对于广告牌这种中等尺寸目标(不是那种极端小的目标),640x640是性价比最高的选择。
3. COCO JSON标注格式解析与核心字段说明
3.1 为什么选择COCO JSON而不是YOLO TXT
现在做目标检测,标注格式主要就是两种:COCO JSON和YOLO TXT。我这次选择COCO JSON,是因为它是目前通用性最好的标注交换格式。YOLO TXT比较简单,每个txt文件对应一张图,一行一个目标,格式是class x_center y_center width height(归一化坐标)。这种格式和YOLO系列绑定太紧,如果后面想换到MMDetection或者Detectron2,又得转换一次。
COCO JSON的好处在于:
- 一个json文件承载所有标注信息,管理方便
- 包含images、annotations、categories三大部分,结构化程度高
- 支持areas、iscrowd等额外属性,扩展性好
- 大模型和框架直接支持,比如Ultralytics YOLOv8、YOLOv11可以直接训练COCO格式
如果后续想从COCO转YOLO格式,用脚本转换非常容易,后面训练章节会给出转换代码。但从YOLO转COCO就没那么直观,所以我建议数据源头统一用COCO。
3.2 COCO JSON的完整结构拆解
一个标准的COCO JSON文件,最顶层是一个字典,包含以下关键字段:
{ "images": [ { "id": 1, "file_name": "ad_001.jpg", "width": 640, "height": 640 } ], "annotations": [ { "id": 1, "image_id": 1, "category_id": 1, "bbox": [x, y, width, height], "area": 1000, "iscrowd": 0 } ], "categories": [ { "id": 1, "name": "billboard" } ] }逐个解释一下:
- images数组:记录每一张图片的id、文件名、宽高。注意这里的宽高必须是原始图片(或者预处理后)的真实宽高,标注的bbox坐标就是基于这个尺寸的像素坐标,不是归一化坐标。
- annotations数组:记录每个标注框的信息。
image_id表示这个框属于哪张图;bbox是[左上角x, 左上角y, 框宽, 框高],单位是像素;area是框面积;iscrowd表示这个目标是否是一组密集目标,广告牌这类单目标一般填0。 - categories数组:记录类别映射。
id要从1开始,name是类别名。如果是多类别,就添加多条记录。
一个容易踩的坑是:bbox坐标越界。如果图像缩放后,标注框的坐标超出了图片边界(比如负坐标或者宽高超出图片范围),训练时一些框架会报错或者产生nan loss。所以标注生成之后最好做一次合法性检查,把越界的框裁剪或者剔除。
3.3 标注规范的制定与质检流程
标注规范对于数据质量的影响非常大,这个项目里我定了几条硬性规则:
- 广告牌的定义:画面中出现的主要广告信息载体,包括灯箱、LED屏、喷绘布、字牌、条幅等,凡是以商业广告信息为主要内容的载体,都标。
- 不标的对象:纯建筑标识(楼名、公司铭牌)、交通指示牌、公益宣传栏(除非带商业广告内容)、画面中的广告内容占整体比例很小的远景广告牌。
- 最小标注尺寸:标注框的宽或者高小于20像素的目标,不标注。这样的目标在640x640输入下对训练的贡献几乎可以忽略,反而会变成噪声。
标注完成之后,做了双重质检:
- 第一轮是程序化检查:检查每个bbox坐标是否越界、宽高是否为0、是否重复标注同一区域、图片文件名是否都能对应上。
- 第二轮是人工抽检:随机抽取20%的图片,可视化标注结果,人工检查框是否贴合目标、是否有漏标误标。
这里补充一个经验:标注框比目标本身大10%-15%其实问题不大,模型能学得过来,但框明显偏到目标以外(比如把旁边的招牌也框进去了),这种错误标注会让训练loss降不下来。热搜词里正好有"错误标注会导致数据标注模型训练集loss降不下来吗",答案是:如果错误标注集中出现在某些难例上,确实会导致loss异常。所以质检环节不可省。
4. 训练集划分策略与YOLO系列模型适配
4.1 训练集与验证集的划分思路
数据集8157张,训练集7137张,剩下1020张作为验证/测试集。这里需要强调的是:划分绝对不是随机打乱。
广告牌识别和通用物体检测最大的区别在于,同一块广告牌可能出现在多张连续拍摄的照片中,如果随机划分,那同一块广告牌的图片可能既进了训练集也进了验证集,验证结果会虚高。正确做法是在图片采集层面就按"广告牌位置/场景"去重之后再做划分,比如某商场外墙的五张照片,要么全进训练集,要么全进验证集,保证验证集里的目标在训练集里没有出现过。
简单做法是:采集时按"地点-时间"给图片分组,然后以组为单位划分。这样验证集评估出来的模型能力,才是真正的泛化能力,而不是记忆能力。
划分比例上,7137/8157大约87.5%的训练集,这个比例略高,但考虑到广告牌场景相对单一,问题不大。如果你后续要加类别或者场景扩展,建议将训练集比例降到80%,留更多数据做验证。
4.2 适配YOLOv8/YOLOv5/YOLOv11的数据格式转换
COCO JSON格式好归好,但YOLO系列训练时需要的是YOLO TXT格式。所以这里需要做一个转换。我直接用一段Python脚本实现COCO转YOLO的格式转换:
import json import os def coco_to_yolo(coco_json_path, output_dir): with open(coco_json_path, 'r') as f: coco_data = json.load(f) # 建立image_id到文件名的映射 img_id_to_info = {} for img in coco_data['images']: img_id_to_info[img['id']] = img # 建立category_id到索引的映射(YOLO类别id从0开始) cat_id_to_idx = {} for idx, cat in enumerate(coco_data['categories']): cat_id_to_idx[cat['id']] = idx os.makedirs(output_dir, exist_ok=True) # 按图片分组标注 img_annotations = {} for ann in coco_data['annotations']: img_id = ann['image_id'] img_annotations.setdefault(img_id, []).append(ann) for img_id, anns in img_annotations.items(): img_info = img_id_to_info[img_id] width = img_info['width'] height = img_info['height'] txt_name = os.path.splitext(img_info['file_name'])[0] + '.txt' txt_path = os.path.join(output_dir, txt_name) with open(txt_path, 'w') as f: for ann in anns: cat_idx = cat_id_to_idx[ann['category_id']] x, y, w, h = ann['bbox'] # 转为归一化中心点坐标 x_center = (x + w / 2) / width y_center = (y + h / 2) / height w_norm = w / width h_norm = h / height # 防止越界导致的训练异常 x_center = min(max(x_center, 0), 1) y_center = min(max(y_center, 0), 1) f.write(f"{cat_idx} {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}\n")转换过程中有一个非常关键的坑:YOLO类别索引是从0开始的,而COCO是从1开始的,而且如果存在"超类别"情况(比如categories里id是1、3、5,有跳号),直接把COCO的category_id当成YOLO类别索引就会错乱。上面代码里用enumerate重新建立了连续索引,可以避免这个问题。
4.3 640x640输入分辨率的训练配置
训练的时候在ultralytics库的环境下,配置一个data.yaml文件就够:
# ad_billboard.yaml path: /path/to/ad_billboard_dataset train: images/train val: images/val nc: 1 names: 0: billboard然后模型训练直接用YOLOv8的命令行或者Python API:
yolo detect train data=/path/to/ad_billboard.yaml model=yolov8s.pt epochs=100 imgsz=640 batch=16 device=0训练过程中的几个参数经验:
- imgsz=640:和数据集分辨率保持一致,不用额外resize,训练效率最高。
- 预训练权重:用yolov8s.pt(COCO预训练),而不是从头训练。广告牌虽然不在COCO的80类里,但预训练权重已经学到了丰富的底层视觉特征(边缘、纹理、形状),迁移学习能让收敛速度提升好几倍,最终精度也更高。
- batch尺寸:取决于显存,16G显存跑yolov8s,batch=16比较合适。如果增大到batch=32会明显OOM,得往下降imgsz。
- epochs:单类任务收敛速度快,一般60-100个epoch就足够,不需要像COCO那样跑300轮。我实测到80个epoch左右AP就基本平稳了。
5. 完整训练流程与模型评估
5.1 环境准备与依赖安装
训练环境我用的是Ubuntu 20.04 + NVIDIA GPU方案,显卡是RTX 3090(24G显存),CUDA版本11.8。依赖安装直接用pip搞定:
pip install ultralyticsultralytics这个库已经集成了YOLOv5/8/11的训练、验证、导出接口,强烈建议用这个,自己从头写训练代码完全是浪费时间。安装完之后用yolo命令行直接可以跑。
如果没有GPU,用CPU训练也不是不行,但收敛速度会让人崩溃。一个小数据集(7137张训练图)用CPU跑一个epoch可能要十几分钟,用GPU只要不到一分钟。如果只有CPU环境,建议先减少epoch缩到20,只是验证流程通不通,不要指望出精度结果。
5.2 训练过程监控
训练跑起来之后,ultralytics会输出每个epoch的loss、精度(Precision)、召回率(Recall)、mAP50、mAP50-95等指标。下面是我实际训练过程的几个关键观测点:
- box_loss、cls_loss、dfl_loss:这三个loss在前10个epoch下降很快,之后进入缓慢下降期,这是正常现象。如果某个loss出现震荡上升,大概率是学习率太大。
- mAP50:这个指标对于广告牌识别来说比mAP50-95更有参考价值。mAP50计算的是IoU阈值0.5时的平均精度,允许一定的框位置误差。广告牌检测在业务上"框大概对"就够用了,不需要像素级精确,所以mAP50做到0.9以上,mAP50-95做到0.7以上,这个模型就可以上线。
- 验证集结果:每轮epoch结束后会在验证集上跑一遍,重点关注验证集mAP有没有掉。如果训练集loss还在降,验证集mAP开始下降,那就是过拟合了,需要提前停止或者加重数据增强。
训练结束后的best.pt和last.pt都在runs/detect/train/weights/目录下,best.pt是验证集指标最好的权重,做推理部署要用这个文件。
5.3 模型评估与业务指标换算
模型训练完之后,用一段脚本在验证集上做批量推理,统计业务指标:
- 漏检率:验证集中实际有广告牌的图片,模型没有检出的比例
- 误检率:模型检测出广告牌的区域,但实际不是广告牌的比例
- 定位准确率:检测框和人工标注框的IoU大于0.5的比例
实际跑下来,业务指标和学术指标之间的差距比较明显。mAP50是0.91,但落到业务上,夜间广告牌漏检偏多,逆光下的广告牌误检率偏高。这说明一个很现实的问题:数据集场景分布决定模型能力的上限。如果业务使用场景里有大量夜间图片,训练集里就必须加入足够比例的夜间样本。
5.4 数据增强与难例挖掘
对于广告牌识别这个小目标、多样化外观的任务,数据增强策略和通用检测任务有区别。ultralytics默认开了hsv增强、平移、缩放等,但在我的实验里,以下几个增强参数影响最大:
yolo detect train ... hsv_h=0.015 hsv_s=0.7 hsv_v=0.4 translate=0.1 scale=0.5 fliplr=0.5- scale=0.5:模拟广告牌在不同距离下的尺度变化,对尺度泛化帮助很大。
- hsv_v=0.4:亮度变化增强,让模型更好适应白天、黄昏、夜间的亮度差。
- fliplr=0.5:水平翻转,增加样本多样性。
另外一个值得做的是难例挖掘。第一轮模型训练完之后,在验证集上找漏检样本,把这些样本加入训练集,重新训练。这种做法在没有更多原始数据的情况下,是提升模型上限最有效的方法。第二次训练之后,我记得夜间广告牌的漏检率从15%降到了6%左右。
6. 常见问题与避坑指南
6.1 COCO JSON标注文件解析报错
常见报错场景是json.load直接抛JSONDecodeError。排查思路如下:
- 用VS Code或者在线JSON格式化工具打开文件,看是不是标准的JSON语法。
- 检查文件编码,推荐统一用UTF-8无BOM格式。Windows下生成的txt/json容易带BOM头,Python的json.load遇到BOM会报错,需要在打开文件时加
encoding='utf-8-sig'。
另外一个是json文件几百MB打不开或者加载慢的问题。图像数量大的时候,annotations数组里几万条记录很正常,json的解析会慢一些。可以用orjson替代标准库的json,实测解析速度提升5-10倍。
6.2 训练loss不下降的可能原因
- 检查标注是否错乱:尤其是bbox坐标是否和类别对应、是否出现全零坐标。用可视化脚本把标注框画到图片上,随机抽200张看看,问题一眼就能看出来。
- 学习率过大:默认lrf参数有时候在自定义数据集上收敛不好,尝试把学习率调低,比如
lr0=0.005。 - 类别不平衡:如果数据集里广告牌数量悬殊(比如商场大广告牌占80%,路边小广告牌占20%),模型会偏向学大广告牌特征,小广告牌容易漏检。可以考虑对少样本场景做过采样。
6.3 广告牌检测中的小目标和遮挡问题
广告牌识别最典型的难例是两种:一是远处的小广告牌(目标在图上只有几十个像素),二是被树木、电线杆遮挡的广告牌。处理建议:
- 小目标:把输入分辨率从640提到960或者1280,通常会带来几个点的AP提升,代价是训练时间增加。如果业务上主要检测近处广告牌,可以不调。
- 遮挡:训练集中保留一定比例的遮挡样本,模型会学到抗遮挡能力。但如果遮挡太严重(超过50%面积),建议还是让模型放弃检测,不要硬标。
6.4 从COCO训练权重迁移的注意事项
热搜词里提到"coco预训练权重",我想提醒一下:COCO的预训练权重是80类通用检测器,它的特征提取网络部分可以直接用,但检测头部分只对80类有效。广告牌这个新类别,输出层的分类数量是1,和COCO的80不一致,所以ultralytics在加载预训练权重时,会自动跳过热启动不匹配的层。这个机制是正常的,不用担心。但从YOLOv8n一直选到YOLOv8x,不同体量模型的推理速度和精度差异很大,广告牌识别是相对简单的单类任务,YOLOv8s是性价比最高的档位,YOLOv8m以上提升的AP有限但推理速度下降明显,部署到边缘设备可能扛不住。
7. 数据集的局限与后续扩展方向
7.1 当前数据集的明显短板
这个数据集最主要的问题在于:
- 单一类别。只能识别"广告牌"这个整体概念,不能区分灯箱、LED屏、喷绘布、字牌等子类别。如果业务需要区分广告牌类型,需要在标注上更细粒度。
- 地域局限。采集的场景主要是城市环境,城乡结合部的广告牌形态差异大,模型在这些场景下的表现需要额外验证。
- 季节覆盖不足。数据集里夏季和秋季图片偏多,冬季(有积雪覆盖)和春季(树木茂密导致遮挡加重)的样本不足。
- 天气覆盖不足。雨雾天气的图片是最大短板,目前模型在雨天的鲁棒性没有测试过。
这些都是后续扩充数据集时需要优先补充的方向。
7.2 从单模型到业务系统的扩展路径
广告牌识别只是一个基础能力,落到真实业务中通常还需要以下扩展:
- 广告牌内容OCR识别:检测到广告牌之后,接OCR模型提取广告文案内容,用于合规审查。
- 广告牌结构化入库:把识别结果(位置、时间、图片快照)写入数据库,建立广告牌档案,做变化检测。
- 多帧视频流处理:从视频流中抓取广告牌的最优画面,避免重复检测,降低计算成本。
- 定期自动巡检:无人机或巡查车辆摄像头上部署模型,自动发现新出现的广告牌或破损广告牌。
对于这几个扩展方向,当前数据集本质上还是单帧静态图片检测,如果要做视频流处理,还需要补充连续帧的时序数据。
最后说点实在的
做完这个数据集和训练全流程,我个人最大的体会是:广告牌识别这种垂直场景任务,数据工程的工作量占了七成,模型训练只占三成。你可能花两周打磨标注规范,比花两周调模型参数带来的收益大得多。尤其是标注质量,宁可少标一百张图,也不要标错五十张。
如果你也准备做类似的数据集,我建议第一步先用LabelImg或者X-AnyLabeling把标注规范固定下来,标个100张图、跑通整个流程,再大规模铺开。另外,640x640分辨率、COCO JSON格式、YOLOv8s预训练权重、80个epoch,这四个起步配置能帮你少走很多弯路。这批8157张广告牌识别数据集训出来的模型,在我本地的验证集上mAP50能到0.9以上,覆盖商场、建筑、道路边的大部分常见广告牌形态,拿来直接接业务验证完全够用了。
本文还有配套的精品资源,点击获取