初识OpenCV——Dlib人脸检测、关键点定位与表情识别

一、引言:从找到人脸到读懂表情

在之前的人脸检测中,我们学习了使用Haar级联分类器来检测人脸。虽然Haar能快速找到人脸的位置,但它存在一些局限:对侧脸、遮挡、光照变化不够鲁棒,检测精度也有待提升。

今天,我们将认识一个更强大的人脸处理库------Dlib 。它是一个现代C++工具库,在机器学习、计算机视觉领域应用广泛,尤其在人脸检测 和人脸关键点定位方面表现出色。

更重要的是,Dlib不仅能找到人脸,还能精确定位68个关键点 ------眼睛、眉毛、鼻子、嘴巴、脸廓的精确坐标。有了这些关键点,我们就能进一步分析表情:微笑、大笑、哭泣、愤怒......

本篇博客将结合代码示例,从人脸检测到关键点定位,再到表情识别,带领大家系统掌握Dlib在人脸分析中的核心应用。

二、Dlib人脸检测

2.1 什么是Dlib的HOG检测器

Dlib的人脸检测器基于HOG(方向梯度直方图)特征 + 线性分类器 + 图像金字塔 + 滑动窗口技术。

核心原理:

  1. 将图像划分为小单元格,计算每个单元格的梯度方向直方图

  2. 将相邻单元格的直方图组合成HOG特征

  3. 使用线性SVM分类器判断每个窗口是否包含人脸

  4. 通过图像金字塔处理不同尺度的人脸

相比OpenCV的Haar级联分类器,Dlib的HOG检测器:

  • 检测效果更好,尤其对正面人脸

  • 误检率更低

  • 速度较慢(因为没有Haar的级联加速机制)

2.2 静态图片人脸检测

python 复制代码
import cv2
import dlib

# 构造人脸位置检测器(HOG)
detector = dlib.get_frontal_face_detector()

# 读取图像
img = cv2.imread("people.png")

# 检测人脸
faces = detector(img, 1)

for face in faces:
    # 获取人脸框的坐标
    x1 = face.left()
    y1 = face.top()
    x2 = face.right()
    y2 = face.bottom()
    # 绘制人脸框
    cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2)

cv2.imshow("result", img)
cv2.waitKey(0)

detector(image, n)参数说明:

参数 含义
image 待检测图像
n 上采样次数,0或1通常足够;值越大越能检测到小人脸,但速度越慢

返回值 :faces 是一个包含所有检测到的人脸框的列表,每个人脸框有 left()、top()、right()、bottom() 四个方法。

cv2.rectangle() 参数说明:

python 复制代码
cv2.rectangle(img, pt1, pt2, color, thickness=1, lineType=cv2.LINE_8, shift=0) -> img
cv2.rectangle(img, rec, color, thickness=1, lineType=cv2.LINE_8, shift=0) -> img
参数 含义
img 要绘制的图像,一般是 numpy.ndarray,BGR 或灰度图
pt1 矩形一个顶点,通常左上角,格式 (x, y)
pt2 与 pt1 相对的顶点,通常右下角,格式 (x, y)
rec 矩形区域,格式 (x, y, w, h),即左上角坐标和宽高
color 颜色。彩色图是 BGR 元组,灰度图直接传一个整数
thickness 线宽,默认 1,-1 或 cv2.FILLED 表示填充整个矩形。
lineType 线型,默认 cv2.LINE_8,可选: cv2.LINE_4 cv2.LINE_8 cv2.LINE_AA(抗锯齿,边缘更平滑)
shift 坐标小数位数,默认 0,一般不用
返回值 返回绘制后的图像,通常就是传入的 img

2.3 视频实时检测------Dlib vs OpenCV对比

我们可以同时使用Dlib和OpenCV的Haar分类器进行检测,直观对比两者的效果:

python 复制代码
import cv2
import dlib

detector = dlib.get_frontal_face_detector()
faceCascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')

