人脸关键点检测是很多人脸应用的基础。对齐、裁剪、贴纸、美颜、视线估计和疲劳判断,都依赖一组稳定的坐标点来定位五官。工程中常见的方案有68点和468点两种。68点来自iBUG 300-W数据集,Dlib的shape_predictor_68_face_landmarks.dat是最广泛使用的模型文件;468点来自Google的MediaPipe Face Mesh,官方将其定位为人脸网格而非简单关键点。二者的点数量和分布粒度不同,坐标输出单位也不同。把这两套体系搞清楚,可以在模型选型和后续坐标处理时避免很多低级错误。

一、68点Landmark的标注体系与检测模型
68点标准来自iBUG 300-W人脸关键点竞赛数据集,后来被Dlib、OpenCV等库广泛采用。编号从0到67,按照固定顺序覆盖人脸主要轮廓:0到16是下颌线,17到21是左眉,22到26是右眉,27到30是鼻梁,31到35是鼻翼,36到41是左眼,42到47是右眼,48到59是外嘴唇,60到67是内嘴唇。这个顺序不是随意定义的,许多后期任务会直接依赖索引区间来截取局部区域,比如用48到67做嘴部状态判断,用36到41和42到47分别计算左右眼开合度。
Dlib的检测流程通常分为两步:先用人脸检测器找到人脸框,再用形状预测器在框内定位68个点。检测器默认基于HOG特征和SVM分类器,CPU上可以实时运行;预测器是回归模型,输入人脸区域灰度图,输出每一个点的像素坐标。下面这段代码演示了如何读取一张图片并标出全部68个点。
import dlib
import cv2
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")
img = cv2.imread("face.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = detector(gray)
for face in faces:
landmarks = predictor(gray, face)
for i in range(68):
x = landmarks.part(i).x
y = landmarks.part(i).y
cv2.circle(img, (x, y), 1, (0, 255, 0), -1)
cv2.imshow("68 landmarks", img)
cv2.waitKey(0)
Dlib输出的坐标是图像像素坐标,原点在图像左上角,x向右增大,y向下增大,与OpenCV的像素坐标系一致。这个特性让它在传统图像处理流程里非常顺手。比如要裁剪人脸区域,可以直接根据最左侧和最右侧的点计算宽度,或者使用第36、45点定位眼角。不过Dlib的68点模型主要基于正脸或轻微侧脸训练,在大角度侧脸、严重遮挡、运动模糊的场景下,点会明显漂移。另外,这套点不包含虹膜中心,只画出了眼睛外轮廓,因此不能直接用于精细的视线追踪。
除了Dlib,OpenCV也提供了基于LBF算法的68点检测器,使用cv2.face.createFacemarkLBF()加载训练模型。它的速度不错,但模型文件需要单独下载,并且不同版本OpenCV的API有差异。总体来看,Dlib的68点模型因为接口稳定、教程多、依赖简单,仍然是很多中小项目的默认选择。
二、468点Face Mesh的标注体系与检测模型
468点体系来自Google的MediaPipe Face Mesh。它最初是为了移动端增强现实和视频通话效果设计的,因此点位密度远高于68点。468个点覆盖了整个人脸网格,并且提供了明确的拓扑连接关系,也就是哪些点组成三角形。它包含左右眼睛各32个点,其中虹膜占11个点;嘴唇区域有40个点,同时包含外唇和内唇;额头、脸颊也布设了更多采样点。相比之下,68点对额头几乎完全缺失,468点则补足了额头区域,这对贴纸、面具、虚拟化妆等需要完整面部表面的任务非常关键。
MediaPipe Face Mesh的检测流程同样分为两步:先用BlazeFace检测器快速定位人脸区域,再用一个轻量级注意力网格模型回归468个三维点。它的坐标不是像素坐标,而是归一化坐标。每个点的x和y通常在0到1之间,表示相对于图像宽和高的比例;z是一个深度相对值,表示该点相对于人脸中心的远近。正因为坐标是归一化的,同一模型在不同分辨率图像上都能工作,但实际绘制时必须乘以图像的实际宽高。下面的代码从摄像头读取视频并绘制468个点。
import cv2
import mediapipe as mp
mp_face_mesh = mp.solutions.face_mesh
face_mesh = mp_face_mesh.FaceMesh(
static_image_mode=False,
max_num_faces=1,
refine_landmarks=True,
min_detection_confidence=0.5
)
cap = cv2.VideoCapture(0)
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
results = face_mesh.process(rgb)
if results.multi_face_landmarks:
for face_landmarks in results.multi_face_landmarks:
h, w, _ = frame.shape
for idx, lm in enumerate(face_landmarks.landmark):
x = int(lm.x * w)
y = int(lm.y * h)
cv2.circle(frame, (x, y), 1, (0, 0, 255), -1)
cv2.imshow("468 landmarks", frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cap.release()
cv2.destroyAllWindows()
代码里refine_landmarks=True会启用虹膜精细定位,返回的光标点中包含虹膜中心。这个参数在需要视线方向、眼动追踪时非常重要。MediaPipe的模型对移动端做了深度优化,官方宣称可以在中端手机上实时运行,CPU和GPU都支持。不过它的模型体积比Dlib的68点模型大,依赖也更复杂一些。好在MediaPipe的Python封装很完善,安装后可以直接调用,不需要额外下载权重文件。
另外需要注意,MediaPipe返回的归一化坐标并不是简单地按图像宽高比例缩放。z值虽然可以提供相对深度信息,但它并不是相机坐标系下的真实距离,只能用于判断点与点之间的前后关系,例如鼻子比脸颊更靠前。如果项目需要真实三维坐标,还必须结合相机内参和外参做投影恢复。
三、坐标含义、转换与两者对比
68点和468点最直观的区别是数量,但更关键的区别是坐标单位和点分布逻辑。68点输出的是整数值像素坐标,每条记录只有x和y;468点输出的是浮点数归一化坐标,每条记录带有x、y、z三个值。这个差异会直接影响后续计算。例如,同样是判断眼睛闭合,68点需要计算第37和41点、第38和40点之间的欧氏距离,再和眼睛宽度做比值;468点则可以选用更靠近眼睑边缘的点,并且因为点位更密,比值计算会更稳定。
| 对比项 | 68点Landmark | 468点Face Mesh |
|---|---|---|
| 数据来源 | iBUG 300-W | MediaPipe Face Mesh |
| 坐标单位 | 像素坐标,整数 | 归一化坐标,浮点数 |
| 是否含z值 | 否 | 是 |
| 虹膜点 | 无 | 有,约11个 |
| 额头区域 | 基本没有 | 有 |
| 典型模型 | Dlib、OpenCV LBF | MediaPipe Face Mesh |
| 适合场景 | 对齐、裁剪、疲劳检测 | 贴纸、表情捕捉、视线估计 |
坐标转换本身并不复杂。要把468点从归一化坐标转成像素坐标,只需要用x乘以图像宽度,y乘以图像高度,z保留即可。反过来,如果要把68点的像素坐标转成归一化坐标,就分别除以宽和高。下面这段代码展示了双向转换。
def normalize_to_pixel(lm, width, height):
return int(lm.x * width), int(lm.y * height)
def pixel_to_normalize(x, y, width, height):
return x / float(width), y / float(height)
如果把68点和468点互相映射,事情会复杂一些。两者没有官方一一对应关系。一个常见做法是从468点中按照语义区域抽取出近似68点的子集,比如选择下颌线的一圈点、眉毛的首尾点、眼睛外轮廓点、鼻梁上下点以及嘴唇外轮廓点。但这样得到的结果与Dlib的68点并不完全等价,因为训练数据和标注规范不同。如果项目已经基于68点做了大量逻辑,不建议强行替换成468点抽取结果;反过来,如果升级到468点,最好同步重新校准距离阈值和比例参数。
四、模型选择、性能与常见问题
选择哪种关键点体系,首先要看任务需要。如果只是做人脸对齐、证件照裁剪、头部姿态粗略估计,或者检测眨眼、打哈欠等简单状态,68点完全足够。它的依赖少、模型小,Dlib在CPU上单次推理通常只需要几毫秒到十几毫秒。对于树莓派、低配工控机或者需要同时处理多路视频的场景,68点是很稳健的选择。尤其在嵌入式环境里,Dlib的HOG检测器加上68点预测器已经能够覆盖大多数基础需求。
如果应用涉及虚拟美妆、面具贴纸、表情捕捉、唇语识别、虹膜追踪或者高精度视线估计,应该优先考虑468点。更密集的点位意味着贴纸可以贴合面部曲面,嘴唇动画可以表现更细微的动作,虹膜点也能支撑眼球方向计算。MediaPipe在移动端实时性很好,但需要引入完整的MediaPipe运行时,部分国产嵌入式平台或老旧设备上可能无法直接使用。此时可以先评估68点是否足够,或者使用106点等第三方方案作为折中。
实际开发中有几个常见问题需要留意。第一,Dlib的68点坐标与OpenCV坐标原点和方向一致,但有些第三方模型输出的坐标原点在左下角,直接绘制会出现上下颠倒。第二,MediaPipe的归一化坐标必须在原始图像尺寸上计算,如果先缩放图片再处理,要记得用处理后的宽高反算。第三,视频场景下点会出现轻微抖动,可以对连续帧坐标做简单加权平滑,例如当前点乘以0.7,上一帧点乘以0.3。第四,多人脸场景下,Dlib的检测器默认返回所有检测到的人脸,需要按面积或位置进行过滤;MediaPipe的max_num_faces参数可以限制处理人数,超过限制的人脸会被直接忽略。第五,Windows路径中模型文件如果写成C:\models\shape_predictor_68_face_landmarks.dat,注意反斜杠需要转义,或者使用原始字符串,避免路径解析错误。
总体来看,68点和468点并不是简单的新旧替代关系,而是面向不同需求的两套方案。68点以轻量和稳定见长,适合基础人脸分析;468点以细粒度著称,适合对表情、视线和面部贴合度要求更高的应用。在项目开始前先明确坐标体系、点位分布和模型性能边界,可以大幅减少后期返工。无论是做人脸对齐还是虚拟形象驱动,理解坐标才是正确处理所有后续逻辑的前提。