简介:face-api.js预训练模型资源包,专为需要在浏览器端实现人脸识别功能的Web开发者准备,覆盖人脸检测、68点关键点定位、表情识别、年龄性别预测与人脸比对等常见任务。资源共18个文件,压缩包10.33MB,以json权重清单和shard分片模型文件为主,包含ssd_mobilenetv1_model、face_landmark_68_model、face_expression_model、age_gender_model、face_recognition_model、mtcnn_model、tiny_face_detector_model等,开发者可直接配合face-api.js的loadFaceDetectionModel、loadFaceLandmarkModel等方法加载使用,无需再从外网逐一搜集模型。已有2118人学习下载。包内模型按任务分类完整,普通检测场景可用轻量级tiny_face_detector,精度要求高时可选用ssd_mobilenetv1;关键点模型提供68点精细版与tiny版两个选择,人脸识别部分包含shard1与shard2分片,便于灵活部署。对于刚接触face-api.js的开发者,这样一套现成模型也能帮助快速跑通人脸检测与识别流程,省去模型文件缺失或版本不匹配的排错时间。 face-api.js 这库我前后折腾了小半个月才彻底玩明白。第一次跑通人脸检测的时候挺兴奋,但紧接着就被模型加载完的页面白屏、浏览器控制台报 404、TensorFlow.js 版本对不上这些坑轮番教育了一遍。翻来覆去查资料才发现,问题的根源几乎都集中在模型的加载和使用上。这篇东西我就把 face-api.js 的模型从文件构成、加载机制到实际选型遇到的问题,按我自己的踩坑顺序完整梳理一遍。
1. 先把 face-api.js 和这五个模型捋清楚
1.1 face-api.js 到底是什么,能做什么
face-api.js 是建立在 TensorFlow.js 之上的 JavaScript 人脸识别库,意味着你不用写一行 Python,也不用搭后端服务,直接在浏览器里就能完成人脸检测、人脸关键点定位、人脸识别、表情识别这些计算机视觉任务。这个库对前端开发者特别友好,API 设计得简洁,加载模型之后调用几个方法就能出结果。
我理解这个库的核心价值在于:它把深度学习模型的推理过程完全搬到了前端,让浏览器直接承担计算任务。对用户来说,数据不需要上传到服务器,隐私性好很多;对开发者来说,不需要维护 GPU 服务器,部署成本大幅降低。我自己在做人脸登录功能原型的时候选它,主要就是看重这两点。
1.2 官方五个模型各自负责什么任务
face-api.js 的模型不是一个大文件,而是按照功能拆成了五个独立模型,每个模型解决一类具体问题:
Tiny Face Detector:轻量级人脸检测模型,负责在图片或视频中框出人脸的位置。它的特点是速度快、模型小,适合实时场景。另一个可选方案是 SSD MobileNet V1,精度更高但更重。
Face Landmark 68 Point Model:在人脸框的基础上进一步定位 68 个关键点,包括眉毛、眼睛、鼻子、嘴巴、下巴轮廓。这个模型有普通版和 Tiny 轻量版两种,关键点检测会为后续的人脸对齐、姿态估计提供基础。
Face Recognition Model:人脸识别模型,通过深度学习网络把人脸图像映射成一个 128 维的特征向量。两张人脸的相似度就是通过计算这两个向量的欧氏距离得到的。这个模型在五个模型中体积最大。
Face Expression Model:表情识别模型,能识别 neutral、happy、sad、angry、fearful、disgusted、surprised 七种基本表情。
Age and Gender Model:年龄和性别估计模型,输入人脸区域输出预测的年龄段和性别概率。
了解了"一个模型干一件事"的拆分方式之后,你会发现使用 face-api.js 的关键其实是搞清楚模型怎么加载、怎么组合、怎么根据场景选择。下一部分我详细讲。
2. 深入拆解模型文件结构和加载机制的底层逻辑
2.1 模型文件为什么会拆成 weights_manifest.json 加一堆 shard
第一次从 face-api.js 官方仓库下载模型时,看到目录里既有xxx-weights_manifest.json又有xxx-shard1、xxx-shard2这类文件,我是有点懵的。后来才明白,这是 TensorFlow.js 的模型存储格式。
TensorFlow.js 加载模型时,需要两个核心部分:模型结构信息和权重数据。在weights_manifest.json这个文件里,记录着模型每个层的名称、形状、数据类型,以及对应权重存放在哪些分片文件中、每个分片文件的路径和字节大小。而shard文件是模型权重参数的二进制内容,当权重数据过大时会切成多个分片存放。
以face_recognition_model为例,它的文件结构是这样的:
/face_recognition_model ├── face_recognition_model-weights_manifest.json ├── face_recognition_model-shard1 └── face_recognition_model-shard2因此,加载时 TensorFlow.js 先读取 manifest 文件解析模型结构,再根据 manifest 里记录的信息去拉取 shard 分片文件。如果路径不对或文件缺失,就会报 manifest 找不到或 404 错误。
2.2 模型加载的三种常见方式和它们的适用场景
face-api.js 提供了多种加载方式,我实际用下来主要就这三种:
方式一:loadFromUri,从模型目录加载
await faceapi.nets.tinyFaceDetector.loadFromUri('/models') await faceapi.nets.faceLandmark68Net.loadFromUri('/models') await faceapi.nets.faceRecognitionNet.loadFromUri('/models')这是最常用的方式,传入一个目录路径,库会自动拼接每个模型对应的文件名。模型文件放在项目的公开目录下,确保浏览器能通过 HTTP 访问到这些静态资源。
方式二:loadFromDisk,适用于 Node.js 后端环境
await faceapi.nets.tinyFaceDetector.loadFromDisk('./models')在 Node.js 环境里用这个方法,直接从本地磁盘读取模型文件,不经过 HTTP 请求。
方式三:loadFromUrl,从任意 URL 加载
await faceapi.nets.faceRecognitionNet.loadFromUrl( 'https://cdn.example.com/models/face_recognition_model-weights_manifest.json' )这个方法要传入 weights_manifest.json 文件的完整 URL。它比 loadFromUri 灵活,因为你可以把模型放到任何地方,比如 CDN、OSS 对象存储。不过要注意,不同模型内部处理 URL 拼接的方式略有差异,如果你的模型文件不是完整目录而是单个文件,这种自定义 URL 的方式会更可控。
我在实际项目中优先使用 loadFromUri,因为简单直观、路径不容易出错。涉及模型要分发到不同环境、走 CDN 的场景,再换 loadFromUrl 灵活配置。每种方式都亲测有效。
2.3 一次性加载多个模型的正确姿势
正常情况下,把几个模型的加载用 Promise.all 并行执行,效率最高:
async function loadModels() { const MODEL_URL = '/models' await Promise.all([ faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL), faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL), faceapi.nets.faceRecognitionNet.loadFromUri(MODEL_URL), faceapi.nets.faceExpressionNet.loadFromUri(MODEL_URL) ]) }但这里有个细节容易被忽略:同时加载多个模型会让浏览器缓存面临压力,特别是face_recognition_model和face_age_gender_model的体积比较大。如果只是做人脸检测和关键点定位,就只加载 tinyFaceDetector 和 faceLandmark68Net,没必要把识别模型也拉下来。按需加载是 face-api.js 项目里必须遵守的原则,因为每个模型文件不是几百 KB 的小体积,加载冗余模型直接影响页面首屏体验。
3. 实际项目中的模型验证与完整实操流程
3.1 从一个静态页面开始,跑通人脸检测
先建一个最简单的 HTML 页面,把检测功能跑通:
<!DOCTYPE html> <html> <head> <script src="https://cdn.jsdelivr.net/npm/face-api.js@0.22.2/dist/face-api.min.js"></script> </head> <body> <video id="video" width="720" height="560" autoplay muted></video> <script> async function detect() { const MODEL_URL = '/models' await faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL) await faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL) const stream = await navigator.mediaDevices.getUserMedia({ video: {} }) const video = document.getElementById('video') video.srcObject = stream video.addEventListener('play', () => { const canvas = faceapi.createCanvasFromMedia(video) document.body.append(canvas) const displaySize = { width: video.width, height: video.height } faceapi.matchDimensions(canvas, displaySize) setInterval(async () => { const detections = await faceapi .detectAllFaces(video, new faceapi.TinyFaceDetectorOptions()) .withFaceLandmarks() const resizedResults = faceapi.resizeResults(detections, displaySize) canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height) faceapi.draw.drawDetections(canvas, resizedResults) faceapi.draw.drawFaceLandmarks(canvas, resizedResults) }, 100) }) } detect() </script> </body> </html>这段代码里,模型加载只写了两个,是刻意为之。因为这个场景只做检测和关键点,不需要识别,少了两个大模型文件,页面资源体积差了很多。TinyFaceDetectorOptions控制检测的灵敏度,后面会细讲。
3.2 人脸识别功能的模型配合方式
真正做识别的时候,模型调用链就不一样了。首先用检测模型定位人脸,然后用关键点模型提取 68 个特征点用于人脸对齐,最后用识别模型将对齐后人脸映射为 128 维特征向量。完整流程:
const labeledDescriptors = await loadLabeledImages() async function loadLabeledImages() { const labels = ['Alice', 'Bob'] return Promise.all( labels.map(async (label) => { const descriptions = [] for (let i = 1; i <= 3; i++) { const img = await faceapi.fetchImage(`/samples/${label}${i}.jpg`) const detection = await faceapi .detectSingleFace(img, new faceapi.TinyFaceDetectorOptions()) .withFaceLandmarks() .withFaceDescriptor() if (detection) { descriptions.push(detection.descriptor) } } return new faceapi.LabeledFaceDescriptors(label, descriptions) }) ) } const matcher = new faceapi.FaceMatcher(labeledDescriptors, 0.6) const queryResult = await faceapi .detectSingleFace(video, new faceapi.TinyFaceDetectorOptions()) .withFaceLandmarks() .withFaceDescriptor() const bestMatch = matcher.findBestMatch(queryResult.descriptor) console.log(bestMatch.toString())这里labeledDescriptors是照片里每个人的特征向量集合,FaceMatcher用欧氏距离计算相似度,第二个参数 0.6 是距离阈值,低于这个值判定为同一人。阈值设太严会出现大量"未识别",设太松又会把不同的人误判为同一人。我自己的经验是:候选人脸图片光照统一时,阈值设 0.5 左右非常准;光照差异大的场景需要放宽到 0.6 到 0.65,没有放之四海而皆准的值,必须跑真实数据验证。
3.3 模型加载前窗口期和加载失败的兜底处理
模型加载是异步操作,而且需要从服务器拉文件,页面启动到模型就绪这个窗口期,用户可能已经打开页面但什么功能都用不了。我处理这个问题时会维护一个全局状态:
const modelReady = { value: false, tasks: [] } async function ensureModelLoaded() { if (modelReady.value) return Promise.resolve() if (modelReady.loadingPromise) return modelReady.loadingPromise modelReady.loadingPromise = (async () => { try { await faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL) await faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL) modelReady.value = true } catch (err) { console.error('模型加载失败', err) throw err } finally { modelReady.loadingPromise = null } })() return modelReady.loadingPromise }这样所有业务逻辑都先await ensureModelLoaded(),加载只需一次,期间重复调用也不会重复发请求。模型加载失败时也能统一捕捉错误并提示用户刷新重试,而不是让业务代码在"模型还没准备好"的隐性状态里默默出错。
4. 模型选型与性能调优的取舍经验
4.1 Tiny Face Detector 和 SSD MobileNet V1 怎么选
face-api.js 的检测模型有两个可选方案:Tiny Face Detector 和 SSD MobileNet V1。我从性能、体积、精度三个维度对比过:
| 对比维度 | Tiny Face Detector | SSD MobileNet V1 |
|---|---|---|
| 模型体积 | 约 190KB,极小 | 约 5.5MB,较大 |
| 检测速度 | 快,适合实时视频 | 慢,帧率明显下降 |
| 检测精度 | 中小尺寸人脸表现可接受 | 更准确,尤其小脸、侧脸 |
| 适用场景 | 视频流实时检测、移动端 | 服务器端或精度优先的场景 |
如果做人脸登录、美颜相机、表情贴纸这种实时互动场景,选 Tiny Face Detector 基本没问题。如果你处理的图片质量参差不齐、人脸面积很小、偏转角度很大,则建议用 SSD MobileNet V1 换召回率,代价是每一帧的推断时间会明显上升。真正生产环境,我会在初始化时做一个简单的动态切换机制:视频中检测不到人脸时允许自动从 Tiny 降级到 SSD MobileNet,虽然牺牲速度但至少不会漏检。
4.2 TinyFaceDetectorOptions 几个关键参数怎么定
inputSize:输入图像的尺寸,必须能被 32 整除。常见取值 320、416、512。数值越大,能检测到的人脸越小,但计算量也越大。视频检测用 416 是性价比很高的平衡点,移动端降低到 320。scoreThreshold:置信度阈值,范围 0 到 1,默认 0.5。低于阈值的检测会被过滤。调高一些能过滤误检,但可能漏掉真的人脸;调低则误检变多。
我在调节参数时发现一个规律:摄像头距离人脸较远、人脸在画面里小时候,scoreThreshold要适当调低到 0.3 到 0.4,同时增大inputSize,不然会频繁出现检测不到人脸的情况。
4.3 性能瓶颈和优化方向
face-api.js 能跑得很顺,也有跑得挣扎的时候。实测下来性能相关的坑主要有三类:
第一,浏览器不支持 WebGL。TensorFlow.js 在 CPU 上的推理速度比 GPU 慢数倍,人脸检测加上关键点检测在低端手机上基本卡成 PPT。入门阶段可以先判断tf.engine是否注册了 backend,必要时提示用户升级浏览器。
第二,视频帧率被检测拖累。每帧都做全套检测,CPU 占用会很高。我用过最有效的优化是跳帧处理,比如每 2 帧执行一次检测,检测结果在中间帧进行线性插值或直接沿用上一帧结果,肉眼几乎无感知,性能提升很明显。
第三,视频分辨率偏高导致计算量爆炸。不一定非要用 720p 的原生分辨率来做检测。把视频流分辨率压到 480p 甚至 360p 再传给 face-api.js,人脸识别精度下降很小,但速度提升明显。在实际产品中,这个优化比调任何参数都管用。
5. 排查模型相关问题的实用清单
5.1 加载时报 manifest not found 或 404
这类错误 90% 是路径问题。loadFromUri('/models')传的是"目录",但库内部会拼出face_recognition_model-weights_manifest.json这样的文件名再发起请求。如果模型文件没放在目录根下,或者目录路径没有以/结尾,就会 404。
排查方法很简单:打开浏览器 Network 面板,看请求失败的具体 URL,然后和磁盘上的目录结构对比。另外要特别确认shard分片文件和 manifest 在同一个目录,因为很多打包工具(如 Vite、Webpack)默认只拷贝入口文件,不会自动把静态模型文件复制到发布目录,这会引发"本地开发正常、上线就 404"的经典问题。
5.2 跨域问题导致模型加载失败
把模型部署到 CDN 或独立静态服务器时,浏览器会因 CORS 限制阻止模型文件的加载。这种情况下必须保证 CDN 或服务器返回正确的Access-Control-Allow-Origin响应头。
如果用的是阿里云 OSS、腾讯云 COS 这类存储,需要在控制台配置跨域规则。如果自己搭建静态服务,Nginx 配置如下:
location /models/ { add_header Access-Control-Allow-Origin *; }需要注意,*代表允许所有域名访问,如果是敏感项目建议替换成明确的域名白名单。
5.3 版本兼容问题:face-api.js 和 TensorFlow.js 的版本配套
face-api.js 内部依赖 TensorFlow.js,如果你在页面里手动引了其他版本的@tensorflow/tfjs,很可能出现加载模型时报Cannot read properties of undefined之类的报错。face-api.js 发布时锁定的 TF.js 版本和最新版不一定兼容。
我的建议是:优先使用 face-api.js 官方自带的捆绑版本,例如通过 CDN 引入face-api.min.js时不要额外再手动引入 TF.js。如果确实需要在 Node.js 环境使用,安装时注意 face-api.js 的 peerDependencies 版本要求,用 npm 安装时留意警告信息,并把 TF.js 固定到对应版本。
6. 模型文件获取与常见使用陷阱
6.1 模型文件去哪下,怎么放到项目里
face-api.js 的官方模型文件托管在 GitHub 仓库的weights目录下,下载时选择对应文件名即可。但是直接下载单个文件比较繁琐,而且不同功能的模型文件分布在同一个目录里,容易搞混。
我更推荐的做法是:用 npm 安装face-api.js时,在node_modules/face-api.js/weights目录下能看到完整模型文件,直接把需要的几个权重文件复制到项目静态目录。
如果你用的是 Vite 或 Webpack,记得用public目录或配置静态资源复制,这样部署后路径才能正确解析。之前有朋友的项目本地一切正常,打包部署后模型全部 404,就是因为构建工具没有把 weights 目录发布出去。
6.2 模型加载失败和模型权重文件损坏的区别
模型下载中断或者从非官方渠道获取的模型文件,可能出现文件损坏的情况。此时浏览器不会明显报 404,而是报错信息里包含奇怪的乱码字符或者json解析错误。
遇到这种情况,先检查 manifest 文件的内容是否正常,确认 shard 文件体积和 manifest 中记录的字节数是否一致。我再强调一次:尽量从官方仓库或 npm 包内获取模型文件,其他渠道为了各类目的"加速"的版本,往往会引入未知变量,出了问题排查起来非常耗费时间。
6.3 模型成功加载后检测结果为空怎么办
模型加载正常但检测不到人脸,这个问题很隐蔽。我最常遇到的情况有三种:
一是人脸区域过小或光线太暗,模型检测不出。此时按前面说的调整inputSize和scoreThreshold。二是视频流还没真正开始输出画面就执行了检测,拿到的帧是黑屏,自然检测不到。解决方法是等video的play事件触发后再开始检测。三是 Canvas 绘制时显示的尺寸和检测用的尺寸不一致,导致画框位置错位,看起来就像检测结果异常。
6.4 Node.js 环境下使用时的特殊坑
在 Node.js 环境里跑 face-api.js,我踩过的最大坑就是缺少 Canvas 和 Polyfill。face-api.js 默认依赖 DOM 元素(HTMLImageElement等),在 Node 里需要引入canvas库并完成全局注入:
npm install canvasconst canvas = require('canvas') const faceapi = require('face-api.js') const { Canvas, Image, ImageData } = canvas faceapi.env.monkeyPatch({ Canvas, Image, ImageData })加载本地模型用loadFromDisk,推理前需要先将图片文件读取为 Buffer,再用canvas转成Image对象。这里最容易遇到的问题是对中文文件名或特殊路径的处理,建议先将图片路径标准化后再加载。
7. 我没有写"模型训练",但你应该知道模型的边界
很多人一接触 face-api.js,第一反应是问"我该怎么训练自己的模型"。实际上,face-api.js 官方提供的五个模型都是预训练好的,使用过程中不需要、也不能直接微调。如果你要做的是"识别特定人的脸",那不需要训练模型,而是提供该人物的多张照片,提取特征向量后和实时人脸进行比对,这个流程我在 3.2 节已经详细演示过了。
但如果你想真正训一个模型,那就已经超出了 face-api.js 的范畴。官方模型底层是用 TensorFlow 训练的,前端只是做推理。尝试自己训练时,你需要掌握足够多的优质人脸数据集、搭建合适的网络结构、处理好训练和推理的格式转换,再通过tfjs-converter转成前端可加载的格式。这个体系的复杂度不是入门阶段应该碰的。
我的实际体验是:先用好预训练模型,把工程链路跑通,产品逻辑验证清楚,再去考虑模型层面的定制,这是投入产出比最高的路径。我在做项目前总想着"模型要自己训才能解决问题",后来发现预训练模型加合理参数配置已经能覆盖 90% 以上的业务需求。
整体来说,face-api.js 的使用核心就是"选模型、加载模型、参数调优、错误排查"这么一条线。模型的加载机制不复杂,但每一步都有不少隐藏的坑,我这篇文章里记录的,都是实际动手过程中真金白银踩出来的经验。照着这篇文章把模型流程走通之后,你再回头看那些标注框偏移、加载失败、性能卡顿的问题,基本都能很快找到方向了。
本文还有配套的精品资源,点击获取