video = cv2.VideoCapture(0)
while True:
    ret, frame = video.read()
    frame = cv2.flip(frame, flipCode=1)  # 水平翻转(镜像)
    image = cv2.resize(frame, (500, 500))
    frame = cv2.resize(frame, (500, 500))
    if not ret:
        break
    
    # Dlib检测
    faces1 = detector(frame, 0)
    for face in faces1:
        x1 = face.left()
        y1 = face.top()
        x2 = face.right()
        y2 = face.bottom()
        cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)

    # OpenCV Haar检测
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    faces2 = faceCascade.detectMultiScale(gray, scaleFactor=1.1, 
                                          minNeighbors=15, minSize=(5, 5))
    for (x, y, w, h) in faces2:
        cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)

    cv2.imshow("Opencv", image)
    cv2.imshow('Dlib', frame)

    if cv2.waitKey(100) == 27:
        break

video.release()
cv2.destroyAllWindows()

对比观察:

  • Dlib检测更稳定,误检少

  • Haar检测速度快,但可能出现误检

  • 两者结合使用,可以互补优势

三、人脸关键点定位------68个关键点

3.1 什么是人脸关键点

人脸关键点(Facial Landmarks) 是指人脸上具有特定语义的位置,如眼角、鼻尖、嘴角等。Dlib的68点模型是最经典的关键点检测模型,将人脸划分为68个关键点:

编号范围 对应部位 点数
0-16 脸部轮廓(下巴线) 17
17-21 右眉毛 5
22-26 左眉毛 5
27-35 鼻子 9
36-41 右眼 6
42-47 左眼 6
48-59 嘴巴外轮廓 12
60-67 嘴巴内轮廓 8

3.2 关键点检测实现

需要先下载预训练模型(可从Dlib官方GitHub获取),本篇使用的模型是:shape_predictor_68_face_landmarks.dat

python 复制代码
import numpy as np
import cv2
import dlib

# 读取图像
img = cv2.imread("yz1.png")

# 构造人脸检测器
detector = dlib.get_frontal_face_detector()
faces = detector(img, 0)

# 加载关键点预测器
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")

for face in faces:
    # 获取关键点
    shape = predictor(img, face)
    # 转换为坐标数组
    landmarks = np.array([[p.x, p.y] for p in shape.parts()])
    
    # 绘制每个关键点
    for idx, point in enumerate(landmarks):
        pos = [point[0], point[1]]
        cv2.circle(img, pos, radius=2, color=(0, 255, 0), thickness=-1)
        cv2.putText(img, str(idx), pos, cv2.FONT_HERSHEY_SIMPLEX, 
                    0.4, (255, 255, 255), 1, cv2.LINE_AA)

cv2.imshow("img", img)
cv2.waitKey()
cv2.destroyAllWindows()

核心API:

  • dlib.shape_predictor(model_path):加载关键点预测模型

  • predictor(img, face):对单个人脸预测68个关键点

  • shape.parts():返回68个关键点对象,每个点有 .x 和 .y 属性

3.3 绘制关键点连线与凸包

有了68个关键点后,我们可以将它们连成线条,或者绘制凸包来表示眼睛、嘴巴等区域:

python 复制代码
import numpy as np
import dlib
import cv2

def drawLine(start, end):
    """将指定的点连接起来"""
    pts = shape[start:end]
    for l in range(1, len(pts)):
        ptA = tuple(pts[l - 1])
        ptB = tuple(pts[l])
        cv2.line(image, ptA, ptB, color=(0, 255, 0), thickness=2)

def drawConvexHull(start, end):
    """将指定的点构成凸包"""
    Facial = shape[start:end + 1]
    mouthHull = cv2.convexHull(Facial)
    cv2.drawContours(image, [mouthHull], -1, (0, 255, 0), 2)

image = cv2.imread("yz1.png")
detector = dlib.get_frontal_face_detector()
faces = detector(image, 0)
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")

for face in faces:
    shape = predictor(image, face)
    shape = np.array([[p.x, p.y] for p in shape.parts()])

    drawConvexHull(36, 41)  # 右眼凸包
    drawConvexHull(42, 47)  # 左眼凸包
    drawConvexHull(48, 59)  # 嘴巴外轮廓
    drawConvexHull(60, 67)  # 嘴巴内轮廓

    drawLine(0, 17)    # 脸颊轮廓
    drawLine(17, 22)   # 右眉毛
    drawLine(22, 27)   # 左眉毛
    drawLine(27, 36)   # 鼻子

cv2.imshow("Frame", image)
cv2.waitKey()

cv2.convexHull():计算点集的凸包(能包含所有点的最小凸多边形),常用于绘制眼睛、嘴巴等区域。

