摘要
闸机刷脸难的不是"认出是谁",而是要在 1 秒内、光线不可控、只允许错一次的条件下给出稳定结论。ArcFace 用加性角度间隔把同一个人的特征压到一起、把不同人推开,输出的 512 维向量做一次余弦比对就能判定,天然适合边缘设备。本文从原理讲到数据整理、训练微调、ONNX 转 BModel,最后给出在土星云 SE110S-WA32、SE110S-WB16、SE110S-WC8 三款设备上的部署方案与性能数据。
一、闸机刷脸难在哪里
闸机是一次通行场景:人走到闸机前,0.5~1 秒内必须决定放行还是拒绝。误拒把员工挡在门外,误放把陌生人放进来,三个约束决定了技术路线。
时间预算紧。解码、检测、对齐、特征提取、比对、活体,全流程要压在 300ms 以内,留给识别模型的只有几毫秒。
环境不可控。逆光、夜间补光、帽子口罩、低头侧脸、眼镜反光,注册照和现场抓拍之间常常差着一次"换装"。
底库要能改。新员工入职当天就要能用,离职要立刻失效,临时访客要限时有效。
早期方案用 LBPH,或拿分类模型的倒数第二层当特征,问题是模型只认识训练过的人,来新人就得重训。工程上最后收敛到度量学习:模型输出与身份无关的向量,用向量距离判断是不是同一个人,ArcFace 是这条线上最成熟的一支。
二、ArcFace 的基础知识
2.1 三件事:检测、对齐、特征
刷脸不是把整张图塞进网络。流程分三步:检测给出人脸框和 5 个关键点(双眼、鼻尖、嘴角);用 5 点做相似变换,把人脸仿射到 112×112 标准模板;对齐后的小图送进识别网络,得到 512 维向量并做 L2 归一化。
对齐最容易被忽略。同一个人抬头、低头、侧脸,只做 resize 会让特征差异很大;按关键点做相似变换,才能统一姿态和尺度。这是同一个模型换机位仍然稳定的前提,也是现场调优优先级最高的一步。

