当前位置: 首页 > news >正文

OpenCV 实战:基于人脸关键点的微笑与大笑识别(附完整代码)

从零开始理解嘴部纵横比(MAR)和嘴下颌宽比(MJR),轻松实现表情识别

一、前言

作为 OpenCV 的初学者,你是否想过用摄像头判断一个人是在微笑还是在大笑?本文带你用dlib获取人脸 68 个关键点,然后通过简单的几何比例计算,实现表情识别。最终效果如下:

二、实现原理

2.1 人脸关键点检测

我们使用 dlib 提供的68 点面部关键点模型,它能标出眉毛、眼睛、鼻子、嘴巴和下颌轮廓的位置。

其中嘴巴周围的关键点编号如下:

  • 48 ~ 60:外嘴唇轮廓

  • 61 ~ 67:内嘴唇轮廓

2.2 嘴部开合程度 —— MAR

MAR(Mouth Aspect Ratio,嘴部纵横比)用来量化嘴巴张开的程度。

公式:

MAR=(A+B+C)/3DMAR=D(A+B+C)/3​

  • A:上嘴唇中点(50)与下嘴唇中点(58)的距离

  • B:上嘴唇左侧内沿(51)与下嘴唇左侧内沿(57)的距离

  • C:上嘴唇右侧内沿(52)与下嘴唇右侧内沿(56)的距离

  • D:左右嘴角(48 和 54)的水平距离

当嘴巴张大时,MAR 会明显增大。我们设置阈值0.5,超过即为“大笑”。

2.3 微笑程度 —— MJR

MJR(Mouth-to-Jaw Ratio,嘴宽与下颌宽之比)用于判断微笑。

公式:

MJR=MJMJR=JM​

  • M:左右嘴角(48 和 54)的距离

  • J:两侧下颌角(3 和 13)的距离

微笑时嘴角向两侧拉伸,M 增大,而 J 基本不变,因此 MJR 变大。阈值0.45以上且未达到大笑标准时,判定为“微笑”。

2.4 判断逻辑

if mar > 0.5: result = "大笑" elif mjr > 0.45: result = "微笑" else: result = "正常"

三、完整代码及逐行注释

import numpy as np import dlib import cv2 from sklearn.metrics.pairwise import euclidean_distances from PIL import Image, ImageDraw, ImageFont def MAR(shape): """计算嘴部纵横比""" A = euclidean_distances(shape[50].reshape(1,2), shape[58].reshape(1,2))[0][0] B = euclidean_distances(shape[51].reshape(1,2), shape[57].reshape(1,2))[0][0] C = euclidean_distances(shape[52].reshape(1,2), shape[56].reshape(1,2))[0][0] D = euclidean_distances(shape[48].reshape(1,2), shape[54].reshape(1,2))[0][0] return ((A+B+C)/3) / D def MJR(shape): """计算嘴宽与下颌宽之比""" M = euclidean_distances(shape[48].reshape(1,2), shape[54].reshape(1,2))[0][0] J = euclidean_distances(shape[3].reshape(1,2), shape[13].reshape(1,2))[0][0] return M / J def cv2AddChineseText(img, text, position, textColor=(0,255,0), textSize=30): """在 OpenCV 图片上绘制中文(因为 cv2.putText 不支持中文)""" img_pil = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) draw = ImageDraw.Draw(img_pil) font = ImageFont.truetype("simsun.ttc", textSize, encoding="utf-8") draw.text(position, text, textColor, font=font) return cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR) # ---------- 初始化 ---------- detector = dlib.get_frontal_face_detector() predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat") # ---------- 读取图片 ---------- img_path = "test.jpg" # 改成你的图片路径 frame = cv2.imread(img_path) if frame is None: print("图片读取失败,请检查路径") exit() # ---------- 人脸检测与关键点提取 ---------- faces = detector(frame, 0) for face in faces: shape = predictor(frame, face) shape = np.array([[p.x, p.y] for p in shape.parts()]) mar = MAR(shape) mjr = MJR(shape) # 表情分类 result = "正常" if mar > 0.5: result = "大笑" elif mjr > 0.45: result = "微笑" # 绘制嘴部轮廓和文字 mouthHull = cv2.convexHull(shape[48:61]) text_pos = (int(mouthHull[0][0][0]), int(mouthHull[0][0][1] - 20)) frame = cv2AddChineseText(frame, result, text_pos, textSize=30) cv2.drawContours(frame, [mouthHull], -1, (0,255,0), 1) # ---------- 显示结果 ---------- cv2.imshow("result", frame) cv2.waitKey(0) cv2.destroyAllWindows()

注意:需要提前下载shape_predictor_68_face_landmarks.dat文件,并安装依赖:

pip install opencv-python dlib scikit-learn pillow numpy

四、知识点扩展

4.1 欧氏距离的多种写法

代码中的euclidean_distances(a,b)[0][0]其实是计算两点之间的直线距离。对于初学者,更推荐使用 NumPy 自带的写法:

# 方法一(最简洁) dist = np.linalg.norm(shape[50] - shape[58]) # 方法二(手动公式) dist = np.sqrt((shape[50][0]-shape[58][0])**2 + (shape[50][1]-shape[58][1])**2)

4.2 为什么使用比例而不是绝对值?

