🔬 视觉疲劳与姿势智能监测

等待摄像头
📷

点击"开始监测"启动摄像头

FPS: --
⚠ 疲劳预警:请立即休息!
⚠ 安全预警:头部过于靠近!
✓ 状态正常,继续实验

👁 眼部疲劳指标

眼睛纵横比 (EAR) --
瞳孔闭合比 (PERCLOS) --%
正常 <15%轻度 15-30%重度 ≥30%

🧭 头部姿态

抬头角
--°
偏头角
--°
歪头角
--°

📏 面部距离

距摄像头 -- cm
安全 >20cm危险 <20cm

技术原理与算法设计

系统流程图

USB 摄像头采集 (720p@30fps)
CLAHE 抗反光预处理
LAB色彩空间 · L通道均衡化 · clipLimit=2.0
MediaPipe Face Mesh
468个面部特征点 · CPU实时推理
是否检测到人脸?
跳过此帧,继续采集
提取12个眼部关键点 + 6个面部基准点
EAR 眼睛纵横比计算
左右眼独立计算 → 取均值
PERCLOS 滑动窗口统计
60秒窗口 · 每秒刷新 · deque O(1)
solvePnP 头部姿态解算
6点PnP迭代求解 → 欧拉角(Pitch/Yaw/Roll) + 距离估算
EAR < 0.25
持续 ≥ 20帧?
距离 < 20cm
且 |Pitch| > 25°
持续 ≥ 90帧?
触发⚠ 疲劳预警
触发⚠ 安全预警
Pillow 中文UI叠加 → 浏览器实时显示

系统从普通USB摄像头获取720p@30fps视频流,每帧首先经过CLAHE抗反光预处理以穿透化学实验室护目镜的镜面反射。 预处理后的图像送入MediaPipe Face Mesh,提取468个面部特征点。从中选取12个眼部关键点计算眼睛纵横比(EAR), 同时选取6个面部基准点通过solvePnP解算三维头部姿态。EAR值被送入60秒滑动窗口统计PERCLOS(瞳孔闭合时间比例)。 系统设有两个独立并行的预警通道——疲劳预警(EAR<0.25连续20帧)和安全预警(面部距离<20cm且前倾角>25°连续90帧)。 所有指标通过Pillow渲染中文文字后叠加到画面,最终返回浏览器端实时显示。

软件基础设置信息

项目名称基于RGB视觉特征的化学实验过程用眼疲劳与姿势智能监测系统(RFPM)
英文名称RGB-based Fatigue & Posture Monitoring System for Chemistry Labs
适用场景化学实验室、自习室、计算机房等需要长时间近距离用眼的场景,尤其适合佩戴护目镜的实验环境
开发语言Python 3.8+
核心依赖OpenCV (cv2)、MediaPipe Face Mesh、NumPy、Pillow (PIL)
AI 模型MediaPipe Face Mesh — Google 468点面部特征定位模型,CPU实时推理,无需GPU
算法核心EAR(眼睛纵横比)+ PERCLOS(瞳孔闭合时间比例)+ solvePnP(头部姿态解算)
硬件要求普通USB摄像头(720p/30fps),树莓派5或同等性能PC,全流程延迟 < 50ms

