初识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-1cv2.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
相关推荐
hhzz2 小时前
【OpenCV 入门到精通 07】滤波、阈值与形态学:图像去噪与形状处理
人工智能·python·opencv·计算机视觉
来让爷抱一个3 小时前
2026 视觉语言模型实战:八帧跳跃不许看走样,百智云精灵图把图文契约写进素材包
人工智能·机器学习
华清远见成都中心3 小时前
OpenCV中颜色空间有哪些区别
人工智能·opencv
渡我白衣3 小时前
Util工具类功能设计与类设计
linux·服务器·网络·c++·人工智能·目标检测·机器学习
workflower4 小时前
AI 转型正从工具部署转向生产关系重构
人工智能·安全·机器学习·机器人·无人机
Q26433650234 小时前
【有源码】基于机器学习的商场商铺运营分群与可视化分析研究 基于Spark的商场商铺经营效率分析与可视化
大数据·hadoop·机器学习·数据挖掘·数据分析·spark·毕业设计
飞猫的边缘AI4 小时前
边缘AI-13:从YOLOv1到YOLO26:目标检测是怎么进化的
人工智能·yolo·目标检测·ai算法·边缘ai·yolo26
Allen_LVyingbo7 小时前
医疗人工智能项目全生命周期管理系统:监管知识建模、工程实现与实证评估(上)
网络·人工智能·机器学习·语言模型·自动化
罗西的思考16 小时前
机器人模型(WM / WAM / VLA)综合分析与对比:从「看」到「想」再到「做」
人工智能·算法·机器学习