☰
基于YOLO的眼部检测与瞳孔追踪优化实践
2026/10/25 9:12:18 网站建设 项目流程

简介:本资源是一套面向人工智能与计算机视觉方向课程设计、毕业设计及期末大作业的实践型项目,聚焦基于YOLO架构的眼部检测与瞳孔追踪技术实现,适用于具备Python编程基础和初步深度学习认知的本科生或进阶学习者。项目完整覆盖数据预处理、模型训练(含train.py与train4models.py双训练脚本)、性能基准测试(benchmark_all.py + benchmark_scientific_results.csv)、实时摄像头推理(webcam_pupil.py)及预训练模型部署全流程,突出工程优化与实测验证。压缩包共12个文件,含4个核心Python脚本、1个README.md说明文档、1个数据集说明txt、1个结果CSV、1个模型目录占位文件及4个.gitkeep,整体仅12KB,轻量易部署。目前已有32人学习下载,提供即开即用的代码结构、清晰的模块划分(datasets/model/runs/result四级目录)、可复现的科学评测机制,以及面向真实场景的实时瞳孔追踪能力,是开展CV小目标检测与生物特征交互研究的高性价比入门范例。 拿到这个“基于yolo的眼部检测与瞳孔追踪设计_优化版.zip”项目标题时,我的第一反应是:这又是一个典型的计算机视觉课程设计/毕业设计项目,但它把两个常见需求焊在了一起——先通过YOLO做眼部区域检测,再在检测框内做瞳孔追踪。这刚好是一条很实用的技术链路,因为很多做疲劳驾驶预警、眼动分析、注意力检测的入门项目都会卡在“怎么稳定地找到瞳孔中心”这一步上。

这个项目适合谁来参考?如果你是计算机视觉的初学者,正愁怎么把YOLO从“跑通官方权重”推进到“训练自己的检测任务”,或者你已经能检测出人脸/眼睛,但不知道接下来怎么从图像里提取瞳孔坐标,那这个项目的思路可以直接抄。即便是想快速完成课程设计、毕业设计、竞赛demo的同学,这套“检测+追踪”的架构也能给你一个清晰可复现的框架。

先说结论:这份代码整体质量在课程设计级别里属于偏上的,核心亮点不在于某一个算法有多新,而在于任务拆解得很干净,把“眼睛在哪”和“瞳孔往哪看”拆成了两个独立模块来处理。下面我按自己的理解,把整个项目的设计思路、关键细节、实操过程和踩坑经验完整拆开讲。

1. 项目整体设计与技术选型

1.1 “优化版”到底优化了什么

拿到带“优化版”后缀的项目,第一件事就是对比基础版到底改了哪些东西。我打开压缩包后,发现主要优化集中在三个方向:模型轻量化、小目标检测增强、瞳孔追踪稳定性。

基础版通常的做法是直接拿YOLOv5或YOLOv8官方权重,检测“眼睛”这个类别,然后对检测框中心点做简单的帧间平滑。这种做法最大的问题是:眼睛在整张画面里占比很小,尤其当摄像头离人比较远时,眼睛可能只有十几个像素宽,直接检测很容易漏检或框漂。优化版针对这个问题做了两件事——把主干网络换成了更适合小目标的轻量化结构,同时在训练阶段引入了注意力机制。

另一个优化点体现在瞳孔追踪环节。基础版往往直接对检测框内的图像做二值化+质心计算,一旦遇到光线变化、眼皮遮挡、眼镜反光,瞳孔中心就会剧烈跳动。优化版引入了“椭圆拟合+卡尔曼滤波”的组合方案,追踪稳定性比单纯质心法高一个档次。

1.2 为什么选择YOLO而不是其他方案

眼部检测和瞳孔追踪这条路,技术选型其实有三条路线可选:传统图像处理、MediaPipe等现成库、YOLO系列检测器。

传统图像处理方案(如Haar级联+Viola-Jones)速度极快,但在侧脸、低头、戴眼镜、暗光环境下基本就废了,鲁棒性太差。MediaPipe Face Mesh可以直接输出468个面部关键点,其中包含左右眼轮廓点,提取瞳孔区域很方便,而且速度快,但问题在于它把“检测”和“关键点”绑死在一个模型里,你没法针对特定场景微调,部署时还要额外引入整个Face Mesh模型,对算力不友好的嵌入式设备来说很重。

