简介:一个面向人脸关键点检测的轻量级模型包,源自insightface项目,基于MXNet实现,可对二维图像中的人脸定位106个关键点,覆盖眼睛、眉毛、鼻子、嘴巴、脸颊等部位,适合面部识别、表情分析、姿态估计及AR/VR面部追踪等场景。压缩包共2个文件:模型结构文件(json)描述神经网络各层与连接方式,权重参数文件(params)存储训练后的参数,二者配合可在MXNet环境中完成加载与推理。整个资源仅4.43MB,结构简洁,便于快速集成到现有视觉流程中。开发者可通过此资源了解轻量级关键点检测模型的架构组织,并直接用于人脸对齐、美颜特效等应用开发,省去从零训练的繁琐过程。目前已有1522人学习下载,适合计算机视觉入门者及需要高精度人脸关键点输出的工程师作为参考。 很多搞人脸方向的朋友应该都有过这种经历:好不容易在网上找到一个看起来挺靠谱的模型包,下载下来是一个2d106det.zip,双击一解压,要么报invalid zip archive: could not find eocd,要么解出来一堆奇奇怪怪的文件夹,兴致直接被浇灭一半。我最近在做实时人脸特效项目,需要106点人脸关键点,正好就是从这样一个压缩包开始的。这篇文章就聊聊我从这个zip包到真正跑通检测流程的全过程,包括那些很多人不会写进文档里的坑。
先说结论:2d106det.zip这类文件,通常不是单纯的“解压就能用”的权重包,它里面往往混合了模型结构定义、预训练权重、示例脚本甚至是对应框架的版本依赖信息。能不能顺利跑起来,一半看解压环节运气,另一半看你对运行环境的理解。下面我会把整个过程拆开,一步一步说清楚。
1. 这个压缩包的真实身份:2D106点人脸关键点检测模型
1.1 为什么是106个点
人脸关键点检测目前常见的有五个点、68点、81点、106点、240点、468点等不同方案。106点的体系在国内很多实时美颜、贴纸、人脸特效项目里用得非常多,因为它既保持了关键点的稠密度,又不像468点那样需要极其庞大的模型才能维持实时性。106点主要覆盖了眉毛、眼睛、鼻子、嘴巴、脸部轮廓等区域,对于常规特效贴纸、表情识别、视线估计来说已经足够了。
从命名上拆解,2d106det可以理解为“2D 106-point detection”,也就是说这个模型输出的是二维坐标下的106个关键点,而不是带深度的3D关键点。你拿到这个zip包,本质上是拿到了一种“从人脸区域的图像到106个坐标”的映射能力。
1.2 包内通常包含什么
我之前在网上找到的这个包,解压后大致结构是这样的:
2d106det/ ├── models/ │ ├── 2d106det.onnx │ ├── 2d106det.pth │ └── face_detector/ ├── utils/ │ ├── align.py │ └── draw.py ├── demo.py ├── requirements.txt └── README.md不同来源的包结构可能差异很大,但核心东西一般逃不出这几类:
- 模型权重文件:常见格式有
.onnx、.pth、.caffemodel、.tflite、.rknn等,对应不同的推理框架。 - 人脸检测器:因为106点模型本身通常不负责找脸,它需要先有人脸框,再对框内区域做关键点回归。所以包里往往还会带一个轻量级的人脸检测模型,比如 RFB、SCRFD、RetinaFace 的轻量版本。
- 对齐和绘制工具:用于把模型输出的坐标映射回原图,以及可视化。
- 示例代码和依赖清单:这个是重点,很多人在这一步翻车。
1.3 与其他关键点方案的对比
我拿常见的68点和468点做个对比,方便你判断这个包是否适合自己项目:
| 方案 | 点位数 | 典型用途 | 模型体积 | 实时性 | 难度 |
|---|---|---|---|---|---|
| 68点 | 68 | 传统表情识别、基础贴纸 | 较小 | 优 | 低 |
| 106点 | 106 | 实时美颜、特效贴纸、视线估计 | 中等 | 优 | 中 |
| 468点 | 468 | 高精度人脸重建、AR面具 | 较大 | 中 | 高 |
106点最大的优势是处于“精度和性能平衡点”。468点的拓扑复杂度高,在高通骁龙或者边缘设备上跑实时推理要费不少劲;68点又太稀疏,做眼睛周围这种细粒度贴纸容易穿模。
2. 解压安装:一次典型的zip文件问题排查
2.1 解压报“could not find EOCD”的真相
我一开始解压这个文件时,直接在系统自带解压工具里双击,结果弹出一个很让人崩溃的提示:
Archive: 2d106det.zip error: invalid zip archive: could not find EOCD这句话的意思是,zip文件末尾需要一个End of Central Directory结构,也就是EOCD记录,它相当于整份zip文件的目录索引和结尾标志。如果解压工具找不到这个标志,就会认为文件不完整或者根本不是合法的zip。
出现这个问题的原因通常有两个:
- 文件下载不完整:服务器传输中断,或者浏览器断点续传出错,导致文件尾部缺失。你可以先看一下下载下来的文件大小,和页面上标注的字节数是否一致。
- 文件本身被包装过:有些资源站的zip不是标准zip,而是在尾部追加了文件尾巴,或者做了自定义加密壳。这种情况用常规解压工具解析不到标准EOCD,就容易误报。
我当时把文件下载了三遍才排查出是下载源的问题。后来我发现一个更稳妥的办法:先检查文件完整性,再解压。
# macOS/Linux shasum -a 256 2d106det.zip # Windows PowerShell Get-FileHash .\2d106det.zip -Algorithm SHA256拿到哈希之后,和下载页面上的哈希对比。如果网站没给哈希,至少用右键“压缩文件预览”看能不能看到条目。如果工具能预览到目录却无法解压,大概率是文件尾部问题,可以尝试用 7-Zip 的“打开压缩包”强制读取,然后逐个文件拖出来。
2.2 分卷压缩包和文件名乱码的处理
我见过不少用户跑来问“解压提示必须有下列压缩分卷 z01”,这其实说明你拿到的不是完整zip,而是分卷压缩的某个分卷(如2d106det.z01、2d106det.zip)。你需要把同一套分卷全部放在同一个目录,并且不能改名,然后再对.zip那个文件执行解压。
更隐蔽的一个坑是文件名乱码。比如你是Windows用户,压缩包是用macOS或者Linux打包的,里面中文文件名很可能在你的系统上显示为日文或乱码。尤其某些包里封装了韩文、日文命名的人脸样本图,解压后文件名直接变成�Ѫ.jpg这种。这时候不需要去找“乱码修复工具”,用支持编码识别的解压工具就好。我自己的做法是:
- 如果只是临时查看:用 7-Zip 打开,选项里设置“列表编码”为 UTF-8。
- 如果是要长期使用:先把整个zip解压到临时目录,然后在终端里用
python的zipfile模块手动解压,并显式指定文件名编码。
import zipfile import os zip_path = "2d106det.zip" target_dir = "2d106det_extracted" os.makedirs(target_dir, exist_ok=True) with zipfile.ZipFile(zip_path) as zf: for info in zf.infolist(): # 尝试把文件名强制解析为 UTF-8,失败则使用 cp437 try: raw = info.filename.encode('cp437') filename = raw.decode('utf-8') except Exception: filename = info.filename target_path = os.path.join(target_dir, filename) os.makedirs(os.path.dirname(target_path), exist_ok=True) with zf.open(info) as src, open(target_path, 'wb') as dst: dst.write(src.read())这样能规避大多数因编码标记缺失导致的乱码问题。
2.3 目录结构整理与直接可用性评估
解压出来之后,别急着运行demo。先做三件事:
- 确认
requirements.txt是否存在,存在就打开看一眼里面框定的版本范围。 - 确认模型权重文件是否存在,且大小不为0。有些网盘分享的包里,模型文件被删除或替换成了下载链接文档。
- 确认
demo.py或其他示例脚本的入口参数。
如果包里的模型文件是.pth这种PyTorch权重,而你的项目里用的是TensorFlow,那这个包就只能提供“参考价值”,不能直接用。遇到这种情况,我会去搜一下这个源码仓库是否提供了ONNX或者TensorRT的导出版本。实在没有,就得自己在本地搭PyTorch环境,把权重转换成目标格式。
3. 部署环境与依赖关系:比解压更值得注意的事
3.1 Python和推理框架版本匹配
模型能用,不代表代码一定能跑。很多开源人脸模型是老项目,它的依赖可能还停留在torch==1.7.0、opencv-python==4.5.*这种版本。你要是直接装最新版PyTorch,有大概率会在加载模型时遇到:
KeyError: 'unexpected key in state_dict: ...'或者:
RuntimeError: Error(s) in loading state_dict for Module:这是因为权重是在旧版本模块定义下训练产生的,旧模型的state_dict里的键名和最新库的模块定义对不上。解决方案不是硬解,而是在项目里用精确版本锁定环境:
conda create -n face106 python=3.8 conda activate face106 pip install -r requirements.txt如果requirements.txt缺失或者没写版本,建议去看压缩包里的模型结构代码。若模型是.pth且结构由torch.nn.Module定义,那么相同的模块定义必须和训练时完全一致,哪怕改变了一个层的命名,都会加载失败。
3.2 模型权重文件的格式识别
models目录下如果有一个.onnx文件和多个.pth文件,优先用.onnx。原因很简单:ONNX是计算图的一种通用交换格式,不依赖PyTorch或者TensorFlow这类特定训练框架,只要你有一个ONNX Runtime就能推理。而且ONNX的部署路径通常更干净,不会遇到模型代码版本不一致的问题。
如果只有一个.pth文件,也先不要太担心。用下面的代码可以快速看这个权重文件是否包含优化器状态(即训练中断后保存的checkpoint,含额外状态,需要特殊处理):
import torch ckpt = torch.load("2d106det/models/2d106det.pth", map_location="cpu") print(type(ckpt)) if isinstance(ckpt, dict): print(ckpt.keys())如果打印出来的是{'state_dict': ..., 'optimizer': ...}这样的字典,说明它不是一个可直接推理的模型,而是训练时保存的checkpoint。你需要提取state_dict,然后配合模型定义才能使用。如果打印出来的是collections.OrderedDict,那它就是裸的state_dict,直接用模型加载即可。
3.3 一个容易忽略的opencv版本问题
在装依赖时,opencv-python的版本坑非常隐蔽。人脸关键点绘制的示例代码里,经常用到cv2.putText、cv2.circle这类基础函数,这些在新旧版本里都差不多。真正容易出问题的是cv2.dnn模块:如果用OpenCV的DNN模块加载人脸检测模型,旧版本支持的层格式可能到了新版本就不兼容了。
比如我在某个版本的OpenCV里加载一个旧的Caffe人脸检测模型,会报:
OpenCV(4.9.0) Error: Unspecified error in function 'ReadProtoFromBinaryFile'这种问题大多出在OpenCV版本升级后,对某些op层不再兼容。建议先固定一个成熟版本,我在实际项目中用的是opencv-python==4.8.1.78,这个版本对常见的人脸检测模型兼容性都不错。
4. 跑通检测流水线:从图片到106个关键点
4.1 计算人脸检测框
106点关键点模型很少会自己直接从全图回归出所有点,它通常依赖一个人脸框。如果你随便丢一张包含多人的图片给106点模型,模型很有可能会把多人脸混合到一个点集里,结果就是点在几个脸之间乱飘。
使用包内自带的人脸检测器时,输出通常是一个边界框[x, y, w, h]或者[x1, y1, x2, y2]。注意很多模型的坐标系原点在图片左上角,y轴向下。拿到框之后,需要做一次轻微的margin扩展,把眼睛、下巴的边缘留出空间。我通常按比例扩展:
def expand_bbox(x1, y1, x2, y2, margin_ratio=0.1): w = x2 - x1 h = y2 - y1 x1 = int(max(0, x1 - margin_ratio * w)) y1 = int(max(0, y1 - margin_ratio * h)) x2 = int(x2 + margin_ratio * w) y2 = int(y2 + margin_ratio * h) return x1, y1, x2, y2为什么要扩?因为关键点回归模型在训练时,其输入图像通常是人脸区域加上一定背景余量。只给一个特别紧的框,模型可能没法正确感知脸在框中的相对位置。
4.2 关键点回归模型的输入输出解析
这个模型通常接收一个(1, 3, 192, 192)或者(1, 3, 112, 112)大小的输入。在把裁剪出来的人脸图像送入模型之前,要按训练时的预处理规范做标准化。常见的标准化有两种:
- 像素值除以255,然后归一化到
[0, 1]。 - 减均值除以标准差,比如均值
[0.5, 0.5, 0.5],标准差[0.5, 0.5, 0.5]。
如果预处理做错了,模型的输出坐标会明显偏离原图,常见现象是“点都朝一个方向偏移”。我踩过一次就是在某个包里,demo代码用的是(img - 127.5) / 128,而我自己图省事直接img / 255,导致输出点整体上移到头皮上。
输出层一般有两种形式:
- 直接输出一个
(1, 106, 2)的坐标数组,坐标是在输入图像分辨率下的。 - 输出一个
(1, 106, 3)或(1, 106, 2)的置信度和坐标组合,后一位是置信度或visibility。
拿到输出后,最关键的一步是坐标缩放:
# 假设模型输入尺寸是 input_size # 人脸框在原图上是 bbox # 模型输出 points 是相对输入图像的坐标(以输入图像的左上角为原点) scale_x = (bbox[2] - bbox[0]) / input_size scale_y = (bbox[3] - bbox[1]) / input_size for point in points: original_x = point[0] * scale_x + bbox[0] original_y = point[1] * scale_y + bbox[1]如果你跳过这一步,直接拿模型输出的坐标画图,关键点会全部位于人脸框左上角附近的小区域里。
4.3 可视化与坐标变换的坑
绘制关键点时,有一个容易忽略的问题:OpenCV的cv2.circle接受的坐标是(int, int),如果坐标是浮点数,直接强转会引入偏移。特别是在做视频流时,人脸抖动本身就有几个像素的噪声,强转会让点看起来一跳一跳的。
更平滑的做法是保留浮点数坐标,只在绘制前做一次round,而不是int。round是四舍五入,int是截断,在视觉上差别不大,但如果后续你要做特征点对齐,这两三个像素的偏差会影响后续贴纸或美颜效果。
如果要把关键点用于贴纸、眼镜这类效果,还需要把人脸关键点的坐标系映射到贴纸素材坐标系,这一步通常用相似变换(scale + rotation + translate)实现。106点模型提供了足够多的点用于估计人脸姿态,你可以用左右眼坐标来计算旋转角,用两眼距离来计算缩放倍率。
5. 实测性能与进阶调优:从能用到好用
5.1 1080P图片上的帧率实测
我用一个普通的i5-12400 CPU,加载ONNX格式的106点模型,配合基于ONNX Runtime的人脸检测,在1080P图片上单次推理时间大约是:
| 阶段 | 耗时 |
|---|---|
| 人脸检测 | 12ms ~ 20ms |
| 关键点回归 | 2ms ~ 5ms |
| 预处理+后处理 | 3ms ~ 8ms |
也就是说,纯CPU单帧全流程大概20到30ms,勉强能到30fps左右,但这是在输入图片没有拉伸到超大尺寸的前提下。如果视频流是4K,预处理耗时会被明显拉长,建议先把图像缩小到宽度不超过1280再做检测。
5.2 调整输入尺寸和模型精度的取舍
多数106点模型的官方输入是192x192或者112x112。如果你想要更高的精度,可以尝试把输入放大到256x256,关键点回归的稳定性会好一些,尤其是眼睛嘴角这种细节区域,但推理耗时也会翻倍。反之,如果做移动端部署,输入调到80x80也不是不能跑,只是大角度侧脸时的点会明显抖动。
实际上,我发现一个更划算的优化手段:在人脸检测阶段,把检测框稍微变大一点,让关键点回归模型有更多上下文。因为人脸关键点模型不只需要脸内部的纹理,还需要一点背景边缘来帮助判断脸的朝向。
5.3 常见异常:检测不到人脸、点漂移和多人脸场景
检测不到人脸,先检查输入的图像通道顺序。如果你用OpenCV读取图片,图像是BGR顺序,但很多模型的预处理要求是RGB。直接用BGR输入模型,在人脸检测阶段可能也能检测到,因为人脸检测器对颜色通道顺序不敏感,但关键点回归模型通常是RGB训练,直接用BGR会导致眼睛、嘴角等局部纹理的响应发生变化,输出点会漂。
点漂移最典型的场景是眨眼和说话时,眼睛和嘴巴附近的点会跟着抖动。这不是模型坏了,而是模型对动态模糊的鲁棒性一般。解决方法是在时间序列上做平滑,最简单的是一阶低通滤波:
smoothed = prev_smoothed * 0.8 + current * 0.2在视频流里,这个平滑系数搭配30fps的效果还不错。再高级一点,就可以用卡尔曼滤波或者One Euro Filter,但那是后话。
多人脸场景下,建议把每一帧的人脸检测框先做人脸跟踪匹配,保证每个ID对应的关键点序列是连续的,否则两个脸一旦发生交叉,关键点标签就乱了。简单做法是使用IoU去匹配相邻帧的人脸框,IoU大于0.3就算同一个ID。这一招对单人场景同样有效,因为能避免人脸框偶尔跳变导致的关键点坐标抖动。
最后再分享一个小技巧:如果你对拿到手的2d106det.zip里的模型效果不满意,不要急着删,先看一眼它检测的人脸框具体在哪,很多时候不是模型精度不够,而是人脸检测框给得太偏或者太大。换一个更强的人脸检测器,关键点精度会有立竿见影的提升。我自己后来就把包里的轻量检测器换成了SCRFD-500M,106点模型无需任何改动,整体效果就明显上了一个档次。
本文还有配套的精品资源,点击获取