资讯动态

可口可乐与百事可乐标志检测:2220张VOC+YOLO数据集实战指南

发布时间:2026/9/28 16:41:56 来源:尧图企业网站定制
简介这份可口可乐与百事可乐标志检测数据集面向目标检测初学者与算法实践者用于训练和验证品牌Logo识别模型可应用于零售货架分析、广告监测等场景。数据集共2223张jpg图片每张均配有对应的VOC格式xml与YOLO格式txt标注标注类别为CocaCola和Pepsi两类框数分别为2268和2398总框数4666采用labelImg工具按矩形框规则标注标注准确合理。压缩包内共约2000个文件以xml标注文件为主另含一份说明文档整体约120.84MB目录结构清晰便于直接接入YOLO或VOC训练流程。目前已有347人学习下载。读者可借此快速搭建双类别检测实验省去自行采集与标注成本并对照两种格式理解数据转换与训练配置适合作为课程设计、毕业设计或算法入门的实战素材。1. 可口可乐与百事可乐标志检测2220 张双标签数据集怎么用起来你手里如果有一批货架、冷柜、自动售货机的照片想自动数出可口可乐和百事可乐的标志各出现了几次第一道坎从来不是模型选型而是数据。网上能直接下到的「可口可乐与百事可乐标志检测数据集2220张2个标签VOCYOLO格式」这类资源价值就在于它把最费人力的标注环节替你做了2220 张图、2 个类别、同时给了 VOC 和 YOLO 两套标注格式。VOC 是 Pascal VOC 那套 XMLYOLO 是每张图一个 txt、每行类别 中心x 中心y 宽 高的归一化坐标。两套格式并存意味着你既能喂给 YOLO 系列也能转去别的框架。这篇不聊虚的就讲这个数据集从解压到跑出第一条训练曲线中间每一步命令、每个参数、每个容易翻车的地方。适合刚拿到数据集想快速验证的人也适合已经跑过 YOLO、想搞清楚 VOC 与 YOLO 互转边界的老手。2. 先看清数据集结构VOC 与 YOLO 两套标注到底差在哪拿到压缩包别急着解压完就开训先花十分钟把目录结构和标注格式摸清楚。这一步做扎实后面 80% 的格式报错都能提前避开。很多人一上来就yolo train结果报Label class xxx is not in the class list回头再查发现是类别名对不上或者坐标没归一化白白浪费一轮调试时间。2.1 解压后应该看到的目录长什么样这类双格式数据集常见做法是压缩包里同时放VOC2007风格目录和一份已经转好的 YOLO 目录或者只给 VOC、让你自己转。先解压再tree一下确认三件事图片在哪、XML 在哪、有没有现成的 txt。# 解压到独立目录避免污染当前工作区 mkdir -p ~/datasets/coke_pepsi cd ~/datasets/coke_pepsi unzip ~/Downloads/coke_pepsi_2220.zip -d . # 看两层目录结构确认图片和标注的分布 find . -maxdepth 3 -type d | sort # 统计图片数量确认是不是 2220 张 find . -type f \( -iname *.jpg -o -iname *.png -o -iname *.jpeg \) | wc -l # 统计 XML 数量 find . -type f -iname *.xml | wc -l # 统计 YOLO txt 数量 find . -type f -iname *.txt | wc -l逻辑说明find -maxdepth 3只往下看三层避免目录太深刷屏图片统计把 jpg/png/jpeg 都算上因为不同来源的图格式可能混着。参数上没什么可调的重点是看三个数字对不对得上——图片数应该接近 2220XML 数应该等于图片数每图一个标注文件txt 数如果存在也应该等于图片数。如果 txt 数明显少于图片数说明 YOLO 格式只转了一部分你得自己补转。提示如果解压出来图片散在多个子目录里先别合并记下每个子目录的路径后面写转换脚本时按目录遍历比事后挪文件安全。2.2 VOC XML 里到底存了什么VOC 格式的每个 XML 描述一张图核心是size和若干object。size给出宽高和通道数object里name是类别名bndbox是左上角和右下角的绝对像素坐标。用一段 Python 快速抽查一个文件比肉眼翻 XML 靠谱。import xml.etree.ElementTree as ET import glob # 抽查第一个 XML打印关键字段 xml_files glob.glob(**/*.xml, recursiveTrue) print(XML 总数:, len(xml_files)) tree ET.parse(xml_files[0]) root tree.getroot() # 图片尺寸 size root.find(size) w int(size.find(width).text) h int(size.find(height).text) print(f图片尺寸: {w} x {h}) # 遍历所有目标框 for obj in root.findall(object): name obj.find(name).text box obj.find(bndbox) xmin int(box.find(xmin).text) ymin int(box.find(ymin).text) xmax int(box.find(xmax).text) ymax int(box.find(ymax).text) print(f类别{name} 框({xmin},{ymin},{xmax},{ymax}))逻辑说明ET.parse解析 XMLfindall(object)拿到所有目标。这里要重点看两件事一是name的取值正常应该只有coca_cola和pepsi两种或者类似命名如果出现第三种拼写说明标注有脏数据二是bndbox的坐标有没有超出size范围超出就是标注错误训练时会报越界。参数上recursiveTrue是为了兼容 XML 放在子目录的情况。2.3 YOLO txt 的五个数字分别是什么YOLO 格式每行五个值class_id center_x center_y width height后四个都是相对图片宽高的归一化值范围 0 到 1。class_id从 0 开始对应一个classes.txt或data.yaml里的类别顺序。抽查一个 txtimport glob txt_files glob.glob(**/*.txt, recursiveTrue) # 过滤掉 classes.txt 这类非标注文件 label_files [f for f in txt_files if classes not in f.lower()] print(标注 txt 总数:, len(label_files)) with open(label_files[0]) as f: for line in f: parts line.strip().split() if len(parts) ! 5: print(异常行:, line) continue cid, cx, cy, bw, bh parts print(f类别id{cid} 中心({cx},{cy}) 宽高({bw},{bh}))逻辑说明先过滤掉classes.txt否则会把类别清单当成标注文件统计。每行必须正好 5 个字段多一个少一个都是格式错误。归一化值如果出现大于 1 或小于 0说明转换时没除以图片宽高这种数据直接训会让 loss 爆炸。参数上没什么可调重点是校验字段数和数值范围。注意VOC 的坐标是绝对像素、左上右下YOLO 是归一化、中心点加宽高。这两套坐标系的换算是最容易出错的地方下一章专门讲转换。3. 把 VOC 转成 YOLO转换脚本与四个边界坑如果你的数据集只给了 VOC或者你想统一用 YOLO 格式训练就得自己转。转换本身不难难的是边界情况坐标越界、类别名映射、图片和 XML 对不上、空标注文件。这一章给一份能直接用的转换脚本再把四个坑逐个说清楚。3.1 一份可直接跑的 VOC 转 YOLO 脚本import os import glob import xml.etree.ElementTree as ET from PIL import Image # 类别映射按你的实际类别名改顺序决定 class_id CLASS_MAP {coca_cola: 0, pepsi: 1} def convert_one(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() # 用 XML 里的 size而不是重新读图避免图片损坏导致中断 size root.find(size) w int(size.find(width).text) h int(size.find(height).text) if w 0 or h 0: return 0 # 尺寸异常跳过 lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in CLASS_MAP: continue # 未知类别跳过避免污染训练 cid CLASS_MAP[name] 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) # 裁剪到图片范围内防止越界 xmin max(0, min(xmin, w)) xmax max(0, min(xmax, w)) ymin max(0, min(ymin, h)) ymax max(0, min(ymax, h)) if xmax xmin or ymax ymin: continue # 裁剪后无效框跳过 # 转成归一化的中心点宽高 cx (xmin xmax) / 2.0 / w cy (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{cid} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) # 写 txt文件名和图片同名 base os.path.splitext(os.path.basename(xml_path))[0] out_path os.path.join(out_dir, base .txt) with open(out_path, w) as f: f.write(\n.join(lines)) return len(lines) if __name__ __main__: xml_files glob.glob(**/*.xml, recursiveTrue) out_dir labels_yolo os.makedirs(out_dir, exist_okTrue) total_boxes 0 empty_count 0 for xp in xml_files: n convert_one(xp, out_dir) total_boxes n if n 0: empty_count 1 print(f转换完成: {len(xml_files)} 个 XML, {total_boxes} 个框, {empty_count} 个空标注)逻辑说明CLASS_MAP决定类别顺序必须和后面data.yaml里的names完全一致否则训练时类别全错。坐标裁剪那几行是防越界的关键很多公开数据集都有几个框超出图片边界不裁的话 YOLO 训练会警告甚至报错。{:.6f}保留六位小数够用且不会让文件太大。最后统计空标注数量空标注文件是合法的表示这张图没有目标但如果空标注占比过高说明数据集质量有问题。参数说明CLASS_MAP按你的实际类别名改out_dir是输出目录建议和图片目录分开方便后面组织数据集结构。3.2 坑一类别名大小写和空格不一致现象转换后训练报Label class xxx is not in the class list或者明明只有两个类别data.yaml里却要写三个。原因XML 里同一个类别可能写成Coca_Cola、coca cola、coca-cola多种形式CLASS_MAP只认一种其余全被跳过导致某些图变成空标注。解决转换前先统计所有name取值。跑一段统计脚本把出现过的类别名和次数打出来再决定映射规则。import glob import xml.etree.ElementTree as ET from collections import Counter counter Counter() for xp in glob.glob(**/*.xml, recursiveTrue): root ET.parse(xp).getroot() for obj in root.findall(object): counter[obj.find(name).text.strip()] 1 for name, cnt in counter.most_common(): print(f{name}: {cnt})看到结果后把大小写、空格、连字符统一再写进CLASS_MAP。3.3 坑二图片和 XML 文件名对不上现象转换脚本跑完txt 数量对但训练时提示找不到某张图的标注或者某张图的标注是空的。原因图片叫IMG_001.jpgXML 叫img_001.xml大小写或前缀不一致或者图片在images/下XML 在annotations/下脚本按 XML 生成 txt但 txt 和图片不在同一目录YOLO 找不到。解决转换后做一次配对检查确保每张图都有同名 txt。import os import glob img_files glob.glob(**/*.jpg, recursiveTrue) glob.glob(**/*.png, recursiveTrue) label_dir labels_yolo missing [] for img in img_files: base os.path.splitext(os.path.basename(img))[0] if not os.path.exists(os.path.join(label_dir, base .txt)): missing.append(img) print(缺标注的图片数:, len(missing)) for m in missing[:10]: print(m)3.4 坑三坐标越界和零宽高框现象训练日志里出现corrupt image/label警告或者 loss 一开始就是 nan。原因XML 里xmax小于xmin或者框完全在图片外转换后归一化值超出 0 到 1。解决转换脚本里已经做了裁剪和有效性判断xmax xmin就跳过。如果你用的是别人的转换脚本务必自己加这两步。转换后再跑一次范围校验import glob bad 0 for tf in glob.glob(labels_yolo/*.txt): with open(tf) as f: for line in f: parts line.strip().split() if len(parts) ! 5: bad 1 continue vals [float(v) for v in parts[1:]] if any(v 0 or v 1 for v in vals): bad 1 print(异常行数:, bad)3.5 坑四空标注文件被误删现象训练时某些图完全没有目标但你又不想删图结果发现这些图的 txt 是空的被某些脚本当成无效文件删掉了。原因空 txt 是合法的表示这张图没有目标YOLO 会把它当负样本用。但有些清洗脚本会把空文件删掉导致图片和标注数量对不上。解决保留空 txt只删图片和标注都缺失的样本。检查时用「图片存在但 txt 不存在」作为删除依据而不是「txt 为空」。4. 组织数据集并跑通第一次 YOLO 训练格式转好了接下来是组织目录、写data.yaml、启动训练。这一章按 YOLOv8 的常见流程走命令和参数都能直接抄。如果你用 YOLOv5 或其他版本目录结构类似参数名略有差异按官方文档对应改即可。4.1 划分训练集验证集并摆好目录YOLO 要求图片和标注分目录且训练集和验证集各有一套。常见做法是 8:2 划分。import os import glob import random import shutil random.seed(42) # 固定随机种子保证可复现 img_files glob.glob(**/*.jpg, recursiveTrue) glob.glob(**/*.png, recursiveTrue) random.shuffle(img_files) split int(len(img_files) * 0.8) train_imgs img_files[:split] val_imgs img_files[split:] for phase, files in [(train, train_imgs), (val, val_imgs)]: os.makedirs(fdataset/images/{phase}, exist_okTrue) os.makedirs(fdataset/labels/{phase}, exist_okTrue) for img in files: base os.path.splitext(os.path.basename(img))[0] shutil.copy(img, fdataset/images/{phase}/{os.path.basename(img)}) label flabels_yolo/{base}.txt if os.path.exists(label): shutil.copy(label, fdataset/labels/{phase}/{base}.txt) else: # 没有标注就写空文件保证一一对应 open(fdataset/labels/{phase}/{base}.txt, w).close() print(f训练集 {len(train_imgs)} 张, 验证集 {len(val_imgs)} 张)逻辑说明random.seed(42)保证每次划分结果一致方便复现。图片和标注分别复制到images和labels下文件名保持一致。没有标注的图片写一个空 txt避免 YOLO 报找不到标注。4.2 写 data.yaml两个类别和路径怎么写path: /home/user/datasets/coke_pepsi/dataset train: images/train val: images/val names: 0: coca_cola 1: pepsi参数说明path是数据集根目录train和val是相对path的图片目录。names的键必须从 0 开始连续顺序和转换时的CLASS_MAP完全一致。如果顺序反了模型会把可口可乐认成百事可乐而且 loss 看起来还挺正常这种错误最隐蔽。注意names里不要写中文不要有多余空格冒号后面空一格。YAML 对缩进敏感用空格不用 Tab。4.3 启动训练关键参数怎么设yolo detect train \ datadataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/coke_pepsi \ nameexp1参数说明modelyolov8n.pt是最小的预训练模型适合先跑通流程显存不够就换yolov8n显存充足可以上yolov8s或更大。imgsz640是常见输入尺寸如果你的图片分辨率远大于 640标志又很小可以提到 960 或 1280但显存占用会明显上升。batch16按显存调报 OOM 就减半。lr00.01是初始学习率小数据集可以降到 0.001 避免震荡。patience20表示 20 轮没提升就早停省时间。4.4 看训练日志哪些指标值得盯训练启动后重点看三列box_loss、cls_loss、mAP50。box_loss下降说明框的位置在收敛cls_loss下降说明类别判断在变好。mAP50是验证集上的主指标前几轮为 0 很正常一般 10 轮后开始上升。如果box_loss一直不降先查标注格式如果cls_loss降不下去查类别映射。# 训练结束后看结果目录 ls runs/coke_pepsi/exp1/ # 重点关注 weights/best.pt 和 results.csvresults.csv里每轮一行可以用 pandas 画曲线也可以直接看最后几行的mAP50。5. 避坑与排查训练不收敛、mAP 上不去怎么查这一章集中说几个实际跑这个数据集时最容易遇到的问题。每条按「现象 → 原因 → 解决」写方便你对号入座。5.1 现象loss 从第一轮就是 nan原因标注里有归一化值超出 0 到 1或者宽高为 0。YOLO 在计算损失时对无效框没有兜底直接产生 nan。解决回到第 3 章的校验脚本把所有 txt 扫一遍找出异常行。重点查bw或bh为 0 的行以及坐标大于 1 的行。修正后重新训练。5.2 现象mAP50 一直在 0.1 以下原因类别映射反了或者data.yaml的names顺序和 txt 里的class_id对不上。模型学的是「0 是百事」你告诉它「0 是可口可乐」指标自然上不去。解决抽一张图用标注可视化工具把框画出来确认类别和框的位置对得上。再检查CLASS_MAP和data.yaml的names是否一致。5.3 现象训练到一半报 CUDA out of memory原因batch或imgsz太大或者没有及时释放缓存。解决先把batch减半再把imgsz从 640 降到 512。如果还不行换更小的模型yolov8n。另外可以在训练命令里加cacheFalse避免把图片全部缓存到显存。5.4 现象验证集指标波动很大原因验证集太小或者划分时没有打乱。2220 张按 8:2 分验证集约 444 张如果某一类在验证集里特别少指标就会跳。解决划分时用分层抽样保证两个类别在训练集和验证集里的比例接近。简单做法是先按类别分组再各自划分或者多跑几次不同随机种子取平均。5.5 现象模型把背景认成标志原因负样本太少或者标注时把一些模糊区域也标成了目标。解决检查空标注图片的数量如果几乎没有空标注模型没见过「没有标志」的图就容易误检。可以适当加入一些纯背景图或者在数据增强里加随机裁剪让模型见到更多背景。6. 进阶用这个数据集验证模型改进是否真的有效数据集跑通只是起点。2220 张、2 个类别的规模正好适合做小规模消融实验——你想试新的损失函数、新的注意力模块、新的数据增强策略都可以在这个数据集上快速验证。但小数据集有个陷阱指标波动大改进带来的提升可能被随机性淹没。这一章讲怎么把实验做得可信。6.1 固定随机种子和划分保证对比公平任何改进对比前提是训练集验证集划分完全一致。把第 4 章的划分脚本固定seed并且把划分结果存成文件后续所有实验都读同一份划分。import json # 保存划分结果 split_info { train: [os.path.basename(p) for p in train_imgs], val: [os.path.basename(p) for p in val_imgs] } with open(split.json, w) as f: json.dump(split_info, f, indent2)后续实验直接读split.json不再重新随机划分。这样 A 模型和 B 模型的验证集完全相同指标才有可比性。6.2 用多次运行取平均而不是只看一次小数据集上单次训练的 mAP 波动可能有 2 到 3 个点。判断一个改进是否有效至少跑 3 次不同种子看均值和方差。如果改进带来的提升小于方差那这个改进在这个数据集上不可信。实验种子mAP50备注baseline420.812原始 YOLOv8nbaseline430.798原始 YOLOv8nbaseline440.805原始 YOLOv8n改进 A420.821加注意力模块改进 A430.815加注意力模块改进 A440.819加注意力模块看均值baseline 约 0.805改进 A 约 0.818提升约 1.3 个点且三次都高于 baseline 的最高值这种改进才值得继续。如果改进 A 有一次掉到 0.79那就得怀疑是随机性。6.3 一个具体技巧用混淆矩阵定位类别混淆YOLO 训练完会输出混淆矩阵。这个数据集只有两个类别混淆矩阵就是 2x2非常直观。如果可口可乐和百事可乐互相误判的比例高说明两个类别的视觉特征区分度不够或者标注时有些模糊样本标错了。这时候与其调模型不如回去清洗标注。# 训练后混淆矩阵一般在结果目录下 ls runs/coke_pepsi/exp1/ # 找 confusion_matrix.png 或 confusion_matrix_normalized.png我自己的习惯是每次跑完新实验先看混淆矩阵再看 mAP。混淆矩阵能告诉你错误发生在哪两个类别之间比一个孤零零的 mAP 数字有用得多。如果两个类别几乎不混但 mAP 还是低那问题多半在框的定位上可以去看验证集的预测可视化。这个数据集规模不大但正好够你把「格式转换 → 目录组织 → 训练 → 排查 → 消融」整条链路走一遍。走完之后你再拿到别的目标检测数据集流程是一样的只是类别数和标注格式可能不同。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