基于 OpenCV Haar + MediaPipe 的人脸情绪检测

以下从代码整体架构出发,拆解书写步骤,并对关键代码做详细解释,帮你理解如何一步步实现 “基于 OpenCV Haar + MediaPipe 的人脸情绪检测” 功能:

一、书写步骤拆解

1. 环境与依赖准备(前置思考)
  • 确定需求:要用 OpenCV 做摄像头采集、人脸检测,MediaPipe 提取关键点,Pillow 处理中文绘制,所以需要导入这些库。
  • 提前安装依赖:pip install opencv-python mediapipe pillow numpy ,确保代码运行环境就绪。
2. 基础工具函数编写
  • 中文字体适配(get_chinese_font 函数):

不同系统中文字体路径不同,通过遍历常见路径,优先加载本地中文字体(如 simhei.ttf ),找不到则用默认字体兜底,保证中文标签能正常显示。

  • 中文绘制逻辑(Chinese_plot_box 函数):

把 OpenCV 的 BGR 图像转成 Pillow 支持的 RGB 格式,用 ImageDraw 绘制带中文标签的矩形框,解决 OpenCV 原生对中文支持差的问题。

3. 核心功能类设计(face_emotion 类)
  • 初始化(__init__ 方法):

打开摄像头并设置分辨率,初始化 MediaPipe 人脸网格模型、OpenCV Haar 级联检测器,定义截图计数器,还维护了 MediaPipe 到 dlib 关键点的映射(虽然后续简化了映射逻辑,但保留了扩展思路 )。

  • 关键点转换(get_dlib_style_landmarks 方法):

从 MediaPipe 468 个关键点里,筛选出情绪识别常用的关键点(嘴唇、眼睛、眉毛区域),转换为类似 dlib 68 点的精简格式,让后续比例计算逻辑更清晰。

  • 主逻辑运行(learning_face 方法):

持续从摄像头取帧 → 用 Haar 级联检测人脸 → MediaPipe 提取关键点 → 计算嘴巴、眉毛、眼睛的几何特征 → 根据特征判断情绪 → 绘制结果并提供截图、退出功能。

4. 程序入口(if __name__ == "__main__")

实例化 face_emotion 类并调用 learning_face 方法,启动整个人脸情绪识别流程。

二、关键代码详细解释

1. 中文字体加载(get_chinese_font 函数)
def get_chinese_font(size):

font_paths = [

"./font/simhei.ttf", # 项目目录自定义字体

"/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc", # Linux 常见字体

"/System/Library/Fonts/PingFang.ttc", # Mac 常见字体

"C:/Windows/Fonts/simhei.ttf" # Windows 常见字体

]

for path in font_paths:

if os.path.exists(path):

return ImageFont.truetype(path, size, encoding="utf-8")

return ImageFont.load_default() # 兜底方案
  • 作用:跨平台加载中文字体,避免因系统差异导致中文显示为方框或乱码。
  • 逻辑:按优先级遍历字体路径,找到存在的字体就用 ImageFont.truetype 加载;全找不到则返回默认字体(可能无法完美显示中文,但保证程序不崩溃 )。
2. 中文绘制(Chinese_plot_box 函数核心片段)
def Chinese_plot_box(image, label, x, sizes, colour=None, line_thickness=None):

cv2img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # OpenCV → Pillow 格式转换

pilimg = Image.fromarray(cv2img)

draw = ImageDraw.Draw(pilimg)

font = get_chinese_font(sizes) # 拿到合适的中文字体

# 计算矩形框厚度、坐标

tl = line_thickness or round(0.002 * (image.shape[0] + image.shape[1]) / 2) + 1

c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3]))

draw.rectangle([int(x[0]), int(x[1]), int(x[2]), int(x[3])], outline=(255, 0, 0), width=tl)

if label:

text_width = draw.textlength(label, font=font) # 计算文本宽度