3.4 视频实时关键点检测

将关键点检测应用到摄像头视频流,可以实现实时人脸分析:

python 复制代码
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")
video = cv2.VideoCapture(0)

while True:
    ret, frame = video.read()
    frame = cv2.flip(frame, 1)
    image = frame.copy()
    if not ret:
        break
    
    faces = detector(frame, 0)
    for face in faces:
        shape = predictor(frame, face)
        landmarks = np.array([[p.x, p.y] for p in shape.parts()])
        
        # 绘制凸包和线条
        drawConvexHull(36, 41)
        drawConvexHull(42, 47)
        drawConvexHull(48, 59)
        drawConvexHull(60, 67)
        drawLine(0, 17)
        drawLine(17, 22)
        drawLine(22, 27)
        drawLine(27, 36)
        
        # 绘制关键点
        for idx, point in enumerate(landmarks):
            pos = [point[0], point[1]]
            cv2.circle(frame, pos, radius=2, color=(0, 255, 0), thickness=-1)
            cv2.putText(frame, str(idx), pos, cv2.FONT_HERSHEY_SIMPLEX, 
                        0.4, (255, 255, 255), 1, cv2.LINE_AA)

    cv2.imshow("Frame", frame)
    cv2.imshow("image", image)
    if cv2.waitKey(100) == 27:
        break

video.release()
cv2.destroyAllWindows()

四、表情识别

有了68个关键点,我们就可以计算各种几何特征来判断表情。

4.1 核心指标定义

MAR(Mouth Aspect Ratio,嘴部宽高比) :衡量嘴巴张开的程度。

其中 H 为嘴内部上下的平均距离,D 为嘴的宽度。

代码实现:

python 复制代码
from sklearn.metrics.pairwise import euclidean_distances

def MAR(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

MJR(Mouth-to-Jaw Ratio,嘴宽与脸颊宽之比) :衡量嘴巴横向拉伸程度。

其中 D 为嘴的宽度,K 为下颚的宽度或者脸颊的宽度。

python 复制代码
def MJR(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))   # 下颚宽度
    return M / J

4.2 表情判断逻辑

基于这两个指标,可以判断表情:

python 复制代码
mar = MAR(shape)
mjr = MJR(shape)
result = "正常"  # 默认表情

if mar > 0.5:
    result = "大笑"
elif mjr > 0.4:
    result = "微笑"

mouthHull = cv2.convexHull(shape[48:61])
frame = cv2add_chinese_text(frame, result, mouthHull[0, 0])
cv2.drawContours(frame, [mouthHull], -1, (0, 255, 0), 1)

阈值参考:

  • MAR > 0.5 → 嘴巴张开较大 → 大笑

  • MJR > 0.4 → 嘴巴横向拉伸 → 微笑

  • 其他 → 正常

4.3 中文输出的实现

OpenCV自带的 cv2.putText() 不支持中文,需要借助PIL库:

python 复制代码
from PIL import Image, ImageDraw, ImageFont

def cv2add_chinese_text(img, text, position, textcolor=(0, 255, 0), textsize=30):
    """向图片中添加中文"""
    if isinstance(img, np.ndarray):
        img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
        draw = ImageDraw.Draw(img)
        fontstyle = ImageFont.truetype("simsun.ttc", textsize, encoding="utf-8")
        draw.text(position, text, textcolor, font=fontstyle)
    return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)

4.4 扩展表情------哭与愤怒

在进阶版本中,我们引入眼睛宽高比(YAR) 来判断更多表情:

python 复制代码
def MAR(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))
    E = euclidean_distances(shape[37].reshape(1, 2), shape[41].reshape(1, 2))
    F = euclidean_distances(shape[38].reshape(1, 2), shape[40].reshape(1, 2))
    G = euclidean_distances(shape[36].reshape(1, 2), shape[39].reshape(1, 2))
    return ((A + B + C) / 3) / D, ((E + F) / 2) / G

判断逻辑:

python 复制代码
mar, yar = MAR(shape)
mjr = MJR(shape)
result = "正常"

if mar > 0.5:
    result = "大笑"
    if yar < 0.25:      # 眼睛眯成一条缝
        result = "哭"
elif mjr > 0.4:
    result = "微笑"