图 1 ArcFace 的原理与网络结构
2.2 从 softmax 到加性角度间隔
普通分类的 softmax 只要求"分对",不要求同类紧凑、异类远离。ArcFace 把分类层的打分改写成角度形式:权重和特征都归一化、特征模长固定为 s,logit 就只剩 cosθ,再给目标类的角度加一个间隔 m:
L = -1/N · Σ log e\^(s·cos(θ_y+m)) / ( e\^(s·cos(θ_y+m)) + Σ e\^(s·cos θ_j) )
间隔把决策边界往目标类那边推,想"分对"就必须让同类特征更紧凑,等价于在超球面上把类间测地距离拉开。常用 s=64、m=0.5(约 28.6°)。m 太大,小底库微调时不容易收敛;m 太小,类内不够紧,阈值就没有腾挪空间。
2.3 网络结构与输入
本文用的权重是 buffalo_l 里的 w600k_r50:IR-SE-ResNet50 主干(残差块内嵌 SE 通道注意力),在约 60 万身份的 WebFace600K 上训练,输出 512 维。输入固定 112×112 RGB,预处理是 (pixel-127.5)×0.0078125。112×112 的 R50 只有 1 GFLOPs 量级,这是它能跑在边缘 SoC 上的关键。
输出层不是分类结果而是特征向量。分类层只在训练时存在,部署时被裁掉,所以底库加人不需要重训模型,把新特征入库就行。
2.4 比对与阈值
特征已经 L2 归一化,余弦相似度就是点积。1:1 场景(刷脸加工号或刷卡)比一次,超过阈值放行;1:N 场景(只刷脸)在底库里取 top-k,要求 top1 超过阈值、并且和 top2 有明显间隔才放行,间隔不足就转人工或要求二次确认。
阈值不能拍脑袋。同人与非同一人的分数分布一定有重叠,阈值就是在两个分布之间切一刀,切在哪取决于业务能接受多少误拒、多少误放,只能用现场数据定。
三、应用场景
园区与写字楼闸机。 底库几千到几万人,早高峰集中通行,要求 300ms 内响应,断网时本地白名单要能继续放行。
工地实名制。 安全帽、口罩、粉尘、强逆光,人脸常常只露半张,底库按班组管理、流动频繁。
校园与宿舍。 夜间归寝、陌生人尾随是重点,通常与刷卡互为备份,刷脸失败不能把人堵死。
酒店、健身房、政企机房。 会员与访客底库、临时授权、与梯控联动,一人一档、留痕可查。
这些场景机位固定(闸机上方 1.2~1.5 米、俯角 10~20 度)、单人依次通过、要求离线可用,把模型放在本地而不是云端是默认选择。
四、算法优势
类内更紧,阈值才有空间。 间隔约束下,同一个人换光照、换角度、戴眼镜,余弦相似度通常还能保持在 0.6 以上,与陌生人之间留出明显空隙。
特征与身份解耦。 新人注册只是提一次特征入库,不重训、不重新编译,离职删一行记录即可。
比对成本可以忽略。 512 维点积是 512 次乘加,1 万人底库全量比对约 500 万次乘加,比一次推理便宜两个数量级,1:N 检索不是瓶颈。
输入小、量化友好。 112×112 让 R50 在边缘 SoC 上进入毫秒级,INT8 量化对特征向量的影响可控,阈值判定基本不变。
与检测解耦,调参可解释。 换检测器、加活体都不影响识别网络和底库;现场只有阈值和间隔两个抓手,出了问题能定位到"人没对齐"还是"阈值定高"。
五、数据收集与整理
5.1 注册照和现场抓拍要分开
注册照进底库:证件照式正面、光照均匀、无遮挡、单人、人脸像素不低于 200×200。同一个人建议放 2~3 张,正脸一张、轻微侧脸一张、日常戴眼镜一张。
现场抓拍用于评测和难例挖掘,必须来自真实机位、真实光照;用注册照当测试集测出来的准确率没有意义。
5.2 采集要覆盖的维度
按"人 × 姿态 × 光照 × 遮挡"排布。50~100 人起步,每人正脸 2 张、左右侧脸(±30°)各 1 张、帽子/口罩/眼镜各 1 张;场景覆盖白天逆光、夜间补光、室内荧光。
闸机现场最容易翻车的是逆光和低头:摄像头在 1.5 米高、俯角 15 度,人低头看手机时只露上半张脸。这两个场景要单独做一组,比多拍 200 张正脸有用。
5.3 清洗:先过滤质量,再去重
同一个人被不同批次录入、或注册了两次,都会让底库出现重复,直接影响 1:N 的 top2 间隔。做法是先用模型提特征聚类,两两余弦超过 0.6 且人工确认的合并为同一人,同时过滤掉人脸过小、模糊、过暗过曝的样本。
import cv2
def face_quality(img, box):
x1, y1, x2, y2 = int(v) for v in box\[:4]
if x2 - x1 < 80: # 人脸像素太小
return False, "too_small"
crop = imgy1:y2, x1:x2
gray = cv2.cvtColor(crop, cv2.COLOR_BGR2GRAY)
if cv2.Laplacian(gray, cv2.CV_64F).var() < 60: # 模糊
return False, "blur"
if gray.mean() < 40 or gray.mean() > 220: # 过暗或过曝
return False, "exposure"
return True, "ok"
feats: N×512,已 L2 归一化;同人相似度通常 > 0.6
sim = feats @ feats.T
np.fill_diagonal(sim, 0)
dups = np.argwhere(sim > 0.6) # 人工复核后合并
5.4 划分与合规
训练、验证、测试必须按人划分,同一个人的照片不能同时出现在训练集和测试集里,否则指标虚高。底库和测试集也要分开,测试集里的人不能提前入库。
采集前要告知并取得同意;优先"只存特征、不存原图",但特征同样要加密;底库要支持按人删除;浴室、更衣室等私密区域不要装可见光摄像头。
六、模型训练
6.1 从公开权重起步
自采几千张人脸不足以从零训练 R50。常规做法是加载 WebFace600K、Glint360K 这类大规模预训练权重,把分类层换成自有身份数,用很小的学习率微调。自有底库只有几百人时,更稳的是不微调,直接用量产权重,把精力放在底库质量和阈值上------几百人的数据微调,收益抵不过过拟合风险。
6.2 损失、采样与超参
损失仍是 ArcFace。微调时 m 可降到 0.3~0.35,避免小样本下类中心被拉歪;批内按人采样,每批 P 个人 × K 张,保证每批都有正样本对。
|--------|--------------------------|
| 项目 | 建议值 |
| 输入 | 112×112 RGB,按 5 点对齐 |
| 主干 | IR-SE-ResNet50,预训练初始化 |
| 特征维度 | 512,L2 归一化 |
| 损失 | ArcFace,s=64,m=0.35~0.5 |
| 优化器 | SGD momentum 0.9 或 AdamW |
| 学习率 | 主干 1e-4,分类层 1e-3,余弦退火 |
| Batch | 256~512(P×K 采样) |
| Epoch | 20~30 |
| 增强 | 水平翻转、亮度对比度、高斯模糊、随机遮挡 |
增强要克制。水平翻转可以用,垂直翻转和大幅旋转会制造不存在的人脸;随机遮挡模拟口罩时,遮挡块要落在下半脸,别盖住眼睛。
6.3 评估口径
1:1 看 TAR@FAR=1e-3,即误放千分之一时的通过率,比准确率有意义得多;1:N 看 top1 准确率和 top1 与 top2 的间隔,业务侧还要盯误拒率(真人被挡)和通行耗时。ArcFace 系列在 LFW 等公开集上的报告准确率早已超过 99.8%,但公开集只适合确认训练和导出没跑偏,不能代表闸机现场。
七、模型转换
7.1 从 PyTorch 到 ONNX
先裁掉分类层、切到 eval(),用固定形状导出。部署阶段不要保留动态 batch 或动态分辨率,固定形状能让编译器做更充分的优化。
model.eval()
torch.onnx.export(
model, torch.randn(1, 3, 112, 112), "w600k_r50.onnx",
input_names="input", output_names="embedding",
opset_version=11, do_constant_folding=True,
)
导出后用 onnxruntime 跑同一张对齐图,与 PyTorch 输出比余弦,应该在 0.9999 以上。
7.2 编译成 BModel
BModel 是设备端推理使用的模型格式,编译在 x86 Linux 主机上完成,流程是 ONNX → MLIR → 量化标定 → BModel。芯片参数:SE110S-WA32 用 bm1684x,SE110S-WB16 用 bm1688,SE110S-WC8 用 cv186x。
model_transform.py --model_name arcface_resnet50 \
--model_def w600k_r50.onnx \
--input_shapes \[1,3,112,112] \
--mean 127.5,127.5,127.5 \
--scale 0.0078125,0.0078125,0.0078125 \
--pixel_format rgb --channel_format nchw \
--mlir arcface_resnet50.mlir
run_calibration.py arcface_resnet50.mlir \
--dataset ./cali --input_num 200 -o arcface_cali
model_deploy.py --mlir arcface_resnet50.mlir \
--quantize INT8 --chip bm1684x \
--calibration_table arcface_cali \
--model arcface_resnet50_int8_1b.bmodel
归一化写进编译参数后,设备端输入就是原始像素,mean/scale 由 TPU 内部完成,前处理设成 passthrough 即可。
7.3 校准集与精度对齐
INT8 校准集建议 200~500 张已经对齐的 112×112 人脸,必须覆盖逆光、侧脸、口罩、眼镜这几类难例,而且不能和测试集是同一批。
转换完成后要做的不是看"像不像",而是拿同一批对齐图分别跑 ONNX 和 BModel,比较两个 512 维向量的余弦:FP16 应在 0.999 以上,INT8 应在 0.99 以上。再比判定一致性------用固定阈值跑一遍测试集,看通过率和误放数有没有变化。INT8 的通过率明显掉了,先补校准集的难例覆盖面,仍不达标就退回 FP16。
八、三款 SE110S 设备部署与性能
8.1 设备规格与平台对应
|-------------|----------|-------------|--------|----------|
| 设备 | 计算平台 | INT8 算力 | 内存 | 定位 |
| SE110S-WA32 | BM1684X | 32 TOPS | 16GB | 集中式多路闸机 |
| SE110S-WB16 | BM1688 | 16 TOPS | 8GB | 中等规模闸机 |
| SE110S-WC8 | CV186X | 7.2 TOPS | 8GB | 末端单机、小底库 |
三款设备的差别不在能不能跑,而在能同时跑多少路、能不能和检测、活体共享算力。
8.2 理论推理耗时
下表取自 sophon-demo ArcFace 例程 的 bmrt_test 结果,测试对象是单张 112×112 人脸,单位为毫秒/张。平台对应关系:SE110S-WA32 → SE7-32(BM1684X),SE110S-WB16 → SE9-16(BM1688),SE110S-WC8 → SE9-8(CV186X)。
|-------------|-------------|-------------|-------------|
| 设备 | FP32 1b | FP16 1b | INT8 1b |
| SE110S-WA32 | 14.180 | 2.243 | 1.189 |
| SE110S-WB16 | 72.690 | 9.934 | 3.426 |
| SE110S-WC8 | 72.839 | 10.039 | 3.547 |
规律很清楚:FP32 在三款设备上都不适合实时业务,SE110S-WB16 和 SE110S-WC8 要 72ms,余量会被吃光;FP16 是精度兜底,SE110S-WA32 的 2.24ms 仍然很快,SE110S-WB16 和 SE110S-WC8 的 10ms 要算进并发预算;INT8 才是常用档位,SE110S-WA32 1.19ms、SE110S-WB16 3.43ms、SE110S-WC8 3.55ms。
4batch 模型主要提升吞吐。例程注明 4batch 的 calculate time 为 4 张合计:SE110S-WB16 为 11.201ms(约 2.80ms/张)、SE110S-WC8 为 11.538ms(约 2.88ms/张)。闸机是单人依次通过,一次只有一张脸,4batch 更适合多人同框核验或离线批量比对底库。
8.3 端到端耗时
模型推理只是链条中的一段。例程在设备端统计的四段时间如下,单位为毫秒/张:
|-------------|---------|--------|---------|--------|---------|--------|
| 设备 | 模型 | 解码 | 预处理 | 推理 | 后处理 | 合计 |
| SE110S-WA32 | INT8 1b | 0.67 | 0.19 | 1.05 | 0.02 | 1.93 |
| SE110S-WB16 | INT8 1b | 0.92 | 0.23 | 3.40 | 0.03 | 4.58 |
| SE110S-WC8 | INT8 1b | 0.92 | 0.23 | 3.46 | 0.04 | 4.65 |
三款设备跑一张人脸的总耗时都在 5ms 以内。真正的大头不在这张表里:检测的输入是 640×640,算力是 112×112 识别网络的十几倍,再叠加活体和抓拍图编码,一次通行的实际耗时通常在 80~200ms。ArcFace 在整条链路里占比不到 10%,选型时不该盯着这 1~5ms,而要看检测、活体和并发路数。

