三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

03-SCRFD人脸检测与关键点

03-SCRFD人脸检测与关键点

SCRFD 人脸检测与五官关键点:从 ROI 到可对齐几何

本文讲透一件事:在本项目里,一张(或一块)BGR 图如何变成 人脸框 + 分数 + 五个关键点。重点是 SCRFD 的 左上角 pad 预处理(不是居中 letterbox)、多 stride 解码公式、全图 Tile 补检、以及人体 ROI 内检脸时的坐标叠层。关键点质量直接决定后续对齐;框对了点错了,识别会「自信地错」。

1. 人脸检测要交付什么

业务侧一次成功的检测至少包含:

box: (x, y, width, height) // 原图或当前坐标系
score: float
kps: Point2f[5] // 左眼、右眼、鼻尖、左嘴角、右嘴角

顺序必须与对齐模板一致。本项目 ArcFace 对齐使用 InsightFace 常见顺序;写反左右眼会导致 112×112 花屏般扭曲。

默认权重路径为./models/scrfd_2.5g_bnkps.onnxbnkps表示带关键点)。输入边长 SCRFD 路径为 640(YuNet 回退为 320)。


2. 为何常在人体 ROI 内检脸

两条路线:

路线优点风险
全图直接 SCRFD实现简单4K 小人脸、算力高、海报脸多
人体框 ROI 内检搜索空间小,符合「脸长在人身上」人体框切掉额头/下巴会丢脸

跟踪路径上 ROI = clamp 后的人体框本身,不做外扩。这与「经验上上下左右 pad」的科普叙述不同——本项目选择更紧的 ROI,换取更少邻人探入,代价是人体框偏短时可能切脸。配置上用人脸分数阈值(默认约 0.40)与过大脸过滤补偿。

图:在人体检测框内裁 ROI 做人脸检测;本项目跟踪路径默认不外扩,仅 clamp 到画幅,减少邻脸入侵。

对每个人体框:

  1. clamp ROI,跳过<32×32
  2. 优先 动态 batch 一次跑多个 ROI;
  3. 把相对 ROI 的box/kps加上roi.x/roi.y回到全图;
  4. 过大脸过滤 去掉过大脸(海报、误检)。

失败模式: 只平移了 box、忘了 kps → 对齐用错点;ROI 过小未放大 → 远脸永远无点。


3. SCRFD 预处理:左上角 pad,不是居中

YOLO 人体常用 居中 letterbox;SCRFD 在本项目明确是 InsightFace 风格 resize + 左上角粘贴——不是居中 Letterbox:
要点:

  • 等比缩放,使较长边贴合 目标边长(方输入);
  • 黑边在右/下,有效内容贴在左上角(0,0)
  • det_scale = new_h / src.rows(与宽度缩放一致,因等比);解码时用inv_scale = 1/det_scale把网络坐标乘回预处理前图。

左上角 pad 预处理:

// InsightFace SCRFD:左上角 pad,不是居中 Letterboxvoidscrfd_prepare_input_bgr(constcv::Mat&src,inttarget_size,cv::Mat&out,double&det_scale){constdoubleim_ratio=(double)src.rows/(double)src.cols;intnew_w=0,new_h=0;if(im_ratio>1.0){new_h=target_size;new_w=(int)std::lround((double)new_h/im_ratio);}else{new_w=target_size;new_h=(int)std::lround((double)new_w*im_ratio);}new_w=std::max(1,std::min(target_size,new_w));new_h=std::max(1,std::min(target_size,new_h));cv::Mat resized;cv::resize(src,resized,cv::Size(new_w,new_h));out=cv::Mat::zeros(target_size,target_size,CV_8UC3);resized.copyTo(out(cv::Rect(0,0,new_w,new_h)));// 贴在 (0,0)det_scale=(double)new_h/(double)std::max(1,src.rows);}

数值例子:ROI 200×500 → 640

im_ratio = 500/200 = 2.5 > 1
new_h = 640, new_w = round(640/2.5) = 256
det_scale = 640/500 = 1.28

640 画布左上 256×640 为有效图,右侧 384 列为黑。

若错误地用居中 pad: 训练/导出假设左上原点,框与点会系统性偏移半个 pad——典型「全往右下漂」。


