文章目录
- 完整代码一览
- 一、环境准备与模型文件
- 二、核心原理:三个关键指标
- 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)/Dshape 是 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/Jshape[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 退出。