
有一次我调试眨眼检测用 dlib 的 68 关键点代码半天就跑通了但阈值怎么调都不对——睁眼被判定成闭眼闭眼又半天不触发。后来我把所有点画在脸上、标上索引号才发现问题根本不是阈值而是我把第 42 个点默认当成了右眼外眼角。真实的 68 点定义里42 号点离右眼外眼角还差着两个位置我的业务直觉和实际索引整整偏了一截。从那天起我养成一个习惯拿到任何人脸关键点模型先不写业务逻辑先把点位定义吃透。这篇文章要聊的就是这件事——人脸关键点到底是怎么定义的68、106、108、194、468 这些常见方案各自长什么样索引对应的物理位置怎么查选型时又该看什么。这篇内容适合三类人一是刚开始做人脸项目、直接调开源模型的开发者二是做美颜、贴纸、表情驱动产品绕不开各种 SDK 点位的工程师三是想理解人脸对齐算法底层逻辑的算法研究员。我尽量把每个方案的索引规律和坑都讲清楚文中的判断都来自真实项目经验不是从文档里抄一遍就完事。1. 一张索引表引发的踩坑为什么“关键点定义”才是底层问题1.1 一个真实的索引混乱事故那次眨眼检测踩坑我复盘了整条链路模型加载没问题检测框画得也对关键点坐标也确实落在了面部轮廓上。问题出在我写 EAR眼部纵横比时直接按“第 42 点到第 47 点是右眼”的常识去取数组下标结果取到的数据根本不是我以为的那只眼睛。更麻烦的是我在代码里还写了“左眼”“右眼”的注释和实际的物理位置完全对不上。这类事故在团队里特别常见尤其是从 dlib 切换到其他 SDK、或者从 68 点切换到 106 点时。很多人以为换方案只是“点数变多”实际上每种方案的索引从 0 开始的指向完全不同——同一个索引号在 dlib 里可能是下巴尖在某个商业 SDK 里可能是上眼皮边缘。关键点定义的本质是一套“索引编号”到“人脸物理位置”的映射约定。模型输出的第 i 个点本身只是一个二维或三维坐标它落在哪里、代表什么语义完全由标注规范决定。不理解这个映射你写的后处理代码就是靠猜。1.2 搞懂定义到底要搞懂什么我把“搞懂一个关键点方案”拆成三个层次。第一层是“能画出点”加载模型后把全部点可视化到脸上知道这些点大致分布在哪里。第二层是“能报出索引”不看图也能说出某个物理位置对应哪几个索引号比如嘴巴外沿是 48 到 59下巴尖是 8。第三层是“能说清边界”知道这套方案在什么场景下会失效比如侧脸时轮廓点会截断表情夸张时嘴唇点会重叠。大部分教程只教你“怎么调用 API”很少讲这套定义背后的设计动机。但实际上每个方案的索引排列都有它的内在逻辑——要么是按顺时针描轮廓要么是按部件分组。摸清这套逻辑后你不需要死记硬背每个点只要记住起点位置和方向就能反推出整张索引表。我下面会按这个思路把主流方案一个个拆开讲。2. 从68点到468点主流人脸关键点方案的演进与定位2.1 一表看尽主流方案在进入细节之前先用一张表把市面上最常见的几种方案摆在一起。这张表里的定位是我个人对这些方案在项目中的真实体感不完全等于官方定义但对选型有参考价值。方案点数常见出处/代表项目维度设计倾向典型场景68 点68dlib、iBUG 300-W 数据集2D学术基准稀疏语义标注通用算法研究、眨眼/张嘴检测、简单贴纸106 点106国内多家人脸 SDK2D业务友好轮廓和眼嘴加密美颜、特效、直播、姿态判断108 点108106 点加双瞳孔中心2D在 106 点基础上补瞳孔锚点瞳距计算、视线估计、美瞳贴图194 点194精细语义标注方案2D面颊与唇周极密精准美颜、唇彩试色、人脸重建素材468 点468/478MediaPipe Face Mesh3D稠密网格顶点AR 贴纸、表情驱动、3D 重建2.2 学术界和工业界为什么走岔了68 点方案之所以成为学术基准是因为 iBUG 300-W 数据集的标注规范被大规模研究沿用。300-W 整合了 LFPW、HELEN、AFW 等数据集统一规范到 68 个语义点。这套规范在“标注成本”和“语义覆盖”之间取了一个平衡17 个点描下颌10 个点描眉毛9 个点描鼻子12 个点描眼睛20 个点描嘴巴整个面部的主要语义部件都覆盖到了。但学术界看重的是“基准可比性”——大家用同一套点算法效果才能横向对比。工业界看重的是“业务好不好用”这就导致 68 点在真实产品里经常不够用。比如瘦脸算法需要沿着下颌边缘做形变控制68 点只有 17 个轮廓点稀疏得连下颚曲线都拟合不光滑再比如美瞳贴图需要精确贴合上下眼睑的弧度68 点每只眼睛只有 6 个点连一个完整的眼睑弧线都描不顺。于是国内商业 SDK 普遍发展出了 106、108、194 点这类方案本质是“为了业务需求给语义点加密”。我见过不少团队抱怨“商业 SDK 的点位索引不公开、不统一”这确实是事实但换个角度看这些并行方案恰恰说明了一个道理不存在某种“标准答案式”的关键点定义只有“适不适合当前业务”的区别。3. 68点逐区详解dlib与iBUG 300-W的索引地图3.1 分区索引速查表dlib 所用的 68 点定义完整继承了 iBUG 300-W 的标注规范索引从 0 到 67。这个方案按区域分为 8 组我先把速查表放在下面然后再逐个区域讲几何规律。区域索引范围点数关键特征点下颌/脸廓0-1617下巴尖是 80 和 16 是脸颊两侧左眉17-21517 靠眉心21 靠外侧右眉22-26522 靠眉心26 靠外侧鼻梁27-30427 在眉心下方30 是鼻尖鼻底31-35533 是鼻小柱底部中心左眼36-41636 外眼角39 内眼角右眼42-47642 内眼角45 外眼角外唇48-591248 左侧嘴角51 上唇中央54 右侧嘴角57 下唇中央内唇60-67860-67 沿内唇边缘成环3.2 各区域的几何排列规律下颌轮廓 0 到 16 是很多人忽略的重点。这 17 个点从人脸左侧脸颊出发沿着下颌骨一路描到下巴最低处再绕到右侧脸颊。索引 8 是下巴尖的底部也就是整个下颌弧线的最低点。做瘦脸时你改动的就是 0 到 16 这条折线围成的形状做脸型判断时下巴是尖是圆主要看索引 6、7、8、9、10 这一段的角度变化。眉毛和鼻梁的区域比较好记。17 到 21 是左眉17 靠近眉心、21 靠外侧22 到 26 是右眉方向与左眉相反22 靠近眉心、26 靠外侧。鼻子部分是 27 到 3527 在眉心下方、两眼之间偏上属于鼻梁起点30 是鼻尖最下端31 到 35 围绕鼻孔下缘分布33 是鼻小柱底部中心。这几个点的几何意义在侧脸姿态估计里很重要因为鼻尖是判断面部朝向的关键锚点。眼睛的 12 个点值得重点记。左眼是 36-4136 外眼角、37 上眼皮外侧、38 上眼皮内侧、39 内眼角、40 下眼皮内侧、41 下眼皮外侧。右眼是 42-4742 内眼角、43 上眼皮内侧、44 上眼皮外侧、45 外眼角、46 下眼皮外侧、47 下眼皮内侧。这个排列不是随意顺时针而是为 EAR 这类几何特征服务的——上下眼皮各有两个点眼角一左一右正好可以算“开合度”和“眼裂长度”。嘴唇的 20 个点是最密集的部分。外唇 48-59 涵盖整个嘴唇外轮廓48 是左侧嘴角、51 是上唇中央54 是右侧嘴角、57 是下唇中央内唇 60-67 是口腔内侧边缘。一个重要规律是外唇的上下对应关系是 51 对 57也就是说嘴巴张开时48-59 外轮廓会明显拉伸而内唇 60-67 在张嘴幅度大时才会露出。做嘴部开合检测时最常用的是 51 和 57 之间的欧氏距离。3.3 镜像左右问题这坑必须提前避68 点定义里的“左眼”“右眼”是以人脸自身的左右来命名的。正面面对镜头时图像中人脸的左侧在你看屏幕时其实位于右侧。dlib 官方文档里说的“left eye”指的是被检测人自己的左眼不是你在屏幕上看到的左边那只眼。这个镜像问题坑过很多人。你以为在写“左眼 EAR”实际算的是人脸右侧的眼睛如果正好用户微侧脸两只眼睛的 EAR 数值不对称阈值就永远调不对。我的建议是在代码里不要用 left_eye、right_eye 这种语义变量名直接用索引区间命名比如 eye_36_41、eye_42_47避免歧义。或者在注释里标明“left_eye 人物自身左眼 图像右侧”。4. 106点、108点与194点美颜产业催生的“业务友好型”点位体系4.1 68点在哪几个业务场景里不够用先说我实际遇到过的三个例子。第一个是瘦脸。68 点的下颌轮廓只有 17 个点左右下颌各约 8 个点用这些点做贝塞尔曲线拟合时下巴两侧的过渡不够平滑稍微加大形变强度就会出现棱角。第二个是美瞳贴图。贴图需要跟随眼球转动而眼球位置主要靠眼角推断68 点只有两眼角 4 个点对眼球中心的估计误差很大。第三个是唇彩试色68 点的内唇只有 8 个点外唇也只有 12 个点唇峰、下唇窝这些关键曲率位置完全缺少锚点。这些问题的共同根源是68 点是“语义够用”不是“几何够密”。工业产品要做像素级的形变和贴图必须加密点位。4.2 106/108点方案的设计思路106 点方案在国内厂商手里演化出了很多变体但大方向一致眉毛加密到各 6 点左右眼睛加密到各 8 点左右鼻子保持 9 个点上下嘴唇外沿加密到 12 个点以上脸廓部分则从 17 个点大幅增加到 40 多个点覆盖额头到下颌的完整边缘。这样分布的好处是脸型调整、额头贴花、下颌修容这类业务都有足够的锚点。108 点基本就是 106 点加两个瞳孔中心点。很多人以为瞳孔点是“额外福利”实际上它解决了一个实际问题——眼球中心是飘的随注视方向移动而眼角相对稳定。用两个瞳孔点配合眼角可以更准地估计眼珠朝向、计算瞳距美瞳贴图和视线估计都会受益。需要注意106/108 并没有一个全球统一的官方标准。不同厂商的索引顺序、起点方向、鼻子或嘴巴的加密位置可能都有差异。比如有的 SDK 从脸部左侧轮廓开始有的从下巴开始有的瞳孔点排在最后有的插在眼睛区域里。换 SDK 时如果直接沿用上一家的索引数组结果大概率是错乱的。所以用这类方案时第一件事永远是拉出厂商提供的点位图逐点核对。4.3 194点方案与“自定义标注”的现实194 点方案常见于高精度美颜和人脸编辑 SDK它最大的特点是脸部边缘和嘴唇区域极度加密。边缘点加密让脸型重塑可以做到“毫米级”平滑嘴唇区域加密则让唇彩试色、微笑弧度调整这类功能有了足够多的曲率锚点。但代价也很明显索引表复杂记忆成本高模型推理耗时也更长不适合低端手机上的实时视频处理。我自己的团队在做一个瘦脸业务时没有直接用现成的 194 点而是在 106 点的基础上去掉部分额头点、保留下颌和脸颊的加密点形成了一套 86 点的自定义方案。当时这么选一是因为 106 点模型已经跑得很稳换 194 点模型要重做性能优化二是因为瘦脸业务只关心下半张脸的轮廓额头点加密没有实际意义。这个例子是想说明关键点定义不是一个只能全盘接受的东西完全可以根据业务裁剪和扩展只要你能保证标注规范的一致性即可。5. 468点Face Mesh别把3D网格当成“更多数量”的2D关键点5.1 FaceMesh的468个点到底是什么MediaPipe Face Mesh 输出的 468 个点在形式上看起来是“人脸关键点”但在定义上跟 68/106 点有本质区别。它不是一个“语义关键点集合”而是一个稠密三维网格的顶点列表。这些顶点之间不仅有坐标还有固定的三角面连接关系共同构成一张可以渲染的人脸 mesh。另外 Face Mesh 还提供 478 点的版本多出来的是两个瞳孔中心点。与 108 点方案加瞳孔点的思路类似478 也是为了让眼部追踪更准。但这里瞳孔点是作为 mesh 的一部分写入跟随 mesh 形变而不是独立的关键点。5.2 “语义索引”与“拓扑顶点”的区别68 点方案里索引 39 永远表示左眼内眼角这是语义索引。但 468 点方案里某个顶点编号本身并不告诉你它是左眼内眼角你需要到官方提供的拓扑连接表里找到名为 LEFT_EYE 的顶点集合才能知道哪些顶点组成了左眼轮廓。这意味着从 68 点切换到 FaceMesh 时最容易踩的坑是凭直觉按编号找“眉毛”“眼睛”结果完全对不上。FaceMesh 的顶点编号是按 mesh 生成顺序编排的不是按脸部语义分区编排的。官方文档和社区开源代码里通常维护一组常量数组比如 FACE_OVAL、LIPS、LEFT_EYE、LEFT_EYEBROW、NOSE 等每个数组是一串顶点索引。使用前必须先加载这套分组定义不能肉眼凭编号判断。5.3 3D坐标带来新的可能性也带来新的注意点FaceMesh 每个点都有 x、y、z 三个坐标z 表示在相机坐标系下的深度。这带来的最大好处是能做姿态估计通过鼻尖、下巴、脸颊边缘的空间关系可以算出头部绕三个轴的旋转角度。这在 AR 贴纸场景里非常有用贴纸能随着头部转动做透视变换。但 3D 坐标的稳定性在纯视频流里未必比 2D 好。z 坐标对光照和遮挡更敏感在暗光、低头、侧光条件下z 值的抖动会比 x、y 明显得多。如果你要做“点头摇头”识别我建议对 z 序列做平滑滤波或者直接用 x、y 的相对位置变化来判断不要裸用原始 z 值。6. 把定义落到代码点位可视化、索引查询与自检套路6.1 绘制并标注68点索引号学习关键点定义最快的方式不是背表而是把自己的照片跑一遍把点和索引号同时画出来。以下代码用 dlib 和 OpenCV 完成这个任务保存一张带索引标注的图片import cv2 import dlib 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, 1) for face in faces: shape predictor(gray, face) for i in range(68): x, y shape.part(i).x, shape.part(i).y cv2.circle(img, (x, y), 2, (0, 255, 0), -1) cv2.putText(img, str(i), (x 2, y - 2), cv2.FONT_HERSHEY_SIMPLEX, 0.3, (0, 0, 255), 1, cv2.LINE_AA) cv2.imwrite(landmark_index_68.jpg, img) print(done, check landmark_index_68.jpg)运行之后你会得到一张布满绿色圆点和红色数字的图。我的建议是第一遍先把这张图和前文的分区表对照着看每看一个区域就在图上找到对应的点第二遍遮住分区表只看图尝试说出某个索引所在的物理区域。两遍下来68 点的索引基本就刻在脑子里了。这张图还有一个用途业务联调时的沟通参考。不管是和标注团队确认点位还是和产品经理对齐“这一步动的是鼻子附近”有一张带索引的图比写一堆文字描述高效得多。6.2 交互式索引查询工具静态图适合学习但做开发时更想要的是“鼠标点到哪就显示这个点是什么部位”。用 OpenCV 的鼠标回调函数可以很快实现一个交互工具import cv2 import dlib detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(shape_predictor_68_face_landmarks.dat) image cv2.imread(face.jpg) gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) shape predictor(gray, detector(gray, 1)[0]) points [(shape.part(i).x, shape.part(i).y) for i in range(68)] def on_mouse(event, x, y, flags, param): if event cv2.EVENT_MOUSEMOVE: for idx, (px, py) in enumerate(points): if abs(px - x) 5 and abs(py - y) 5: print(findex{idx}, pos({px}, {py})) cv2.namedWindow(landmark query) cv2.setMouseCallback(landmark query, on_mouse) while True: cv2.imshow(landmark query, image) if cv2.waitKey(1) 0xFF ord(q): break cv2.destroyAllWindows()这段代码不复杂但它解决了一个实际问题当你在 SDK 文档里看到一个索引号又想知道它到底对应哪里时不用反复跑整条推理链路直接移动鼠标就能查。我每次接入新的人脸 SDK都会先写这样一个几十行的小工具把输出点位置和索引打印出来效率提升非常明显。6.3 给“点位理解”做一个冒烟测试可视化只能让你“看到”点要验证你是否真的理解了定义可以跑三个冒烟测试。第一个测试是眨眼睁眼时计算左眼 EAR闭眼时再算一次如果睁眼时数值比闭眼时大说明眼睛区域索引取对了。第二个测试是张嘴计算外唇 51 和 57 之间的距离张嘴时距离变大说明嘴唇上下索引对应正确。第三个测试是侧脸人脸向左转时左侧轮廓点0 到 4 附近的 x 坐标会向右移动如果移动方向相反说明左右定义理解反了。这三个测试覆盖了眼、嘴、轮廓三个最容易出错的位置。如果每换一套方案都先跑这三个测试后面写业务逻辑时就能省掉大量 Debug 时间。我在团队里要求所有新接手人脸项目的同学必须完成这一步很多人一开始觉得多余后来都被这三个测试救过。7. 按业务选型不同场景该认准哪几个索引7.1 场景-方案-核心索引对照表到了实际项目里选哪套关键点方案取决于你要解决什么业务问题。下面这张表是从实战角度做的梳理索引号以 68 点方案为例其他方案需要换成对应的点位图。业务场景推荐方案最常用的索引/定义眨眼检测、活体人脸68点或106点左眼 36-41、右眼 42-47或对应眼皮序列微笑/张嘴检测68点或106点外唇 48-59、内唇 60-67重点是 51 与 57 距离瘦脸/脸型微调106点或194点下颌轮廓序列68 点方案建议补充额外轮廓点美瞳/眼妆贴图106点或108点上下眼皮序列和瞳孔中心点AR贴纸/表情驱动468点 Face Mesh对应 FACE_OVAL、LEFT_EYE、LIPS 等拓扑分组头部姿态估计68点或468点鼻尖30号与下巴尖8号的相对位移7.2 选型不是点数越多越好很多人容易陷入“点数越多越高级”的误区。实际上关键点方案的选择要同时考虑三个维度业务适配度、推理速度、点位稳定性。106 点的推理耗时通常比 68 点高但在手机上依然可接受194 点则在低端机上做实时视频处理会比较吃力468 点 FaceMesh 虽然点数最多但它借助的是 MediaPipe 优化过的 GPU 推理管线在移动端的实测速度反而可能比纯 CPU 推断的 194 点快不少。还有一个经常被忽视的指标是“点位时序稳定性”。同一个点在第 10 帧和第 11 帧之间如果跳动超过几个像素再密的点也白搭。我接触到的一些商业 SDK虽然对外宣传点位数很高但在暗光下部分轮廓点会轻微漂移这时就要做好时间轴上的平滑滤波。选择方案之前最好先拿几个真实场景的视频片段做稳定性测试而不是只看宣传页上的点数指标。最后说一个个人很受用的习惯每当接入一套新的关键点方案我会把它的点位索引表和分区说明放到代码仓库的 docs 目录下并在业务代码里为每个用到的索引区间写注释标明“这段索引代表什么物理位置”。这样一个月后自己回来看代码或者同事接手时都不需要重新对着 SDK 文档猜半天。人脸关键点这个领域方案繁多、定义各异真正陪你走远的不是记住了多少索引号而是形成了一套快速理解和验证任意新定义的方法论。