目录
[一、理解 Dlib 人脸轮廓绘制](#一、理解 Dlib 人脸轮廓绘制)
[1. 什么是人脸轮廓绘制?](#1. 什么是人脸轮廓绘制?)
[2. 轮廓绘制核心步骤](#2. 轮廓绘制核心步骤)
[二、实战案例 1:静态图片人脸轮廓绘制](#二、实战案例 1:静态图片人脸轮廓绘制)
[1. 完整代码(可直接运行)](#1. 完整代码(可直接运行))
[2. 关键细节说明](#2. 关键细节说明)
[三、实战案例 2:摄像头 / 视频实时轮廓检测](#三、实战案例 2:摄像头 / 视频实时轮廓检测)
[1. 完整代码(可直接运行)](#1. 完整代码(可直接运行))
[2. 实时场景注意事项](#2. 实时场景注意事项)
在计算机视觉的人脸分析任务中,"轮廓绘制" 是连接 "关键点定位" 与 "高阶应用" 的桥梁。通过将检测到的人脸关键点连接成轮廓,我们能更直观地识别面部结构(如眼睛、嘴唇轮廓),为表情识别、人脸美化、疲劳检测等场景提供基础支持。本文将基于 Dlib 和 OpenCV,详细讲解人脸轮廓绘制的核心原理、实现步骤,并通过 "静态图片标注" 和 "摄像头实时检测" 两个案例,提供可直接运行的代码与调试指南。
一、理解 Dlib 人脸轮廓绘制
1. 什么是人脸轮廓绘制?
人脸轮廓绘制,本质是通过 "线条连接" 或 "凸包拟合",将 Dlib 检测到的 68 个面部关键点转化为可视化轮廓的过程。它不是简单的点标记,而是通过两种核心方式还原面部结构:
- 直线连接:适用于连续分布的关键点(如下巴轮廓、眉毛),直接用线段连接相邻关键点,形成流畅线条;
- 凸包拟合 :适用于闭合区域(如眼睛、嘴唇),通过
cv2.convexHull()
计算覆盖所有关键点的 "最小凸多边形",再绘制闭合轮廓,更贴合器官的自然形状。
简单来说,关键点是 "点",轮廓绘制是把 "点" 连成 "线",让人脸结构从抽象坐标变成直观图形。
2. 轮廓绘制核心步骤
基于 Dlib+OpenCV 的轮廓绘制流程,是在 "关键点定位" 基础上增加 "轮廓生成" 环节,共 7 个核心步骤:
-
导入依赖库 需导入
dlib
(人脸检测与关键点定位)、cv2
(图像处理与绘制)、numpy
(坐标数据处理)。 -
加载预训练模型
- 人脸检测器:用
dlib.get_frontal_face_detector()
加载,用于定位人脸区域; - 关键点预测器:用
dlib.shape_predictor()
加载shape_predictor_68_face_landmarks.dat
,用于获取 68 个关键点坐标。
- 人脸检测器:用
-
读取输入数据
- 静态场景:用
cv2.imread()
读取本地图片; - 动态场景:用
cv2.VideoCapture()
初始化摄像头或本地视频流。
- 静态场景:用
-
检测人脸区域 调用检测器的
detector()
方法,传入图像数据,返回包含人脸位置的矩形列表(每个矩形对应一张人脸)。 -
定位面部关键点 对每个检测到的人脸,调用预测器的
predictor()
方法,获取 68 个关键点坐标,再转为numpy
数组方便后续处理。 -
生成并绘制轮廓
- 直线连接:调用自定义函数,按关键点索引范围(如下巴 0-16 号点)连接相邻点;
- 凸包拟合:调用自定义函数,对闭合区域关键点(如眼睛 36-41 号点)计算凸包,再绘制闭合轮廓。
-
显示与交互 用
cv2.imshow()
展示结果,cv2.waitKey()
实现交互(如按指定键退出),最后释放资源。
二、实战案例 1:静态图片人脸轮廓绘制
静态图片场景适合快速验证轮廓绘制效果,尤其适合调试关键点索引范围(避免画错部位),步骤清晰易上手。
1. 完整代码(可直接运行)
python
import numpy as np
import dlib
import cv2
# ---------------------- 自定义轮廓绘制函数 ----------------------
def draw_line(start_idx, end_idx, img, landmarks, color=(0, 255, 0), thickness=2):
"""
直线连接关键点(适用于连续轮廓,如下巴、眉毛)
:param start_idx: 起始关键点索引
:param end_idx: 结束关键点索引(不包含)
:param img: 待绘制图像
:param landmarks: 关键点坐标数组(68x2)
:param color: 线条颜色(BGR格式)
:param thickness: 线条粗细
"""
# 获取指定范围的关键点
pts = landmarks[start_idx:end_idx]
# 遍历相邻点,绘制直线
for i in range(1, len(pts)):
pt_a = tuple(pts[i-1]) # 上一个点
pt_b = tuple(pts[i]) # 当前点
cv2.line(img, pt_a, pt_b, color, thickness)
def draw_convex_hull(start_idx, end_idx, img, landmarks, color=(0, 255, 0), thickness=2):
"""
凸包拟合绘制闭合轮廓(适用于眼睛、嘴唇)
:param start_idx: 起始关键点索引
:param end_idx: 结束关键点索引(包含)
:param img: 待绘制图像
:param landmarks: 关键点坐标数组(68x2)
:param color: 轮廓颜色(BGR格式)
:param thickness: 轮廓粗细
"""
# 获取指定范围的关键点(闭合区域需包含首尾)
facial_pts = landmarks[start_idx:end_idx + 1]
# 计算凸包(最小凸多边形)
hull = cv2.convexHull(facial_pts)
# 绘制闭合轮廓(-1表示绘制所有轮廓)
cv2.drawContours(img, [hull], -1, color, thickness)
# ---------------------- 主程序逻辑 ----------------------
if __name__ == "__main__":
# 1. 读取图片(替换为你的图片路径)
img_path = "test_face.jpg"
img = cv2.imread(img_path)
if img is None:
print(f"错误:无法读取图片 {img_path},请检查路径!")
exit()
# 2. 加载Dlib模型
# 人脸检测器
detector = dlib.get_frontal_face_detector()
# 关键点预测器(确保模型文件在代码同级目录)
model_path = "shape_predictor_68_face_landmarks.dat"
try:
predictor = dlib.shape_predictor(model_path)
except Exception as e:
print(f"错误:加载关键点模型失败 - {e}")
print("提示:模型可从 https://github.com/davisking/dlib-models 下载")
exit()
# 3. 检测人脸
faces = detector(img, 0) # 0表示不上采样(提高速度)
if len(faces) == 0:
print("未检测到人脸,请更换包含正面人脸的图片!")
exit()
# 4. 处理每张人脸,绘制轮廓
for face in faces:
# 获取68个关键点坐标
shape = predictor(img, face)
landmarks = np.array([[p.x, p.y] for p in shape.parts()])
# ---------------------- 绘制关键轮廓 ----------------------
# 1. 凸包拟合:眼睛(右眼36-41,左眼42-47)
draw_convex_hull(36, 41, img, landmarks, color=(0, 255, 0)) # 右眼
draw_convex_hull(42, 47, img, landmarks, color=(0, 255, 0)) # 左眼
# 2. 凸包拟合:嘴唇(外唇48-59,内唇60-67)
draw_convex_hull(48, 59, img, landmarks, color=(0, 0, 255)) # 外唇
draw_convex_hull(60, 67, img, landmarks, color=(255, 0, 0)) # 内唇
# 3. 直线连接:面部轮廓(下巴0-16、左眉17-21、右眉22-26、鼻子27-35)
draw_line(0, 17, img, landmarks, color=(255, 255, 0)) # 下巴
draw_line(17, 22, img, landmarks, color=(255, 255, 0)) # 左眉
draw_line(22, 27, img, landmarks, color=(255, 255, 0)) # 右眉
draw_line(27, 36, img, landmarks, color=(255, 255, 0)) # 鼻子
# 5. 显示结果与交互
cv2.imshow("Dlib Face Contour (Image)", img)
print("操作提示:按 's' 保存结果,按任意其他键关闭窗口!")
key = cv2.waitKey(0) # 无限等待按键
# 保存结果(按s键)
if key == ord('s'):
save_path = "face_contour_result.jpg"
cv2.imwrite(save_path, img)
print(f"结果已保存至 {save_path}")
# 释放资源
cv2.destroyAllWindows()
2. 关键细节说明
- 函数解耦:将 "直线连接" 和 "凸包拟合" 封装为独立函数,便于后续修改颜色、粗细,避免代码冗余;
- 索引范围:严格对应 68 个关键点的部位(如下巴 0-16、右眼 36-41),画错索引会导致轮廓错位(比如把左眼画成嘴巴);
- 错误处理:增加图片读取、模型加载、人脸检测的失败判断,避免程序崩溃,同时给出调试提示。
运行结果如下:

调试模式:

三、实战案例 2:摄像头 / 视频实时轮廓检测
实时场景更贴近实际应用(如直播美颜、驾驶疲劳检测),核心是在 "静态逻辑" 基础上增加 "视频帧循环处理",需注意帧率控制和资源释放。
1. 完整代码(可直接运行)
python
import numpy as np
import dlib
import cv2
# ---------------------- 复用轮廓绘制函数 ----------------------
def draw_line(start_idx, end_idx, img, landmarks, color=(0, 255, 0), thickness=2):
pts = landmarks[start_idx:end_idx]
for i in range(1, len(pts)):
pt_a = tuple(pts[i-1])
pt_b = tuple(pts[i])
cv2.line(img, pt_a, pt_b, color, thickness)
def draw_convex_hull(start_idx, end_idx, img, landmarks, color=(0, 255, 0), thickness=2):
facial_pts = landmarks[start_idx:end_idx + 1]
hull = cv2.convexHull(facial_pts)
cv2.drawContours(img, [hull], -1, color, thickness)
# ---------------------- 主程序逻辑 ----------------------
if __name__ == "__main__":
# 1. 初始化视频流(二选一:摄像头或本地视频)
# 选项1:调用默认摄像头(笔记本通常为0,外接摄像头试1)
cap = cv2.VideoCapture(0)
# 选项2:读取本地视频文件(替换为你的视频路径)
# cap = cv2.VideoCapture("smile_video.mp4")
# 检查视频流是否打开
if not cap.isOpened():
print("错误:无法打开摄像头/视频文件,请检查设备!")
exit()
# 2. 加载Dlib模型(同静态案例)
detector = dlib.get_frontal_face_detector()
model_path = "shape_predictor_68_face_landmarks.dat"
try:
predictor = dlib.shape_predictor(model_path)
except Exception as e:
print(f"错误:加载模型失败 - {e}")
cap.release() # 提前释放摄像头
exit()
# 3. 实时处理视频帧
print("操作提示:按 ESC 键退出程序!")
while True:
# 读取一帧视频(ret:读取状态,img:帧数据)
ret, img = cap.read()
# 处理帧读取失败(如摄像头断开、视频结束)
if not ret:
print("视频流已结束或无法获取帧!")
break
# 可选:水平翻转帧(摄像头默认镜像,翻转后更自然)
img = cv2.flip(img, 1)
# 4. 检测人脸并绘制轮廓
faces = detector(img, 0)
for face in faces:
# 获取关键点坐标
shape = predictor(img, face)
landmarks = np.array([[p.x, p.y] for p in shape.parts()])
# 绘制轮廓(同静态案例,颜色和部位一致)
draw_convex_hull(36, 41, img, landmarks, (0, 255, 0)) # 右眼
draw_convex_hull(42, 47, img, landmarks, (0, 255, 0)) # 左眼
draw_convex_hull(48, 59, img, landmarks, (0, 0, 255)) # 外唇
draw_convex_hull(60, 67, img, landmarks, (255, 0, 0)) # 内唇
draw_line(0, 17, img, landmarks, (255, 255, 0)) # 下巴
draw_line(17, 22, img, landmarks, (255, 255, 0)) # 左眉
draw_line(22, 27, img, landmarks, (255, 255, 0)) # 右眉
draw_line(27, 36, img, landmarks, (255, 255, 0)) # 鼻子
# 5. 显示实时结果
cv2.imshow("Dlib Real-Time Face Contour", img)
# 控制帧率与退出(waitKey(1)≈1000fps,数值越大帧率越低)
if cv2.waitKey(1) == 27: # 27是ESC键的ASCII码
break
# 6. 释放资源(必须执行,避免摄像头占用)
cap.release()
cv2.destroyAllWindows()
2. 实时场景注意事项
- 帧率控制 :
cv2.waitKey(1)
的参数决定帧率,1ms 对应约 1000fps(流畅),若电脑性能不足可改为 5ms(200fps),避免卡顿; - 镜像翻转 :
cv2.flip(img, 1)
将摄像头画面水平翻转,符合人眼 "自拍习惯",不翻转会导致 "左右相反"(如你抬左手,画面显示抬右手); - 资源释放 :
cap.release()
必须放在循环外,否则会提前关闭摄像头,导致只显示一帧; - 多脸支持:代码支持同时检测多张人脸(如双人入镜),会自动为每张脸绘制轮廓,无需额外修改。
四、常见问题调试指南