图 2 三款设备推理性能对比
8.4 路数与选型
按 25fps 视频、人脸进入画面后每秒处理 5 帧、每帧先检测再识别的节奏估算:SE110S-WA32 可以带 16~32 路闸机,SE110S-WB16 带 8~16 路,SE110S-WC8 带 4~8 路。这是估算,实际以现场压测为准,分辨率、码流、活体策略和业务进程都会改变结果。
8 路以上集中部署优先 SE110S-WA32:算力、内存、解码都有余量,可同时跑检测、识别和活体。中等规模选 SE110S-WB16,16 TOPS 足够多路闸机并发。末端单机、底库几千人以内选 SE110S-WC8,但 FP16 慢,务必把 INT8 精度对齐做扎实。
8.5 部署链路与命令
设备端按这个顺序组织:取流 → 硬件解码 → 人脸检测 → 5 点对齐 → BModel 提特征 → 底库比对 → 活体与质量判定 → 开闸与留痕。识别模型只负责特征,检测和活体各自独立,替换其中一个不影响其余环节。
python3 arcface_bmcv.py \
--input ./datasets/test \
--bmodel ./models/arcface_resnet50_int8_1b.bmodel \
--dev_id 0
跑通后接摄像头,重点看四件事:检测框是否稳定、对齐后的人脸是否端正、同人相似度是否在 0.6 以上、陌生人是否稳定低于阈值。

