16、人脸检测:FaceDetector API介绍、检测人脸区域、绘制人脸边框
人脸检测,说白了就是让浏览器帮你找到画面里「哪块是人脸」。
我最早接触这个需求,是在做一个远程面试系统的时候。候选人那边光线暗一点,人脸就找不到了,面试官直接投诉说「这人是不是低头玩手机呢」——嗯,从那以后我就开始认真研究浏览器自带的人脸检测能力。
FaceDetector API 是什么?
它是 Shape Detection API 的一部分,属于浏览器原生能力。不需要引入 OpenCV、TensorFlow 这些重型库,一行代码就能开始检测人脸。
我个人习惯先看浏览器兼容性。目前 Chrome 系(包括 Edge)支持得最好,Firefox 和 Safari 还在路上。你可以在 about://gpu 里确认一下你的浏览器是否启用了相关特性。
核心能力:
- 检测视频帧或图片中的人脸区域
- 返回每个人脸的边界框(bounding box)
- 支持连续帧检测,适合实时视频流
基本用法:创建检测器
先看看怎么初始化一个 FaceDetector 实例:
// 创建检测器,可以传配置参数
const faceDetector = new FaceDetector({
maxDetectedFaces: 5, // 最多检测5张脸
fastMode: true // 快速模式,牺牲精度换速度
});
这里有个坑,我踩过。如果你不传 fastMode: true,在低端手机上检测一帧可能要 200ms 以上,视频直接就卡成幻灯片了。所以我建议做实时检测时,一定把这个参数打开。
检测人脸区域
检测的核心方法就一个:detect()。它接收一个 ImageBitmapSource,可以是 video 元素、canvas、img 或者 ImageBitmap。
async function detectFaces(videoElement) {
try {
const faces = await faceDetector.detect(videoElement);
return faces;
} catch (error) {
console.error('人脸检测失败:', error);
return [];
}
}
返回的 faces 是一个数组,每个元素包含:
| 属性 | 说明 |
|---|---|
boundingBox |
人脸矩形区域,包含 x、y、width、height |
landmarks |
面部关键点(眼睛、鼻子、嘴巴等),可能为空 |
为什么会返回空数组?我曾经在项目里遇到过,原因是视频分辨率太低,人脸小于 64x64 像素时检测器直接忽略。所以建议视频分辨率至少 640x480。
绘制人脸边框
检测到人脸后,下一步就是在画面上画框。通常的做法是把视频画面绘制到 canvas 上,然后在 canvas 上叠加矩形。
function drawFaceBoxes(canvas, faces) {
const ctx = canvas.getContext('2d');
ctx.strokeStyle = '#00ff00';
ctx.lineWidth = 3;
faces.forEach(face => {
const { x, y, width, height } = face.boundingBox;
ctx.strokeRect(x, y, width, height);
});
}
嗯,这里要注意:canvas 的尺寸必须和视频的实际显示尺寸一致,否则框的位置会偏移。我习惯在 loadedmetadata 事件里统一设置 canvas 尺寸。
小技巧:如果你想让边框更「酷」一点,可以画圆角矩形,或者加一个半透明的背景色填充。我个人喜欢在框的左上角加一个「Face #1」的标签,方便调试。
完整示例:实时检测 + 绘制
把上面几个步骤串起来,就是一个完整的实时人脸检测流程:
const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const faceDetector = new FaceDetector({
maxDetectedFaces: 5,
fastMode: true
});
async function detectAndDraw() {
// 检测
const faces = await faceDetector.detect(video);
// 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制视频帧
ctx.drawImage(video, 0, 0);
// 绘制人脸框
faces.forEach(face => {
const { x, y, width, height } = face.boundingBox;
ctx.strokeStyle = '#00ff00';
ctx.lineWidth = 3;
ctx.strokeRect(x, y, width, height);
});
// 下一帧
requestAnimationFrame(detectAndDraw);
}
// 启动视频流后调用
navigator.mediaDevices.getUserMedia({ video: true })
.then(stream => {
video.srcObject = stream;
video.onloadedmetadata = () => {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
detectAndDraw();
};
});
避坑指南:我曾经在循环检测时忘记加 try/catch,结果有一次用户切到后台再切回来,detect() 抛了个 InvalidStateError,整个页面直接白屏。所以一定要包异常处理。
性能优化建议
实时人脸检测对性能要求不低,尤其是移动端。我总结了几条经验:
- 降低检测频率:不必每帧都检测,每 3-5 帧检测一次就够了,中间帧复用上次的结果
- 缩小检测区域:如果画面中人脸不会突然出现在边缘,可以只检测中心区域
- 使用
fastMode:前面说过了,这个一定要开 - 避免频繁创建检测器:一个
FaceDetector实例可以复用,不要每次检测都 new 一个
知识体系总览
下面这张图帮你理清本章的核心逻辑:
常见问题与避坑
最后分享几个我实际踩过的坑:
- 检测不到人脸:检查视频分辨率是否太低,或者人脸是否被遮挡。我曾经戴个口罩测试,结果检测器直接罢工了。
- 边框抖动:如果每帧检测结果都在轻微变化,边框会像「呼吸」一样抖动。可以加一个平滑算法,取最近几帧的平均值。
- 多张人脸漏检:
maxDetectedFaces默认值在不同浏览器上不一样,建议显式设置。 - 内存泄漏:如果检测器不再使用,记得把视频流关掉,否则 canvas 会一直占用 GPU 资源。
人脸检测这个功能,说简单也简单,说复杂也复杂。浏览器原生 API 帮我们省去了很多底层工作,但实际落地时还是有不少细节要处理。希望今天的内容能帮你少走一些弯路。