0

0

face-api.js 浏览器人脸识别:精确识别多个人脸的实践指南

DDD

DDD

发布时间:2025-10-20 09:05:32

|

269人浏览过

|

来源于php中文网

原创

face-api.js 浏览器人脸识别:精确识别多个人脸的实践指南

本教程详细阐述了如何使用 face-api.js浏览器环境中实现稳定且准确的多目标人脸识别。针对常见的多人脸误识别问题,文章深入分析了 `labeledfacedescriptors` 和 `facematcher` 的正确构建与使用方法,确保每个已知人脸都能被独立且准确地识别,并提供了完整的 svelte 代码示例及优化建议。

在现代 Web 应用中集成人脸识别功能,face-api.js 是一个强大且易于使用的 JavaScript 库。然而,开发者在使用过程中常会遇到一个挑战:当系统需要识别多个已知人脸时,可能会出现所有检测到的人脸都被错误地标记为同一个人的情况,即使“未知”人脸能够被正确识别。本文将深入探讨这一问题,并提供一个健壮的解决方案,确保 face-api.js 能够准确地区分和识别画面中的多个人脸。

理解问题:单人识别与多目标误识别

问题的核心在于 face-api.js 中 LabeledFaceDescriptors 的构建方式。原始代码可能存在以下逻辑缺陷:

  1. 共享描述符数组: 在为每个已知客户(customer)生成 LabeledFaceDescriptors 时,可能错误地使用了同一个全局数组来存储所有客户的人脸描述符(descriptor)。
  2. FaceMatcher 初始化: 如果 FaceMatcher 使用了包含混合描述符的共享数组进行初始化,它将无法区分不同的人脸,因为它认为所有描述符都属于同一个“身份池”。

这导致的结果是,当摄像头前出现多个人脸时,face-api.js 虽然能检测到所有面部,但在进行匹配时,由于内部描述符的混淆,它会倾向于将所有已知面孔都识别为第一个或最接近的匹配项,从而出现“只识别一个人”的假象。

核心概念回顾:face-api.js 中的关键组件

要解决上述问题,我们需要正确理解 face-api.js 中的几个核心概念:

1. 人脸检测与特征提取

  • detectSingleFace() / detectAllFaces(): 用于从图像或视频中检测人脸。
  • .withFaceLandmarks(): 提取人脸关键点,如眼睛、鼻子、嘴巴的位置。
  • .withFaceDescriptor(): 基于人脸关键点生成一个高维向量(人脸描述符),该描述符是人脸的唯一数字指纹,用于后续的匹配。

2. LabeledFaceDescriptors:标签化人脸描述符

这是解决多目标识别问题的关键。faceapi.LabeledFaceDescriptors 类用于将一个特定的人脸描述符数组与一个唯一的标签(如人名)关联起来。它的构造函数是 new faceapi.LabeledFaceDescriptors(label: string, descriptors: Float32Array[])。

  • label: 标识这个人的名称。
  • descriptors: 一个包含该人物多个人脸描述符的数组。通常,为了提高识别鲁棒性,我们会为同一个人提供多张不同角度或表情的照片,并从中提取多个描述符。

3. FaceMatcher:人脸匹配器

faceapi.FaceMatcher 类用于将一个未知人脸的描述符与一组已知的 LabeledFaceDescriptors 进行比较,找出最佳匹配。

ColorMagic
ColorMagic

AI调色板生成工具

下载
  • 初始化: new faceapi.FaceMatcher(labeledDescriptors: LabeledFaceDescriptors[], distanceThreshold?: number)。它接收一个 LabeledFaceDescriptors 对象的数组,其中每个对象代表一个已知人物。
  • 匹配: faceMatcher.findBestMatch(queryDescriptor: Float32Array)。它会计算查询描述符与所有已知描述符之间的距离,并返回距离最近的匹配项(FaceMatch 对象),包含匹配的标签和距离。distanceThreshold 用于判断匹配的严格程度,距离越小表示相似度越高。

解决方案:构建准确的 LabeledFaceDescriptors

解决多目标误识别问题的核心在于确保每个已知人物都拥有独立且正确关联的 LabeledFaceDescriptors 对象。

错误示例分析

在原始代码中,getLabeledFaceDescriptions 函数可能将所有客户的描述符都推送到一个名为 descriptions 的全局数组中。随后,在 map 函数内部,针对每个 customer 调用 new faceapi.LabeledFaceDescriptors(customer.name, descriptions)。此时,descriptions 数组已经包含了所有(或部分)客户的描述符,导致每个 LabeledFaceDescriptors 对象都引用了同一个混淆的描述符集合,从而使 FaceMatcher 无法区分不同的人。

正确实现方式

我们应该为每个客户创建一个独立的描述符数组,并用该数组来构建其对应的 LabeledFaceDescriptors 对象。