4. 多尺度锚点与解码公式

SCRFD 在多个 feature stride 上预测。每个位置 2 个 anchor。中心:

cx = x * stride, cy = y * stride

框为 距离中心的左右上下距离(distance),再乘 stride:

x0 = (cx - dlstride) * inv_scale
y0 = (cy - dt
stride) * inv_scale
x1 = (cx + drstride) * inv_scale
y1 = (cy + db
stride) * inv_scale

关键点同理:

kx = (cx + oxstride) * inv_scale
ky = (cy + oy
stride) * inv_scale

其中inv_scale = 1/det_scale,把网络输入坐标还原到 预处理前的图(ROI 或全图缩放图)。

手算例子

stride=8,特征格(x,y)=(10,20)cx=80, cy=160
dl,dt,dr,db = 2,3,2.5,4(网络输出)→ 乘 8 得16,24,20,32
det_scale=0.5,inv=2

x0=(80-16)*2=128, y0=(160-24)*2=272
x1=(80+20)*2=200, y1=(160+32)*2=384
脸约 72×112(预处理前坐标)

某关键点偏移(1.5, -0.5)

kx=(80+1.5*8)2 = 184, ky=(160-0.58)*2 = 312

图:SCRFD 输出人脸框与五点——左眼、右眼、鼻尖、左嘴角、右嘴角;点序必须与对齐模板一致。


5. 分数阈值与「能检 ≠ 能识别」

ROI 配置默认人脸分数阈值约 0.40。

全图路径里常见更低阈(如 0.25~0.3)以提高召回。分层建议:

人脸短边(原图像素)检测识别
<20多半噪声不可用
20~40存在性极不稳
40~80较可靠正脸可试
>80适合锁定

过大脸过滤:短边超过max_face_short_ratio × min(帧宽,帧高),或相对同伴过大(同伴倍数),多半是海报/误检。


6. 全图 Tile:overlap 0.25,NMS 0.45

当允许 tile 且分辨率够大时,走滑窗人脸检测:
与人体 Tile 的差异:

  • 门槛同样约 1280×720,但 4 格阈是 2400×1350(不是 3840);
  • tile 保持矩形(按 base 各向 ×1.25),不强制正方形;
  • 最小块 160(人体侧常 320);
  • 框与 每个关键点 都要加 ROI 原点。

合并策略:

  1. 先对缩小后的全图跑一次 SCRFD;
  2. 若失败或空 → 直接 tile;
  3. 若只找到 ≤1 张脸且图够大 → 补充 tile,再与原结果合并,nms_dets_inplace(merged, 0.40)

人脸 NMS 的排序键是 score × area(不是纯 score):
偏好「又准又大」的脸,有利于丢掉碎小重复框。


7. 坐标系叠层:最易踩的坑

一张脸可能经历多层变换:

原图
→(可选)全图 downscale,记录 sc
→(可选)人体 ROI 裁切,原点 (rx,ry)
→ 网络输出
→ × inv_scale 回到 pad 前图
→ + (rx,ry) 回到 downscale 图或原图
→ × (1/sc) 回到原图

图:人脸框与五点必须共用同一套逆变换;少加一次 ROI 原点或漏除 downscale,点会漂出五官。

在全图路径上,downscale 与 tile ROI 原点都要与五点同步回加。

调试口诀:

  1. 在送进 SCRFD 的那张图上画框点 → 已偏:解码/pad;
  2. 该图对、拼回原图偏 → ROI 或 downscale;
  3. 框对点飘 → 只修了 box 忘了 kps。

8. 关键点质量门禁(检测出口就该做)

送去对齐前建议丢掉:

  • 分数过低且面积过小;
  • 两眼距离 ≈0 或左右交叉;
  • 五点大量落在脸框外;
  • 明显贴 ROI 边的半张脸(除非业务强制半脸尝试)。

目测:

  1. 正脸时两眼连线近似水平;
  2. 鼻尖在两眼中点偏下;
  3. 嘴角大致对称且间距小于眼距;
  4. 五点整体落在框内。

侧脸大角度、口罩、墨镜、手遮嘴:框可能仍在,点不可信——应让上层降权,而不是硬对齐。


