dlib+OpenCV 68点人脸关键点检测实战:环境搭建与代码解析 简介面向计算机视觉的进阶学习场景这份资料演示了如何以dlib、OpenCV和Python为基础对眼睛、鼻子、嘴唇、下巴等面部关键点进行定位与绘制。配套的Python脚本可直接运行shape_predictor_68_face_landmarks.dat模型文件支持提取68个面部特征点PDF文档则对实现思路和参数做了讲解便于对照学习。资源共4个文件涵盖py源码、dat预训练模型、pdf说明和jpg效果样例整体约70.27MB结构紧凑、即下即用。当前已有1633人学习下载适合在基础人脸检测后希望深入理解人脸关键点原理与代码实现的读者用来做课堂作业、项目原型或二次开发参考。1. 人脸检测进阶从人脸框到 68 个五官关键点人脸检测做到后半程你会发现一个矩形框远远不够用。框住整张脸之后还要知道眼睛在哪、鼻子在哪、嘴唇和下巴的轮廓怎么走才能继续做疲劳检测、美颜贴纸、表情分析和姿态估计。这份资源就是往这个方向走一步用 dlib 的 68 点人脸关键点模型配合 OpenCV 做图像读取与绘制Python 脚本 detect_face_parts.py 把眼睛、鼻子、嘴唇和下巴分别检出并可视化。资源包里除了脚本还带了预训练的 shape_predictor_68_face_landmarks.dat 和一份 PDF 图文说明适合已经跑通过人脸检测、想深入关键点定位的开发者。整份资源不依赖 GPU一台普通 CPU 机器就能复现这也是我推荐先拿它入门关键点检测的原因。2. 环境搭建让 Python、OpenCV 和 dlib 在一个环境里跑通很多人拿到这份资源第一反应是直接跑 detect_face_parts.py结果卡在环境上。dlib 的安装体验一直不算友好尤其在国内网络环境下经常编译到一半中断OpenCV 则相对温和但也会出现装了 cv2 还是 import 不上的情况。我建议先把环境装成「可复现」的样子再碰代码这样后面所有排查都有边界。2.1 为什么是 dlib OpenCV Python 这套组合先解释一下这套组合的分工OpenCV 负责读图、写图、画点和画轮廓是典型的图像处理底座dlib 负责两件事一是用内置的 HOG 检测器找出人脸框二是用预训练的 68 点模型回归出五官关键点。Python 在这中间做胶水把检测框传给 predictor再把 68 个点按索引拆给眼睛、鼻子、嘴唇和下巴。dlib 的 68 点模型本质上是基于残差树的级联回归器不是深度卷积网络。这意味着它在普通 CPU 上也能跑得动单张 640 分辨率的图片大概几十毫秒这对很多实时场景是够用的。相比 mediapipe 这类方案dlib 的依赖更传统模型文件就是一个 dat部署时拷贝文件就能用相比 OpenCV 自带的人脸检测器dlib 的 HOG 检测器在正脸场景下召回率更高而且关键点模型是配套的不需要自己再写对齐。OpenCV 内部其实也提供了基于 Haar 或 DNN 的人脸检测器但 Haar 容易在光线变化大时漏检DNN 又要多下载模型权重。dlib 的 HOG 加线性分类器介于两者之间速度和精度都够用最关键的是一套 API 同时覆盖检测和关键点代码结构干净。你在网上搜 opencv 图像处理项目大部分教学代码也是这套组合原因无它能落地。还有一个容易忽略的点这套方案不依赖 GPU。很多人一听到人脸检测就以为要深度学习训练其实 dlib 的 68 点模型是传统回归方法CPU 单帧处理速度足够这也意味着你可以在没有显卡的机器、甚至一些嵌入式板子上复现。2.2 安装步骤从 Python 环境到 dlib 编译我一般会先建一个干净的虚拟环境避免把系统 Python 弄乱。你如果已经装了 Anaconda直接在 Anaconda Prompt 里执行下面这段conda create -n faceparts python3.8 -y conda activate faceparts pip install opencv-python pip install cmake pip install dlib逻辑说明第一步创建 Python 3.8 的独立环境避免和别的项目相互污染第二步安装 OpenCVopencv-python 这个包名会同时提供 cv2 模块第三步安装 cmake因为 dlib 在多数平台上是源码编译安装cmake 是它的构建系统第四步安装 dlib。整个过程里最容易翻车的是 dlib在 Windows 上需要 Visual Studio 的 C 生成工具在 Linux 上需要 g 和 make。安装命令里有个顺序值得注意先装 opencv-python再装 cmake最后装 dlib。cmake 放在 dlib 之前是为了让 dlib 的构建系统能找到路径如果把 cmake 放在最后虽然也能装成功但不是最优。此外如果遇到pip install dlib下载速度极慢可以换国内 PyPI 镜像源比如pip install -i https://pypi.tuna.tsinghua.edu.cn/simple dlib。这不算绕开问题只是把网络因素排除掉。如果你用的是 Linux 系统先执行apt install build-essential cmake再 pip install dlib如果用的是 Windows安装 Visual Studio Build Tools 时勾选「使用 C 的桌面开发」那一项。这些准备做完dlib 编译会顺畅很多。版本选择上opencv-python 4.x 系列都能用dlib 选当前时间点的稳定版即可。get_frontal_face_detector 和 shape_predictor 这两个函数从 19.10 到 19.24 都保持兼容你不需要为了这个资源特意降级 Python3.8 到 3.10 都可以只要 dlib 能编译通过。安装完成后用下面这段验证环境和模型文件python -c import cv2; print(cv2, cv2.__version__) python -c import dlib; print(dlib, dlib.__version__) python -c import os; print(os.path.exists(shape_predictor_68_face_landmarks.dat))第三行如果输出 False说明你的工作目录不对或者模型文件还没放进来。常见问题里还有一种「anaconda prompt 里面没有 opencv」其实就是当前 conda 环境没有安装或者激活错了环境回上一步检查即可。如果 pip 编译 dlib 一直失败另一个常见做法是走 conda 的预编译包conda install -c conda-forge dlib。这个通道准备了大多数平台的二进制能绕开本地编译缺点是环境依赖由 conda 管理和你后续用 pip 装的包需要保持版本协调。我自己的习惯是优先 pippip 不通再 conda因为 conda-forge 的 dlib 更新节奏偏慢。2.3 跑通前先确认资源包结构资源包拿到手先看有没有这几个文件detect_face_parts.py 是主脚本shape_predictor_68_face_landmarks.dat 是预训练模型11.jpg 是示例图PDF 是图文说明。把这四个文件放到同一个目录再执行python detect_face_parts.py脚本里默认读 11.jpg如果成功会在窗口里弹出或者保存一张带五官标记的结果图。这里有一个细节dlib 模型文件路径是相对路径还是绝对路径决定了你在哪个目录下运行。我一般会把工作目录切到资源包所在目录再敲 python 命令能少踩路径坑。提示如果脚本运行结果和你预期不符先确认当前目录下os.path.exists(shape_predictor_68_face_landmarks.dat)是否为 True模型文件路径问题占了这类报错的七成。PDF 文档我建议按照脚本执行的顺序对照着看它里面把每一步的输入输出都截图了尤其是 11.jpg 的处理结果可以作为你复现成功与否的基线。如果跑出来的图和 PDF 里的示例差异很大基本可以断定是环境或者模型文件的问题而不是代码逻辑的问题。环境到这里就绪接下来进入正题detect_face_parts.py 到底怎么把 68 个点分给五官。3. 核心代码解析detect_face_parts.py 的检测流程与五官划分3.1 68 点模型的坐标分布dlib 输出的 68 个关键点不是随机的它们严格遵循一套固定顺序这套顺序由训练时使用的标注规范决定最常被引用的是 iBUG 300-W 数据集的标准。每个索引落在面部某个固定解剖位置上。理解这个顺序比死记代码重要因为你后面做眼睛闭合检测、嘴巴开合判断都要按这个索引去取点。对应关系如下部位索引范围点数说明下巴轮廓0-1617从左侧下颌角到右侧下颌角包含下巴尖左眉17-215左眼上方眉骨右眉22-265右眼上方眉骨鼻梁27-304从眉心到鼻尖鼻底与鼻孔31-355鼻孔下缘和两侧左眼36-416左眼轮廓右眼42-476右眼轮廓外嘴唇48-5912唇部外轮廓内嘴唇60-678上下唇交界线眼睛的 6 个点顺序是从眼头到眼尾再折回可以简单理解为一个六边形唇部的 12 个外点加 8 个内点正好覆盖嘴唇内外两圈。下巴的 17 个点从脸颊一侧绕到另一侧如果你只想取下巴尖可以直接用索引 8。在实际代码里prediction 返回的 shape 对象支持part(i)方法得到的是 dlib.point包含 .x 和 .y 两个属性。把这两个值取出来就是你在图上画点的坐标。3.2 主流程加载模型、检测人脸、预测关键点detect_face_parts.py 的骨架是这样的import dlib import cv2 detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(shape_predictor_68_face_landmarks.dat) img cv2.imread(11.jpg) rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) faces detector(rgb, 1) for face in faces: shape predictor(rgb, face) for i in range(68): x shape.part(i).x y shape.part(i).y cv2.circle(img, (x, y), 1, (0, 255, 0), -1) cv2.imwrite(output.jpg, img)逻辑说明detector 返回的是 dlib.rectangles一个矩形列表即使图中只有一张脸也要用 for 循环遍历。每个 face 是 dlib.rectangle用来限定关键点回归的搜索区域。predictor 接收两个参数第一是 RGB 图像第二是人脸矩形返回 68 个点。参数说明detector(rgb, 1)的第二个参数是金字塔上采样次数1 表示把图像放大一倍后再检测能发现更小的人脸代价是耗时更长追求速度时改成 0。cv2.imread读取的图是 BGR 顺序而 dlib 内部按 RGB 处理所以要先cvtColor否则最后的绘制结果会出现颜色错乱。这段代码画的是一个个点效果是绿色小圆点铺满面部轮廓。如果 faces 为空说明这张图里没有检出人脸。你可以先确认图片里确实有人脸再确认上采样次数。另一个隐藏因素是图片亮度HOG 检测器对光照比较敏感太暗或者太亮都会被漏检这时候先做一下直方图均衡化再检测效果会改善。3.3 按五官分组把 68 个点画成不同颜色的轮廓直接画点不够直观把点按部位组合起来才是关键。先定义索引字典import numpy as np FACE_PARTS { jaw: list(range(0, 17)), left_eyebrow: list(range(17, 22)), right_eyebrow: list(range(22, 27)), nose: list(range(27, 36)), left_eye: list(range(36, 42)), right_eye: list(range(42, 48)), mouth: list(range(48, 68)), }然后对每个部位取点转成 numpy 数组用 convexHull 得到凸包再绘制for name, idxs in FACE_PARTS.items(): pts np.array([(shape.part(i).x, shape.part(i).y) for i in idxs], dtypenp.int32) hull cv2.convexHull(pts) cv2.drawContours(img, [hull], -1, (0, 255, 0), 1)这里 convexHull 做的事情是把离散点包成一个封闭多边形画出来就是一条包围该部位轮廓的线。参数说明第一个参数必须是二维点集numpy 的 shape 是 (n, 1, 2) 或 (n, 2) 都行dtype 要能转成 int32。每个部位画一种颜色可以这样区分眼睛画蓝色嘴唇画红色鼻子画黄色下巴画绿色。改法就是给 drawContours 传不同 RGB 值。这样一来输出的图片上五官边界一目了然这也是资源包里示例图 11.jpg 对应效果的来源。这个分组字典还可以继续扩展。比如想单独提取眼睛区域做后续识别可以用 boundingRect 得到外接矩形x, y, w, h cv2.boundingRect(pts) roi_img img[y:y h, x:x w] cv2.imwrite(f{name}.jpg, roi_img)参数说明boundingRect 返回外接矩形的左上角坐标和宽高roi_img 就是裁出来的局部图。这个技巧在保存眼睛和嘴唇区域时很常用下一步可以喂给更精细的分类模型。注意nose 的索引 27 到 35 是一整段鼻梁加鼻孔如果你想单独看鼻尖可以只取 30 到 35或者只取 27 到 30。实际业务里鼻梁线更常用于姿态估计鼻孔线更常用于口罩佩戴检测所以这个拆分不是死的。到这里detect_face_parts.py 的核心链路已经清楚了。剩下的问题在于不同图片、不同摄像头画面里这套检测的稳定性能到什么程度这就是下一章要处理的参数与边界问题。4. 参数与边界把检测器调到适合你的场景4.1 检测器参数上采样次数和输入尺寸怎么权衡dlib 的 HOG 人脸检测器只有一个明显的旋钮upsample_times。0、1、2 对应不同的精确度和耗时。可以用一个简单实验来测同一张多人图分别用 0、1、2 跑一遍看检测到的人脸数量和耗时。upsample_times能检测的最小人脸单帧耗时适用场景0较大约 80 像素以上最低摄像头距离近人脸占比大1中等约 40 像素以上中等通用场景推荐2较小约 20 像素以上较高拥挤场景需要召回小脸实际调参时我通常先固定为 1如果发现漏检很多小脸再升到 2如果发现 CPU 占用过高就换回 0。注意 upsample_times 越大金字塔层数越多但超过 2 之后的收益很小速度却成倍下降所以这个参数不是越大越好。另一个常见调整是输入图片尺寸。dlib 的检测器内部是 80x80 的滑动窗口如果你传入的图是 4000x3000 的高清图检测窗口要滑几百万次非常慢。常见做法是先把边长缩到 800 到 1000 像素再送进 detector关键点坐标再按缩放比例映射回原图。这一步很多人会漏导致「检测结果对不上原图」的错觉。想量化耗时可以加一行计时import time start time.time() faces detector(rgb, 1) print(fdetect time: {(time.time() - start) * 1000:.1f} ms)逻辑说明time.time 返回的是秒乘 1000 转成毫秒。这个打印结果能直接告诉你参数调整对性能的影响而不是靠感觉猜。4.2 正脸与侧脸边界在哪里dlib 的 68 点模型训练数据以正脸和轻微偏转为样本所以它对正脸的精度最高。我实测的感受是左右偏转 45 度以内基本稳定超过 45 度后人脸框还能检测到但关键点会逐渐往边缘漂移尤其是远离相机的那只眼睛六个点会挤在一起低头和抬头超过 30 度鼻子和嘴唇的垂直位置误差也会明显变大。遮挡也是个现实问题。戴眼镜时眉骨附近的关键点会轻微向镜框方向偏移但眼睛本身影响不大戴口罩会把鼻子和嘴唇整块盖住这部分点基本就没法用了。如果你做的是防疫相关的检测最好只依赖眼睛和眉毛的点放弃 27 到 67 这些下半脸索引。这也能看出 68 点模型的一个结构性弱点它假设人脸是可分的一旦下半脸被遮挡数据缺口直接导致回归结果失真。处理这种边界情况我一般会加一个前置步骤先估算脸的偏转角度超过阈值就放弃关键点或者用旋转镜像来获取另一侧特征。很多商业方案用 3DMM 或深度学习关键点网络解决但这套资源的目标是轻量 CPU 方案所以边界清晰反而是优点——你知道它什么时候可靠。另外小分辨率人脸是另一个雷区。如果人脸宽度小于 60 像素68 点回归的精度会断崖式下降眼睛和嘴唇容易出现交叉。解决办法是提高上采样次数或者在检测前对局部区域做放大而不是直接对整张大图跑。4.3 从关键点坐标到业务指标检测出五官坐标后下一步通常是把坐标变成判断。一个很常见的指标是眼睛纵横比用来判断眼睛是否闭合公式是把左眼六个点中竖直方向的两对点和水平方向的一对点取欧氏距离竖距的平均值除以横距。实现如下def eye_aspect_ratio(eye_pts): vertical_1 np.linalg.norm(eye_pts[1] - eye_pts[5]) vertical_2 np.linalg.norm(eye_pts[2] - eye_pts[4]) horizontal np.linalg.norm(eye_pts[0] - eye_pts[3]) return (vertical_1 vertical_2) / (2.0 * horizontal)参数说明eye_pts 的顺序就是 36-41 或 42-47 的顺序0 和 3 是眼头和眼尾1/5、2/4 是上下眼皮对应点。这个值在睁眼时稳定在 0.3 左右闭眼时会降到 0.1 以下所以作为疲劳判断的阈值非常有效。类似的指标可以算嘴部开合度。取外嘴唇上下各两个点比如索引 51 和 57计算欧氏距离再除以两眼瞳孔的距离作为归一化。这个值在说话和打哈欠时有明显峰值可以用来做简单的说话检测。关键点是所有指标都来自 68 点坐标但直接把原始坐标用于判断很脆弱归一化到面部尺度后才能跨人比较。从资源包的角度看detect_face_parts.py 把坐标提取这一步做好你已经可以自己去接各种业务判断了。第 5 章说的坑几乎都是我在接这些业务时实际遇到的。5. 避坑手册dlib 编译、模型加载和环境错乱的五个教训这些坑我基本都踩过一遍按「现象 → 原因 → 解决」的顺序写方便你对照。5.1 dlib 编译失败CMake 报错现象pip install dlib执行到一半终端刷出一堆红色报错常见的有Failed to find CMake或error: could not find Visual Studio。原因dlib 从源码编译时需要完整的 C 工具链。Windows 上如果没有安装 Visual Studio 的 C 生成工具cmake 找不到编译器Linux 上如果缺少 g 和 make同样会中途失败。解决Windows 先安装 Build Tools勾选「使用 C 的桌面开发」装好后重启终端再 pip install dlibLinux 执行sudo apt install build-essential cmake。如果实在不想碰编译用conda install -c conda-forge dlib直接拿预编译包这一步能绕开九成编译问题。注意优先 pip、管道不通再 conda顺序不要反过来否则后续在纯 pip 项目里容易出现环境混乱。5.2 模型文件加载报错RuntimeError现象运行时弹出RuntimeError: Unable to open shape_predictor_68_face_landmarks.dat或者提示文件不存在。原因shape_predictor 的路径是相对路径Python 进程的当前工作目录和资源包目录不一致。尤其你在 IDE 里直接点运行IDE 的工作目录可能停在项目根目录而不是资源包目录。解决最稳妥的方法是把路径写成绝对路径或者在脚本开头用os.chdir切到模型所在目录。我自己的习惯是在调用 shape_predictor 之前先用os.path.exists检查一次不存在就直接打日志退出避免后面出现难懂的报错堆栈。你也可以在资源包目录下新建一个 test.py写from detect_face_parts import detector来复用省得每次改路径。5.3 import 报错No module named cv2 或 dlib 不是预期模块现象import cv2提示模块找不到或者import dlib后调用 get_frontal_face_detector 时提示 attribute error。还有人会写import opencv然后看到No module named opencv一脸疑惑。原因前者通常是当前 Python 环境和你 pip 安装的环境不是同一个。比如在 Anaconda Prompt 里激活了 base 环境却在另一个终端里装了包。后者常见原因是项目目录下存在一个名为 dlib.py 或 cv2.py 的自定义文件Python 导入时优先找到了这个同名文件导致真正的库被遮蔽。至于 import opencv 报错是因为 OpenCV 的 Python 模块名是 cv2不是 opencv包名和模块名不一样。解决先执行python -c import sys; print(sys.executable)确认解释器路径再执行pip list | grep dlib看包是否真的装进了当前环境。如果确认存在同名文件把项目里的 dlib.py 改名或删除。这个坑在初学者里出现频率极高因为很多人喜欢把代码文件命名成库名。5.4 检测结果颜色怪异皮肤变蓝背景偏红现象关键点位置看起来是对的但整张图的颜色像滤镜一样异常人脸皮肤发蓝背景发红。原因OpenCV 的imread返回 BGR 顺序而 dlib 检测器内部按 RGB 处理图像内容。虽然检测框坐标不受影响但你后面如果对 RGB 图做了画框再写回原图或者用 matplotlib 显示就会看到明显颜色偏差。解决统一做一次颜色空间转换。读图后立即cv2.cvtColor(img, cv2.COLOR_BGR2RGB)给 dlib 用绘制仍在原 BGR 图上进行显示时再转回 RGB。你可以做个实验把转换去掉直接跑一遍立刻能看到反差。这个问题的隐蔽之处在于它不影响检测坐标只影响显示效果所以很多人以为是哪里的参数写错了其实是通道顺序的经典坑。5.5 小图和侧脸关键点漂移现象人脸能框出来但关键点落在眉毛上、眼睛跑到脸颊上或者嘴唇轮廓严重变形。原因人脸区域过小或者姿态偏转过大。dlib 的回归模型对最小可检测人脸宽度有隐式要求一般小于 60 像素就开始不稳定侧脸时另一半脸的标注在训练集里分布不均回归结果自然往中间塌。解决对输入图片做一次预处理把人脸区域裁剪放大后再传给 predictor或者提高 detector 的上采样次数。如果业务场景是大侧脸、低头、遮挡严重建议换用专门的姿态鲁棒模型dlib 这套适合「正脸 分辨率足够」的轻量场景。资源包里没有解决这类问题的魔法理解它的边界比硬调参数更重要。6. 进阶验证把静态检测改成实时视频流并确认资源可复现拿到资源包后我建议第一次跑通先不做任何修改按默认参数跑示例图 11.jpg。看到输出图里五官被不同颜色轮廓标出来再开始改。如果你想做的是实时摄制视频的人脸检测与标注把静态脚本改成视频流就是下一步。一个比较可靠的验证顺序是先单张图再连续视频最后换你自己的图片测边界。改成视频流其实只需要把读取图片的代码换成 VideoCapture 循环cap cv2.VideoCapture(0) while True: ok, frame cap.read() if not ok: break rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) faces detector(rgb, 1) for face in faces: shape predictor(rgb, face) # 在这里复用 3.3 的绘制逻辑 cv2.imshow(face parts, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()参数说明VideoCapture(0) 表示打开第一个摄像头按 q 退出循环。注意摄像头读入的 frame 默认就是 BGR绘制直接用 frame传给 dlib 前再转 RGB。实时视频里 upsample_times 建议改回 0保证帧率如果是 1080p 的画面可以先把帧缩到 640 宽再送检关键点坐标再映射回原图。这套脚本跑通后你才算真正把这台机器的环境、模型、代码全部建立起来了。从那以后我每拿到一个人脸检测资源都会强制走一遍「单张图 → 摄像头流 → 换自己的图看边界」的验证流程问题定位快很多。有时候资源本身没问题是运行环境没对齐这个流程能帮你把变量隔离开。希望帮到你。本文还有配套的精品资源点击获取