ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

基于YOLO的眼镜检测:数据集解析与训练部署全流程

基于YOLO的眼镜检测:数据集解析与训练部署全流程 简介面向目标检测与YOLO系列算法开发者这份眼镜检测数据集压缩包包含已划分好的训练、验证与测试集配有data.yaml配置文件适配YOLOv5、YOLOv7、YOLOv8、YOLOv9、YOLOv10及YOLO11等主流算法下载后即可开展模型训练与效果验证。资源包大小127.93MB共2000个文件以xml标签文件为主同时包含YOLO格式txt标签两种标注格式分目录存放使用起来较为方便。标签格式说明详细坐标采用归一化设计适合初学者快速上手。目前已有128人学习适合需要做眼镜检测、佩戴状态识别或算法效果对比的开发者可免去自行采集与标注数据的时间从数据准备到模型评估均有现成内容可循对于需要快速验证算法性能的团队或个人还能缩短前期准备周期减少因数据格式不统一带来的调试成本整体实用价值较高。1. YOLO眼镜检测数据集能解决什么问题闸机要判断过闸的人有没有戴眼镜、商场大屏要统计试戴镜框的时长、安防系统要把眼镜从人脸区域单独抠出来——这类任务如果从头收图、自己标框两三周时间就耗进去了。这个标题里最值钱的不是“2948张”这个数量而是“带标签”这三个字说明图像已经配好了目标检测需要的人工标注压缩包解出来之后可以直接喂给YOLO训练省去最枯燥的标注环节。适合两类人一是刚接触YOLO、想用一份现成数据把训练到推理全流程跑通的学生和转岗工程师二是要做眼镜相关业务验证、需要快速出一个baseline的算法工程师。需要提醒的是YOLO版本迭代很快从v5到v8到现在的v11命令形态基本没变这篇文章以当前最常用的YOLOv8为主线先讲清数据格式再给完整训练路径。2. 解析zip内部结构一张图如何变成一行标签2.1 解压后先看目录布局拿到压缩包之后第一件事不是解压后立刻训练而是先把目录结构看清楚。这类数据集最常见的打包方式是images和labels两个文件夹平级严格对应每个jpg都在labels里有同名txt。执行下面的命令unzip yolo算法-眼镜检测数据集-2948张图像带标签-玻璃.zip -d ./glass_data tree -L 2 glass_data常见的输出长这样glass_data ├── images │ ├── 000001.jpg │ ├── 000002.jpg │ └── ... └── labels ├── 000001.txt ├── 000002.txt └── ...如果tree命令展示的目录和这个不一样比如多了voc格式的xml文件夹就需要先做格式转换。但标题里明确写了“带标签”且面向YOLO所以大概率就是txt格式。解压之后我习惯顺手统计一下文件数量是否对得上ls glass_data/images/*.jpg | wc -l ls glass_data/labels/*.txt | wc -l两个数字应该都接近2948。如果images比labels多说明存在没有标注的空图这类图之后要么删掉要么作为负样本单独处理。这一步不花时间但能提前发现数据不齐的问题避免训练到一半报错找不到对应标签。2.2 标签文件里五个数字的真实含义随便打开一个labels目录下的txt文件内容类似这样0 0.482033 0.365288 0.318098 0.280191这是YOLO格式的标准标注一行对应图像里的一个目标框共五个字段第一个是类别id从0开始后面四个依次是目标框中心点的x、中心点的y、框的宽度、框的高度全部除以图像宽高做了归一化。归一化是YOLO格式最核心的一点训练时无论输入图像压到640还是800这些相对坐标都不需要重算。提示如果你发现某个txt里的cx或cy大于1说明标注脚本换算出错这个文件需要修复。YOLO训练不会因为坐标越界直接报错但损失会异常mAP上不去。这个数据集的名字里带“玻璃”两个字需要特别确认一件事类别到底是一类还是两类。常见做法里这类眼镜数据集的labels会区分“glasses”和“glass”两个类别前者指完整的眼镜目标镜框加镜片后者单独标注镜片或透明玻璃区域。具体是不是这样解压之后立刻执行统计cat glass_data/labels/*.txt | awk {print $1} | sort | uniq -c输出里如果有两个数字分别对应class id 0和1说明是双类别如果只有0说明“玻璃”只是压缩包命名的修饰词实际标注只有眼镜一个类。这个结果直接决定后面的data.yaml怎么写必须先确认。2.3 把标签画回图像验证明标框是否贴合看txt数字只能确认格式确认不了标注质量。最直观的验证方式是把每个框画回原图肉眼扫一遍。这里给一个可以直接跑的脚本#!/usr/bin/env python3 import cv2 import os img_dir glass_data/images label_dir glass_data/labels class_names [glasses, glass] # 按2.2统计结果调整 for f in sorted(os.listdir(img_dir)): if not f.endswith(.jpg): continue img cv2.imread(os.path.join(img_dir, f)) h, w img.shape[:2] txt_path os.path.join(label_dir, os.path.splitext(f)[0] .txt) if not os.path.exists(txt_path): continue with open(txt_path) as fp: for line in fp: parts line.strip().split() if len(parts) ! 5: continue cid, cx, cy, bw, bh map(float, parts) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, class_names[int(cid)], (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imwrite(check_ f, img)脚本的逻辑很简单读取图像尺寸h和w将归一化的中心点和宽高换算回像素坐标然后画矩形框和类别名。参数说明(cx - bw / 2) * w是把中心点坐标转成左上角x同理算出左上角y和右下角x、ymax(0, y1 - 5)防止文字画到图像边界外。跑完之后扫一遍check_*.jpg重点看三类问题框没有包住整副眼镜、一个眼镜目标被重复画了两个框、玻璃区域框到了背景上。这三类问题直接影响训练收敛速度。2.4 标注工具的兼容性CVAT导出的数据集怎么处理如果你手里的zip是从CVAT这类标注平台导出的labels目录里通常还会多一个classes.txt里面按行写类别名。CVAT导出到YOLO格式时会保证images、labels、classes.txt三者一致。但有些版本导出的类别顺序是按标注时的创建顺序排的不一定和业务预期一致。拿到数据集后把classes.txt的内容和2.2统计出来的class id顺序对照一下确认0对应的是“眼镜”而不是“玻璃”再写data.yaml。顺序搞反的后果是训练能跑通但推理时显示出来的类别名全是错位的。3. 用YOLOv8把2948张图跑起来环境、目录与训练命令3.1 先搭一个不折腾的Python环境训练YOLO不需要从源码编译直接用ultralytics包就行。我一般用conda新建独立环境避免污染已有的Pythonconda create -n yolo python3.10 -y conda activate yolo pip install ultralytics opencv-pythonGPU这块分情况NVIDIA显卡正常安装CUDA版PyTorch即可ultralytics会自动检测AMD显卡需要先装ROCm版的PyTorch再装ultralytics训练命令不用改纯CPU也能跑但2948张图、100个epoch在CPU上可能要跑一晚上建议先用yolov8n这种最小模型验证流程再决定是否升级硬件。环境配置里最容易出问题的是torch和CUDA版本不匹配如果训练时报CUDA error: no kernel image is available大概率是torch装成了CPU版重装GPU版即可。3.2 重建目录并划分训练集与验证集原始zip里的images和labels通常是整体混在一起的没有区分train、val。YOLOv8的data.yaml需要明确指定train和val两个子目录所以要先按比例拆分。下面的脚本按8:2划分固定随机种子保证每次运行结果一致import os import random import shutil random.seed(42) src_img glass_data/images src_label glass_data/labels dst glasses_yolo for split in [train, val]: os.makedirs(f{dst}/{split}/images, exist_okTrue) os.makedirs(f{dst}/{split}/labels, exist_okTrue) imgs [f for f in os.listdir(src_img) if f.endswith(.jpg)] random.shuffle(imgs) val_cnt int(len(imgs) * 0.2) # 约590张留作验证 val_set set(imgs[:val_cnt]) for img_name in imgs: base os.path.splitext(img_name)[0] txt_name base .txt split val if img_name in val_set else train shutil.copy(os.path.join(src_img, img_name), os.path.join(dst, split, images, img_name)) if os.path.exists(os.path.join(src_label, txt_name)): shutil.copy(os.path.join(src_label, txt_name), os.path.join(dst, split, labels, txt_name))几个关键设计随机种子固定为42复现结果前20%的图像进入验证集剩下的进训练集复制图片时同时复制对应txt并且用if os.path.exists做了保护——万一某张图没有标签训练集里也不会出现孤立的无标注图片。如果没有这层保护YOLO训练时遇到images里有图但labels里没txt会直接跳过该图并打印警告不报错但会浪费数据。提示划分验证集时不要做按人物ID的去重。如果同一个人的多张照片横跨train和val模型相当于“提前见过这个人”验证指标会虚高真实场景的泛化能力要打个折扣。这个数据集如果按人物采集最好按人物文件夹划分而不是按单张图片随机划分。3.3 data.yaml的names必须和标签实测一致在glasses_yolo同级目录下创建glasses_data.yamlpath: /your/absolute/path/glasses_yolo train: train/images val: val/images nc: 2 names: 0: glasses 1: glasspath建议写绝对路径避免YOLO相对路径解析的坑nc和names必须和2.2的类别统计结果对上。如果统计出来只有一个类就把nc改成1names只留glasses。写完之后可以先跑一个快速的冒烟测试确认数据和模型能正常对接yolo detect predict modelyolov8n.pt sourceglasses_yolo/val/images/000001.jpg这个命令先用预训练权重跑一张验证图主要验证环境和路径没问题不代表数据集训练成功。真正训练开始前看一眼输出日志里的train和val图片数量确认和划分结果一致。3.4 训练命令逐参数拆解yolo detect train \ dataglasses_data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ patience15 \ projectruns/glasses \ nameexp1参数说明modelyolov8n.ptn代表nano最小的YOLOv8模型权重约6MB适合第一次跑通流程。硬件允许再换yolov8s或yolov8m精度会提升但训练时间成倍增加。epochs100对2948张图来说100轮足够让损失收敛。如果跑完发现val loss还在持续下降可以加到150。imgsz640输入分辨率。眼睛在图像里占比小的话640可能不够后面讲如何调整。先按默认640稳住流程。batch1612GB显存跑yolov8n没问题6GB显存降到8否则会OOM。OOM报错信息里会直接提示CUDA out of memory这时候减小batch或imgsz即可。patience15连续15轮验证损失没有改善就提前终止防止空跑。project和name输出目录训练结束后权重在runs/glasses/exp1/weights/best.pt。训练日志里最需要盯的是几个loss值。YOLO的损失由三部分构成box_loss衡量预测框和真实框的位置偏差cls_loss衡量类别预测误差dfl_loss是分布焦点损失负责边框回归的精细度。如果box_loss在前10轮没有明显下降优先怀疑标注框本身不贴目标回到2.3的可视化脚本再检查一遍。3.5 用自己的loss曲线判断训练状态训练结束后打开runs/glasses/exp1/results.png这是一张综合曲线图。正常状态train和val的box_loss、cls_loss同步下降曲线平滑metrics/precision和metrics/recall稳步上升后趋于平缓。如果train_loss持续下降但val_loss在某个点掉头向上这是典型的过拟合处理手段优先级从高到低增大数据增强的强度、减小模型规模、提前终止。baseline阶段不需要立刻调这些先让整个流程转起来拿到第一版权重再说。从YOLO目标检测流程的角度讲到这里你其实已经走完了“数据准备→模型训练→权重产出”的完整闭环接下来才进入调优和价值验证阶段。4. 把眼镜检测模型调到稳定评估指标、阈值与数据深坑4.1 用val命令拿到可信的mAP训练完的第一件事不是急着推理图片而是先跑验证集评估yolo detect val \ dataglasses_data.yaml \ modelruns/glasses/exp1/weights/best.pt输出表格里有两个关键指标mAP50和mAP50-95。mAP50是IoU阈值取0.5时的平均精度眼镜检测这类目标不算小、不算密的数据集mAP50到0.9以上才算及格mAP50-95是多个IoU阈值的平均更严格数值通常比mAP50低0.1到0.2。如果mAP50很高但mAP50-95明显偏低说明框的位置精度不够边界贴合不好这时候优先检查标注框有没有留白太多而不是继续加训练轮数。4.2 Precision和Recall曲线决定conf阈值results.png里除了loss还有PR曲线这条曲线是调置信度阈值最重要的依据。点开PR曲线找到precision和recall两条线交叉的位置那个点对应的置信度就是当前模型的近似最佳阈值。比如交叉点在0.35那么推理时conf0.35能兼顾误检和漏检。如果应用场景是“宁可错报也不能漏”的安防场景就把conf下调到0.15用误检换召回如果场景是精确统计眼镜数量conf上调到0.5以上减少误报。YOLO的默认conf是0.25但这个值只是通用默认值不同数据集的最佳值差别很大一定要拿PR曲线说话不要拍脑袋。4.3 眼镜在画面里太小怎么办这个数据集里的图如果以人脸特写为主目标尺寸不会太小如果包含大量全身照或多人场景眼镜目标可能只有20到30个像素宽。YOLOv8对这类小目标比较吃力。按照成本从低到高依次尝试三个手段第一把imgsz从640提到960或1280。分辨率越高小目标在特征图上的像素越多显存不够就同时减小batch。第二开启多尺度训练。训练命令加一个参数yolo detect train ... imgsz640 scale0.9scale0.9表示每次迭代随机缩放的程度相当于变相做数据增强让模型见过更多尺寸的眼镜。第三如果上面两个还不够考虑切片推理。常见做法是用Slicing Aided Hyper Inference把大图切成多个小图分别推理再把结果拼回去。对密集小目标有效但推理时间成倍增加不适合实时场景。实际项目中我一般先试imgsz效果不明显再上切片不会一上来就全套。4.4 标签噪声清洗过滤掉异常框打开任何一份真实数据集的labels都可能找到少量问题框比如框宽高为0、坐标越界、两个框几乎完全重叠。这类噪声用脚本批量清洗比肉眼检查靠谱。下面的脚本扫描所有标签并报告异常import os label_dir glasses_yolo/train/labels issues [] for txt in os.listdir(label_dir): path os.path.join(label_dir, txt) with open(path) as fp: for line in fp: parts line.strip().split() if len(parts) ! 5: issues.append((txt, 字段数不是5)) continue cid, cx, cy, bw, bh map(float, parts) if bw 0 or bh 0: issues.append((txt, 宽高为0)) if cx 0 or cx 1 or cy 0 or cy 1: issues.append((txt, 中心点越界)) if bw 1 or bh 1: issues.append((txt, 宽高超过1)) for item in issues: print(item)bw 1这种情况经常出现在标注工具误操作说明一个框比整张图还宽训练时会让anchor匹配失效拉低召回。发现问题框之后直接删除对应行不要整图删除除非这张图的标注全部有问题。4.5 类别不平衡怎么处理2.2的类别统计如果显示两个类数量悬殊比如glasses有2800个标注glass只有100多个模型大概率会偏向训练样本多的类别导致玻璃镜片经常漏检。YOLO没有直接的cls_weight参数常用做法有两个。一是对少数类图像做重复采样在训练集里把glass占比高的图片复制几份再训练注意验证集不能复制否则评估指标失真二是干脆把两个类合并成一个“眼镜”类在data.yaml里把glass类的标签id改成和glasses一样牺牲类别区分度换取更稳的检测率。这两种方案选哪个取决于业务是否需要知道“玻璃”这个独立类别。如果不需要合并更省事。5. 部署落地导出ONNX、批量推理与视频流防抖5.1 导出ONNX格式脱离PyTorch环境运行训练得到的best.pt是PyTorch权重部署到生产环境时通常需要转成ONNX这样不依赖ultralytics包也能跑推理yolo export modelruns/glasses/exp1/weights/best.pt formatonnx imgsz640 opset12导出之后用一个轻量Python脚本确认输出张量的形状import onnxruntime as ort sess ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) print(sess.get_outputs()[0].shape)YOLOv8默认的输出形状是[1, 84, 8400]8400是640×640输入下三个尺度特征图拼接出的预测框总数84 4框坐标 1目标置信度 79或2类别数取决于你的nc。看到这个形状就说明导出成功可以接推理了。5.2 用ONNX Runtime跑通一张图的完整推理import cv2 import numpy as np import onnxruntime as ort session ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) input_name session.get_inputs()[0].name img cv2.imread(test.jpg) orig_h, orig_w img.shape[:2] resized cv2.cvtColor(img, cv2.COLOR_BGR2RGB) resized cv2.resize(resized, (640, 640)) / 255.0 blob np.transpose(resized, (2, 0, 1))[None].astype(np.float32) outputs session.run(None, {input_name: blob})[0][0] # [8400, 84] boxes outputs[outputs[:, 4] 0.25] for det in boxes: cx, cy, bw, bh, obj_conf det[:5] cls_id int(np.argmax(det[5:])) cls_conf det[5 cls_id] x1 int((cx - bw / 2) * orig_w) y1 int((cy - bh / 2) * orig_h) x2 int((cx bw / 2) * orig_w) y2 int((cy bh / 2) * orig_h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2)逻辑说明先按640×640做预处理模型输出经过阈值过滤后的每个检测框坐标还原时用orig_w和orig_h把归一化坐标映射回原图尺寸这一步和2.3画标签的换算完全一致。注意输出里的置信度分成两部分第4列是目标存在概率类别列里的最大值是类别概率实际综合置信度用obj_conf * cls_conf更合理。5.3 视频流推理时抑制眼镜框抖动静态图片推理没问题之后接视频流会遇到新问题眼镜框在连续帧之间来回跳一会儿贴着眼眶一会儿往外扩两三个像素。原因很简单单帧推理没有时序概念。常见做法是对检测框做指数移动平均ema_boxes {} def smooth_box(key, new_box, alpha0.3): if key not in ema_boxes: ema_boxes[key] new_box else: old ema_boxes[key] ema_boxes[key] [alpha * n (1 - alpha) * o for n, o in zip(new_box, old)] return ema_boxes[key]alpha越大跟踪越灵敏但抖动越大越小越稳定但延迟越高。对闸机、门禁这类慢速场景alpha取0.2左右效果比较好如果是识别快速转头的人脸需要把alpha提高到0.5。实际操作中还要按检测框中心点的距离做目标关联两帧之间距离最近的框认为是同一个目标这本质上是一个极简版的追踪器。别小看这个细节眼镜框在视频里闪烁会让下游业务系统完全无法接受。5.4 批量推理脚本一次处理整个文件夹最后给一个直接能用的批量推理脚本把结果写成JSON方便接业务import os import json import cv2 import numpy as np import onnxruntime as ort session ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) inp_name session.get_inputs()[0].name results {} for img_name in os.listdir(test_imgs): img cv2.imread(os.path.join(test_imgs, img_name)) h, w img.shape[:2] blob cv2.dnn.blobFromImage(img, 1/255.0, (640, 640), swapRBTrue) out session.run(None, {inp_name: blob})[0][0] dets [] for det in out[out[:, 4] 0.25]: cx, cy, bw, bh det[:4] cls_id int(np.argmax(det[5:])) score float(det[4] * det[5 cls_id]) dets.append({ cls: cls_id, conf: round(score, 4), bbox: [round((cx - bw/2)*w, 1), round((cy - bh/2)*h, 1), round((cx bw/2)*w, 1), round((cy bh/2)*h, 1)] }) results[img_name] dets with open(output.json, w) as fp: json.dump(results, fp, indent2)脚本里的cv2.dnn.blobFromImage一行完成了缩放、归一化、通道转换的操作比手动预处理更稳。JSON里的bbox字段是xyxy格式方便直接对接目标跟踪或业务数据库。继续往下做就可以把模型接到业务里了——如果对当前精度还不满意优先回头查标注质量而不是继续堆epoch。本文还有配套的精品资源点击获取
返回列表