【机器学习】基于 dlib 面部关键点的多表情分类
2026/7/22 21:29:28 网站建设 项目流程

文章目录

  • 完整代码一览
  • 一、环境准备与模型文件
  • 二、核心原理:三个关键指标
    • 1. EAR(眼睛纵横比)—— 判断睁眼/闭眼
    • 2. MAR(嘴巴纵横比)—— 判断嘴巴张开程度
    • 3. MJR(嘴宽脸宽比)—— 判断嘴巴拉宽程度
  • 三、逐行代码详解
    • 1. 导入库与定义指标函数
    • MAR 函数
    • MJR 函数
  • 辅助函数
    • 中文绘制 cv2AddChineseText
    • 绘制眼睛轮廓 drawEye
    • 3. 主程序结构
    • 4. 主循环
      • 提取关键点并计算指标
      • 表情判定逻辑
    • 绘制与显示

完整代码一览

import numpy as np import dlib import cv2 from sklearn.metrics.pairwise import euclidean_distances from PIL import Image,ImageDraw,ImageFont#MAR:嘴巴纵横比defMAR(shape):A=euclidean_distances(shape[50].reshape(1,2),shape[58].reshape(1,2))B=euclidean_distances(shape[51].reshape(1,2),shape[57].reshape(1,2))C=euclidean_distances(shape[52].reshape(1,2),shape[56].reshape(1,2))D=euclidean_distances(shape[48].reshape(1,2),shape[54].reshape(1,2))return((A+B+C)/3)/D#EAR:眼睛纵横比defeye_aspect_ratio(eye):A=euclidean_distances(eye[1].reshape(1,2),eye[5].reshape(1,2))B=euclidean_distances(eye[2].reshape(1,2),eye[4].reshape(1,2))C=euclidean_distances(eye[0].reshape(1,2),eye[3].reshape(1,2))ear=((A+B)/2.0)/Creturnear#MJR:嘴宽/脸宽比值defMJR(shape):M=euclidean_distances(shape[48].reshape(1,2),shape[54].reshape(1,2))J=euclidean_distances(shape[3].reshape(1,2),shape[13].reshape(1,2))returnM/J#OpenCV 中文绘制defcv2AddChineseText(img,text,position,textColor=(255,0,0),textSize=30):ifisinstance(img,np.ndarray):img=Image.fromarray(cv2.cvtColor(img,cv2.COLOR_BGR2RGB))draw=ImageDraw.Draw(img)font=ImageFont.truetype("simhei.ttf",textSize,encoding="utf-8")draw.text(position,text,textColor,font=font)returncv2.cvtColor(np.array(img),cv2.COLOR_RGB2BGR)defdrawEye(eye):eyeHull=cv2.convexHull(eye)cv2.drawContours(frame,[eyeHull],-1,(0,255,0),2)detector=dlib.get_frontal_face_detector()predictor=dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")cap=cv2.VideoCapture(0)COUNTER=0whileTrue:ret,frame=cap.read()ifnot ret:breakfaces=detector(frame,0)forface in faces:shape=predictor(frame,face)shape=np.array([[p.x,p.y]forp in shape.parts()])rightEye=shape[36:42]leftEye=shape[42:48]rightEAR=eye_aspect_ratio(rightEye)leftEAR=eye_aspect_ratio(leftEye)ear=(leftEAR+rightEAR)/2.0mar=MAR(shape)mjr=MJR(shape)print('mar',mar,'\tmjr',mjr)result="正常"ifmar>0.5:result="大笑"elif mjr>0.45:result="微笑"elif mar>0.45and ear<0.3:COUNTER+=1ifCOUNTER>=30:result='哭'elif ear>0.35:result='愤怒'else:COUNTER=0drawEye(leftEye)drawEye(rightEye)info="EAR: {:.2f}".format(ear[0][0])frame=cv2AddChineseText(frame,info,(0,30))frame=cv2AddChineseText(frame,result,(50,100))mouthHull=cv2.convexHull(shape[48:61])cv2.drawContours(frame,[mouthHull],-1,(0,255,0),1)cv2.imshow("Frame",frame)ifcv2.waitKey(1)==27:breakcap.release()cv2.destroyAllWindows()

一、环境准备与模型文件

在运行代码之前,请确保已安装以下库:

pip install dlib opencv-python scikit-learn Pillow

并下载 dlib 的 68 点关键点模型文件:

下载地址
解压后得到 shape_predictor_68_face_landmarks.dat,放在脚本同一目录。

中文绘制需要字体文件,本代码使用 simhei.ttf(黑体),你可以从 Windows 系统字体文件夹(C:\Windows\Fonts)复制,或使用其他中文字体(如 msyh.ttc),并修改代码中的字体名称。

二、核心原理:三个关键指标

1. EAR(眼睛纵横比)—— 判断睁眼/闭眼

我们在上一篇文章中已经详细介绍过,它基于眼睛 6 个点:

睁眼时 EAR ≈ 0.25~0.3,闭眼时接近 0。

用于区分“愤怒”(眼睛睁圆,EAR 较大)和“哭”(眼睛闭合,EAR 很小)。

2. MAR(嘴巴纵横比)—— 判断嘴巴张开程度