elif yar > 0.3:          # 眼睛睁得很大
    result = "愤怒"

表情判断矩阵:

表情 MAR MJR YAR
正常 <0.5 <0.4 0.25-0.3
微笑 <0.5 >0.4 正常
大笑 >0.5 - >0.25
哭 >0.5 - <0.25
愤怒 <0.5 <0.4 >0.3

五、完整表情识别系统

将以上所有功能整合,构建一个实时的表情识别系统:

python 复制代码
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))
    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))
    E = euclidean_distances(shape[37].reshape(1, 2), shape[41].reshape(1, 2))
    F = euclidean_distances(shape[38].reshape(1, 2), shape[40].reshape(1, 2))
    G = euclidean_distances(shape[36].reshape(1, 2), shape[39].reshape(1, 2))
    return ((A + B + C) / 3) / D, ((E + F) / 2) / G

def MJR(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))
    return M / J

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

while True:
    ret, frame = cap.read()
    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, yar = MAR(shape)
        mjr = MJR(shape)
        result = "正常"
        
        if mar > 0.5:
            result = "大笑"
            if yar < 0.25:
                result = "哭"
        elif mjr > 0.4:
            result = "微笑"
        elif yar > 0.3:
            result = "愤怒"
        
        mouthHull = cv2.convexHull(shape[48:61])
        eye1Hull = cv2.convexHull(shape[36:42])
        eye2Hull = cv2.convexHull(shape[42:48])
        
        frame = cv2add_chinese_text(frame, result, mouthHull[5, 0])
        cv2.drawContours(frame, [mouthHull], -1, (0, 255, 0), 1)
        cv2.drawContours(frame, [eye1Hull], -1, (0, 255, 0), 1)
        cv2.drawContours(frame, [eye2Hull], -1, (0, 255, 0), 1)
    
    cv2.imshow("Frame", frame)
    if cv2.waitKey(1) == 27:
        break

cv2.destroyAllWindows()
cap.release()

六、Dlib vs OpenCV 人脸检测对比

对比维度 Dlib HOG OpenCV Haar
检测原理 HOG特征 + SVM Haar特征 + 级联分类器
准确率 高 中
误检率 低 较高
速度 较慢 快
侧脸检测 一般 差
小人脸 需要上采样 需要调参
关键点 支持68点 不支持

七、总结

本篇博客系统学习了Dlib在人脸分析中的三大应用:

技术 核心方法 关键API
人脸检测 HOG + SVM get_frontal_face_detector()
关键点定位 68点回归模型 shape_predictor()
表情识别 几何特征计算 MAR、MJR、YAR
相关推荐
YOLO数据集集合3 小时前
红外无人机与鸟类目标检测数据集 | 红外检测 无人机识别 鸟类检测 低空安防 反无人机 yolov11模型9151期
yolo·目标检测·无人机
量子-Alex6 小时前
【大模型后训练SFT】Finetuning with Sampling: SFT Learns Better Than You Think
人工智能·深度学习·机器学习
天国梦7 小时前
哪个英语教学软件功能比较全面?我按五个维度拆了一遍
人工智能·机器学习
成为深度学习高手10 小时前
TimeBridge:用Integrated Attention与Cointegrated Attention分别桥接短期平稳与长期协整的时序预测模型
人工智能·机器学习·数据挖掘
fl17683110 小时前
工业镀金钨铜合金散热器表面缺陷识别污渍划痕分割数据集labelme格式1000张2类别低分辨率
人工智能·机器学习
牧子川10 小时前
YOLO-AMC:裂缝检测再进化,给 YOLOv11 换上更合适的「注意力」
yolo·目标检测·yolo11·裂缝检测
成为深度学习高手11 小时前
SEMixer:以随机注意力增强patch语义、渐进混合多尺度的轻量长期时序预测模型
人工智能·深度学习·机器学习·数据挖掘·时间序列
企业数字化笔记11 小时前
视频像素分割怎么选?HSV 阈值、传统掩膜、语义分割和实例分割的效果与代价
opencv·音视频
阡陌数智12 小时前
大模型领域自适应微调:小样本场景下过拟合抑制与数据构建方法论
人工智能·深度学习·机器学习
Omics Pro12 小时前
之江实验室NAR|虚拟细胞3阶段训练范式
数据库·人工智能·算法·机器学习·自然语言处理