系列目录
- (三)关键点检测:为什么 106 个点全挤在 5×7 像素里
- (六)mask、膨胀与泊松融合:最后 10% 的细节
找脸,没那么简单
在换脸之前,你得先找到脸。
这听起来简单——谁不认识一张脸呢?但对计算机来说,"在图里找到一张脸"意味着要在几百万个像素中,判断每个位置、每种尺寸下是否存在人脸。而且人脸可以是 20 像素的小头像,也可以是占满整张图的大头照。
SCRFD(Sample and Computation Redistribution for Face Detection)用了一个巧妙的多尺度策略来解决这个问题。
三个尺度,各管一摊
SCRFD 在三个特征图层级上同时做检测。每个层级的网格密度不同:
总计:16,800 个锚点,均匀铺满 640×640 的输入图像。
为什么是这三个步长?因为人脸在图像中的常见尺寸范围大约是 20-600 像素。步长 8 的网格(80×80)每个格点覆盖 8×8 像素区域,适合捕捉小脸。步长 32 的网格(20×20)每个格点覆盖 32×32 像素区域,适合大脸。
三个尺度互补,几乎不会漏掉任何尺寸的人脸。
每个锚点输出什么?
对 16,800 个锚点中的每一个,SCRFD 输出:
[score] [left, top, right, bottom] [x0,y0, x1,y1, x2,y2, x3,y3, x4,y4]
↑ ↑ ↑
置信度 边界框(4个值) 5个关键点(10个值)
- score:这个位置有人脸的概率。已过 sigmoid 激活,范围 (0, 1)
- bbox:边界框的四个值,但不是直接坐标,而是距离格式
- landmarks:左右眼、鼻尖、左右嘴角。同样是距离格式
距离格式:一个小而重要的设计
SCRFD 的边界框不输出 (x1, y1, x2, y2) 或 (cx, cy, w, h)。
它输出 [left_dist, top_dist, right_dist, bottom_dist]。
这四个值分别表示:从锚点中心向左、上、右、下各走多远到达边界框的边缘。
解码公式:
# 锚点中心
cx = j * stride # j 是网格列号
cy = i * stride # i 是网格行号
# 距离 → 坐标
x1 = cx - left_dist * stride
y1 = cy - top_dist * stride
x2 = cx + right_dist * stride
y2 = cy + bottom_dist * stride
为什么用距离格式而不是直接输出坐标?
因为"距离"是一个相对于锚点的量,范围天然受限于锚点间距(stride),数值更稳定。直接输出绝对坐标的话,模型需要学习"这张图里第 537 个像素是人脸的左上角"——这比学习"从这里往左走 3 格"难得多。
同样的道理,5 个关键点也是距离格式:
landmark_x = cx + pred_dx * stride
landmark_y = cy + pred_dy * stride
NMS:去重
16,800 个锚点,同一个脸可能被几十个锚点同时"命中"。
NMS(Non-Maximum Suppression,非极大值抑制)负责去重。它的逻辑很简单:
- 删掉和它重叠超过 40%(IoU > 0.4)的其他框
defnms(boxes, scores, iou_threshold):
order = np.argsort(scores)[::-1] # 按分数降序
keep = []
while len(order) > 0:
keep.append(order[0]) # 保留当前最高分
# 计算当前框与其余框的 IoU
iou = compute_iou(boxes[order[0]], boxes[order[1:]])
# 保留重叠 < 阈值的框
order = order[1:][iou <= iou_threshold]
return keep
最终,每张人脸只保留一个最可靠的框。
Letterbox:处理任意尺寸
SCRFD 的输入是 640×640。但用户的照片可能是任意尺寸——竖屏 1080×1920 或横屏 1920×1080。
直接拉伸会把人脸压扁。正确做法是 letterbox:等比缩放后居中贴在 640×640 画布上,空白处填黑。
scale = min(640/h, 640/w)
new_h, new_w = int(h*scale), int(w*scale)
resized = cv2.resize(img, (new_w, new_h))
canvas = np.zeros((640, 640, 3)) # 黑色画布
canvas[pad_top:pad_top+new_h, pad_left:pad_left+new_w] = resized
检测完成后,所有坐标要按同样的比例和偏移映射回原图尺寸:
x_orig = (x_640 - pad_left) / scale
y_orig = (y_640 - pad_top) / scale
小结
SCRFD 的人脸检测部分整体很顺利。真正让我头疼的是下一步——106 个关键点的定位。
下一篇聊聊 2d106det 模型:我是怎么花了两个小时才发现 106 个关键点全挤在 5×7 像素里的。
下一篇:(三)关键点检测:为什么 106 个点全挤在 5×7 像素里