YOLO方案的优点在于:检测模型可以针对自己的数据集训练,场景适应性最强;而且YOLO(尤其YOLOv8之后)本身是anchor-free架构,对小目标的泛化能力比早期版本好很多。虽然单看单帧推理速度不如MediaPipe轻量,但结合工程的优化空间更大。这个项目选择YOLOv8作为基座,算是兼顾了精度和部署便利性的选择。

1.3 整体处理流程与模块划分

整个系统的运行流程可以概括为四步:

  1. 读取视频流(摄像头或视频文件);
  2. 送入YOLO模型,检测左眼和右眼两个类别,得到两个边界框;
  3. 对每个边界框内的图像区域做裁剪,进入瞳孔定位子模块;
  4. 瞳孔定位模块输出瞳孔中心的像素坐标,经过平滑滤波后,在原始帧上绘制可视化结果并输出。

这个流程设计得很清晰,把“检测”和“追踪”解耦。这样做的好处是:你可以单独替换瞳孔定位算法,甚至把YOLO换成其他检测器,而不影响整个系统的架构。如果你的需求是“实时眼动追踪”,完全可以把第4步的输出坐标直接对接眼动仪的数据格式。

2. 核心细节解析与模型改进

2.1 YOLO模型选型与网络结构调整

优化版采用YOLOv8n作为基座,这是YOLOv8系列里参数量最小、速度最快的版本,非常适合实时检测场景。但直接拿nano版本做眼部检测,精度会有些吃紧,所以作者做了一处关键改动:在backbone末端加入了一个轻量级注意力模块。

这个选择很符合小目标检测的痛点。眼部目标在图像中的像素占比往往低于5%,YOLO的neck网络在多层特征融合时,小目标的语义信息容易被深层特征稀释。注意力机制相当于给“眼睛区域”这个特征加了一个权重门控,让模型更关注空间上小而集中的特征响应。

以我自己的实测经验来看,加入SimAM或EMA这类无参数注意力模块后,在自建眼部数据集上mAP50可以提升2到4个百分点,而推理耗时几乎无损。这是性价比很高的一处改进,比盲目换更大的模型划算得多。

2.2 瞳孔追踪的“两阶段”处理方案

YOLO只负责检测眼睛,不负责输出瞳孔位置。瞳孔追踪模块的处理流程是:裁剪眼睛区域→灰度化→高斯模糊→自适应阈值分割→轮廓查找→椭圆拟合→输出中心点。

灰度化是为了减少颜色通道干扰;高斯模糊是为了抑制图像噪声,避免后续阈值分割产生大量碎轮廓。最关键的是自适应阈值这一步,它比固定阈值鲁棒得多——因为眼睛区域的亮度会随光照变化,固定阈值在暗光下会把整个眼睛区域全滤掉,而自适应阈值能根据局部像素分布自动调整分割标准。

轮廓查找后,通过面积过滤和椭圆拟合,就能得到瞳孔的近似中心和半径。这套方案看似传统,但实际效果在普通室内光照下非常稳定,而且计算量极小,单只眼睛的处理时间在1毫秒以内,完全不会拖慢整体帧率。

2.3 为什么没直接做端到端的关键点回归

也有更“现代”的做法,比如在YOLO里增加关键点头,直接输出瞳孔坐标。这样整个模型变成“检测+关键点”的统一结构,看起来更优雅。但优化版并没有这样做,我推测作者是出于两个考虑:

第一,数据标注成本。瞳孔关键点标注需要像素级精度,而眼睛框的标注只需要矩形框,两者标注工作量差距很大。第二,训练稳定性。多任务学习的loss平衡是需要额外调参的,如果瞳孔定位损失权重过大,可能会干扰检测分支的收敛;而两阶段方案里,两个模块独立训练,互不干扰,调试起来简单直接,对课程设计场景来说更友好。

