Face-api.js 开发者教程:从入门到实战

目录

  1. 什么是 Face-api.js?
  2. 为什么选择 Face-api.js?
  3. 准备工作:环境搭建
    • 创建项目
    • 安装依赖
    • 下载模型文件
  4. 核心概念:模型与 API
  5. 实战演练:构建你的第一个应用
    • 加载模型
    • 访问摄像头
    • 进行人脸检测
    • 检测面部特征点
    • 估算年龄与性别
    • 进阶:面部识别
  6. 完整代码示例
  7. 最佳实践与优化技巧
  8. 总结与资源

什么是 Face-api.js?

Face-api.js 是一个基于 TensorFlow.js 的 JavaScript 库,它允许你在浏览器端直接运行预训练的计算机视觉模型,而无需任何服务器端处理,这意味着你可以将面部识别、检测等功能直接集成到你的 Web 应用中,保护用户隐私,并提供即时响应。

它支持多种任务:

  • 人脸检测:在图像或视频中定位人脸的位置。
  • 面部标志点检测:找到脸上的 68 个关键点(如眼睛、鼻子、嘴唇的轮廓)。
  • 面部识别:识别图像中的人是谁,需要先进行“注册”。
  • 年龄与性别估算:估算人脸的年龄范围和性别。
  • 表情识别:识别出人脸所表达的情绪(如开心、悲伤、惊讶)。

为什么选择 Face-api.js?

  • 纯前端实现:所有计算都在用户的浏览器中完成,数据无需上传到服务器,隐私性极高。
  • 易于集成:对 JavaScript 开发者非常友好,API 设计简洁。
  • 基于 TensorFlow.js:利用了谷歌强大的机器学习框架,性能和准确性都有保障。
  • 实时性:可以在视频流中进行实时检测,适用于直播、视频通话等场景。

准备工作:环境搭建

1 创建项目

创建一个新的项目目录,并初始化一个 package.json 文件。

mkdir face-api-demo
cd face-api-demo
npm init -y

2 安装依赖

我们需要安装 face-api.js 和一个用于简化视频处理的库 @tensorflow/tfjs-converter(虽然 face-api.js 通常会自动处理,但显式安装更稳妥)。

npm install face-api.js @tensorflow/tfjs

3 下载模型文件

这是最关键的一步!face-api.js 的所有功能都依赖于预训练好的模型文件,你需要从它的 GitHub 仓库中下载这些文件。

操作步骤:

  1. 访问 face-api.js 的 Releases 页面
  2. 找到最新的稳定版本(v0.22.2)。
  3. 在该版本的 Assets 中,下载 models-weights_manifest.jsonface-api.js 文件。
  4. 在你的项目根目录下,创建一个名为 public 的文件夹,并将下载的这两个文件放入 public 文件夹中。
  5. face-api.js 的 weights 目录 下载所有模型文件(.bin.json),并将它们也放入 public 文件夹。

你的项目结构应该看起来像这样:

face-api-demo/
├── node_modules/
├── public/
│   ├── age_gender_model-weights_manifest.json
│   ├── face_expression_model-weights_manifest.json
│   ├── face_landmark_68_model-weights_manifest.json
│   ├── face_landmark_68_point_model-weights_manifest.json
│   ├── face_recognition_model-weights_manifest.json
│   ├── face_detection_model-weights_manifest.json
│   ├── faceapi.min.js
│   ├── faceapi.js
│   ├── ... (所有 .bin 和 .json 权重文件)
├── package.json
└── ... (我们将创建的 HTML 和 JS 文件)

核心概念:模型与 API

face-api.js 的核心是加载不同的模型来执行不同的任务。

  • faceapi.nets.tinyFaceDetector.loadFromUri('/public'): 一个轻量级的人脸检测模型,速度快,但精度稍低,适合实时视频。
  • faceapi.nets.ssdMobilenetv1.loadFromUri('/public'): 一个更精确的人脸检测模型,但速度较慢。
  • faceapi.nets.faceLandmark68Net.loadFromUri('/public'): 用于检测 68 个面部标志点。
  • faceapi.nets.faceRecognitionNet.loadFromUri('/public'): 用于面部识别,计算人脸的“特征向量”。
  • faceapi.nets.ageGenderNet.loadFromUri('/public'): 用于估算年龄和性别。
  • faceapi.nets.faceExpressionNet.loadFromUri('/public'): 用于识别表情。

工作流程:

  1. 在应用启动时,异步加载你需要的模型。
  2. 当用户授权摄像头后,获取视频流。
  3. 在一个 requestAnimationFrame 循环中,不断从视频中捕获帧。
  4. 使用 detectAllFacesdetectSingleFace 等方法对捕获的帧进行分析。
  5. 将返回的结果(如边界框、特征点等)绘制到 <canvas> 上。

实战演练:构建你的第一个应用

我们将创建一个简单的 Web 页面,它能够打开摄像头,实时检测人脸,并在画布上绘制出边界框和特征点。

1 创建 HTML 文件

public 文件夹下创建 index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">Face-api.js Demo</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            margin-top: 20px;
        }
        #video, #canvas {
            border: 2px solid black;
            max-width: 100%;
        }
        #canvas {
            position: absolute;
        }
    </style>
</head>
<body>
    <h1>Face-api.js 实时人脸检测</h1>
    <div style="position: relative;">
        <video id="video" width="720" height="560" autoplay muted></video>
        <canvas id="canvas"></canvas>
    </div>
    <button id="startBtn">开始检测</button>
    <!-- 引入 face-api.js -->
    <script src="faceapi.js" type="text/javascript"></script>
    <!-- 引入我们自己的 JS 逻辑 -->
    <script src="app.js" type="text/javascript"></script>