text_height = font.getbbox(label)[3] - font.getbbox(label)[1] # 计算文本高度

t_size = (text_width, text_height)

# 调整矩形坐标,避免 y 轴方向逻辑错误

rect_x0, rect_y0 = int(x[0]), int(x[1])

rect_x1, rect_y1 = c1[0] + t_size[0], c1[1] - t_size[1] - 3

if rect_y1 < rect_y0:

rect_y1 = rect_y0 # 保证矩形下边界不高于上边界

# 绘制带背景的文本

draw.rectangle([rect_x0, rect_y0, rect_x1, rect_y1],

outline=(255, 0, 0), fill=(255, 0, 0), width=1)

draw.text((rect_x0, rect_y0), label, (255, 255, 255), font=font)

image = cv2.cvtColor(np.array(pilimg), cv2.COLOR_RGB2BGR) # 转回 OpenCV 格式

return image
  • 核心目的:在 OpenCV 图像上绘制带中文标签的矩形框,解决 OpenCV putText 对中文支持差的问题。
  • 关键细节
    • 格式转换:OpenCV 图像是 BGR 通道,Pillow 处理 RGB ,所以需要 cvtColor 转换。
    • 文本尺寸计算:用 textlength 算宽度,getbbox 算高度(不同 Pillow 版本可能有差异,但逻辑通用 )。
    • 坐标安全校验:通过 if rect_y1 < rect_y0 避免绘制矩形时出现 “下边界在上边界上方” 的错误。
3. 人脸检测与关键点处理(learning_face 方法核心片段)
def learning_face(self):

while self.cap.isOpened():

flag, im_rd = self.cap.read() # 从摄像头取帧

if not flag:

break

# 1. Haar 级联检测人脸

gray = cv2.cvtColor(im_rd, cv2.COLOR_BGR2GRAY)

faces = face_cascade.detectMultiScale(gray, 1.1, 5)

# 2. MediaPipe 提取关键点

rgb_frame = cv2.cvtColor(im_rd, cv2.COLOR_BGR2RGB)

results = face_mesh.process(rgb_frame)

# 3. 处理检测结果(有脸 + 有关键点)

if len(faces) > 0 and results.multi_face_landmarks:

for (x, y, w, h) in faces:

# 绘制人脸框

im_rd = cv2.rectangle(im_rd, (x, y), (x + w, y + h), (0, 0, 255), 2)

self.face_width = w # 记录人脸宽度,用于比例计算

# 遍历关键点,转换格式并计算特征

for face_landmarks in results.multi_face_landmarks:

# 绘制 MediaPipe 关键点(可视化调试用)

mp_drawing.draw_landmarks(

image=im_rd,

landmark_list=face_landmarks,

connections=mp_face_mesh.FACEMESH_CONTOURS,

landmark_drawing_spec=drawing_spec,

connection_drawing_spec=drawing_spec)

# 转换关键点为 [x,y] 列表

landmarks = [(int(lm.x * w), int(lm.y * h))

for lm in face_landmarks.landmark]

# 4. 计算情绪特征(嘴巴、眉毛、眼睛)

# 嘴巴宽高比

mouth_left, mouth_right, mouth_top, mouth_bottom = landmarks[61], landmarks[291], landmarks[13], landmarks[14]

mouth_width = (mouth_right[0] - mouth_left[0]) / self.face_width

mouth_height = (mouth_bottom[1] - mouth_top[1]) / self.face_width

# 眉毛特征(高度、间距、倾斜度)

left_brow, right_brow = [landmarks[244], landmarks[174], landmarks[158], landmarks[157], landmarks[177]], [landmarks[46], landmarks[424], landmarks[413], landmarks[414], landmarks[433]]

brow_sum, frown_sum, line_brow_x, line_brow_y = 0, 0, [], []

for lb, rb in zip(left_brow, right_brow):

brow_sum += (lb[1] - y) + (rb[1] - y)

frown_sum += rb[0] - lb[0]