因为人脸距离摄像头的远近会影响绝对距离值,而比例(如 MAR、MJR)具有尺度不变性,无论人脸大小如何,比例都能稳定反映表情特征。

4.3 如何确定阈值?

代码中使用的mar > 0.5mjr > 0.45是经验值。实际应用中,你可以收集几十张自己不同表情的照片,计算各自的 MAR 和 MJR,画出分布直方图,再取合适的临界值。

五、改进与扩展:加入眼睛判断

目前的算法只依赖嘴部特征,无法区分“大笑”和“闭眼大笑”(可能是假笑或疲劳)。为了更精准,可以引入眼睛纵横比(EAR)来判断眼睛的闭合程度。

EAR 公式(与 MAR 类似):

EAR=∥p37−p41∥+∥p38−p40∥2⋅∥p36−p39∥EAR=2⋅∥p36​−p39​∥∥p37​−p41​∥+∥p38​−p40​∥​

其中 36~41 是左眼的六个关键点。

你可以这样扩展逻辑:

  • 如果mar > 0.5ear < 0.25,则可能是在假笑或疲劳

  • 如果mar > 0.5ear > 0.3,才是真正的大笑

(此处插入眼睛关键点编号图)

有兴趣的读者可以自行实现这个增强版。

六、运行常见问题

问题解决方案
dlib.shape_predictor加载失败检查.dat文件路径是否正确,建议使用绝对路径
中文显示为方框simsun.ttc换成你系统里存在的中文字体,例如msyh.ttc(微软雅黑)
图片读取失败确保图片路径不含中文,且文件确实存在
检测不到人脸尝试换一张正面、光线均匀的照片

七、总结

通过这篇笔记,你学会了:

  • 使用 dlib 检测人脸和 68 个关键点

  • 计算 MAR 和 MJR 两个几何特征

  • 基于简单阈值实现微笑 / 大笑 / 正常分类

  • 在 OpenCV 图片上绘制中文

  • 了解到眼睛纵横比(EAR)作为进一步的优化方向

人脸表情识别是一个很有趣的入门项目,你可以把它扩展到实时摄像头疲劳驾驶检测情绪分析等场景。希望这篇笔记对你有帮助!

源码与模型下载:文中代码可以直接复制使用,shape_predictor_68_face_landmarks.dat可以在 dlib 官网或 GitHub 上找到。


本文为作者学习 OpenCV 过程中的笔记,欢迎交流讨论。

http://www.cnnetsun.cn/news/1865459.html

相关文章:

  • 3分钟解锁网易云音乐NCM文件:ncmdumpGUI完整使用指南
  • 终极Rufus使用指南:从零开始创建可启动USB的完整教程
  • 我的树莓派网事博客nginx访问日志分析
  • 保姆级教程:用ChatGPT辅助开发你的第一个Edge浏览器插件(从写代码到上架)
  • 5分钟快速上手:使用Buzz实现高效离线音频转录与翻译的完整指南
  • LaTeX文档编写的AI助手:集成Qwen3-0.6B-FP8自动生成与校对技术文档
  • ARM 架构 JuiceFS 性能优化:基于 MLPerf 的实践与调优曰
  • 黑客滥用 GitHub 和 GitLab 托管恶意软件并实施凭证钓鱼攻击
  • PlotJuggler时间序列可视化:从数据采集到深度分析的完整指南
  • Phi-3-Mini-128K文本风格迁移效果:一键生成技术文档、诗歌或邮件
  • MathLive静态资源路径重构:从诊断到修复的完整解决方案
  • WuWa-Mod终极指南:快速解锁《鸣潮》游戏无限体验
  • TsubakiTranslator:Galgame实时翻译完整指南与终极方案
  • Adobe Illustrator脚本套件:数字化转型利器实现90%设计效率提升与成本优化
  • 微信小程序的理发店美容预约
  • Windows终极解决方案:3步快速配置Coolapk-Lite UWP客户端,告别安卓模拟器
  • 终极免费Windows字体自定义工具:让系统界面焕然一新
  • MOT避坑指南:为什么你的跟踪器在遮挡场景总丢ID?试试TrackTrack的轨迹视角关联(TPA)
  • 怎么轻松搞定电脑散热?3分钟快速上手FanControl中文版
  • LDDC歌词工具:让每首音乐都拥有完美同步的字幕体验
  • GPU显存优化实践:Pixel Language Portal在FP16精度下保持语义还原度的量化部署教程
  • 如何快速掌握RSA-Library:5个实用技巧与常见问题解答
  • 通俗易懂深入浅出OSPF-LSA类型讲解匀
  • 如何3步完成黑苹果系统配置:OpCore-Simplify智能自动化工具终极指南
  • L3GD20陀螺仪FIFO时序与嵌入式实时驱动解析
  • 为什么选择Zabbix6.4而不是Prometheus?K8s监控方案深度对比与实战
  • 长芯微LMP6295完全P2P替代SM6295,是一种超小型的集成式低压高精度半导体压力传感器
  • 〔ROS2 实战笔记-1〕Navigation2 导航框架解析
  • 【AIAgent通信协议设计黄金法则】:20年架构师亲授5大避坑指南与实时协同优化方案
  • Tabula解密:如何突破PDF数据提取的技术瓶颈与业务价值实现