04-人脸对齐与ArcFace识别
人脸对齐与 ArcFace 识别:Umeyama、112×112 与余弦阈值
本文讲透一件事:检测到的五点如何变成标准脸,标准脸如何变成可比较的向量,向量如何用余弦相似度判定「是不是同一个人」。对齐核心是 Umeyama 相似变换与 kRef5 模板;识别权重为w600k_r50.onnx;业务锁定门槛为主角锁定相似度阈值(0.32)。
1. 为何必须对齐
识别网络训练时几乎只见过摆正、缩放到固定大小(本项目 112×112)的脸。若把歪头、仰拍、任意裁切直接塞进网络:
- 眼睛可能跑到图像下半部;
- 尺度不一导致纹理频率变化;
- 同一人在特征空间被几何扰动打散。
对齐目标:用检测五点估计变换,把脸搬进训练分布的标准座位。
可以把对齐理解成「证件照规范动作」自动化:眼睛水平、脸居中、尺度统一——不是为了好看,是为了让网络始终在熟悉的几何里工作。
2. 标准五点模板 kRef5
在 112×112 画布上:
- 两眼约在高度 52,水平距约 35.2;
- 鼻尖居中略下 (56, 72);
- 嘴角更靠下 (y≈92),间距约 29.2(小于眼距)。
这是 InsightFace / ArcFace 生态的常用模板。自定义五点却沿用别人的预训练权重,等于把脸摆进网络没见过的座位,分数会系统性异常。
图:112×112 标准五点位置;检测点经相似变换贴合到这些锚点后,再送入 ArcFace。
3. Umeyama:估计 2D 相似变换
相似变换含:旋转 + 统一缩放 + 平移(不含非均匀拉伸)。相似变换估计如下:
映射:
[x’] [ a b tx ] [x]
[y’] = [ -b a ty ] [y]
[1 ] [ 0 0 1 ] [1]
其中尺度s = hypot(a,b),旋转θ = atan2(b,a)(按实现约定)。
Umeyama 估计相似变换核心:
staticcv::Matestimate_sim_transform(conststd::array<cv::Point2f,5>&src){doublesx=0,sy=0,dx=0,dy=0;for(inti=0;i<5;i++){sx+=src[i].x;sy+=src[i].y;dx+=kRef5[i][0];dy+=kRef5[i][1];}sx/=5;sy/=5;dx/=5;dy/=5;doublesxx=0,syy=0,sxy=0,syx=0,var_s=0;for(inti=0;i<5;i++){doublesrcx=src[i].x-sx,srcy=src[i].y-sy;doubledstx=kRef5[i][0]-dx,dsty=kRef5[i][1]-dy;sxx+=srcx*dstx;syy+=srcy*dsty;sxy+=srcx*dsty;syx+=srcy*dstx;var_s+=srcx*srcx+srcy*srcy;}if(var_s<1e-8)returncv::Mat();doublea=(sxx+syy)/var_s;doubleb=(sxy-syx)/var_s;doubletx=dx-a*sx-b*sy;doublety=dy+b*sx-a*sy;return(cv::Mat_<double>(2,3)<<a,b,tx,-b,a,ty);}为何不用自由仿射?
| 类型 | 自由度 | 优点 | 风险 |
|---|---|---|---|
| 相似 | 旋转+等比+平移 | 不把脸拉扁 | 大姿态残差大 |
| 仿射 | 再含剪切/非均匀缩放 | 更能硬凑五点 | 扭曲五官,伤特征 |
ArcFace 预训练假设偏相似变换;乱升自由度会「对齐看起来贴点、特征却坏」。
图:检测五点(源)通过 Umeyama 相似变换贴到 kRef5(目标),再 warpAffine 得到 112×112。
4. warpAffine 出图
- 输入是 整帧或足够大的图 + 全图坐标五点(不是只喂紧脸框);矩阵会把五官搬进 112;
- 双线性插值;
- 常数边填充 默认黑边:姿态过大时边角可能黑——这是质量信号。
对齐失败外观: 眼睛不水平、鼻子歪、半张脸黑边、两眼距在 112 图上极小。此类图 绝不能静默送识别。
数值直觉
假设检测左眼 (200,180)、右眼 (260,182),模板眼距 35.24、检测眼距 ≈60.03:
粗尺度 s ≈ 35.24 / 60.03 ≈ 0.587
再叠加旋转(检测眼连线斜率约 2/60)与平移,使眼落到 (38.3,51.7)、(73.5,51.5)。手算完整 Umeyama 较繁,但可用「对齐后把五点画回 112 图」验收:应几乎压在 kRef5 上。
5. 对齐质量门禁
建议显式检查:
- 五点是否都有效、非 NaN;
var_s < 1e-8导致 M 空;- warp 后黑边占比是否过大;
- 112 图上两眼距离是否过小(尺度崩了);
- 重投影误差:把 kps 乘 M 后与 kRef5 的平均距离。
门禁失败应返回明确错误,而不是输出一张扭曲图——后者产生「有相似度、其实是垃圾」的分数,最难排查。
可视化三件套:原图+五点、112 对齐脸、(可选)模板点叠画。
6. ArcFace:脸 → 向量
默认模型:./models/w600k_r50.onnx。
ResNet50 骨干、WebFace600K 一类数据训出的 ArcFace 头;输出通常 512 维(以 ONNX 输出维为准)。流程:
- 对齐得 112×112 BGR;
- 按训练约定转 RGB、归一化、通道优先的张量排布;
- 前向得
feat[D]; - L2 归一化。
图:L2 归一化后,两向量点积即 cosθ;越接近 1 越像,业务用阈值 T 划分匹配。
公式
sim = cosθ = (a·b) / (|a||b|) = a·b (当 |a|=|b|=1)
| sim | 直觉 |
|---|---|
| →1 | 很像 |
| →0 | 几乎正交,不像 |
| <0 | 更不像(视训练分布) |
一边归一化、一边不归一化 会让阈值体系整体崩盘。
7. 业务阈值:0.32 不是魔法,但是契约
主角锁定相似度阈值(0.32)的用途包括:轨迹平均/最大相似度是否够格参与主角锁定、过低则跳过跟拍等。含义:
- 低于 0.32:默认不认为「够像底图」;
- 达到/超过:进入锁定候选逻辑(通常还要结合多帧、prominence 等,但本篇只讲相似度本身)。
为何不能照搬论文数字?
学术测试集多为近正脸、高清;监控是俯拍、压缩、侧脸、口罩。同一模型在证件照域 0.4 很严,在监控域可能需要 0.32 才锁得住——或反过来。阈值必须在目标域用正负样本对扫出来。
ROC 思维
收集:
- 正对:同一人不同帧 vs 底图;
- 负对:路人 vs 底图。
画分数分布,选误识率可接受的工作点。跟拍场景通常 更不能接受误识(路人锁成主角),故偏严,再用多帧聚合把拒识拉回来。
8. 底图质量决定上限
参考脸(thumb)同样:检测 → 对齐 → embedding。烂底图无人能救:
| 检查项 | 建议 |
|---|---|
| 人脸边长 | 尽量 ≥112,再缩到 112 |
| 姿态 | 近正脸 |
| 遮挡 | 无口罩墨镜手挡 |
| 清晰度 | 无运动模糊 |
| 人数 | 有且仅有一张主脸 |
入库门禁失败应拒绝注册。这是性价比最高的质量闸。
9. 侧脸、口罩、墨镜
- 大侧脸:可对齐但对齐后有效纹理少,sim 偏低 → 姿态过滤或等正脸;
- 口罩:鼻嘴区域失效;非口罩模型分数下沉;
- 墨镜:眼点不稳,对齐与识别双杀;
- 逆光剪影:纹理近无,分数无意义。
策略:轨迹保留历史最佳正脸特征;或要求锁定前至少一次高质量正脸。
10. 轨迹级聚合 vs 单帧拍板
更稳的做法:
- 每 track 维护最近 N 次有效 sim;
- 用均值或中位数;
- 连续 M 次超过 T 才锁定;
- 锁定后提高变更门槛,避免路人偶发高分抢走。
示例:单帧 0.30 < 0.32,但近 5 帧均值 0.35 且连续 3 帧 >0.30 → 业务可锁。同一模型,决策策略不同,产品表现天差地别。
11. 1:1 与 1:N
本场景多为 1:1(现场脸 vs 用户底图)。扩到 1:N 时:
- 比较次数↑,假阳性期望↑;
- 需开集:最高分不够高则「陌生人」;
- 1:1 的 0.32 不能直接当 1:N 阈值。
12. 归一化细节为何致命
| 方式 | 动作 | 风险 |
|---|---|---|
| 0~1 | /255 | 漏除会饱和 |
| -1~1 | 常见 InsightFace | 与 0~1 搞混 |
| 减均值除方差 | 按数据集 | 通道均值写错即漂 |
底图与现场必须走同一embed函数。几何对齐对了、数值规范化错了,一样认不出。
13. 端到端分数旅程
- SCRFD 给出全图五点;
- 相似变换估计 +
warpAffine→ 112×112; w600k_r50.onnx前向 → 512-d,L2 norm;- 与 thumb embedding 点积得
sim; - 与主角锁定相似度阈值(0.32)比较,并结合轨迹统计决定是否锁定。
任一步通道反了、未对齐、未归一化,都会表现为「所有人很低」或「所有人很高」。
14. 常见故障对照
| 现象 | 优先检查 |
|---|---|
| 所有人 sim 都很低 | 通道/归一化、未对齐、模型不匹配 |
| 所有人 sim 都很高 | 未 L2、比错向量 |
| 只有侧脸低 | 正常;加姿态门禁 |
| 换底图全好 | 旧底图质量差 |
| 对齐花屏 | 点序与 kRef5 不一致 |
| 偶发路人高分 | 单帧决策;改轨迹聚合 |
| M 为空 | 五点重合/var_s 退化 |
15. 相似变换残差与大姿态
当脸偏航很大,五点无法同时贴近平面模板,Umeyama 仍给出「最小二乘最好」的 M,但残差大,warp 后必扭曲。应用层应:
mean_reproj_err = mean_i || M(src_i) - kRef5_i ||
超过经验像素阈(例如数个像素量级,需按实现标定)→ 本帧不参与锁定投票。
16. 安全与偏见(科普)
embedding 不是原图,仍属生物特征相关数据:控访问、控留存、与用户 ID 映射分离。不同肤色/年龄误差可能不同,评估集应覆盖目标人群。误识业务代价通常高于拒识,阈值策略要反映这一点。
17. 实现检查清单
- 关键点顺序与 kRef5 一致;
- 输出确为 112×112,颜色通道正确;
- 特征 L2 后再点积;
- 底图与现场同一套预处理;
- 阈值来自目标域(本项目契约值 0.32 作起点/默认);
- 对齐失败有错误路径,禁止 silent 黑图提特征;
- 底图入库有质量门禁;
- 线上尽量轨迹聚合。
18. 分数带日常解读(相对本域)
| 分数带(相对 0.32) | 直觉 | 动作 |
|---|---|---|
| ≫0.32 | 很像 | 强投票 |
| ≈0.32~略高 | 临界 | 多帧确认 |
| 略低 | 不确定 | 继续观察 |
| ≪0.32 | 不像或质量崩 | 丢弃本帧 |
切勿把他域论文阈值直接印刷到产品;换镜头与压缩,分布会移动。
19. 同一人分数为何波动
表情、转头、光照、压缩都会改纹理。可怕的不是波动,而是用单点极值决策。看分布中心与高质量帧占比:只有偶发尖峰、多数很低 → 怀疑误匹配或门禁失效。
20. L2 归一化与数值坑
voidPersonFocusFaceRecogOrt::l2_normalize(std::vector<float>&feat){doublenorm=0;for(floatv:feat)norm+=(double)v*v;norm=std::sqrt(norm);if(norm>1e-8){for(float&v:feat)v=(float)(v/norm);}}数值例子: 未归一化向量若范数约为 12,两向量夹角其实不大,但原始点积可能到几十;另一对范数都很小的噪声向量点积却接近 0。若不归一化就设阈值,等于拿「长度」和「角度」混在一起比,阈值无法跨样本迁移。
若norm ≤ 1e-8(全零或崩坏输出),函数 不除,特征保持原样——后续点积无意义。应对:检测 embedding 范数,异常则丢弃本帧。
特征比较 在双方已归一化前提下直接累加点积;没有在 compare 里再次归一化。因此必须保证 对齐脸嵌入 / 人脸嵌入 出口已经 L2。若某条 GPU 捷径漏了 normalize,会出现「偶发分数爆炸」。
21. 轨迹打分如何吃 0.32
锁定不只看单帧是否 ≥0.32,还会把轨迹统计折成综合分。核心逻辑(人物聚焦模块):
解读:
- 硬门:
avg与max_sim同时低于 0.32 → 该轨没有资格; - 软合成: 更看重
max_sim(0.58),其次高分占比(0.30),均值权最小(0.12)——鼓励「曾经有过清晰正脸」; - prominence: 人在画面中更大、更居中加分,避免角落路人偶发像而夺锁。
手算例子
轨 A:avg=0.28, max=0.41, hi_ratio=0.3, prom=0.6
硬门:max=0.41≥0.32 → 通过
thumb_core = 0.120.28 + 0.580.41 + 0.300.3 = 0.0336+0.2378+0.09 = 0.3614
score = 0.720.3614 + 0.28*0.6 ≈ 0.260 + 0.168 = 0.428
轨 B:avg=0.35, max=0.36, hi_ratio=0.8, prom=0.3(稳定但峰值一般、不太居中)
thumb_core = 0.120.35 + 0.580.36 + 0.300.8 = 0.042+0.2088+0.24 = 0.4908
score = 0.720.4908 + 0.28*0.3 ≈ 0.353 + 0.084 = 0.437
两轨分数接近时,日志里的avg/max/n/hi/prom/score比单看一个 0.32 开关更有信息量。
22. ArcFace 损失直觉(公式级)
训练时类别权重与特征都落在超球面上,logits 用角度:
cos(θ_y) → ArcFace: cos(θ_y + m)
L = -log( e^s·cos(θ_y+m) / (e^s·cos(θ_y+m) + Σ_j≠y e^s·cos(θ_j)) )
- m:角度间隔,迫使同一类更聚、类间更开;
- s:尺度,放大梯度。
推理时你只拿到特征,用余弦比。换模型必须重标定阈值:不同m/s、不同数据训出的分数分布不可直接共用 0.32。
23. 对齐失败的可复现实验
固定一张正脸图,故意搞坏五点:
| 操作 | 112 图外观 | 典型 sim 变化 |
|---|---|---|
| 左右眼交换 | 五官镜像拧曲 | 同人 sim 暴跌 |
| 鼻尖平移 20px | 脸被拉斜 | 中幅下降 |
| 五点全缩到中心 | 黑边巨大 / M 近退化 | 不可用 |
| 只用脸框四角假装五点 | 完全错位 | 噪声级 |
这能快速证明:识别问题优先查几何,再查模型。
24. expand-pad 重试与相似度
现场比对偶发失败时,代码会对脸框做约 0.18 比例外扩再 embed(扩大上下文、减轻切下巴)。外扩过大可能引入邻人肩膀纹理,sim 漂移;过小则对齐黑边增多。与检测 ROI「不外扩」不同,这是 识别前对已检脸框 的补偿,两者不要混为一谈。
25. 余弦与欧氏的换算
双方单位向量时:
||a-b||² = 2 - 2·(a·b) = 2(1 - sim)
故sim=0.32对应欧氏距离:
||a-b|| = sqrt(2(1-0.32)) = sqrt(1.36) ≈ 1.166
sim=0.50→sqrt(1.0)=1.0;sim=0.80→sqrt(0.4)≈0.632。用哪一种报数都行,但团队内部必须统一,并与主角锁定相似度阈值(0.32)的余弦语义一致。