line_brow_x.extend([lb[0], rb[0]]), line_brow_y.extend([lb[1], rb[1]])

# 眉毛倾斜度计算(最小二乘法拟合直线)

brow_k = 0

if len(line_brow_x) > 1 and len(line_brow_y) > 1:

z1 = np.polyfit(line_brow_x, line_brow_y, 1)

brow_k = -round(z1[0], 3)

# 眼睛睁开程度

left_eye, right_eye = [landmarks[386], landmarks[385], landmarks[384], landmarks[398], landmarks[380], landmarks[374]], [landmarks[159], landmarks[158], landmarks[157], landmarks[173], landmarks[145], landmarks[133]]

eye_sum = 0

for le in left_eye[:3]: # 上眼睑

for le2 in left_eye[3:]: # 下眼睑

eye_sum += le2[1] - le[1]

for re in right_eye[:3]:

for re2 in right_eye[3:]:

eye_sum += re2[1] - re[1]

eye_hight = (eye_sum / 12) / self.face_width

# 5. 情绪判断逻辑

emotion = "自然"

if mouth_height >= 0.03:

emotion = "震惊" if eye_hight >= 0.056 else "高兴"

else:

emotion = "生气" if brow_k <= -0.3 else "自然"

# 6. 绘制结果

cv2.putText(im_rd, emotion, (x, y + h + 30),

cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)

im_rd = Chinese_plot_box(im_rd, label=emotion,

x=[x, y - 30, x + 100, y], sizes=20)

# 显示人脸数量

cv2.putText(im_rd, f"人脸数量: {len(faces)}", (20, 50),

cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 1)

else:

cv2.putText(im_rd, "未检测到人脸", (20, 50),

cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 1)

# 操作提示、截图、退出逻辑...

cv2.imshow("人脸情绪识别", im_rd)
  • 流程拆解
    • 取帧与预处理:cap.read() 读摄像头,转灰度图给 Haar 级联,转 RGB 给 MediaPipe。
    • 人脸与关键点检测:Haar 找人脸框,MediaPipe 提取 468 个关键点,再筛选出情绪相关的精简点。
    • 特征计算
      • 嘴巴:通过左右嘴角、上下嘴唇关键点算宽高比,判断是否 “张嘴”。
      • 眉毛:统计眉毛关键点的高度和间距,用最小二乘法拟合直线算倾斜度,判断是否 “皱眉 / 挑眉”。
      • 眼睛:统计上下眼睑关键点的垂直距离,算睁开程度,区分 “惊讶 / 高兴”。
    • 情绪判断:用多条件分支,结合嘴巴、眉毛、眼睛的特征,输出 “自然、高兴、震惊、生气” 等结果。
    • 可视化与交互:用 cv2.putText 和 Chinese_plot_box 绘制结果,提供截图(s 键)和退出(q 键)功能。
4. 程序启动(入口代码)
if __name__ == "__main__":

my_face = face_emotion()

my_face.learning_face()
  • 作用:Python 脚本标准入口,确保直接运行脚本时才启动人脸情绪识别流程,避免被其他模块导入时意外执行。

三、核心设计思路总结

  1. 多库协同:用 OpenCV 做基础采集和简单人脸检测,MediaPipe 做高精度关键点提取,Pillow 解决中文绘制,各司其职。
  1. 兼容与适配:通过字体遍历、坐标校验等逻辑,解决跨平台(Windows/Linux/Mac)和库版本差异问题。
  1. 简化与扩展:把 MediaPipe 468 点精简为类似 dlib 68 点的格式,既简化计算,又保留了扩展为更复杂情绪识别(如 7 分类)的可能性。

理解这些步骤后,你可以根据实际需求调整:比如优化关键点映射、扩展更多情绪分类(结合机器学习模型 )、或替换更精准的人脸检测模型(如 OpenCV DNN 模型 )。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

遗憾是什么.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值