9. 多人脸消歧:谁才是这具身体上的脸

一个人体 ROI 内可能出现主人脸、邻人探入、海报、镜面。启发式:

  • 选分数最高且面积合理者;
  • 中心更靠人体框 上半部;
  • 排除中心落在下半身者;
  • 已有轨迹时选靠近预测脸位者;
  • 面积异常大且贴边 → 警惕海报。

人脸与人体轨迹匹配:脸心在人体框内且 IoU 最大(阈值约 0.05)。消歧错了,对齐和识别会非常自信地错——比检不出更难查。


10. 姿态、遮挡与分数

情况关键点建议
小角度侧脸通常在对侧略偏可识别,看相似度
大侧脸/后脑可能在不可信丢弃等正脸
手遮嘴嘴角乱可跟,识别慎用
口罩嘴失效降期望
墨镜眼不稳双风险
低头看手机偏小点挤一团等抬头

11. Batch ROI:性能与正确性

优先动态 batch:多人 ROI 一次 Run。注意:

  • 每张 ROI 的检测缩放 / pad 信息必须分条保存;
  • 空或过小 ROI 不要占位;
  • batch 失败时有串行回退;GPU 路径失败可能直接告警返回。

批处理是性能优化,不是正确性来源:先保证单 ROI 框点正确,再拼 batch。


12. 轻量回退 YuNet

加载时若 SCRFD 权重不可用,可走 YuNet(输入 320)。回退精度通常较差,但输出契约仍是框+点+分。必须确认关键点索引与 SCRFD 一致,否则对齐模板错位。


13. 完整几何例子:人体框 → 全图五点

人体框(800,200)-(1000,700),即200×500。clamp 后作为 ROI,原点(800,200)

ROI 内 SCRFD(经左上 pad)得到相对 ROI:

face box: (40, 30, 60, 70)
kps[0] 左眼: (55, 48)

回到全图:

box → (840, 230, 60, 70)
左眼 → (855, 248)

若此前全图曾 ×0.5 downscale,还要再 ×2。少一步,鼻尖会落到脸颊外,Umeyama 残差爆炸。


14. 时序:让脸别抖

单帧框点有噪声。对同一 track:

  • 短窗指数平滑脸框;
  • 五点平滑时防止左右眼交叉——可先平滑框再约束点;
  • 遮挡刚结束几帧降低平滑,让新观测快速纠正。

平滑改善的是识别分数的时间稳定性,不是 mAP。


15. 白天/夜间阈值

时段分数分布建议
白天清晰真脸偏高,海报也不低阈可略高,靠 ROI/过大脸过滤挡海报
夜间低照真脸整体下移略降阈,同时提高最小脸边长

原则:阈值服务「可识别的脸」,不是理论最大召回。


16. 与对齐模块的交接契约

检测交给对齐的最小字段:

  • 原图像素系脸框;
  • 原图像素系五点(含顺序说明);
  • 检测分数;
  • 可选:短边、粗姿态(两眼距/脸宽)。

对齐侧不应猜测「是否 mirror」;契约稳定,联调成本下降一个数量级。


17. 常见问题清单

现象可能原因
有人无脸ROI 过紧切额、阈过高、侧脸、未 tile 补检
脸框抖单帧噪声;需平滑/跟踪
对齐扭曲点序与 kRef5 不一致
海报脸无人体附着或过大脸过滤过松
框对点在框外只逆变换了 box
全往右下偏误用居中 letterbox
邻人脸当主人ROI 过大或消歧弱
远人无脸全图未 tile;ROI 太小未放大

18. 参数直觉表

参数过严过松
face_score_thresh漏侧脸/远脸虚影、海报增多
max_face_short_ratio放弃近景大脸海报进对齐
tile NMS 0.45过低误伤贴脸两人
最小 ROI 32丢远人噪声 ROI

19. 为何五点比框「更值钱」

同样侧脸:框与分数可能仍好看,但点落在脸颊时,相似变换会把五官拧到怪位置,embedding 漂,相似度断崖。工程质量应双线:

  • 检测召回:有没有框;
  • 关键点质量:点是否在五官上(可视化抽检)。

只盯检测 mAP、不看点,会出现「检测指标好看、识别却不稳」的假象。

← 返回列表