人脸识别一节课教案:从打开摄像头到屏幕上写出名字
大一新生的第一节人脸识别课,最重要的不是让他们记住某个模型的名字,而是让他们亲眼看见「人脸识别不是黑箱,是一条可以拆解的链子」。整节课按一条端到端流水线展开:数据从哪来、脸怎么框出来摆正、把脸压成一串数字、两串数字怎么比、最后用几行代码把整条链子跑通。每个术语第一次出现时都配一句白话解释,公式与训练细节一概不展开。
一、开场:人脸识别到底在做什么
学生进教室时,脑子里的人脸识别通常是一个黑箱动作:摄像头对着脸,机器就报出名字。这堂课要把这个黑箱拆成两件看得见、想得通的事:把脸变成一串数字,再看两串数字离得有多近。
第一件事听起来反直觉。一张脸是有颜色、有五官、有表情的图片,怎么就成了「一串数字」?这件事在机器学习的术语里叫 embedding(特征向量)。一句话白话解释——embedding 就是把一张脸「翻译」成一串浮点数(比如 128 个),每个数字代表脸的某种特征,组合在一起就成了这张脸的「数字指纹」。FaceNet 一篇论文给了一张非常直观的画面:

图注:图里 anchor、positive、negative 是三个圆点,分别代表「我自己的一张脸」「同一个人的另一张脸」「另一个人的一张脸」。三条距离线分别是 anchor 到 positive(同一人)和 anchor to negative(不同人)之间的距离,外加一个 margin α。三元组损失的训练目标是:让 anchor 到 positive 的距离变小、让 anchor 到 negative 的距离变大、并强制两者之间至少差一个 margin。换句话说,每张脸在数字空间里都有自己的位置,同一人的脸靠得近,不同人的脸离得远。
第二件事更简单:两张脸变成两串数字以后,我们只要算一下这两串数字的距离。距离近就是像同一人,距离远就不是同一人。 白话说就是「两串数字差多少」。这条分数线,在术语里叫阈值——白话就是「判像不像的那条分数线」。
下面这张图把整条链子的形状一次性画清楚:

图注:左端是一批对齐后的人脸图,中间经过一个深度卷积网络(deep CNN)抽特征,输出再经过 L2 归一化(ℓ2 normalization)压到一个单位超球面上(unit hypersphere),最终落成 128 维的 embedding 向量。训练时这条链子接三元组损失,推理时直接用这个 128 维向量比距离。这就是后面整节课要一步步兑现的「链子」长什么样。
到这里,学生应该能把「人脸识别」翻译成一句话:把脸变成 128 维数字,再算两串数字的距离,设条分数线看距离够不够近。这句话就是后面五章要兑现的承诺。
二、数据从哪来:四个公开数据集与自采数据的红线
第一节课是「从无到有」:手里没有图,什么模型都跑不起来。这一节把「人脸数据」从一个抽象名词具象化成四个有名字、有规模量级、有用途的数据集:
| 数据集 | 发布年 | 类别 | 主要用途 | 规模量级(公开常识值,未现场核验) | 课堂使用建议 |
|---|---|---|---|---|---|
| LFW | 2007-2008 | 验证 | 1:1 比对 benchmark | 约 1.3 万图 / 5 千身份 | 开场 5 分钟 demo |
| CASIA-WebFace | 2014 | 训练 | 入门级深度模型训练 | 约 1 万身份 / 50 万图 | 单卡 GPU mini 训练 |
| VGGFace2 | 2017-2018 | 训练 | 主流模型训练 | 约 9 千身份 / 330 万图 | 超出课堂范围,作认知锚点 |
| MS-Celeb-1M | 2016 | 训练 | 工业级训练 | 约 1000 万图 / 10 万身份级 | 只作认知锚点,不下载 |
规模数字均来自数据集主页的公开常识值,不应在课堂里硬编码为权威事实。LFW 适合做验证基准——给两张图问「是不是同一人」,是 FaceNet、ArcFace 论文几乎都跑过的「标准考卷」。CASIA-WebFace 和 VGGFace2 是训练数据集,体量递增。MS-Celeb-1M 是工业级规模,但 2019 年因合规争议被部分下线,课堂上只作认知锚点——知道有这种东西,不真下载使用。
课堂最小数据集方案分两步:第一步用 LFW 中的一对同名图和一对异名图跑 face_recognition 库的 compare_faces,5 分钟就能看见 True/False 输出;第二步用 5-10 张同学正面、清晰、不同角度的授权头像作为「班级人脸库」,这是 45-90 分钟项目里的录入环节。
自采数据的硬底线有三条:书面授权(拍摄前口头告知 + 书面签字)、只在本机处理(不传到云端)、课后销毁。这三条不是负担,是为了跑模型时不踩肖像权的雷。能公开用的图,等于「数据集官方授权 OR 同学授权 OR 自己」,不能用的图,是「网上随便扒的公众人物照片 + 未授权的同学 / 路人照」。
三、检测 + 对齐:这一步决定后面成败
把数据装进项目以后,第一件要做的不是建模,而是「把脸找出来、摆正」。这一步在课堂里通常被当成预处理一笔带过,但它是后面所有事情的成败关键:模型再厉害,送进去的是糊的、偏的、过曝的,输出的特征向量也带噪声。下面分四条并存的路线讲。
3.1 dlib 68 点:最直观的对齐锚点
人脸检测是「在照片里把脸框出来」,白话就是「脸在哪」。人脸对齐是「把检测到的脸摆正,统一眼睛、鼻子位置,让所有脸都同角度才好比对」。dlib 库给出的 shape_predictor_68_face_landmarks 是入门教程最经典的锚点:脸上有 68 个稳定定位点,按区域分布在下颌、眉、鼻、眼、嘴五个区域。

