一.dlib人脸检测

一.基于dlib人脸检测的两种方法

dlib 的人脸检测功能在计算机视觉领域应用广泛,它提供了基于传统机器学习(HOG)和深度学习(CNN)的两种成熟方案,让开发者可以根据项目需求在速度和精度之间灵活选择。

二.核心原理

HOG + 线性SVM模型(传统方法):这是 dlib 默认且最常用的检测器,其核心流程如下:

  • HOG 特征提取:先将图像划分为小的细胞单元(如8x8像素),计算每个单元内像素的梯度方向直方图,以此来捕捉人脸的边缘和轮廓特征(如眼睛、鼻子、嘴巴的形状)。
  • 滑动窗口检测:使用一个固定大小的窗口(如64x64像素)在图像上滑动,并对每个窗口内的 HOG 特征进行分类。
  • SVM 分类器:dlib 使用预训练的线性支持向量机(SVM)来判断窗口内的特征是否构成人脸。SVM 通过寻找一个最优超平面来区分人脸和非人脸区域。
  • 多尺度与后处理:为了检测不同大小的人脸,dlib 会构建图像金字塔,在多个缩放尺度上运行检测器。最后,使用非极大值抑制(NMS)来合并重叠的检测框,保留置信度最高的结果。

CNN模型(深度学习方法):这是 dlib 提供的基于深度学习的高精度检测器,其底层架构为 MMOD(Max-Margin Object Detection)。它使用卷积神经网络自动学习人脸的高层语义特征,能够直接预测人脸边界框的坐标和置信度,因此在复杂场景下表现更好。

三.使用方法

1.导入dlib库,创建人脸检测器(使用H0G算法、线性分类器、金字塔图像结构和滑动窗口检测等技术),读入图片img。

import dlib
detector = dlib.get_frontal_face_detector()
img = cv2.imread('reba2.webp')

2.使用人脸检测器检测图像中的人脸,返回检测到的人脸框。(detector)

  • image:待检测的可能含有人脸的图像。
  • n:表示采用上采样的次数。上采样会让图像变大,能够检测到更多人脸对象,提高小人脸的检测效果。通常建议将此参数设置为 0 或 1。较大的值会增加检测的准确性,但会降低处理速度。
  • faces:返回检测图像中的所有人脸框。
faces = detector(img, 0)

3.遍历人脸框,获取每个人脸框的坐标,分别绘制出每张人脸。

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)

4.显示捕获到的人脸框

cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

5.运行结果,效果不错

二.dlib与opencv人脸检测的对比

下表从算法原理、性能表现和适用场景等维度,对 OpenCV 与 dlib 的常见人脸检测方案进行对比:

方案算法原理特点
OpenCV Haar 级联基于图像灰度值的矩形特征差异,使用 AdaBoost 级联分类器经典快速,但对光照、姿态和遮挡非常敏感,误检率相对较高
OpenCV DNN 模块一个推理引擎,可加载预训练的深度学习模型(如 SSD、Caffe)精度高、鲁棒性强,通常作为现代应用的首选
dlib HOG + SVM提取图像的梯度方向直方图特征,使用线性 SVM 分类CPU 上的速度标杆,在正面和轻微非正面人脸上表现稳健
dlib CNN (MMOD)基于最大边距目标检测的深度学习模型精度极高,对遮挡、姿态变化鲁棒,但在无 GPU 加速时速度很慢

检测人脸时的区别,opencv检测到的人脸范围较大,dlib检测到的人脸范围较小,可以看到左边把额头大部分都框进去了,右边却没有。

三.dlib实现关键点定位

这里需要加载 dlib 官方预训练的 68 点人脸关键点检测器。它的核心作用不是判断“图里有没有人脸”,而是在已经知道人脸位置(矩形框)的前提下,进一步定位人脸上的 68 个特征点坐标。 包括眼睛、鼻子、眉毛、轮廓等

(shape_predictor_68_face_landmarks.dat)

使用方法

1.导入opencv,dlib,numpy库

import cv2
import dlib
import numpy as np

2.加载图片,生成人脸检测器,对图片进行人脸检测,返回检测图像中的所有人脸,载入模型(加载预测器,人脸68关键点检测器)

img = cv2.imread("./reba2.webp")
detector = dlib.get_frontal_face_detector()
faces = detector(img, 0)
predictor = dlib.shape_predictor("./shape_predictor_68_face_landmarks.dat")

3.遍历每一张人脸,获取他们的关键点,将关键点转换为坐标(xy)的形式

