资讯动态

眼镜检测数据集实战:基于YOLOv8从标签校验到模型训练部署全流程

发布时间:2026/10/5 8:42:02 来源:尧图企业网站定制
简介这是一份面向计算机视觉入门与进阶开发者的YOLO系列目标检测数据集专用于眼镜玻璃检测场景可供算法课程设计、竞赛练兵或实际项目验证直接使用。资源已预先划分好训练集、验证集与测试集并附带data.yaml配置可直接用于YOLOv5、v7、v8、v9、v10及YOLO11等常见版本模型的训练与评估包内提供2000个XML格式的VOC标注文件同时包含对应的YOLO格式txt标签分别存放于独立文件夹便于用LabelImg等工具查看或直接供训练脚本读取压缩包约127.93MB目录结构清晰免去自行切分的麻烦。目前已有128人学习下载。使用这套数据可省去手动标注与划分工作直接体验完整YOLO训练流程并对照两种标签格式理解坐标归一化规则适用于教学实验或算法效果验证尤其适合需要快速搭建数据管线的学习者。1. 眼镜检测数据集到手后先别急着训练2948张图像带标签到底能做什么眼镜检测数据集说白了就是一堆人像图每张图里有人脸标注文件里记录眼镜框所在的位置和大小。2948张带标签的图放在 YOLO 工作流里足够训练一个能区分“戴了眼镜”和“没戴眼镜”的检测模型也能给安全帽检测、考勤打卡、疲劳驾驶这类场景做前置模块。适合谁刚接触目标检测的人想拿自定义数据验证 YOLOv8 的效果或者工程团队需要快速出一个眼镜检测 demo 来评估业务可行性。这篇文章不绕理论直接按数据集落地路径来拆包、核对标签、训练、排障、导出、部署。2. 拆包核验从 zip 到 YOLO 可训练目录先核对四件事数据集拿到手第一反应都是解压完直接丢进训练脚本。常见做法是先花十分钟确认目录、后缀、坐标格式和类别编号否则后面训练报错会浪费更多时间。下面这四件事是我处理这类“xxx数据集-带标签.zip”时必做的。2.1 解压后先看目录结构images 和 labels 必须一一对应先解压再看目录长什么样。多数目标检测数据集会分成 images 和 labels 两个顶层目录也可能是 train/images、train/labels 这种带划分的结构。unzip yolo算法-眼镜检测数据集-2948张图像带标签-玻璃.zip -d glasses_data cd glasses_data find . -maxdepth 2 -type d逻辑说明unzip指定-d glasses_data把文件解到独立目录避免直接铺在当前目录下污染环境。之后用find只看两层目录确认有没有 images、labels 或者 train/val 子目录。参数说明-d后的目标目录如果不存在会自动创建-maxdepth 2是控制目录展示深度避免把每张图像的路径都列出来。这一步能最快暴露“压缩包里混进乱七八糟文件”的情况。如果看到的是清晰的两目录结构就继续核对一一对应关系import os img_dir images label_dir labels imgs sorted(os.listdir(img_dir)) labels sorted(os.listdir(label_dir)) # 找有图像但没有对应 txt 的文件 missing [ f for f in imgs if not os.path.exists(os.path.join(label_dir, os.path.splitext(f)[0] .txt)) ] print(图像数:, len(imgs), 标签数:, len(labels)) print(缺失标签的图像:, missing[:10], 共, len(missing), 个)逻辑说明YOLO 训练要求每张图像有一个同名.txt比如img_001.jpg对img_001.txt。脚本遍历图像目录用os.path.splitext(f)[0]去掉后缀再拼上.txt去 labels 目录找同名文件找不到就记进缺失列表。这一步能提前发现“某张图没有标注”这类问题。参数说明sorted保证文件列表顺序稳定方便人工抽查[:10]只打印前 10 条避免控制台刷屏。如果缺失数量大于零先把这些文件挪到unlabeled/目录再训练否则超参数搜索阶段会报空标签警告。我一般会顺手打印len(missing)用来判断是极个别脏数据还是整个目录配对错位。2.2 读一个标签 txt判断是 YOLO 格式还是 VOC 格式确认完数量对应再打开随便一个标签文件看内容长什么样。这一步决定了后面训练要不要做格式转换。head -5 labels/000001.txt逻辑说明head -5只看前 5 行。YOLO 格式每行是 5 列数字类别 id、归一化中心点 x、归一化中心点 y、归一化宽、归一化高全部是浮点数。如果看到这样的内容说明这个数据集已经按 YOLO 格式整理过可以直接喂给训练脚本。如果你看到的是object、bndbox这类 XML 标记那就是 Pascal VOC 格式。很多公开数据集初始给 VOC 标注需要先转成 YOLO txt。常见做法是写一个转换脚本把xmin, ymin, xmax, ymax除以图像宽高换算成归一化中心点坐标# VOC 转 YOLO 的核心换算逻辑 x_center (xmin xmax) / 2 / img_width y_center (ymin ymax) / 2 / img_height w (xmax - xmin) / img_width h (ymax - ymin) / img_height逻辑说明VOC 给的是绝对值坐标YOLO 要的是 0 到 1 的归一化值。除以图像宽高后无论原图是 1920 还是 640坐标都落在统一尺度下。这里有一个坑如果原图有 EXIF 旋转信息直接用原始像素换算会让框偏移后面第 4 章会细说。参数说明img_width和img_height必须是图像实际的像素尺寸不是标签里的尺寸。我从这个数据集的命名习惯推测它大概率已经是 YOLO 格式就算不是上面的换算逻辑也够用了。2.3 类别编号确认单类“眼镜”和多类标签的差异YOLO 训练前还要清楚类别 id 的含义。文件名叫“眼镜检测数据集”不代表只有一类。有些数据集把“戴眼镜”和“不戴眼镜”分别编码为 0 和 1也有的只保留眼镜一类。跑一下统计就明白了import glob from collections import Counter cls_counts Counter() line_counts [] for f in glob.glob(labels/*.txt): with open(f, encodingutf-8) as fp: lines fp.readlines() line_counts.append(len(lines)) for line in lines: cls_counts[line.split()[0]] 1 print(类别 id 分布:, dict(cls_counts)) print(平均每张图目标数:, sum(line_counts) / len(line_counts)) print(空标签文件数:, sum(1 for n in line_counts if n 0))逻辑说明统计每个 txt 第一列的出现次数类别 id 就是它。如果只出现0说明是单类检测后面写nc: 1、names: [glasses]就够。如果出现0和1那说明还有“无眼镜”或“墨镜”之类第二类需要看数据集的标签说明。参数说明encodingutf-8是保险措施中文标签文件有时是 GBK 编码不加会抛 UnicodeDecodeErrordict(cls_counts)把 Counter 转成普通字典打印更干净。空标签文件数量很重要——如果有很多空 txt图像上明明有眼镜却没标注会直接拉低召回率。这个时候必须回到标注工具里补标签或者先删掉这些图。3. 用 YOLOv8 把 2948 张图训成眼镜检测模型最小可复现命令目录核对完就可以进训练环节。现在最稳的路线是 YOLOv8 的ultralytics包因为它把数据加载、训练、验证、导出都收在一个命令里适合自定义数据集快速迭代。下面这套流程我在类似小数据集上跑了很多次按步骤走基本不翻车。3.1 安装 ultralytics 并设计 data.yaml先装包。建议用 Python 3.9 以上推荐 3.10避免一些依赖在旧版本上编译报错。pip install ultralytics逻辑说明ultralytics会连带安装torch、torchvision、opencv-python等核心依赖。如果机器上有 NVIDIA 显卡建议提前装好 CUDA 版 PyTorch否则默认的 CPU 版训练 2948 张图会慢到怀疑人生。装完可以跑yolo predict modelyolov8n.pt sourcehttps://ultralytics.com/images/bus.jpg验证环境能输出检测框就说明环境通了。接下来写数据集配置。这是 YOLO 训练里唯一必须手写的文件路径写错比模型选错更容易翻车。# data.yaml path: /absolute/path/to/glasses_data train: images val: images_valid nc: 1 names: 0: glasses逻辑说明path是数据集根目录的绝对路径最好别用相对路径因为训练脚本的工作目录一换就容易找不到。train和val填的是相对path的子目录。这里把 train 指向全部 imagesval 单独指向images_valid表示验证集是拆分出来的独立目录。如果偷懒把 train 和 val 都写成 images训练时 mAP 会虚高因为模型已经见过的图又用做评估。参数说明nc必须和标签里的类别 id 数量一致。如果第 2 章统计出有 2 个类别这里写nc: 2names 里要加1: no_glasses。我一般会先用脚本划分一份images_valid从 2948 张里随机抽 300 张左右做验证保证训练集和验证集没有重叠。3.2 训练命令与关键参数数据集配置好之后启动训练就一行命令。先从小模型开始不要一上来就用 xL 规模。我个人习惯先用yolov8s跑一个 100 轮的基线再根据结果决定换yolov8m还是调数据。yolo detect train \ datadata.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ patience20 \ projectglasses_det \ nameexp0逻辑说明modelyolov8s.pt会下载预训练权重迁移学习能极大缩短收敛时间。project和name决定输出目录训练日志、权重文件都保存在glasses_det/exp0/下。用项目名区分不同实验调参时不会混。参数说明imgsz640是训练分辨率。眼镜在监控画面里通常偏小如果目标是远距离检测可以把imgsz提到 768 或 896但显存占用会明显上升。batch16在 11GB 显存的卡上基本够用显存不够就降到 8。patience20表示验证集 mAP 连续 20 轮不提升就自动停止防止白跑时间如果想让模型充分训练也可以把它改成 50 或直接设 0 关闭早停。训练中途想停按CtrlC然后重新运行命令会从最近的 checkpoint 续训。这里有个小技巧resumeTrue参数或直接yolo detect train resume modelglasses_det/exp0/weights/last.pt能接着上次的轮次继续不必从头再来。3.3 训练日志怎么看loss 和 mAP 都要盯训练开始后终端会每 1 轮刷一行日志。别只看 mAP第一优先看box_loss和cls_loss是不是在稳步下降。正常情况是前 20 轮下降快后面变平如果某个 loss 在某个轮次突然上升多半是学习率冲过头或数据里有脏标注。指标看什么异常信号box_loss每轮下降突然翻倍检查标签是否越界cls_loss每轮下降不降反升检查类别 id 是否错乱mAP50在验证集上越高越好训练集 mAP 很高但验证集很低说明过拟合mAP50-95反映框定位精度低于 0.3 时优先检查标注是否粗糙训练结束后用下面命令把验证集指标跑一遍拿到最终的 PR 曲线和混淆矩阵yolo detect val modelglasses_det/exp0/weights/best.pt datadata.yaml逻辑说明best.pt是验证集 mAP 最优的权重last.pt是最后一轮权重。我一般只用best.pt因为早停后last.pt往往已经过拟合。在项目交付时还要单独跑一遍测试集这个数据集如果没给测试集就从验证集里再抽一部分避免模型在验证集上过拟合造成评估虚高。4. 数据与训练排障2948 张图里最常见的 5 个坑数据集不大但正因为不大任何一个小毛病都会被放大。我在跑类似数据集时踩过不少坑这里挑 5 个最容易让新手卡住的问题按现象、原因、解决的顺序写。4.1 标签越界loss 不降或直接变 nan现象训练前几轮 loss 还正常到了第十轮左右突然变成nan或者 loss 一直挂在很高的下不去。原因标签 txt 里的坐标没有归一化或者某一行w和h大于 1。YOLO 默认对坐标做了约束越界目标在计算损失时会出现极端梯度导致数值溢出。这种问题在人工标注的数据集里尤其常见标注工具偶尔会把框拉到图像外沿。解决训练前先扫一遍所有标签文件import glob for f in glob.glob(labels/*.txt): with open(f) as fp: for lineno, line in enumerate(fp, 1): parts line.split() if len(parts) ! 5: print(列数错误:, f, 第, lineno, 行) continue try: _, cx, cy, w, h map(float, parts) except ValueError: print(非数字:, f, 第, lineno, 行) continue if not (0 cx 1 and 0 cy 1 and 0 w 1 and 0 h 1): print(越界:, f, 第, lineno, 行, parts)逻辑说明逐行解析检查是否 5 列、是否能转成 float、坐标是否落在 0 到 1 区间。越界的整行直接删掉或者用 OpenCV 把框裁剪回图像范围内。处理完再训练nan基本就消失了。参数说明enumerate(fp, 1)的起始值是 1方便直接对应 txt 里的行号。删行时要小心如果一张图只有一个框且被删了这张图就变成空标签属于下一个坑。4.2 空标签文件导致训练样本被静默跳过现象训练日志正常但cls_loss始终不下降后来才发现模型根本没学到眼镜特征还以为是模型问题。原因部分 txt 是 0 字节文件代码统计时没有报错因为空文件符合 YOLO 的“背景样本”逻辑。YOLO 会把空标签图当作负样本参与训练但如果数据集里空标签太多模型会倾向把一切预测为背景。解决统计空标签文件数量如果超过总数的 20%就不要把空文件直接喂进训练集。要么回到标注工具补标签要么把对应的图像移出训练目录。一步到位的做法是在第 2.1 节的脚本里加一个判断empty [ f for f in labels if os.path.getsize(os.path.join(label_dir, f)) 0 ] print(空标签数:, len(empty))逻辑说明os.path.getsize返回文件字节数0 就是空文件。这些文件名对应的图像要从images里移走否则训练集里残留大量无目标样本影响正负样本比例。少量空标签问题不大但超过 100 个就要警惕这个数据集的质量。4.3 图像与标签错位文件名对得上但内容对不上现象训练 mAP 不低但可视化检测结果时给一张戴墨镜的人像模型在额头上画框给一张不戴眼镜的人像模型还是乱画。典型的“学废了”状态。原因标注工具导出时文件名按顺序重排了但图片内容没有跟着走导致0001.txt里的框实际属于另一张图。这种错位光靠数量核对发现不了。解决随机抽几十对图像和标签把框画回去人工看。用 OpenCV 写一个小工具import cv2, os img_dir images label_dir labels for name in sorted(os.listdir(img_dir))[:50]: img_path os.path.join(img_dir, name) label_path os.path.join(label_dir, os.path.splitext(name)[0] .txt) if not os.path.exists(label_path): continue img cv2.imread(img_path) h, w img.shape[:2] with open(label_path) as fp: for line in fp: _, cx, cy, bw, bh map(float, line.split()) x int((cx - bw / 2) * w) y int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x, y), (x2, y2), (0, 0, 255), 2) cv2.imwrite(check_ name, img)逻辑说明把归一化坐标乘回图像宽高画红色矩形框输出到check_前缀的新文件。人工翻一遍这些图如果框不在眼镜上就说明标签和图像错位这时候需要用标注工具重新导出或者检查数据集作者是否给了文件映射表。参数说明[:50]只抽查前 50 张实际抽 100 张比较稳但可视化多了也看不过来。cv2.imwrite会把图写到当前目录注意不要污染原数据目录。4.4 类别 id 不连续nc 明明写了 1 却报错现象训练启动时报IndexError: index 2 is out of bounds for axis 0 with size 1或者验证时 PR 曲线里出现一个不存在的类别。原因标签里的类别 id 是 0 和 2但中间缺了 1。YOLO 的nc参数是类别总数不是最大 id 加 1如果数据里出现 id2而nc2就会越界。解决先看第 2.3 节的类别统计如果发现最大 id 与类别数不匹配把所有 id 重映射成连续值。用最简单的方式import glob, os mapping {0: 0, 2: 1} # 把旧 id 映射到新 id for f in glob.glob(labels/*.txt): lines open(f).readlines() with open(f, w) as fp: for line in lines: parts line.split() if not parts: continue parts[0] str(mapping[int(parts[0])]) fp.write( .join(parts) \n)逻辑说明读取每个 txt把第一列数字替换成映射后的 id再原样写回。注意要在备份后执行因为这是原地修改。做完之后nc就写映射后的类别数 2names 也改成对应的两个名字。4.5 重复图像让验证集虚高现象训练完看验证集 mAP50 高达 0.95但拿到真实场景测试只有 0.5 左右差距大得离谱。原因数据集中存在大量连拍帧或相似角度同一张脸以不同文件名出现多次。训练集里学过的图像换了个名字又出现在验证集模型等于考试时偷看了答案。解决用感知哈希对图像去重。常见做法是计算每张图的 pHash 值汉明距离小于阈值就认为是重复帧只保留一张。如果不想引入 extra 库更简单的做法是按文件名排序后每隔 N 张抽一张做验证集人为拉大验证集与训练集的时间间隔。我一般会在拆分验证集前先跑一遍去重避免相似帧串集。pip install imagehashfrom PIL import Image import imagehash, os lookup {} for name in sorted(os.listdir(images)): phash imagehash.phash(Image.open(os.path.join(images, name))) if phash in lookup: lookup[phash].append(name) else: lookup[phash] [name] dups {k: v for k, v in lookup.items() if len(v) 1} print(重复组数:, len(dups))逻辑说明imagehash.phash把图像压缩成 64 位哈希值内容几乎一样的两张图哈希值相差不超过 5。把哈希值当作字典 key同一个 key 下有多张图就是重复组。输出重复组数训练前每组只保留一张能有效降低验证集虚高。5. 验证与导出用指标、ONNX 和推理脚本把模型落地训练完不等于交付。眼镜检测要落到实际业务里至少还要做三件事跑一次正式评估、导出轻量推理格式、在真实图片上试阈值。这一章按顺序来。5.1 用 val 命令看 mAP50 与混淆矩阵评估命令很简单但输出得会看yolo detect val modelglasses_det/exp0/weights/best.pt datadata.yaml逻辑说明训练完以后最佳权重在glasses_det/exp0/weights/best.ptval子命令会在验证集上跑前向输出 mAP50、mAP50-95、精确率、召回率并在runs/detect/val下生成混淆矩阵图片。混淆矩阵里“真实背景被预测成眼镜”那一格如果数值偏高说明误检严重需要调置信度阈值或补充负样本。参数说明如果data.yaml里的val目录指向了全量 images评估结果仍然会虚高。我习惯专门留一个images_valid目录给验证集确保评估用的是模型没见过的图。因为这里用统一命令验证输出结果会直接存到glasses_det/exp0/目录下不用额外指定project。5.2 导出 ONNX 并用 Python 脚本推理训练好的 PyTorch 权重不能直接给 C 或移动端用导出 ONNX 是一张通用通行证yolo export modelglasses_det/exp0/weights/best.pt formatonnx imgsz640逻辑说明ONNX 格式被 OpenCV DNN、ONNX Runtime、TensorRT 都支持。导出成功后得到best.onnx推理时可以脱离 PyTorch 环境很多嵌入式设备的推理框架只认 ONNX。Python 侧验证导出的模型有没有问题用ultralytics直接跑一张真实图片最省事from ultralytics import YOLO model YOLO(glasses_det/exp0/weights/best.onnx) results model(test_glasses.jpg, conf0.25, iou0.45) for r in results: for b in r.boxes: cls int(b.cls[0]) conf float(b.conf[0]) xyxy b.xyxy[0].tolist() print(class_id:, cls, conf:, round(conf, 3), box:, [round(v, 1) for v in xyxy])逻辑说明加载 ONNX 模型后model()直接传图像路径返回results对象。遍历r.boxes取出类别、置信度和坐标。如果这个脚本能跑通并输出眼镜框说明导出的模型没问题可以接着做集成。参数说明conf0.25是置信度阈值低于它的一律过滤iou0.45是 NMS 的 IoU 阈值用于合并重叠框。这两个值先按默认来实际场景再调。打印里的round(v, 1)只是让输出更干净不影响数值。5.3 实际场景的 conf 和 NMS 参数调整业务场景和竞赛数据不一样眼镜检测经常出现在门禁闸机、工位摄像头这类画质一般的设备里。这时候模型输出的置信度普遍偏低默认 0.25 会把很多真实眼镜框滤掉。我一般按下面两组参数试场景confiou说明高清近景人脸0.350.5滤掉低置信度假框监控远距离小目标0.150.6拉高召回NMS 放宽避免漏检参数说明conf调低会让更多候选框进入结果误检变多适合“宁可多框不可漏检”的场景iou调高会让 NMS 保留更多重叠框适合眼镜遮挡严重的情况。真实落地建议在测试视频上跑一遍统计每帧框数框数爆炸就调高 conf框数太少就调低 conf。不要迷信默认参数。6. 进阶从“检测到眼镜”到“判定是否佩戴”的落地技巧模型只负责告诉我“图里有没有眼镜”但很多业务要的不是框而是“这个人有没有戴眼镜”。这就需要在检测结果后面加一层判断逻辑。如果是闸机或人脸识别这种人脸占比较大的场景常见做法是先用一个人脸检测器拿到人脸框再计算眼镜框与人脸上半部分的 IoU 重合度。重合率超过阈值就判定为已佩戴def is_wearing(face_box, glasses_boxes, iou_thresh0.1): if not glasses_boxes: return False fx1, fy1, fx2, fy2 face_box face_upper (fx1, fy1, fx2, fy1 (fy2 - fy1) * 0.5) for gb in glasses_boxes: if compute_iou(face_upper, gb) iou_thresh: return True return False逻辑说明face_upper取人脸框的上半部分因为眼镜只可能出现在这里compute_iou就是标准 IoU 计算。阈值 0.1 看起来小但眼镜框本身面积小上半脸 IoU 能达到 0.2 以上就算重合。如果你发现总是漏判把阈值降到 0.05如果总误判就升到 0.2。这层规则代码比模型调参稳定得多。还有一个调参方向是针对小目标。眼镜在监控画面里可能只有十几个像素宽训练时把imgsz从 640 提高到 896同时关闭 mosaic 增强里对单张图像缩放过度的策略能明显提升小目标召回。如果你用的是 2948 张的原始数据集mAP50 卡在 0.5 上不去先别急着换大模型用伪标签加一轮自动标注把错漏检的图补进训练集往往比盲目增加模型深度更有效。第一次做眼镜检测时我偷懒没拆验证集直接拿全量数据训练结果项目汇报时被真实场景的误检打脸。后来重新切验证集、调了 conf模型才真正可用。希望这个流程能帮你少走一圈弯路也希望帮到你的项目早一点跑出可信的指标。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