</body>
</html>

2 创建 JavaScript 逻辑文件

public 文件夹下创建 app.js,我们将在这里编写所有核心代码。

const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const startBtn = document.getElementById('startBtn');
const displaySize = { width: video.width, height: video.height };
// 1. 加载模型
async function loadModels() {
    // 使用 async/await 异步加载
    await faceapi.nets.tinyFaceDetector.loadFromUri('/public');
    await faceapi.nets.faceLandmark68Net.loadFromUri('/public');
    await faceapi.nets.faceRecognitionNet.loadFromUri('/public');
    await faceapi.nets.ageGenderNet.loadFromUri('/public');
    await faceapi.nets.faceExpressionNet.loadFromUri('/public');
    console.log('所有模型加载完成!');
}
// 2. 启动摄像头
async function startVideo() {
    try {
        const stream = await navigator.mediaDevices.getUserMedia({ video: {} });
        video.srcObject = stream;
        // 等待视频元数据加载完成
        return new Promise((resolve) => {
            video.onloadedmetadata = () => {
                resolve(video);
            };
        });
    } catch (err) {
        console.error("无法访问摄像头: ", err);
        alert("请允许访问摄像头以使用此功能。");
    }
}
// 3. 调整画布大小以匹配视频
function resizeCanvasToDisplaySize() {
    displaySize.width = video.videoWidth;
    displaySize.height = video.videoHeight;
    canvas.width = displaySize.width;
    canvas.height = displaySize.height;
}
// 4. 检测并绘制人脸
async function detect() {
    // 清除上一帧的绘制
    const ctx = canvas.getContext('2d');
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    // 使用 tinyFaceDetector 进行检测
    const detections = await faceapi
        .detectAllFaces(video, new faceapi.TinyFaceDetectorOptions())
        .withFaceLandmarks()
        .withFaceExpressions()
        .withAgeAndGender();
    // 调整检测结果以匹配显示尺寸
    const resizedDetections = faceapi.resizeResults(detections, displaySize);
    // 绘制检测结果
    faceapi.draw.drawDetections(canvas, resizedDetections);
    faceapi.draw.drawFaceLandmarks(canvas, resizedDetections);
    faceapi.draw.drawFaceExpressions(canvas, resizedDetections);
    // 在每个检测到的人脸旁边绘制标签(年龄、性别等)
    resizedDetections.forEach((detection) => {
        const box = detection.detection.box;
        const drawOptions = { anchor: [box.x + box.width + 10, box.y] };
        const age = Math.round(detection.age);
        const gender = detection.gender;
        const genderProbability = Math.round(detection.genderProbability * 100);
        const expression = Object.keys(detection.expressions).reduce((a, b) => detection.expressions[a] > detection.expressions[b] ? a : b);
        const drawText = `${gender} (${genderProbability}%) - ${age} years - ${expression}`;
        new faceapi.draw.DrawTextField([drawText], box.topRight, drawOptions).draw(canvas);
    });
    // 递归调用,实现实时检测
    requestAnimationFrame(detect);
}
// 主函数
async function main() {
    await loadModels();
    await startVideo();
    video.addEventListener('play', () => {
        resizeCanvasToDisplaySize();
        detect();
    });
}
// 绑定按钮事件
startBtn.addEventListener('click', main);

3 运行你的应用

你可以启动一个简单的 HTTP 服务器来运行你的应用,因为浏览器出于安全考虑,不允许直接从 file:// 协议访问摄像头。

如果你安装了 http-server,可以直接运行:

npx http-server public

或者使用 Python:

# 确保你已安装 Python
cd public
python -m http.server 8000

然后在浏览器中打开 http://localhost:8000(或你看到的地址),点击“开始检测”按钮,允许摄像头权限,你就能看到神奇的效果了!


完整代码示例

上面的 app.jsindex.html 就是完整的示例代码,你只需要将它们和下载好的模型文件放在正确的位置即可。


最佳实践与优化技巧

  • 按需加载模型:不要一次性加载所有模型,根据你的应用需求,只加载必要的模型,可以显著减少初始加载时间。
  • 选择合适的检测器
    • TinyFaceDetector: 速度最快,适合实时视频。
    • SsdMobilenetv1: 精度更高,适合静态图片或对精度要求高的场景。
  • 性能优化
    • 降低检测频率:你不需要以 60fps 的速度进行检测,可以使用 setIntervalsetTimeout 来降低检测频率,例如每 200ms 检测一次。
    • 缩小视频尺寸:在将视频帧送入模型之前,可以将其绘制到一个较小的离屏 canvas 上,然后再进行检测,这会大大减少计算量,但会牺牲一些精度。
    • 使用 requestAnimationFrame:这是在浏览器中创建流畅动画的标准做法,可以确保你的绘制和检测与浏览器的刷新率同步。
  • 错误处理:始终使用 try...catch 块来处理摄像头访问失败或模型加载失败的情况,并向用户友好的提示。

总结与资源

恭喜!你已经学会了如何使用 Face-api.js 在浏览器中实现强大的人脸识别功能。

核心要点回顾:

  1. 环境搭建:安装库,下载模型文件。
  2. 加载模型:使用 await faceapi.nets.xxx.loadFromUri() 异步加载。
  3. 获取视频:使用 navigator.mediaDevices.getUserMedia()
  4. 检测循环:在 video.play 事件中,使用 requestAnimationFrame 循环调用检测函数。
  5. 绘制结果:使用 faceapi.draw 中的辅助函数将检测结果可视化。

进一步学习的资源:

你可以基于这个基础,尝试构建更复杂的应用,比如人脸解锁、表情互动游戏、或者一个简单的员工考勤系统,祝你编码愉快!