目录

端侧人脸识别实战:MobileFaceNet 特征向量与 1:N 检索落地指南

为什么人脸识别特别适合端侧

门禁考勤、相册聚类、刷脸支付,人脸识别是端侧视觉里商业价值最高的任务之一。相比云服务,端侧方案把照片留在设备里,离线可用,单次推理成本为零,隐私合规压力也小——这在 2026 年越来越重要。

但和图像分类不同,人脸识别本质是度量学习:模型不输出"这是谁",而是把一张脸编码成 512 维特征向量,用向量距离判断"是不是同一个人"。这个设计带来一个巨大优势:新增人员不需要重训模型,往特征库里加一条向量记录即可,非常适合人员变动的业务。

一条完整的人脸识别链路分四步:

1
检测(SCRFD) → 5点对齐 → 特征提取(MobileFaceNet) → 比对(余弦相似度)

开源方案选型

环节 开源项目 适用说明
检测+对齐+特征提取 insightface(deepinsight/insightface) 一站式,官方导出 ONNX;buffalo_l 包内含 SCRFD-10G 检测与 w600k_mbf 识别模型
端侧推理 onnxruntime-mobile / LiteRT 跨平台,支持 INT8 量化,手机 CPU 跑得动
1:N 特征检索 tinyfaiss / sqlite-vec 万级特征库用 FAISS 系;小库直接用 numpy 暴力搜索即可

buffalo_l 里的识别模型 w600k_mbf 是 MobileFaceNet 的 512 维版本:参数量约 1.1M,FP32 ONNX 约 4.3MB,INT8 量化后约 1.2MB,112x112 输入在手机 CPU 上单次推理 10-30ms(官方典型值)。检测器 SCRFD-10G 约 5MB,单帧 20-50ms。整套链路在主流手机上能做到 30fps 实时。

特征提取与 1:1 验证

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import onnxruntime as ort
import numpy as np

# insightface 官方导出的 MobileFaceNet ONNX 模型
sess = ort.InferenceSession("w600k_mbf.onnx",
                            providers=["CPUExecutionProvider"])

def embed(face_img):
    # face_img: 已按 5 点关键点对齐的 112x112 RGB 图,值域 [0,1]
    inp = face_img.transpose(2, 0, 1)[None].astype(np.float32)
    feat = sess.run(None, {"input.1": inp})[0][0]
    return feat / np.linalg.norm(feat)   # L2 归一化

# 1:1 验证:向量已归一化,余弦相似度就是点积
sim = float(np.dot(embed(img_a), embed(img_b)))
print("相似度:", sim, "-> 同一人" if sim > 0.5 else "-> 不同人")

阈值 0.5 只是经验起点:门禁、支付等安全场景要往高调(0.6 以上,宁拒勿误);相册聚类等体验场景往低调(0.4 左右,宁误勿拒)。上线前用自己数据集的真负样本对扫一遍 ROC 曲线再定阈值,别拍脑袋。

1:N 检索:特征库加最近邻

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import numpy as np

# 注册:每人 3-5 张不同角度/光照的图,特征存库(可落盘 sqlite/faiss)
gallery = {"alice": embed(alice_img), "bob": embed(bob_img)}

def search(query_img, top_k=3):
    q = embed(query_img)
    scores = sorted(((np.dot(q, v), name)
                     for name, v in gallery.items()),
                    reverse=True)[:top_k]
    return [(name, round(s, 4)) for s, name in scores]

特征库用向量数据库(sqlite-vec、tinyfaiss)持久化后,新员工入职只需一条 INSERT,这就是度量学习相对分类模型的核心优势。

INT8 量化:体积减 3/4

量化前先准备代表数据集(几十到几百张对齐后的人脸图即可),用 onnxruntime 做静态量化:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from onnxruntime.quantization import (quantize_static, QuantType,
                                      CalibrationMethod,
                                      CalibrationDataReader)

class CalibReader(CalibrationDataReader):
    def __init__(self, images):
        self.data = [{"input.1": img} for img in images]  # 已归一化
        self.idx = 0
    def get_next(self):
        if self.idx >= len(self.data):
            return None
        d = self.data[self.idx]; self.idx += 1
        return d

quantize_static(
    "w600k_mbf.onnx", "w600k_mbf_int8.onnx",
    CalibReader(calib_images),
    per_channel=True, weight_type=QuantType.QInt8,
    calibration_method=CalibrationMethod.MinMax,
)

INT8 后体积从 4.3MB 降到 1.2MB,精度损失通常在 1-2%。但量化会轻微改变特征空间分布,必须用量化后的模型重新生成特征库,否则相似度整体偏移,原来的阈值全部失效——这是最容易踩的坑。

踩坑清单

  1. 活体检测不能省:照片、视频攻击是端侧人脸最大风险。简单方案是随机动作指令(眨眼、摇头),或双目深度图,别让模型裸奔。
  2. 对齐质量决定上限:特征模型再准,5 点关键点对齐歪了也白搭。insightface 的检测模型会输出关键点,取 face.embedding 前确保走完整条 pipeline,不要自己随便裁图。
  3. 小样本注册:每人注册 3-5 张不同角度、光照的图,特征取平均或存多份,1:N 命中率明显提升。
  4. NPU 算子兼容:INT8 模型里部分算子组合(Resize、Transpose 变体)可能不被 NPU 支持,先跑支持性检查,不行就回退 CPU 或混合精度。
  5. 特征库版本管理:换模型版本、换量化位宽后必须整库重建,新旧特征向量不能混用。

小结

端侧人脸识别的技术栈已经非常成熟:insightface 负责检测与特征提取,ONNX Runtime/LiteRT 负责推理,INT8 量化把体积和延迟压到手机可接受范围。真正拉开差距的是工程细节——阈值调参、活体检测、特征库管理。把上面五步跑通,你就拥有了一套可完全离线运行的人脸识别底座,门禁、考勤、相册分类都可以直接往上搭。