基于YOLO的眼部检测与瞳孔追踪优化方案 📅 发布时间:2026/8/31 14:04:09 👁 浏览次数: 简介本资源是一套面向人工智能课程设计、毕业设计与期末大作业的深度学习实践项目聚焦于基于YOLO架构的眼部检测与瞳孔追踪技术实现适用于计算机视觉初学者及进阶学习者开展实操训练与算法优化研究。压缩包共12个文件含4个核心Python脚本如webcam_pupil.py用于实时摄像头追踪、train.py与train4models.py支持多模型训练调优、1个性能评估CSV文件benchmark_scientific_results.csv、1个数据集说明文档及配套README.md等整体仅12KB轻量易部署。项目已提供完整目录结构含model/、datasets/、runs/等标准YOLO工作路径与预训练模型占位便于快速复现实验流程代码模块分工明确涵盖数据加载、模型训练、基准测试与实时推理全流程附带科学化评估机制可直接用于课堂演示、课程报告或毕设原型开发。目前已有32人学习下载。 做眼部检测和瞳孔追踪这个方向有一阵子了从最初拿传统图像处理硬怼到后来切到YOLO做眼部定位再配合阈值分割和霍夫变换做瞳孔追踪中间踩的坑比想象中多。最近把整套方案整理成基于yolo的眼部检测与瞳孔追踪设计_优化版核心思路就是用YOLO先锁定眼睛区域再在这个小区域内做瞳孔的精确定位和连续追踪既保证了检测鲁棒性又控制住了实时性预算。这套方案适合两类人一类是入门目标检测没多久、想找个能落地的实际项目的同学另一类是做视线估计、疲劳驾驶检测或者眼动交互应用需要一套可靠的瞳孔追踪前端。整条链路——从数据标注、模型训练、导出部署到瞳孔追踪算法实现——我都会聊到关键参数和代码也会给出来照着跑一遍基本能通。就算你之前完全没碰过YOLO只要会基本的Python和OpenCV也能跟得上。1. 项目整体设计与思路拆解1.1 为什么选YOLO做眼部检测而不是传统方法很早之前我试过用Haar级联和HOGSVM做眼睛检测怎么说呢实验室环境下还行一到实际场景就拉胯。人脸稍微偏转、戴眼镜、光线变化大一点漏检误检就冒出来了。Haar级联本质上是基于特征模板的滑动窗口分类它对正面端正的人脸效果尚可但对局部目标的鲁棒性很一般HOGSVM虽然特征表达强一些但滑动窗口加金字塔的检测方式太慢根本跑不满实时视频流。YOLO这一类anchor-based的单阶段检测器思路完全不同。它把检测当成一个回归问题整张图一次前向推理直接输出目标框和类别概率速度和精度在这个量级上平衡得最好。用在眼部检测这个场景里有几个实实在在的好处第一眼睛的位置信息是和人脸强相关的YOLO在大尺度上下文里能学到眼睛长在脸的上半部分这种隐含关系所以单眼被遮挡或者只露半张脸时也能给出置信度较高的框第二端到端训练不需要单独设计特征数据喂进去调参就行对工程落地太友好了。顺手做了个小对比供参考方案推理速度CPU戴眼镜鲁棒性角度变化标注成本Haar级联快差差无预训练HOGSVM慢中等中等高YOLO优化版方案中等偏快好好中等1.2 优化版整体框架是怎么搭的这版优化的核心不是单纯把YOLO的模型换大而是在系统层面做了分工。整体流程分三段先用YOLO在整帧上检测眼睛区域这一步是粗定位然后从原图裁剪出眼睛的小块区域在这个小区域内做灰度化、直方图均衡、阈值分割再用轮廓分析和椭圆拟合找到瞳孔中心这一步是精定位最后把瞳孔中心的坐标序列做一阶低通滤波或者卡尔曼滤波输出平滑的追踪轨迹。为什么把瞳孔追踪从YOLO里拆出来单做原因很简单YOLO适合框级目标检测但瞳孔是圆形小目标而且中心坐标的精度要求是亚像素级的直接让YOLO回归瞳孔中心点会很不稳。与其让一个检测模型干两件事不如让YOLO干它擅长的粗定位瞳孔追踪交给传统的图像处理算法。实测下来这种YOLO粗定位 传统算法精定位的组合在1080P视频上单帧处理时间能控制在30ms以内GPU比端到端检测瞳孔中心的方式稳定得多。2. 数据集准备与YOLO模型训练2.1 数据集来源与标注规范训练眼部检测模型数据集无非两条路开源数据集和自己标。开源方面BioID人脸数据集包含1521张灰度图每张都有眼睛坐标标注很适合做精度基准GI4E数据集是专门做眼睛定位的戴眼镜的样本不少。不过开源数据集的缺点是场景单一实际工程里最好还是叠加一部分自采数据手机、普通USB摄像头都行关键是把多角度、多光线、戴不戴眼镜的场景覆盖到。标注工具我用的LabelImg标注类别就两个left_eye和right_eye。这里有个值得注意的细节不要只标一个eye类别因为后续做瞳孔追踪时左右眼的坐标需要分开处理特别是做疲劳检测时要分别计算两眼的状态。标注框不要卡得太紧稍微包含一点上下眼睑的皮肤区域模型反而学得更稳。我自己踩过这个坑最开始按瞳孔边缘紧贴着标训练出来的框抖动特别厉害后期追踪时裁剪区域太窄阈值分割经常把整块区域都判成瞳孔。标注完的数据按照8:1:1划分训练集、验证集和测试集然后转成YOLO格式。YOLO的标签格式是txt文件每一行是class_id x_center y_center width height其中坐标都是相对于图片宽高的归一化值。转换脚本不复杂但你得记住一个坑LabelImg默认导出的是Pascal VOC的xml格式坐标是左上角和右下角的绝对像素值转的时候一定记得做归一化width和height取的是框的实际像素宽高除以图片宽高不是中心坐标的差值我第一次写这个转换脚本时就因为这里算错训练出来的loss直接不收敛。2.2 训练配置与参数调优模型我选的YOLOv5s理由很实际YOLOv8官方代码库对自定义数据训练的细节封装更多但对老手来说YOLOv5的工程文档和社区方案更成熟遇到问题好查。如果你机器显卡显存小于6GYOLOv5s再加合适的batch size也是绰绰有余的。数据集的yaml配置长这样# eye.yaml train: ./dataset/images/train val: ./dataset/images/val test: ./dataset/images/test nc: 2 names: [left_eye, right_eye]训练命令我建议在YOLOv5仓库目录下执行关键参数如下python train.py --img 640 --batch 32 --epochs 150 --data eye.yaml \ --weights yolov5s.pt --cache --device 0几个参数的选择逻辑说一下。输入分辨率这里用640眼睛在整帧里属于小目标分辨率太低小目标特征会丢失冲到1280的话精度提升有限但推理时间翻倍。batch size 32是在8G显存下的可行值如果你的显卡只有4G降到16或者8配合--cache把数据缓存到内存里能明显加快训练速度。epochs设150前面先跑个80轮看loss趋势如果已经收敛到平台期提前终止也行不用死等。预训练权重选的yolov5s.pt而不是从零训练这里有个业内共识用COCO预训练权重微调特别是对小数据集来说收敛速度快得多最终精度也更高。因为预训练模型已经学到了通用的纹理和边缘特征你的任务只需要在这个基础上做迁移。我的经验是眼睛检测数据集哪怕只有两三千张图用预训练权重训练也能在mAP上达到90%以上从零训练的话可能只有70%上下浮动。2.3 训练完成的模型导出与压缩训练完的权重是.pt格式实际部署的时候直接加载PyTorch模型当然可以但推理速度和兼容性都不理想。我一般会导出成ONNX格式再用ONNX Runtime推理这样在CPU上的速度会快不少而且后续如果要部署到树莓派或者手机端ONNX的生态也更方便转换。python export.py --weights runs/train/exp/weights/best.pt --include onnx --opset 12导出之后建议用onnxruntime的InferenceSession做一次推理验证确认导出前后输出一致。这里有个容易踩的坑YOLO的ONNX输出是一个1x25200x7的张量25200是三个检测层的anchor组合数量7是x_center、y_center、width、height、objectness、两个类别的置信度。后处理要自己写NMS千万别直接用OpenCV的dnn.readNetFromONNX然后以为输出直接就是检测框这个误解让我调试了整整一个下午。3. 瞳孔追踪核心算法实现3.1 预处理从检测框到瞳孔候选区拿到YOLO输出的眼睛框之后第一步是从原图裁剪出眼睛区域。裁剪的时候我会在YOLO框的基础上向外扩20%的边距为什么呢因为YOLO的框即使训得再好也存在几个像素的抖动裁剪区域包含一点额外背景能给后续的瞳孔检测留出容错空间。但也不要扩得太多扩太多会把眉毛、眼影等干扰项带进来阈值分割阶段容易把这些区域误判成瞳孔。裁剪出来的图像先转灰度然后做直方图均衡化。这一步很关键因为瞳孔追踪对光照敏感直方图均衡化能把暗光下的瞳孔和虹膜对比度拉开。接下来做高斯滤波核大小取5x5sigma取0。高斯滤波的作用是抑制图像噪声减少后续二值化时出现的细小噪点。做完这三步眼睛区域就变得干净了。3.2 瞳孔定位阈值分割与椭圆拟合的配合瞳孔在眼睛图像里是最暗的区域所以最直接的思路就是用阈值分割把它抠出来。但阈值怎么定是个问题固定阈值在光照变化下必翻车。我用的自适应方法有两个第一种是大津法OTSU。OTSU自动计算一个能把像素分成前景和背景两类的全局阈值原理是让类间方差最大。因为瞳孔和周围虹膜、眼白的灰度差异明显OTSU在大多数光照条件下都能给出不错的分离效果。代码如下import cv2 import numpy as np def find_pupil_otsu(eye_img): eye_gray cv2.cvtColor(eye_img, cv2.COLOR_BGR2GRAY) eye_gray cv2.equalizeHist(eye_gray) eye_gray cv2.GaussianBlur(eye_gray, (5, 5), 0) _, thresh cv2.threshold(eye_gray, 0, 255, cv2.THRESH_BINARY_INV cv2.THRESH_OTSU) contours, _ cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return None # 取面积最大的轮廓 pupil_contour max(contours, keycv2.contourArea) if cv2.contourArea(pupil_contour) 30: return None # 椭圆拟合求中心 if len(pupil_contour) 5: ellipse cv2.fitEllipse(pupil_contour) center ellipse[0] return center return None注意这里用的是THRESH_BINARY_INV因为瞳孔是暗的取反之后瞳孔变成白色前景方便提取轮廓。cv2.fitEllipse要求轮廓点至少5个所以要做个长度判断。面积阈值30是用来过滤噪声小轮廓的这个值可以根据你的摄像头分辨率调整1080P下30比较合理如果画面更小就相应降低。第二种方法是局部阈值加形态学操作。在强反光或者戴眼镜的情况下瞳孔里会有高光点OTSU会把瞳孔中心的高光区域判成背景导致检测到的轮廓中间有个洞。这时候先用cv2.morphologyEx做闭运算把洞填上再做轮廓提取效果会好很多。闭运算的核我用的是3x3椭圆核迭代2次。如果瞳孔被眼睑遮挡了半边椭圆拟合会失败或者拟合出的形状明显偏扁这种情况要设置一个离心率阈值离心率太大说明拟合结果不合理应该丢弃本次检测等下一帧再来。3.3 追踪平滑让坐标不再抖成筛子单帧的瞳孔中心坐标即使检测对了连续看也会有小幅抖动这是图像噪声和分割结果微变带来的。直接把这个坐标用于视线估计或者眼动交互体验会非常差。我的解决方案是一阶低通滤波公式很简单smooth_x alpha * raw_x (1 - alpha) * smooth_x_prev smooth_y alpha * raw_y (1 - alpha) * smooth_y_prevalpha取0.4左右比较合适。alpha越大响应越快但抖动也越大alpha越小越平滑但延迟也越高。如果你做的是疲劳检测这种不要求极低延迟的场景alpha可以降到0.3如果是眼动游戏交互alpha可以调高到0.5。如果想更稳一点可以用卡尔曼滤波。不过说实话对一个2D坐标点的追踪卡尔曼滤波的增益没有想象中那么大反而是低通滤波加得合理时延控制更直观。我实际工程里用的是低通滤波只有在实验对比需要展示高级感的时候才上卡尔曼。这里分享一个更实用的技巧当检测失败时用上一帧的平滑坐标作为当前帧的估计值同时把alpha临时调大让系统快速跟上真实位置。这个丢帧续命机制在眨眼或者头部快速转动时非常有用能显著减少追踪轨迹的中断。4. 常见问题与排查技巧实录4.1 训练指标全是0怎么办这个坑在YOLO相关的社区里快被问烂了——训练跑了几个epochloss不降precision和mAP全是0。我排查过几十次这类问题大概率是以下几个原因按概率排序第一标签格式错了。最常见的是转换脚本里坐标算错归一化后的坐标超出0到1范围或者类别索引从1开始而不是从0开始。检查方法很简单把训练集里任意一张图对应的txt文件打开用代码把坐标重新映射回像素坐标画在原图上肉眼看框的位置对不对。这一步能过滤掉八成问题。第二数据集划分泄露。训练集和验证集里有相同的图片或者同一段视频的连续帧同时出现在两边模型在训练集上背住了这些样本验证集上却表现平平甚至为0。解决办法是按视频序列划分不要随机打乱分帧。第三训练图片本身的问题。图片有损坏、通道数异常、或者全黑全白这类图片在预处理阶段就会影响模型。排查手段是在训练脚本里加一个数据加载后的可视化检查把加载出来的batch绘制成网格图看一下确认数据管线没问题。4.2 戴眼镜场景下瞳孔追踪失效戴眼镜会引入两大问题镜片反光和镜框遮挡。镜片反光会在瞳孔中心形成高光斑做阈值分割时瞳孔被挖掉一块。前面提到的闭运算能解决一部分但如果反光太严重闭运算也填不回来。我的做法是加一个高光修复步骤检测到瞳孔轮廓内有大面积高亮区域时用瞳孔边缘的灰度值填充这个区域再做一次膨胀模拟出完整的瞳孔形状。这个填充逻辑本质上是先找洞再补洞在OpenCV里可以先对阈值图像做距离变换找到高光区域中心再从高光区域边界向外搜索最近的瞳孔边缘像素取灰度值进行填充。单帧填充耗时大约0.5ms对实时性影响可以忽略。镜框遮挡的问题则靠YOLO那一步解决——我训练的时候专门在数据集里加了大量戴眼镜的样本让模型输出的眼睛框尽量包含完整的眼睛区域这样即使有遮挡裁剪区域也足够大瞳孔定位时能利用没有被遮挡的部分做椭圆拟合。4.3 实时性优化技巧很多人跑完这套流程发现CPU推理速度只有十几帧就开始急着换模型。实际上有几个性价比极高的优化手段按操作难度排序第一输入尺寸降级。YOLO检测不需要每次都跑全分辨率先把视频帧resize到640x640做检测然后用检测框的坐标映射回原图裁剪。这个方案几乎零成本速度提升明显。第二跳帧检测。YOLO检测没必要每帧都跑人的头部运动再快一两帧内眼睛框的位置变化也有限。我实际部署时是每3帧做一次YOLO检测中间两帧直接用上一帧的检测框做瞳孔追踪。这样瞳孔追踪是逐帧的但YOLO不是逐帧的整体帧率能提升两倍以上。第三推理后端调优。如果用的是ONNX Runtime设置session.set_intra_op_num_threads(4)能利用多核CPU如果是GPU环境用TensorRT做FP16量化YOLOv5s的推理速度能从几毫秒进一步压到1-2毫秒。量化后精度下降约1-2个点对眼睛检测来说完全可接受。这三招叠下来我实测在i5-8500的CPU上能达到40帧以上的处理速度在GTX 1660上能跑到100帧以上。如果到这个程度还嫌慢那问题就不在推理引擎而在你的业务逻辑里——检查一下有没有在循环里做不必要的图像拷贝或者频繁的格式转换这些细节往往比模型本身更吃性能。5. 优化版相比原版改进了什么说到优化版这三个字我得聊聊这版到底优化了哪些东西。第一版方案我犯了一个典型错误试图让YOLO直接回归瞳孔中心点训练一个13类的模型——左右眼各一个类别再加一个瞳孔点类别。瞳孔目标太小标注误差又大模型训练出来中心点预测的抖动非常严重眼动轨迹根本没法用。优化版最大的改动就是把瞳孔追踪从YOLO的任务里剥离出去各司其职。第二个优化点是检测框的后处理。原版直接对YOLO输出的所有候选框做NMS然后取置信度最高的框。优化版增加了一个位置约束利用双眼的几何关系如果检测出左眼但没有检测出右眼就在左眼框的镜像位置附近搜索被遗漏的右眼候选框。这个先验知识非常朴素但实际提升明显侧面入脸的情况下能挽回不少漏检。第三个优化点是模型输入输出原版用的是YOLOv5m我换成了YOLOv5s并配合蒸馏训练。蒸馏的过程也不复杂先用YOLOv5m训练一个教师模型然后用教师模型的输出作为软标签辅助训练学生模型YOLOv5s。蒸馏后的YOLOv5s在精度上接近YOLOv5m但推理速度提升约40%对实时追踪来说这笔账非常划算。如果你对精度要求没那么极端直接训练YOLOv5s也可以只是要准备好接受边缘场景下多几个像素的框偏移。最后在代码结构上也做了调整。原版所有逻辑堆在一个main.py里改个阈值要在几百行里翻找。优化版把YOLO检测、瞳孔追踪、平滑滤波、可视化分别封装成独立的类类与类之间通过简单的数据结构传递结果后续要接GUI、接串口、接数据库都很方便。工程代码这块结构清晰带来的幸福感是长久的别图一时省事把代码全揉在一起。踩过这么多坑我个人的体会是眼部检测与瞳孔追踪这类项目难点从来不在某一个算法有多高级而在每个环节之间的衔接是否稳。YOLO的框稍微偏一点、追踪的阈值稍微差一格、滤波参数稍微调错一个数量级整个系统的表现就会天差地别。把这些细节磨平了再回头看最初那个只有几十行脚本的demo你会清楚每一处设计背后的取舍逻辑这会比单纯跑通一个项目学到更多东西。本文还有配套的精品资源点击获取