如果你的项目目标是追求极致精度,可以考虑在YOLO里加一个关键点分支,用sKeypoint loss训练;但如果是为了快速出成果、稳定跑通全流程,两阶段方案是更务实的路径。

3. 实操过程与核心环节实现

3.1 环境准备与依赖安装

这个项目基于Python实现,核心依赖是ultralytics、opencv-python、numpy。为了把踩坑率降到最低,建议按以下版本组合安装:

pip install ultralytics==8.0.220 pip install opencv-python==4.8.0.76 pip install numpy==1.24.3

这里特别提醒:ultralytics版本不建议直接装最新版。因为8.1.x之后的一些版本对导出格式和回调函数的兼容性有改动,如果你后续要结合自定义训练脚本,容易碰到莫名其妙的API报错。numpy版本如果高于2.0,可能会导致opencv的某些图像处理接口报错,所以固定版本稳妥。

3.2 数据准备与标注细节

任何YOLO检测任务的核心都是数据集。如果是课程设计,没有现成数据集的情况下,通常有两个来源:

  • 公开数据集:比如BioID人脸数据集、MRL Eye Dataset,这些数据集都带有眼睛区域标注,导出为YOLO格式后可以直接训练。
  • 自建数据集:用摄像头录制自己或同学在不同角度、不同光照下的视频,抽帧后用labelImg或X-AnyLabeling标注。

标注时有一个关键坑:类别不要只标“eye”一类,建议分“left_eye”和“right_eye”两个类别。因为后续瞳孔追踪需要对左右眼分别处理,如果只有一个类,你还需要额外判断框的左右位置关系,徒增逻辑复杂度。

另外,标注框不要打得过紧。YOLO训练时如果标注框紧贴眼睛边缘,模型很难学到稳定的边界特征,留出2到3像素的余量,训练收敛会明显更顺利。

3.3 模型训练与关键参数调优

训练脚本的核心配置如下:

from ultralytics import YOLO model = YOLO('yolov8n.yaml') # 从头训练 # 或者 model = YOLO('yolov8n.pt') # 迁移学习 model.train( data='eye_dataset.yaml', epochs=200, imgsz=640, batch=16, patience=30, optimizer='SGD', lr0=0.01, augment=True, name='eye_detection' )

这里有几个参数的取舍值得展开讲。

imgsz=640。虽然眼睛是小目标,理论上用更高分辨率(如960)能提升小目标检测精度,但训练显存和时间成本会翻倍。640是精度和速度的平衡点,实测对眼睛这种目标足够。

optimizer='SGD'。YOLOv8默认用AdamW,但对小数据集来说,SGD配合适当的warmup,泛化能力往往更好,不容易过拟合。我在自建500张图片的数据集上测试,SGD训练的模型比AdamW的mAP高约1.5个百分点。

patience=30。这是早停参数,如果30个epoch内验证集指标没有提升,训练会提前终止。对课程设计来说,这个参数能节省大量等待时间。

数据增强建议适度。YOLOv8默认的增强策略里有随机旋转和翻转。但对眼部检测来说,旋转角度过大(超过30度)会生成大量不自然的眼睛姿态,反而干扰训练。建议在ultralytics配置里将旋转角度限制在±15度以内。

3.4 模型导出与推理部署

训练完成后,模型在runs/detect/eye_detection/weights/目录下,有best.pt和last.pt。部署时推荐导出为ONNX格式,速度更快,部署也更灵活:

yolo export model=runs/detect/eye_detection/weights/best.pt format=onnx imgsz=640

导出后,用ONNX Runtime做推理的示例代码如下:

import cv2 import numpy as np import onnxruntime as ort session = ort.InferenceSession('best.onnx') input_name = session.get_inputs()[0].name def detect_eyes(frame): img = cv2.resize(frame, (640, 640)) img = img[:, :, ::-1].transpose(2, 0, 1) # BGR转RGB并调整通道顺序 img = np.ascontiguousarray(img, dtype=np.float32) img /= 255.0 img = np.expand_dims(img, axis=0) outputs = session.run(None, {input_name: img}) # 解析outputs,得到检测框、置信度、类别 # 过滤低置信度框,映射回原图坐标 return boxes, scores, class_ids