for face in faces:  # 获取每一张脸的关键点(实现检测)
    shape = predictor(img, face)  # 获取关键点
    # 将关键点转换为坐标(xy)的形式
    # landmarks = np.matrix([[p.x, p.y] for p in shape.parts()])
    landmarks = np.array([[p.x, p.y] for p in shape.parts()])

4.绘制每一张脸的关键点(绘制shape中的每个点),

    for idx, point in enumerate(landmarks):
        # 当前关键点的坐标
        pos = [point[0], point[1]]
        # 针对当前关键点,绘制一个实心圆
        cv2.circle(img, pos, 2, (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.putText 用于在图像上绘制文本,各参数含义如下:

  • img:要绘制文本的目标图像。
  • str(idx):要绘制的文本内容,这里将当前关键点的索引 idx 转为字符串,用于标注每个关键点的编号。
  • pos:文本左下角的起始坐标,即当前关键点的位置 (x, y)。
  • cv2.FONT_HERSHEY_SIMPLEX:字体类型,表示使用普通大小的衬线字体。
  • 0.4:字体缩放比例,数值越小文字越小,这里用于在关键点附近绘制较小的编号。
  • (255, 255, 255):文本颜色,这里为白色(BGR 顺序)。
  • 1:线条粗细,即文字的笔画宽度,数值越大文字越粗。
  • cv2.LINE_AA:线条类型,表示使用抗锯齿线条,让文字边缘更平滑。

5.展示图片

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

6.运行结果

四.轮廓绘制

使用方法

1.导入库

import numpy as np
import dlib
import cv2

2.读取图片,创建人脸位置检测器,检测人脸,读取人脸关键点定位模型

image = cv2.imread("reba2.webp")
detector = dlib.get_frontal_face_detector()  # 构造脸部位置检测器
faces = detector(image, 0)  # 检测人脸方位置
# 读取人脸关键点定位模型
predictor = dlib.shape_predictor("./shape_predictor_68_face_landmarks.dat")

3.遍历人脸,获取关键点位置,将关键点转换为坐标(x,y)的形式,分别绘制凸包和点线。

凸包(Convex Hull):在平面几何中,凸包是指包含一组点集的最小凸多边形。直观理解,可以想象用一根橡皮筋把所有点紧紧箍住,橡皮筋最终形成的形状就是这组点的凸包。凸包上的任意两点连线都完全落在凸包内部或边界上,因此它能够简洁地勾勒出点集的外围轮廓。

在人脸关键点定位中,凸包常用于将离散的关键点连接成平滑的轮廓区域。例如,将眼睛周围的关键点(如索引 36 到 41)求凸包后,可以绘制出覆盖整个眼部的闭合区域,从而更直观地展示眼睛、嘴巴等部位的形状和范围。

for face in faces: # 对检测到的rects,逐个遍历
    shape = predictor(image, face)  # 获取关键点
    # 将关键点转换为坐标(x,y)的形式
    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)  </code></pre>

函数1:

drawConvexHull(start, end):这是一个自定义函数,用于将指定范围内的关键点构成一个凸包,并绘制成轮廓。一般眼睛、嘴巴等部位使用凸包来绘制,各参数含义如下:

  • start:起始关键点的索引,表示从第几个关键点开始取点。
  • end:结束关键点的索引,表示取到第几个关键点为止。
  • Facial = shape[start : end+1]:从全部 68 个关键点中截取索引从 start 到 end 的这一段点集。因为切片是左闭右开,所以结束位置要写成 end+1 才能把第 end 个点包含进来。
  • mouthHull = cv2.convexHull(Facial):调用 OpenCV 的凸包函数,对截取出的点集求凸包,得到包围这些点的最小凸多边形顶点集合。
  • cv2.drawContours(image, [mouthHull], -1, (0, 255, 0), 2):把求得的凸包绘制到图像上。其中 image 是目标图像,[mouthHull] 表示要绘制的轮廓列表,-1 表示绘制所有轮廓,颜色为绿色 (0, 255, 0),线宽为 2 像素。
def drawConvexHull(start,end):
    # 将指定的点构成一个凸包,绘制成轮廓,一般眼睛、嘴使用凸包用来绘制
    Facial = shape[start: end+1]
    mouthHull = cv2.convexHull(Facial)  # 凸包函数
    cv2.drawContours(image, [mouthHull], -1, (0, 255, 0), 2)

    函数2:

    drawLine(start, end):这是一个自定义函数,用于将指定范围内的关键点按顺序用线段连接起来,绘制成点线轮廓。一般眉毛、鼻子、脸颊等部位使用点线来绘制,各参数含义如下:

    • start:起始关键点的索引,表示从第几个关键点开始取点。
    • end:结束关键点的索引,表示取到第几个关键点为止。
    • pts = shape[start : end]:从全部 68 个关键点中截取索引从 start 到 end-1 的这一段点集。因为切片是左闭右开,所以第 end 个点不会被包含进来。
    • for l in range(1, len(pts)):从第 1 个点开始遍历到最后一个点,每次取相邻的两个点来画线。
    • ptA = tuple(pts[l - 1]):取当前线段的前一个点,并转换为元组形式,作为线段的起点。
    • ptB = tuple(pts[l]):取当前线段的后一个点,并转换为元组形式,作为线段的终点。
    • cv2.line(image, ptA, ptB, (0, 255, 0), 2):在图像上从 ptA 到 ptB 绘制一条直线。其中 image 是目标图像,颜色为绿色 (0, 255, 0),线宽为 2 像素。
    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, (0, 255, 0), 2)

    4.展示图片

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

      5.运行结果

      五.笑容检测

      使用方法

      1.导包

      import numpy as np
      import dlib
      import cv2
      from sklearn.metrics.pairwise import euclidean_distances  # 计算欧氏距离
      from PIL import Image, ImageDraw, ImageFont
      

      2.创建人脸检测器, 检测人脸,读取人脸关键点定位模型,读入两张图片,frame1,frame2,检测人脸返回faces1,2.

      detector = dlib.get_frontal_face_detector()
      predictor = dlib.shape_predictor("./shape_predictor_68_face_landmarks.dat")
      frame1 = cv2.imread("2.jpg")
      frame2 = cv2.imread("3.jpg")
      faces1 = detector(frame1, 0)
      faces2 = detector(frame2, 0)

      3.遍历人脸,获取关键点位置,将关键点转换为坐标(x,y)的形式,调用MAR函数,计算嘴部的高宽比,调用MJR函数,计算嘴宽/脸颊宽,默认表情为正常,当我们的嘴部的高宽比大于0.5,表情变为大笑;当我们的嘴宽/脸颊宽大于0.45,表情为微笑,将嘴部形状绘制为凸包,把我们的中文文字添加到图片上,最后画出轮廓。

      for face in faces1:
          shape = predictor(frame1, face)  # 获取关键点
          # 将关键点转换为坐标(x, y)的形式
          shape = np.array([[p.x, p.y] for p in shape.parts()])
          mar = MAR(shape)    # 计算嘴部的高宽比
          mjr = MJR(shape)    # 计算“嘴宽/脸颊宽”
          result = "正常"      # 默认是正常表情
          print("mar", mar, "\tmjr", mjr)     # 测试一下实际值,可以根据该值确定
          if mar > 0.5:   # 可更具项目要求调整阈值。
              result="大笑"
          elif mjr > 0.45:  # 超过闽值为微笑
              result = "微笑"
          mouthHull = cv2.convexHull(shape[48:61])  # 构造嘴型凸包
          frame1 = cv2AddChineseText(frame1, result, mouthHull[0,0]) # 多人脸
          cv2.drawContours(frame1, [mouthHull], -1, (0, 255, 0), 1)
      

      函数1:

      MAR(shape):这是一个自定义函数,用于计算嘴部的高宽比(Mouth Aspect Ratio),通过比较嘴部关键点的纵向距离与横向距离来判断嘴巴的张开程度,从而辅助识别笑容。各参数含义如下:

      • shape:包含 68 个人脸关键点坐标的数组,每个元素为 (x, y) 形式的坐标。
      • A = euclidean_distances(shape[50].reshape(1, 2), shape[58].reshape(1, 2)):计算第 50 号关键点与第 58 号关键点之间的欧氏距离,即嘴部左侧上下唇之间的垂直距离。
      • B = euclidean_distances(shape[51].reshape(1, 2), shape[57].reshape(1, 2)):计算第 51 号关键点与第 57 号关键点之间的欧氏距离,即嘴部中间上下唇之间的垂直距离。
      • C = euclidean_distances(shape[52].reshape(1, 2), shape[56].reshape(1, 2)):计算第 52 号关键点与第 56 号关键点之间的欧氏距离,即嘴部右侧上下唇之间的垂直距离。
      • D = euclidean_distances(shape[48].reshape(1, 2), shape[54].reshape(1, 2)):计算第 48 号关键点与第 54 号关键点之间的欧氏距离,即嘴部左右两侧嘴角之间的水平距离(嘴宽)。
      • return ((A + B + C) / 3) / D:先对 A、B、C 三个垂直距离取平均值,得到嘴部的平均高度,再除以嘴宽 D,得到嘴部的高宽比。嘴巴张得越大,该比值越大,因此可通过设定阈值(如 0.5)来判断是否在大笑。
      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

      函数2:

      MJR(shape):这是一个自定义函数,用于计算嘴宽与脸颊宽的比值(Mouth-to-Jaw Ratio),通过比较嘴部宽度与下颌宽度来判断嘴巴的横向张开程度,从而辅助识别微笑。各参数含义如下:

      • shape:包含 68 个人脸关键点坐标的数组,每个元素为 (x, y) 形式的坐标。
      • M = euclidean_distances(shape[48].reshape(1, 2), shape[54].reshape(1, 2)):计算第 48 号关键点与第 54 号关键点之间的欧氏距离,即嘴部左右两侧嘴角之间的水平距离(嘴宽)。
      • J = euclidean_distances(shape[3].reshape(1, 2), shape[13].reshape(1, 2)):计算第 3 号关键点与第 13 号关键点之间的欧氏距离,即下颌左右两侧之间的宽度(脸颊宽)。
      • return M / J:用嘴宽 M 除以脸颊宽 J,得到嘴宽与脸颊宽的比值。微笑时嘴角向两侧拉伸,嘴宽相对脸颊变宽,该比值会增大,因此可通过设定阈值(如 0.45)来判断是否为微笑。
      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

      函数3:

      cv2AddChineseText(img, text, position, textColor, textSize):这是一个自定义函数,用于在 OpenCV 图像上绘制中文文本。由于 OpenCV 自带的 cv2.putText 不支持中文,因此借助 PIL(Pillow)库来实现中文的绘制。各参数含义如下:

      • img:要绘制中文的目标图像,通常为 OpenCV 读取的 BGR 格式图像。
      • text:要绘制的文本内容,这里传入的是表情识别结果,如“正常”“微笑”“大笑”等中文文字。
      • position:文本绘制的起始坐标,即文本左上角在图像上的位置 (x, y)。
      • textColor:文本颜色,默认值为 (0, 255, 0),即绿色(RGB 顺序)。
      • textSize:字体大小,默认值为 30,数值越大文字越大。
      • isinstance(img, np.ndarray):判断传入的图像是否为 OpenCV 的 numpy 数组类型。如果是,则说明是 OpenCV 图像,需要先转换为 PIL 图像才能绘制中文。
      • cv2.cvtColor(img, cv2.COLOR_BGR2RGB):将 OpenCV 的 BGR 颜色空间转换为 PIL 使用的 RGB 颜色空间,避免颜色显示错乱。
      • Image.fromarray(...):将 numpy 数组转换为 PIL 的 Image 对象。
      • ImageDraw.Draw(img):在 PIL 图像上创建一个绘图对象,后续的文本绘制操作都通过它来完成。
      • ImageFont.truetype('simsun.ttc', textSize, encoding="utf-8"):加载中文字体文件(宋体 simsun.ttc),并指定字体大小和 utf-8 编码,确保中文能够正常显示。
      • draw.text(position, text, textColor, font=fontStyle):在指定位置绘制文本,其中 position 是坐标,text 是内容,textColor 是颜色,font 是字体样式。
      • cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR):绘制完成后,将 PIL 图像转换回 numpy 数组,并把 RGB 颜色空间转换回 OpenCV 使用的 BGR 格式,最后返回给调用方继续处理
        def cv2AddChineseText(img, text, position, textColor=(0, 255, 0), textSize=30):
            """向图片中添加中文"""
            if (isinstance(img, np.ndarray)):  # 判断是否0penCV图片类型
                img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))  # 实现array到image的转换
            draw = ImageDraw.Draw(img)  # 在img图片上创建一个绘图的对象
            # 字体的格式
            fontStyle = ImageFont.truetype('simsun.ttc', textSize, encoding="utf-8")
            draw.text(position, text, textColor, font=fontStyle)  # 绘制文本
            return cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)  # 转换回0penCV格式

        4.展示图片

        cv2.imshow("Frame1", frame1)
        cv2.imshow("Frame2", frame2)
        cv2.waitKey(0)
        cv2.destroyAllWindows()

          5.运行结果

          更多推荐