MAR 是 Mouth Aspect Ratio 的缩写,基于嘴巴 6 个点(内唇上下)和宽度:

嘴巴张开(大笑、惊讶)时,MAR 明显增大(> 0.5)。

嘴巴闭合时,MAR 较小(< 0.4)。

3. MJR(嘴宽脸宽比)—— 判断嘴巴拉宽程度

MJR 是 Mouth-to-Jaw Ratio,即嘴巴宽度与下颌宽度(脸部宽度)的比值:

微笑时,嘴巴会向两侧拉宽,MJR 增大(> 0.45)。

正常表情时,MJR 较小。

通过这三个指标的组合,我们可以区分多种表情。

三、逐行代码详解

1. 导入库与定义指标函数

import numpy as np import dlib import cv2 from sklearn.metrics.pairwise import euclidean_distances #提供欧氏距离计算 from PIL import Image,ImageDraw,ImageFont #中文绘制

MAR 函数

defMAR(shape):A=euclidean_distances(shape[50].reshape(1,2),shape[58].reshape(1,2))B=euclidean_distances(shape[51].reshape(1,2),shape[57].reshape(1,2))C=euclidean_distances(shape[52].reshape(1,2),shape[56].reshape(1,2))D=euclidean_distances(shape[48].reshape(1,2),shape[54].reshape(1,2))return((A+B+C)/3)/D

shape 是 68 个点的坐标数组,索引 48~60 是嘴巴区域。

50/58、51/57、52/56 是三对垂直方向上的内唇点,取平均得到嘴巴高度;48 和 54 是嘴角两端,代表嘴巴宽度。

MAR = 平均高度 / 宽度,张嘴时增大。

MJR 函数

defMJR(shape):M=euclidean_distances(shape[48].reshape(1,2),shape[54].reshape(1,2))# 嘴宽 J=euclidean_distances(shape[3].reshape(1,2),shape[13].reshape(1,2))# 下颌宽度returnM/J

shape[48] 和 shape[54] 是嘴角,其距离即嘴宽。

shape[3] 和 shape[13] 是下巴轮廓两端的点(相当于脸部宽度)。

微笑时嘴巴变宽,比值增大。

辅助函数

中文绘制 cv2AddChineseText

defcv2AddChineseText(img,text,position,textColor=(255,0,0),textSize=30):ifisinstance(img,np.ndarray):img=Image.fromarray(cv2.cvtColor(img,cv2.COLOR_BGR2RGB))draw=ImageDraw.Draw(img)font=ImageFont.truetype("simhei.ttf",textSize,encoding="utf-8")draw.text(position,text,textColor,font=font)returncv2.cvtColor(np.array(img),cv2.COLOR_RGB2BGR)

将 OpenCV 图像转为 PIL 图像,绘制文字后再转回 OpenCV BGR 格式。

绘制眼睛轮廓 drawEye

defdrawEye(eye):eyeHull=cv2.convexHull(eye)cv2.drawContours(frame,[eyeHull],-1,(0,255,0),2)

3. 主程序结构

detector=dlib.get_frontal_face_detector()predictor=dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")cap=cv2.VideoCapture(0)COUNTER=0

初始化检测器、预测器、摄像头,COUNTER 用于连续闭眼帧计数。

4. 主循环

whileTrue:ret,frame=cap.read()ifnot ret:breakfaces=detector(frame,0)

逐帧读取,检测人脸。

提取关键点并计算指标

forface in faces:shape=predictor(frame,face)shape=np.array([[p.x,p.y]forp in shape.parts()])rightEye=shape[36:42]leftEye=shape[42:48]rightEAR=eye_aspect_ratio(rightEye)leftEAR=eye_aspect_ratio(leftEye)ear=(leftEAR+rightEAR)/2.0mar=MAR(shape)mjr=MJR(shape)print('mar',mar,'\tmjr',mjr)

获取 68 点,截取左右眼和嘴巴,计算三个指标。print 输出到控制台便于调试。

表情判定逻辑

result="正常"ifmar>0.5:result="大笑"elif mjr>0.45:result="微笑"elif mar>0.45and ear<0.3:COUNTER+=1ifCOUNTER>=30:result='哭'elif ear>0.35:result='愤怒'else:COUNTER=0

采用级联判断:

如果 MAR > 0.5,判定为“大笑”(嘴巴张得很大)。

否则如果 MJR > 0.45,判定为“微笑”(嘴巴拉宽但未张很大)。

否则如果 MAR > 0.45 且 EAR < 0.3(嘴巴张开且眼睛闭合),可能是“哭”,需要连续 30 帧确认(与疲劳检测类似)。

否则如果 EAR > 0.35(眼睛睁得很圆),判定为“愤怒”。

否则为“正常”。

绘制与显示

drawEye(leftEye)drawEye(rightEye)info="EAR: {:.2f}".format(ear[0][0])frame=cv2AddChineseText(frame,info,(0,30))frame=cv2AddChineseText(frame,result,(50,100))mouthHull=cv2.convexHull(shape[48:61])cv2.drawContours(frame,[mouthHull],-1,(0,255,0),1)

绘制眼睛和嘴巴的凸包轮廓(绿色),显示 EAR 值和表情结果。

最后显示画面,按 ESC 退出。

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

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

立即咨询