// 假设 $customers 和 $baseURL 是 Svelte store 或可访问的变量
// $customers 结构示例: [{ name: 'Customer A', image_url: '/path/to/a.jpg' }, ...]
async function getLabeledFaceDescriptors(customers, baseURL) {
  const labeledDescriptors = await Promise.all(
    customers.map(async (customer) => {
      if (!customer.image_url) {
        console.warn(`Customer ${customer.name} has no image_url, skipping.`);
        return null;
      }

      const descriptorsForThisCustomer = []; // 为每个客户创建一个独立的描述符数组
      // 可以根据需要从多张图片或同一图片的不同检测中获取多个描述符
      // 这里假设每位客户有一张图片,从中提取一个描述符
      try {
        const img = await faceapi.fetchImage(baseURL + customer.image_url);
        const detection = await faceapi
          .detectSingleFace(img)
          .withFaceLandmarks()
          .withFaceDescriptor();

        if (detection && detection.descriptor) {
          descriptorsForThisCustomer.push(detection.descriptor);
        } else {
          console.warn(`No face detected for ${customer.name} from image: ${baseURL + customer.image_url}`);
        }
      } catch (error) {
        console.error(`Error processing image for ${customer.name}:`, error);
        return null;
      }

      if (descriptorsForThisCustomer.length > 0) {
        // 使用该客户专属的描述符数组创建 LabeledFaceDescriptors
        return new faceapi.LabeledFaceDescriptors(
          customer.name,
          descriptorsForThisCustomer
        );
      }
      return null;
    })
  );
  // 过滤掉任何未能成功获取描述符的客户
  return labeledDescriptors.filter((d) => d !== null);
}

实现多目标人脸识别流程

以下是一个完整的 Svelte 组件示例,展示了如何在浏览器中实现多目标人脸识别。

1. 模型加载

在开始人脸识别之前,需要加载 face-api.js 所需的模型。

// Svelte script context
import { onMount, onDestroy } from 'svelte';
import * as faceapi from 'face-api.js';

let video;
let detections;
let width = 640; // 调整为适合您的视频流尺寸
let height = 480;
let canvas, ctx;
let container;
let faceMatcher; // 全局 FaceMatcher 实例

// 假设 $customers 和 $baseURL 是 Svelte store,需要在实际应用中注入
// 例如: import { customers, baseURL } from './stores';
// 或者通过 props 传递
export let customers = []; // 假设通过 props 传递客户数据
export let baseURL = ''; // 假设通过 props 传递基础 URL

const detectionOptions = {
  withLandmarks: true,
  withDescriptors: true,
  minConfidence: 0.5,
  MODEL_URLS: {
    Mobilenetv1Model:
      "https://raw.githubusercontent.com/ml5js/ml5-data-and-models/main/models/faceapi/ssd_mobilenetv1_model-weights_manifest.json",
    FaceLandmarkModel:
      "https://raw.githubusercontent.com/ml5js/ml5-data-and-models/main/models/faceapi/face_landmark_68_model-weights_manifest.json",
    FaceRecognitionModel:
      "https://raw.githubusercontent.com/ml5js/ml5-data-and-models/main/models/faceapi/face_recognition_model-weights_manifest.json",
  },
};

onDestroy(() => {
  if (video) {
    video.pause();
    if (video.srcObject) {
      video.srcObject.getTracks().forEach(track => track.stop());
    }
    video.srcObject = null;
    video.remove();
  }
  if (canvas) {
    canvas.remove();
  }
});

onMount(() => {
  initFaceRecognition();
});

// ... (getLabeledFaceDescriptors 函数如上所示) ...

async function initFaceRecognition() {
  // 获取视频流
  video = await getVideo();

  // 创建画布
  canvas = createCanvas(width, height);
  ctx = canvas.getContext('2d');

  // 加载模型
  await Promise.all([
    faceapi.nets.ssdMobilenetv1.loadFromUri(detectionOptions.MODEL_URLS.Mobilenetv1Model),
    faceapi.nets.faceRecognitionNet.loadFromUri(detectionOptions.MODEL_URLS.FaceRecognitionModel),
    faceapi.nets.faceLandmark68Net.loadFromUri(detectionOptions.MODEL_URLS.FaceLandmarkModel),

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

1010

2023.08.02

golang map内存释放
golang map内存释放

本专题整合了golang map内存相关教程,阅读专题下面的文章了解更多相关内容。

77

2025.09.05

golang map相关教程
golang map相关教程

本专题整合了golang map相关教程,阅读专题下面的文章了解更多详细内容。

40

2025.11.16

golang map原理
golang map原理

本专题整合了golang map相关内容,阅读专题下面的文章了解更多详细内容。

67

2025.11.17

java判断map相关教程
java判断map相关教程

本专题整合了java判断map相关教程,阅读专题下面的文章了解更多详细内容。

47

2025.11.27

js正则表达式
js正则表达式

php中文网为大家提供各种js正则表达式语法大全以及各种js正则表达式使用的方法,还有更多js正则表达式的相关文章、相关下载、相关课程,供大家免费下载体验。

530

2023.06.20

js获取当前时间
js获取当前时间

JS全称JavaScript,是一种具有函数优先的轻量级,解释型或即时编译型的编程语言;它是一种属于网络的高级脚本语言,主要用于Web,常用来为网页添加各式各样的动态功能。js怎么获取当前时间呢?php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

576

2023.07.28

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

760

2023.08.03

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

3

2026.03.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 6万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 3.4万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号