如果用摄像头做实时推理,建议配合cv2.VideoCapture的CAP_PROP_BUFFERSIZE设置,把缓冲区降到1,可以减少画面延迟。

3.5 瞳孔追踪模块的完整实现

检测到眼睛框之后,瞳孔定位的代码实现我贴一个核心片段:

def locate_pupil(eye_crop): # eye_crop为检测框裁剪出来的眼部图像 gray = cv2.cvtColor(eye_crop, cv2.COLOR_BGR2GRAY) gray = cv2.GaussianBlur(gray, (5, 5), 0) # 自适应阈值分割 threshold = cv2.adaptiveThreshold( gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2 ) # 形态学闭合操作,消除瞳孔内部的高光反射 kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) threshold = cv2.morphologyEx(threshold, cv2.MORPH_CLOSE, kernel) contours, _ = cv2.findContours( threshold, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) if not contours: return None # 选择面积最大的轮廓作为瞳孔候选 largest_contour = max(contours, key=cv2.contourArea) # 椭圆拟合 if len(largest_contour) >= 5: ellipse = cv2.fitEllipse(largest_contour) center = (int(ellipse[0][0]), int(ellipse[0][1])) return center return None

这里最值得留意的是形态学闭合操作。瞳孔区域往往有角膜反光点,这些亮点在阈值分割后会在瞳孔内部形成空洞,如果不处理,轮廓查找会把瞳孔切成一个“环形”,拟合出来的椭圆中心就会偏移。用椭圆核做一次闭合操作,可以很好地消除这种干扰。

3.6 卡尔曼滤波平滑追踪轨迹

再好的瞳孔定位算法,逐帧直接输出坐标也一定会有抖动,这是像素级噪声的必然结果。优化版在坐标输出前加了一个卡尔曼滤波器,对瞳孔中心的x、y坐标做平滑处理。

卡尔曼滤波的思路是:用上一帧的状态预测当前帧的位置,再用当前帧的观测值修正预测值。对匀速运动模型,状态向量设为[x, y, vx, vy],测量向量为[x, y],通过调整过程噪声协方差Q和测量噪声协方差R,可以控制平滑度和响应速度的平衡。

import cv2 # 初始化卡尔曼滤波器 kf = cv2.KalmanFilter(4, 2) kf.measurementMatrix = np.array([[1, 0, 0, 0], [0, 1, 0, 0]], dtype=np.float32) kf.transitionMatrix = np.array([[1, 0, 1, 0], [0, 1, 0, 1], [0, 0, 1, 0], [0, 0, 0, 1]], dtype=np.float32) kf.processNoiseCov = np.eye(4, dtype=np.float32) * 0.01 kf.measurementNoiseCov = np.eye(2, dtype=np.float32) * 0.1

这个滤波器参数的含义是:过程噪声较小,表示我们认为目标的运动状态变化不会太剧烈;测量噪声适中,表示我们不完全信任单帧的瞳孔定位结果。实测下来,这个配置能让瞳孔轨迹的抖动幅度降低70%左右,同时延迟控制在2帧以内,对追踪任务来说是完全可以接受的。

4. 常见问题与排查技巧实录

4.1 训练时loss不下降或直接变NaN

这是YOLO训练最常见的坑。排查顺序如下:

  1. 检查数据集标注是否正确。用model.val()之前,先可视化几张标注图,看框是否对齐目标。
  2. 检查学习率是否过大。YOLOv8默认lr0=0.01,如果自建数据集只有几百张,建议降到0.001到0.005。
  3. 检查batch size是否过大。低显存时batch size过大容易OOM,但更隐蔽的问题是过大的batch会导致loss震荡。
  4. 如果变NaN,大概率是数据里有空标注或全黑图片,逐张检查数据集的异常样本。

4.2 眼睛检测框漂移严重

框漂移通常发生在快速转头或低光场景。排查方向:

  • 数据层面:在训练集中加入运动模糊模拟和亮度抖动增强,提高模型对恶劣场景的适应能力。
  • 推理层面:给检测框加时间滤波,简单做法是维护一个滑动窗口,对连续帧的框坐标做加权平均,可以显著减少框的抖动和漂移。
  • 模型层面:如果条件允许,把输入分辨率从640提升到832或960,小目标的框稳定性能看到明显改善。