图 3 从取流到开闸的链路
九、检测效果展示
下图是用与编译同一份 ONNX 在 x86 上真实推理得到的(检测 + 5 点对齐 + 1:N 检索),叠加样式按闸机端界面绘制;人脸图来自公开测试集 LFW,编号为匿名工号。

图 4 刷脸通行与拒绝的实测可视化
前两行是同一个人的两张不同照片:抓拍图与注册照对齐后的相似度分别为 0.784 和 0.605,top1 命中对应工号,与 top2 分别拉开 0.72 和 0.56 的间隔,判定放行。后两行是未注册的陌生人:top1 只有 0.057 和 0.077,低于阈值,判定拒绝。
阈值不是凭感觉定的。把 99 张测试人脸两两比对,共 4849 对非同一人样本,相似度均值 0.004、99.9% 分位 0.211、最大 0.336;同一人的两对样本落在 0.605 和 0.784。两个分布之间留出约 0.27 的空档,阈值取 0.40 既能挡住绝大多数陌生人,又不会误伤真人。另外,100 张测试图里有 1 张在 0.5 置信度下未检出人脸,这也是检测环节需要留余量的原因。

图 5 非同一人相似度分布与阈值位置
十、几条工程经验
1. 对齐比换模型更有效。 关键点歪了,换多大的主干都救不回来。
2. 注册照决定上限。 底库照片模糊、侧脸、戴帽子,后面怎么调阈值都难受。
3. 阈值用现场分布定。 公开集上的 0.4 只是起点,逆光与室内机位能差 0.1。
4. 底库必须去重。 同一个人两条记录,1:N 的 top2 间隔会被自己人吃掉。
5. INT8 先看判定一致性。 向量余弦是过程指标,通过率和误放数才是结果指标。
6. 活体和质量分不能省。 照片、视频回放、口罩遮挡都要拦,宁可多一次刷卡。
十一、总结
ArcFace 把"认人"变成了向量比对:112×112 输入、512 维特征、一次余弦判定,底库可增量维护,模型与身份解耦。工程关键不在模型,而在数据------对齐是否标准、注册照是否干净、阈值是否用现场分布定。
部署侧,三款设备跑 INT8 的单张人脸耗时 1.19ms、3.43ms、3.55ms,端到端 1.93ms、4.58ms、4.65ms,都远低于 300ms 预算;真正决定能带多少路的是检测、活体和并发调度。SE110S-WA32 适合 16~32 路,SE110S-WB16 适合 8~16 路,SE110S-WC8 适合 4~8 路。先单机跑通、把 INT8 精度对齐、再用现场数据定阈值,比堆算力更省事。