图注:白底示意图,68 个红色星号按编号 1-68 排成大致人脸轮廓的形状,外圈一圈椭圆对应下颌 + 眉毛,内部若干簇更密集的点对应鼻、眼、嘴。每个点的位置是稳定的——同一张脸每次检测出来的 68 个点几乎落在同一组像素上,这正是「对齐」能稳定工作的前提。
把这张抽象索引图映射到真实人脸上看:

图注:真实人脸照片叠加了绿色折线,沿眉弓、上下眼睑、鼻梁鼻翼、上下唇、下颌脸颊外轮廓描了一遍。这就是「68 点对齐在脸上画出来长什么样」。
detection + alignment 联合 API 是 get_frontal_face_detector() 出框 → shape_predictor() 出 68 点。底层用的特征是 HOG + 线性分类器 + 图像金字塔 + 滑窗(histogram of oriented gradients),学生不需要背具体公式,只要看见「这是一条三步调用链」就够。
3.2 MTCNN:检测和对齐可以一起做
dlib 是「先检测再对齐」的两步路线。MTCNN(Multi-task Cascaded Convolutional Networks)给出了另一条思路:检测 + 对齐可以一次完成。Zhang 等人的 MTCNN 论文 把这件事拆成三个阶段接力:

图注:从左到右是 P-Net、R-Net、O-Net 三个子网络,输入尺寸按 12×12 → 24×24 → 48×48 逐级变大,特征维数按 32 → 128 → 256 逐级变深。最右端用花括号引出三类输出:人脸分类(是不是脸)、bbox 回归(框在哪)、landmark 定位(5 个关键点)。
白话讲这段三阶段级联:「先撒大网粗看,再用更细的网过滤一遍,最后才确定脸在哪、五官在哪」——像讲义的三段式:开场 5 分钟定个轮廓,中间 25 分钟展开要点,最后 10 分钟收尾定稿。三段讲完一份讲义,三段看完一张脸。这是「级联过滤」这个抽象概念的具体化,也对应 O-Net 一次性同时输出人脸框与 5 点 landmark。
3.3 RetinaFace:现代多任务检测器
2020 年起,单阶段多任务检测器成为主流。RetinaFace 在一次前向传播中同时输出四类东西:人脸分数、人脸框、5 点 landmark、像素级 3D 信息。RetinaFace 论文 把这件事用一张图说清:

图注:左侧人脸图堆经过特征提取分流到三个监督分支:顶部「Existing branch」做人脸分类和框回归,中部「Extra-supervision」做 5 点 landmark 回归,底部「Self-supervision」做像素 3D 重建(通过 differentiable 3D mesh renderer)。这就是「现代检测器一次能输出更多」的具体画面。
配合下面这张架构总览看:

图注:左侧 5 层 backbone 特征金字塔 (C2-C5, C6/P6),右侧 4 层 FPN 加顶部特征,汇入 Context Module 后输出多任务损失。学生只需要看到「特征进来 → 多任务损失出去」的整体形状,不需要展开 Context Module 内部 128/64/64 分支的设计动机。
补充一个工具生态细节:face_alignment 库(pip install face-alignment)默认的检测器是 SFD,不是 RetinaFace;RetinaFace 是该库支持的 speed-favouring 备选之一(通过 face_detector='retinaface' 调用)。
3.4 OpenCV Haar cascade:机房最低门槛备胎
dlib 在 Windows 上要编译,机房经常装不上。这时候有两条 OpenCV 应急路线:
第一条是 Haar cascade——OpenCV 官方教程 给出的特征内核直觉:

图注:6 个黑白矩形模板,分 Edge Features(上下 / 左右分区)、Line Features(白-黑-白竖条 / 横条)、Four-rectangle features(2×2 棋盘)三行。每个特征值 = 白矩形像素和 − 黑矩形像素和。「为什么叫 cascade / 为什么是黑白矩形相减」从这张图能一眼看出。
第二条是 OpenCV 自带的 YuNet 检测 ONNX 模型(cv2.FaceDetectorYN,约 338 KB)加上 SFace 识别 ONNX 模型(cv2.FaceRecognizerSF,约 36.9 MB),前者 CPU 实时、精度比 Cascade 高很多;后者下载体积需留意。
Haar cascade 跑出来的实际效果长这样:

图注:真实照片上叠了洋红色人脸椭圆框 + 双眼蓝色圆圈,窗口标题为「Capture - Face detection」。这是
detectMultiScale的真实输出格式,不是「人脸识别」结果——它只做检测,不做识别。
3.5 脏图:这一步决定后面成败
无论走上面四条路线里的哪一条,脏图都会被原封不动传到下一步。下面这张示意是脏图的常见类型:
脏图要么在检测阶段就被丢弃(没检出脸),要么就算检出脸,后面 embedding 也会带噪声。课堂上不必展开剔除策略的具体代码,学生只要记住「这一步决定后面成败」就够了。
四、建模:把脸压成一串数字
把脸框出来、摆正以后,下一步是把每张脸变成一串数字。这件事在术语里叫 embedding(特征向量)——把一张脸「压成」一个 128 维或 512 维的浮点数向量,数字的组合代表这张脸的「数字指纹」。
4.1 FaceNet:把 128 维空间铺开
Schroff 等人的 FaceNet 论文 是把这个想法推到台前的奠基之作。它做了三件事:
第一,把一张脸压成 128 个浮点数(128 维向量)。论文里 §3.1「Embedding Dimensionality」一节做了 64/128/256/512 维度的消融,最终选定 128 维。白话讲就是「脸被翻译成 128 个数字,组合起来代表这张脸长啥样」。
第二,把所有向量都做 L2 归一化(ℓ2 normalization),让所有脸都落到单位超球面(unit hypersphere,半径为 1 的高维球面)上。这是为什么后续可以用 L2 距离或余弦相似度比对的几何基础——球面上「直线距离近」与「方向接近」是同一件事的两面。
第三,用三元组损失(triplet loss)训练:每次随机抽三张脸(两张同一人 + 一张不同人),让同一人靠得更近、不同人推得更远,两类之间至少差一个 margin α。前面 §1.1 看过的 anchor/positive/negative 图就是这步的视觉化。
4.2 ArcFace:在球面上按方向比脸像不像
FaceNet 之后主流改进是 Deng 等人的 ArcFace 论文。它把 FaceNet 的思路再推一层:所有脸仍然压到同一个球面上,但比较的不是距离,而是方向。而且在自己班的方向上还要比别的班再多靠近一个固定的角度 margin m。
下面这张图把这件事和上一代方法(Softmax)放在一起对比:

图注:4 个子图横向对比 Softmax、SphereFace、CosFace、ArcFace 在二分类场景下的决策边界(虚线)和 margin 区域(灰色阴影)。Softmax 的 margin 是弯月形(基于欧氏距离切出的楔形),SphereFace 是非线性角度锥,CosFace 沿 cos 方向拉出窄带,ArcFace 沿弧线方向均匀留出角度扇形——「在自己班方向上再多靠近 m 这么一点点」。指着 ArcFace 子图说「看这个扇形,它就是在球面上把两个班中间多切了一条 m 角度的缝」就行。
ArcFace 的训练流程长这样:

图注:Input Image → Backbone → 1×512 维 embedding → ℓ2 normalization → 与 K 个 sub-center 做矩阵乘法算子类相似度 → max pooling 得类相似度 → arccos 拿角度 → 在目标类角度上加 m → cos 拿 cos(θ + m) → 乘 s 放大 → softmax + cross entropy loss。重点不是每个算子的细节,而是「ℓ2 归一化 → arccos → 加 m → cos → 乘 s → softmax」这条数据流的形状。和前面 FaceNet 模型图相比,后者到「压到球面」就停了,ArcFace 把这条链又拉长了一段(加了 m)。
为什么 ArcFace 在 2026 年仍是事实标准?三个交叉证据:第一,论文 co-author Jia Guo 是 InsightFace 开源项目的 active contributor;第二,论文提到的 ArcFace / Sub-center ArcFace / PartialFC 是 InsightFace 默认的识别损失函数;第三,PyPI 上的 insightface 2.0 在 2026 年 9 月刚发版,2026 年仍在维护。也就是说,今天学生学的不是过时的 2018-2019 年论文,而是工业界在用的 loss。
4.3 不用记的细节
margin m 的具体数值(论文默认 0.5)、特征放大系数 s(默认 64)、triplet 的 hard sample mining 工程技巧——按 plan 一概不展开。课堂只讲直觉:embedding 就是用一串数字描述一张脸;embedding 空间里,同一个人的方向靠得更近、不同人的方向离得更远。
五、识别:两串数字谁近谁远
两串数字都拿到了,下一步就是算距离、和阈值比、判断是不是同一人。
5.1 距离是什么
face_recognition 库 默认用 L2 距离(欧氏距离)算两串数字差多少。白话就是「两串数字逐项相减平方再加和开根号」。在前面 §1.2 讲过的「所有脸压到同一个球面」上,L2 距离近 ⇔ 余弦相似度大,两种度量在球面上等价。两种度量都能用来比脸像不像。
5.2 阈值是什么
阈值是判像不像的那条分数线。白话就是「分数线」。两串数字的距离小于阈值 → 判为同一人;大于阈值 → 判为不同人。
阈值太高 = 把同一人当成不同人(漏识率上升)。阈值太低 = 把不同人当成同一人(误识率上升)。课堂可以用 LFW demo 真实跑出来,故意调高 / 调低阈值,让学生看见 True/False 翻转的具体瞬间。这是把分数线从名词变成可操作旋钮的关键一步。
两个必须区分的数字:face_recognition 库默认的 0.6 阈值是工程经验值(empirical default),不是数学最优;FaceNet 论文 §5.3 报出的 LFW 最优 L2 阈值是 1.242。两个数字来源不同,学生在自己数据上都得调。
5.3 度量识别 vs 分类器识别:open-set 的根本区别
现实中的人脸识别几乎都是 open-set——你不会预先知道今天会有谁来、要识别谁。这时候就要在两种思路里选一种:
脸变成 embedding 之后,直接拿两串数字算距离并和阈值比——这叫度量识别(metric recognition)。face_recognition 库的 compare_faces 默认就是这样:新来一个人,只要他 / 她的 embedding 离某个已录入身份的 embedding 距离小于阈值,就算认识;不需要预先把这个人放进分类器的训练集。
另一种做法是训一个分类器认人:把全校学生当 N 个类别训,softmax 输出 N 个概率分布。问题是离开训练名单就失效——新来一个学生必须重新训练整个分类器。闭集场景下能用,open-set 现实场景下行不通。
指着前面 §4.2 看的 ArcFace 决策边界图(margincompare-1.png)再说一遍:Softmax 子图那条弯月形边界是「已知 N 个班级的闭集」,ArcFace 子图那条扇形 margin 是「不依赖名单的 open-set 度量」。几何形状本身就反映了两种思路的本质区别。现实场景几乎都是度量识别。
六、整条流水线跑起来
把前面五章的概念兑现成可运行代码。face_recognition 库 是课堂最小骨架:
pip install face_recognition
4 个英文函数对应流水线 4 步:
import face_recognition
# 1. 读图
img_known = face_recognition.load_image_file("known.jpg")
img_unknown = face_recognition.load_image_file("unknown.jpg")
# 2. 检测人脸(返回每张脸在图中的位置)
loc_known = face_recognition.face_locations(img_known)
loc_unknown = face_recognition.face_locations(img_unknown)
# 3. 把每张脸压成 128 维向量
enc_known = face_recognition.face_encodings(img_known, loc_known)[0]
enc_unknown = face_recognition.face_encodings(img_unknown, loc_unknown)[0]
# 4. 比两串数字
distance = face_recognition.face_distance([enc_known], enc_unknown)[0]
is_same = face_recognition.compare_faces([enc_known], enc_unknown)[0]
load_image_file 读图 → face_locations 检测人脸 → face_encodings 抽 128 维向量 → compare_faces 比对 → 输出 True/False。CPU 即可——dlib 的 ResNet 推理在普通笔记本 CPU 上约 1 秒一张图,机房零 GPU 门槛。
6.1 课堂 45 分钟切片
LFW 中抽 1 对同名 + 1 对异名图,直接跑 compare_faces 看 True/False 输出 + face_distance 数值 + 调阈值看翻转。
6.2 课堂 90 分钟切片
45 分钟基础上加三步:① 采集 5-10 张同学正面授权头像;② 录入为「班级库」;③ 摄像头实时抓帧比对,在屏幕上打印最像的同学名字。这是整堂课的动手高潮——学生亲眼看见自己的脸被识别出来。
6.3 进阶路线:InsightFace
如果机房装不上 dlib(Windows 上编译失败很常见),InsightFace 是进阶替代:
from insightface.app import FaceAnalysis
app = FaceAnalysis()
app.prepare()
faces = app.get(image)
FaceAnalysis() 一行调用 + 默认 buffalo_l 模型包(326 MB,首次自动下载,含 SCRFD-10GF 检测器 + WebFace600K 训练的 ResNet50 识别头 + 2d106/3d68 landmarks),精度比 face_recognition 默认的 dlib ResNet 模型高很多,不需要 C++ 编译,Python 3.10+ 即可。
下面这张图展示了 InsightFace 默认的 SCRFD 检测器在密集人群自拍上的真实运行结果:

图注:InsightFace README 的 SCRFD 检测器 demo。数百个黄色矩形框叠在密集人群面部,框内含绿色关键点和紫蓝色人脸蒙版,右上角是 0.5-1.0 的置信度色条,左上角保留「World's Largest Selfie / Powered by Lumia 730」水印。这是「工业级检测器在密集场景下的鲁棒性」具体画面,不是端到端 pipeline 架构图。
6.4 合规收尾
重申三条底线:公开数据集通常仅限非商业研究用途;自采数据必须书面授权(拍摄前口头告知 + 书面签字);只在本机处理不传到云端;课程结束后销毁。
2026 年的演进方向点一句留给后续课程:今天讲的是 2015-2019 的主流做法(FaceNet 128 维 + ArcFace 角度 margin),2026 年学界和工业界在三个方向演进——fairness(对不同肤色 / 性别 / 年龄的公平性)、privacy(本地化处理 / 差分隐私)、deepfake detection(对抗生成式伪造);以及 transformer-based embedding(用 ViT 替代 CNN backbone)。
到这里,学生应该能用一句话回答:人脸识别不是黑箱,是一条可以拆解的链子;每个术语都对应一个英文函数或一个具体步骤;4 个英文函数就能跑通整条链路。
参考文献
本专题引用的核心资料如下,建议深入学习时阅读原文:
- FaceNet: A Unified Embedding for Face Recognition and Clustering — arXiv:1503.03832
- ArcFace: Additive Angular Margin Loss for Deep Face Recognition — arXiv:1801.07698
- Joint Face Detection and Alignment Using Multitask Cascaded Convolutional Networks (MTCNN) — arXiv:1604.02878
- RetinaFace: Single-shot Multi-level Face Localisation in the Wild — arXiv:1905.00641
- face_recognition — The world's simplest facial recognition API for Python — https://github.com/ageitgey/face_recognition
- InsightFace — Face Analysis Project — https://github.com/deepinsight/insightface
- dlib — 68-point face landmarks detector (shape_predictor_68_face_landmarks) — http://dlib.net/face_landmark_detection.py.html
- OpenCV — Cascade Classifier Tutorial — https://docs.opencv.org/4.x/db/d28/tutorial_cascade_classifier.html
- 人脸数据集简介:LFW / VGGFace2 / CASIA-WebFace / MS-Celeb-1M — LFW 入口 http://vis-www.cs.umass.edu/lfw/;CASIA-WebFace 仓库 https://github.com/Cysu/person_search;VGGFace2 https://www.robots.ox.ac.uk/~vgg/software/vgg_face/;MS-Celeb-1M https://msceleb.org