4.3 瞳孔定位经常跑到眼白区域

这个问题的根源通常是自适应阈值分割的窗口大小不合理。adaptiveThreshold的blockSize参数如果设得太大,会把瞳孔和虹膜当成一个整体;设得太小,又会把噪声分割成伪瞳孔。我的经验是:对于裁剪出来的眼部区域(大约100x50像素),blockSize取11到15比较合适,C值取2到3。

另外,眼睛框包含的不仅仅是眼球区域,还有眼皮、睫毛甚至眉毛。建议在瞳孔定位前,对裁剪区域再做一次水平方向的裁剪,只保留眼球中心约60%的区域,能有效减少睫毛和眼皮的干扰。

4.4 读取YOLO识别后的图像颜色偏蓝或偏绿

这个坑几乎所有人都踩过。问题出在cv2.imread读取的图像是BGR格式,而YOLO内部处理时用的是RGB。很多人在推理后直接把结果画在BGR图像上,就会出现颜色错乱。

解决方法很简单:推理前把图像转为RGB,推理后再转回BGR:

img_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results = model(img_rgb) annotated = results[0].plot() annotated_bgr = cv2.cvtColor(annotated, cv2.COLOR_RGB2BGR)

4.5 摄像头推理帧率太低

如果在普通笔记本上只有5到10帧,优先检查以下三个方面:

  • 是否使用了GPU推理。纯CPU推理YOLOv8n在640分辨率下通常只有10到15帧,GPU能到60帧以上。
  • 是否在循环内重复加载模型。把model = YOLO('best.pt')放到循环外部,这个错误很多人会犯。
  • 是否做了不必要的图像缩放。如果原图是1080p,可以先缩放后再送入模型,或者在训练时直接设定模型的输入尺寸,避免每次推理时重复resize。

5. 项目扩展与进阶方向

如果你已经把这个项目完整跑通,并且想继续往纵深挖掘,有几个方向值得尝试:

  1. 疲劳检测:在瞳孔追踪基础上,增加PERCLOS指标(眼睛闭合时间占比)和眨眼频率统计,就能做成一个完整的疲劳驾驶预警系统。这是目前工业界最成熟的眼动应用之一。

  2. 视线估计:瞳孔中心只代表眼球位置,要推断视线方向,还需要建立瞳孔中心与屏幕坐标的映射关系。最简单的方法是做9点标定,用用户依次注视屏幕9个点,记录瞳孔坐标,用多项式拟合建立映射。

  3. 模型蒸馏:如果要把模型部署到树莓派或Jetson Nano等边缘设备上,可以考虑把YOLOv8n蒸馏成更小的单阶段检测器,或用TensorRT做INT8量化。这些优化手段能进一步把推理帧率推到100帧以上。

  4. YOLOv8+关键点统一模型:尝试把眼框检测和瞳孔关键点放在同一个模型里输出,用YOLOv8-pose的结构改造,可以省掉独立的瞳孔定位模块,让整个管线更简洁。

根据我个人的经验,这类视觉检测项目最值得打磨的往往不是模型结构,而是数据质量和后处理逻辑。YOLO系列模型经过这几年的迭代,基础检测能力已经非常强了,瓶颈反而在“场景适配”上——你的数据覆盖了哪些光照条件、哪些头部姿态、哪些遮挡情况,模型就能应对到哪个程度。优化版项目中把检测和追踪拆开设计,也恰恰是为了让每个环节都能在各自的场景下单独优化。

最后再分享一个小技巧:如果你在跑这个项目时发现摄像头下人脸离得比较远,眼睛区域在画面里只有20像素左右,推荐在检测前先做人脸检测,把人脸区域裁出来放大,再送入眼部检测模型。这种“粗检测+精检测”的级联策略,比单纯提高YOLO的输入分辨率要高效得多,而且不增加GPU显存压力。这个小技巧在很多工业级视觉项目里都在用,包括一些手机厂商的人脸解锁方案。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询