软件系统设计

  1. CLAHE 抗反光预处理("护目镜穿透"技术):化学实验室护目镜(PC镜片)在日光灯照射下产生高强度镜面反射(像素值≥240),直接覆盖眼部特征区域,导致MediaPipe检测失败率高达约42%。本系统采用LAB色彩空间的CLAHE(限制对比度自适应直方图均衡化),将BGR帧转换至LAB空间后,仅对L(亮度)通道应用CLAHE(clipLimit=2.0, tileGridSize=8×8),再还原为BGR送入MediaPipe。实测将反光场景检测成功率从约58%提升至约93%,额外延迟仅约4ms。
  2. EAR 眼睛纵横比计算:从MediaPipe 468个面部特征点中提取左右眼各6个关键标志点(左眼索引[362,385,387,263,373,380];右眼索引[33,160,158,133,153,144]),通过欧氏距离比值公式 EAR=(‖p2-p6‖+‖p3-p5‖)/(2×‖p1-p4‖) 计算眼睛开合程度。左右眼独立计算后取均值,消除单侧遮挡或检测误差的影响。EAR为比值量纲,对头部轻微晃动(±15°)具有天然鲁棒性。
  3. PERCLOS 瞳孔闭合时间比例统计:采用60秒滑动窗口(容量1800帧@30fps),基于Python collections.deque实现O(1)入队/出队。每30帧(1秒)刷新一次PERCLOS值,公式为 PERCLOS=(EAR低于0.25的帧数/窗口总帧数)×100%。分级标准:<15%正常、15%-30%轻度疲劳、≥30%重度疲劳。
  4. solvePnP 头部姿态解算与距离估算:从468个面部特征点中选取6个最稳定的标志点(鼻尖#1、下颌#152、左眼角#33、右眼角#263、左嘴角#61、右嘴角#291),通过OpenCV SOLVEPNP_ITERATIVE算法(基于Levenberg-Marquardt优化)解算三维头部姿态。输出三个欧拉角:Pitch(抬头角)、Yaw(偏头角)、Roll(歪头角)。同时基于针孔相机模型估算面部到摄像头的物理距离:Distance=(焦距×人脸实际高度200mm)/像素人脸高度。
  5. 双通道联合预警机制:系统首创性地将眼疲劳监测与实验室安全预警融合为两个独立并行通道。通道一(疲劳预警):当双眼EAR均值<0.25连续≥20帧(约667ms,超过正常眨眼150-400ms),触发"⚠ 疲劳预警:请立即休息!"。通道二(安全预警):当面部距离<200mm且|Pitch|>25°连续≥90帧(3秒防抖缓冲),触发"⚠ 安全预警:头部过于靠近!"。两个通道互不依赖,可同时触发。
  6. 中文UI叠加渲染:基于Pillow(PIL)实现FreeType中文字体渲染引擎,自动检测系统字体(优先微软雅黑/黑体/WQY微米黑)。通过ImageDraw在BGR帧上叠加:EAR实时值、PERCLOS百分比、三维头部姿态角、面部距离(厘米),以及疲劳/安全预警信息(红/橙色高亮背景)。文字带半透明黑色背景框,确保在任意画面亮度下可读。

参数配置

参数默认值说明
EAR_THRESH0.25EAR疲劳判定阈值,低于此值认定为闭眼。基于人脸解剖学比例设定
CLOSED_EYES_FRAME20 帧触发疲劳预警的连续闭眼帧数(约667ms@30fps),区分正常眨眼(150-400ms)
clahe_enabledTrueCLAHE预处理开关,关闭可提速约4ms/帧,适用于无反光环境
WINDOW_DURATION60 秒PERCLOS统计滑动窗口时长,60秒为疲劳检测领域的标准窗口
UPDATE_INTERVAL30 帧PERCLOS值刷新频率(每秒1次),避免逐帧刷新导致数值抖动
DANGER_DISTANCE_MM200 mm安全预警距离阈值(20cm),基于化学实验室安全规范设定
DANGER_PITCH_DEG25°安全预警前倾角阈值,超过此角度且距离过近时触发安全预警
DANGER_DURATION_FRAMES90 帧安全预警防抖缓冲帧数(3秒@30fps),排除短暂姿势调整的误触发
REAL_FACE_HEIGHT_MM200 mm东亚成年人人脸平均高度,用于针孔相机模型距离估算
CLAHE clipLimit2.0对比度限制阈值,控制局部均衡化强度
CLAHE tileGridSize(8, 8)CLAHE网格尺寸,8×8在效果与性能间平衡最优

关键算法实现

以下代码摘自原始桌面版 somnolence_detection.py(~260行单文件实现),展示了四个核心算法模块。

EAR 眼睛纵横比计算

def calculate_EAR(self, eye_points):
    """眼睛纵横比 (Eye Aspect Ratio)

    基于眼睛6个关键点的距离比值,判断眼睛开合程度。

            ● p1
       p2 ●       ● p6
       p3 ●       ● p5
            ● p4

       EAR = (||p2-p6|| + ||p3-p5||) / (2 × ||p1-p4||)

    技术要点:
    - EAR为比值量纲,对头部轻微晃动(±15°)具有天然鲁棒性
    - 左右眼独立计算后取均值,消除单侧遮挡或检测误差
    - EAR < 0.25 认定为闭眼状态(基于人脸解剖学比例)
    """
    A = np.linalg.norm(eye_points[1] - eye_points[5])
    B = np.linalg.norm(eye_points[2] - eye_points[4])
    C = np.linalg.norm(eye_points[0] - eye_points[3])
    ear = (A + B) / (2.0 * C)
    return ear

CLAHE 抗反光预处理

def apply_clahe(self, bgr_frame):
    """CLAHE 限制对比度自适应直方图均衡化

    化学实验室护目镜(PC镜片)产生高强度镜面反射(像素值≥240),
    直接覆盖眼部特征区域,导致MediaPipe检测失败率高达约42%。

    处理流程:
      BGR帧 → LAB色彩空间分离 → L通道CLAHE → 合并LAB → 还原BGR

    效果对比(实测数据):
      无预处理(原始RGB):  检测成功率约58%  |  额外延迟 0ms
      全局直方图均衡化:      检测成功率约71%  |  额外延迟约2ms
      LAB-CLAHE(本方案):   检测成功率约93%  |  额外延迟约4ms
    """
    lab = cv2.cvtColor(bgr_frame, cv2.COLOR_BGR2LAB)
    l, a, b = cv2.split(lab)
    l_eq = self.clahe.apply(l)
    lab_eq = cv2.merge([l_eq, a, b])
    return cv2.cvtColor(lab_eq, cv2.COLOR_LAB2BGR)

solvePnP 头部姿态解算与距离估算

def estimate_head_pose(self, mesh_points, frame_shape):
    """基于6个面部基准点的3D头部姿态解算

    选取MediaPipe 468点中最稳定的6个标志点:
      #1(鼻尖) #152(下颌) #33(左眼角) #263(右眼角) #61(左嘴角) #291(右嘴角)

    3D面部模型坐标(mm级,基于东亚成年人平均面部几何):
      鼻尖(0,0,0)  下颌(0,-330,-65)
      左眼角(-225,170,-135)  右眼角(225,170,-135)
      左嘴角(-150,-150,-125)  右嘴角(150,-150,-125)

    相机内参矩阵 K_int = [[w,0,w/2],[0,w,h/2],[0,0,1]]
    焦距≈图像宽度(Webcam标准针孔模型近似)

    输出三个欧拉角 + 面部距离估算:
      Pitch(抬头角) Yaw(偏头角) Roll(歪头角)
      Distance = (焦距 × 人脸实际高度200mm) / 像素人脸高度
    """
    h, w = frame_shape[:2]
    image_points = mesh_points[self.FACE_LANDMARKS_2D].astype(np.float64)
    camera_matrix = np.array(
        [[w, 0, w / 2], [0, w, h / 2], [0, 0, 1]], dtype=np.float64
    )
    try:
        success, rvec, _ = cv2.solvePnP(
            self.FACE_MODEL_3D, image_points,
            camera_matrix, self.dist_coeffs,
            flags=cv2.SOLVEPNP_ITERATIVE
        )
        if success:
            self.pitch, self.yaw, self.roll = self._rvec_to_euler(rvec)
    except cv2.error:
        pass
    # 针孔相机模型距离估算
    y_coords = mesh_points[:, 1]
    h_pixel = y_coords.max() - y_coords.min()
    if h_pixel > 1:
        self.distance_mm = (w * self.REAL_FACE_HEIGHT_MM) / h_pixel

@staticmethod
def _rvec_to_euler(rvec):
    """Rodrigues旋转向量 → 欧拉角(Pitch/Yaw/Roll)"""
    R, _ = cv2.Rodrigues(rvec)
    pitch = np.degrees(np.arctan2(R[2, 1], R[2, 2]))
    yaw   = np.degrees(np.arctan2(R[2, 0],
                     np.sqrt(R[0, 0]**2 + R[1, 0]**2)))
    roll  = np.degrees(np.arctan2(R[1, 0], R[0, 0]))
    return pitch, yaw, roll

PERCLOS 滑动窗口统计

def update_perclos(self, ear_value):
    """瞳孔闭合时间比例 (PERCLOS) 滑动窗口更新

    在60秒滑动窗口内统计EAR低于阈值的帧数占比:
      PERCLOS = (EAR < 0.25的帧数 / 窗口总帧数) × 100%

    技术要点:
    - 窗口容量: 1800帧 (60s × 30fps)
    - 刷新频率: 每秒1次 (每30帧),避免逐帧抖动
    - 数据结构: collections.deque, O(1)入队/出队
    - 分级标准: <15%正常 | 15-30%轻度 | ≥30%重度
    """
    self.ear_buffer.append(ear_value)
    self.perclos_frame_counter += 1
    if self.perclos_frame_counter % self.UPDATE_INTERVAL == 0:
        below = sum(1 for e in self.ear_buffer if e < self.EAR_THRESH)
        total = len(self.ear_buffer)
        self.perclos = (below / total * 100.0) if total > 0 else 0.0

双通道联合预警逻辑

# ===== 通道一:疲劳预警 =====
# 条件:双眼EAR均值 < 0.25,连续 ≥ 20帧(约667ms)
if avg_ear < self.EAR_THRESH:
    self.counter += 1
else:
    self.counter = 0   # EAR恢复正常时立即归零,避免累积误报

if self.counter >= self.CLOSED_EYES_FRAME:
    # 屏幕叠加红色背景:⚠ 疲劳预警:请立即休息!
    # 20帧阈值 ≈ 667ms,可区分正常眨眼(150-400ms)与疲劳微睡眠

# ===== 通道二:安全预警 =====
# 条件:面部距离 < 200mm(20cm)AND |Pitch| > 25° AND 持续 ≥ 90帧(3秒)
if (self.distance_mm < self.DANGER_DISTANCE_MM
    and abs(self.pitch) > self.DANGER_PITCH_DEG):
    self.danger_counter += 1
else:
    self.danger_counter = 0

if self.danger_counter >= self.DANGER_DURATION_FRAMES:
    # 屏幕叠加橙色背景:⚠ 安全预警:头部过于靠近!
    # 双条件AND逻辑防止单纯凑近或单纯前倾的误报
    # 90帧防抖缓冲(3秒)避免短暂姿势调整触发误报

技术指标汇总

指标项数值备注
EAR判定阈值0.25低于此值判定为闭眼,基于人脸解剖学比例
正常眨眼区分能力150-400ms疲劳预警20帧阈值(667ms)可有效区分
疲劳预警触发延迟约667ms(20帧)超过正常眨眼时长,避免误报
PERCLOS统计窗口60 秒滑动窗口,每秒刷新,deque实现O(1)
PERCLOS正常范围< 15%用眼状态良好;15-30%轻度疲劳;≥30%重度
3D姿态解算方法solvePnP + Rodrigues6点PnP迭代求解(SOLVEPNP_ITERATIVE)
可输出欧拉角3(Pitch/Yaw/Roll)全自由度头部姿态监测
面部距离估算误差约10-15%基于面部高度近似模型(针孔相机原理)
安全距离阈值200 mm(20 cm)基于化学实验室安全规范
安全预警防抖时长90帧(3秒)排除短暂姿势调整的误触发
CLAHE反光检测提升约58% → 约93%LAB空间L通道CLAHE预处理
全流程处理延迟< 50ms满足30fps实时性要求
摄像头要求720p / 30fps普通USB摄像头即可,无需红外/深度传感器
面部特征点数量468 点MediaPipe Face Mesh CPU实时推理
代码行数约260行单文件实现,无外部配置文件