OpenCV计算机视觉实战(31)------人脸识别详解
0. 前言
在人脸识别领域,从检测、对齐到特征编码和比对,再到抗假脸的活体检测,每一步都关乎系统的准确性与安全性。本文以 OpenCV 为依托,深入展示如何构建一个端到端的人脸识别系统:首先结合 DNN 与跟踪器,实现多尺度、非极大值抑制与 KCF 跟踪融合的人脸检测与精确关键点定位;接着利用 Torch 模型批量提取归一化特征向量,并通过欧氏距离与余弦相似度在大规模库中快速比对;最后,将眨眼、头部微动与轻量级 CNN 活体检测模型融合投票,构筑多模态活体防护,显著提升对照片与视频攻击的抵御能力。
1. 人脸检测与关键点定位
使用 OpenCV DNN 模型对人脸进行检测,并借助 LBF 模型提取 68 个面部关键点,为后续对齐、特征提取和活体检测提供坐标依据。
1.1 实现过程
DNN人脸检测:readNetFromCaffe加载ResNet SSD--Caffe模型,输入blob,输出多个人脸框Facemark LBF:OpenCV提供cv2.face.createFacemarkLBF,需先下载并加载LBF模型文件- 批量关键点拟合:
facemark.fit接受原图与人脸矩形列表,返回对应的68点坐标 - 可视化:人脸框用绿框,高精度关键点用红点标记
python
import cv2
import numpy as np
# 功能:在视频流中检测人脸并绘制 68 点关键点
# 1. 加载 DNN 人脸检测器(ResNet SSD)
detector = cv2.dnn.readNetFromCaffe(
'deploy.prototxt',
'res10_300x300_ssd_iter_140000_fp16.caffemodel'
)
# 2. 加载 Facemark LBF 关键点检测器
facemark = cv2.face.createFacemarkLBF()
facemark.loadModel('lbfmodel.yaml')
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
if not ret: break
h, w = frame.shape[:2]
# 检测人脸
blob = cv2.dnn.blobFromImage(frame, 1.0, (300,300),
(104,117,123), swapRB=False)
detector.setInput(blob)
dets = detector.forward()[0,0,:,:]
faces = []
for i in range(dets.shape[0]):
conf = float(dets[i,2])
if conf > 0.6:
x1 = int(dets[i,3]*w); y1 = int(dets[i,4]*h)
x2 = int(dets[i,5]*w); y2 = int(dets[i,6]*h)
faces.append((x1,y1,x2-x1,y2-y1))
cv2.rectangle(frame,(x1,y1),(x2,y2),(0,255,0),2)
# 关键点检测
if faces:
_, landmarks = facemark.fit(frame, np.array(faces))
for lm in landmarks:
for (x,y) in lm[0]:
cv2.circle(frame, (int(x),int(y)), 2, (0,0,255), -1)
cv2.imshow('Face+Landmarks', frame)
if cv2.waitKey(1)==27: break
cap.release()
cv2.destroyAllWindows()

关键函数解析:
cv2.dnn.blobFromImage(img, scalefactor, size, mean, swapRB):构建网络输入detector.forward():返回形状为[1,1,N,7]的检测结果cv2.face.createFacemarkLBF():创建LBF面部关键点检测器facemark.fit(img, faces):对每个ROI拟合关键点
1.2 优化思路
NMS+ 多尺度检测:结合cv2.dnn.NMSBoxes消除重叠检测框,并在不同输入尺度上检测小脸- 人脸对齐:利用
68点关键点对人脸做仿射变换,对齐到统一姿态,后续编码更鲁棒 - 跟踪融合:用
cv2.TrackerKCF_create()对检测到的人脸进行短期跟踪,减少DNN推理次数,提升帧率
python
import cv2
import numpy as np
# 功能:多尺度 DNN 检测 + NMS + 68 点对齐 + KCF 跟踪融合
detector = cv2.dnn.readNetFromCaffe('deploy.prototxt','res10_300x300_ssd_iter_140000_fp16.caffemodel')
facemark = cv2.face.createFacemarkLBF(); facemark.loadModel('lbfmodel_68.yaml')
trackers = [] # 存储 (tracker, bbox)
def align_face(img, pts):
# 用眼角和嘴角计算仿射矩阵,对齐至标准位置``
src = np.float32([pts[36], pts[45], pts[33]]) # 左眼外、右眼外、鼻尖
dst = np.float32([[30,30],[70,30],[50,60]])
M = cv2.getAffineTransform(src, dst)
return cv2.warpAffine(img, M, (100,100))
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
if not ret: break
h, w = frame.shape[:2]
# 1. 跟踪优先
new_trackers = []
for tr, bbox in trackers:
ok, b = tr.update(frame)
if ok:
x,y,ww,hh = map(int,b)
new_trackers.append((tr,(x,y,ww,hh)))
cv2.rectangle(frame,(x,y),(x+ww,y+hh),(255,0,0),2)
trackers = new_trackers
# 2. 检测补充
blob = cv2.dnn.blobFromImage(frame,1.0,(300,300),(104,117,123))
detector.setInput(blob); dets = detector.forward()[0,0]
boxes,confs = [],[]
for d in dets:
if d[2]>0.6:
x1,y1,x2,y2 = (d[3:7]*[w,h,w,h]).astype(int)
boxes.append([x1,y1,x2-x1,y2-y1]); confs.append(float(d[2]))
idxs = cv2.dnn.NMSBoxes(boxes,confs,0.6,0.4)
if len(idxs):
faces = []
for i in idxs.flatten():
faces.append(tuple(boxes[i]))
x,y,ww,hh = boxes[i]
tr = cv2.TrackerKCF_create()
tr.init(frame,(x,y,ww,hh))
trackers.append((tr,(x,y,ww,hh)))
# 3. 关键点与对齐
if faces:
_, lms = facemark.fit(frame, np.array(faces))
for lm,face in zip(lms,faces):
pts = lm[0].astype(int)
aligned = align_face(frame, pts)
cv2.imshow('Aligned Face', aligned)
for (xk,yk) in pts:
cv2.circle(frame,(xk,yk),2,(0,0,255),-1)
cv2.imshow('Detection+Tracking', frame)
if cv2.waitKey(1)==27: break
cap.release(); cv2.destroyAllWindows()

关键函数解析:
cv2.dnn.NMSBoxes():剔除重叠度过高的检测框cv2.TrackerKCF_create():对检测结果进行轻量级跟踪,减少DNN推理cv2.getAffineTransform() + warpAffine():根据眼、鼻关键点对齐人脸到统一参考模板
2. 特征编码与比对
利用 OpenCV DNN 加载预训练的 Torch FaceNet 模型,将对齐后的面部裁剪为 128 维特征向量,后续通过欧氏距离比对不同人脸相似度。
2.1 实现过程
- 人脸对齐:输入裁剪并对齐至
96 × 96大小 Torch模型加载:readNetFromTorch支持OpenFace提供的小型FaceNet- 特征提取:网络输出
128维归一化向量 - 相似度度量:欧氏距离小于阈值(如
0.6) 时判定为同一人
python
import cv2
import numpy as np
# 功能:提取人脸特征编码并比对欧氏距离
# 加载 FaceNet Torch 模型
embedder = cv2.dnn.readNetFromTorch('openface.nn4.small2.v1.t7')
def get_embedding(face_img):
blob = cv2.dnn.blobFromImage(face_img, 1.0/255, (96,96),
(0,0,0), swapRB=True, crop=False)
embedder.setInput(blob)
vec = embedder.forward()
return vec.flatten()
# 假设有两张对齐好的人脸 faceA, faceB
faceA = cv2.imread('personA.jpg')
faceB = cv2.imread('personB.jpg')
embA = get_embedding(faceA)
embB = get_embedding(faceB)
# 计算欧氏距离
dist = np.linalg.norm(embA - embB)
print(f'Euler distance between faces: {dist:.2f}')
关键函数解析:
cv2.dnn.readNetFromTorch(model):加载.t7网络权重blobFromImage:缩放到网络要求尺寸,并归一化net.forward():前向提取特征向量np.linalg.norm:计算两个向量间的欧氏距离
2.2 优化思路
- 批量人脸编码:将多张人脸同时送入网络,加速批处理
- 比对库管理:维护多名用户的特征库,支持距离阈值自动学习(基于验证集)
- 余弦相似度:除欧氏距离外,还可用余弦相似度衡量编码向量夹角
python
import cv2, numpy as np
# 功能:批量提取编码,构建人脸特征库并支持余弦比对
embedder = cv2.dnn.readNetFromTorch('openface.nn4.small2.v1.t7')
def get_embeddings(faces):
blobs = cv2.dnn.blobFromImages(
faces, 1.0/255, (96,96), (0,0,0), swapRB=True, crop=False)
embedder.setInput(blobs)
vecs = embedder.forward()
# L2 归一化
norms = np.linalg.norm(vecs, axis=1, keepdims=True)
return vecs / norms
# 假设 facesA, facesB 为裁剪且对齐好的人脸列表
embA = get_embeddings(facesA) # shape (N,128)
embB = get_embeddings(facesB)
# 欧氏距离与余弦相似度
dists = np.linalg.norm(embA[:,None]-embB[None,:], axis=2)
cosine = (embA @ embB.T) # 两向量已归一化,直接点积
print('Euclid min:', dists.min())
print('Cosine max:', cosine.max())
关键函数解析:
blobFromImages():一次性批量构建输入Blob,加速多脸推理- 向量归一化
vec / ||vec||:确保欧氏距离与余弦相似度可比 - 余弦相似度
u·v:对于归一化特征,值越接近1越相似
3. 活体检测技术
通过监测眨眼动作实现简易活体检测:基于面部关键点计算眼睛长宽比 (EAR),检测到连续几帧内长宽比突变则认为是真人眨眼,抵御照片欺骗。
3.1 实现过程
EAR公式:根据六个关键点计算眼睛长宽比,健康范围内波动表示眨眼- 阈值与计数:当
EAR低于阈值连续多帧后计为一次眨眼 - 活体判定:检测到一定次数眨眼则判定真人,反之可怀疑照片攻击

关键函数解析:
scipy.spatial.distance.euclidean(p, q):计算两点间欧氏距离eye_aspect_ratio(eye_pts):实现EAR公式 (‖p2−p6‖ + ‖p3−p5‖)/(2*‖p1−p4‖)- 帧计数与阈值:确保短时抖动不误判
3.2 优化思路
- 头部微动:结合面部关键点估计头部欧拉角,检测自然微动
- 深度学习活体检测:引入轻量级
CNN(如MobileNet+二分类) 对ROI做帧级真伪判定 - 融合策略:将眨眼、头动和
CNN判断结果通过加权投票,提升活体检测准确率
python
# 功能:眨眼 + 头动 + CNN 活体判定融合
# 1. CNN 模型加载(TensorFlow Lite 示例)
import tflite_runtime.interpreter as tflite
interpreter = tflite.Interpreter('liveness.tflite'); interpreter.allocate_tensors()
def cnn_liveness(face_roi):
# 预处理并推理返回真(1)/假(0)
inp = cv2.resize(face_roi,(64,64))/255.0
inp = np.expand_dims(inp.astype(np.float32),0)
interpreter.set_tensor(input_idx, inp)
interpreter.invoke()
return interpreter.get_tensor(output_idx)[0,1] # 活体概率
while True:
# 检测&关键点
# ... 获得 aligned_face 和 EAR, head_angle ...
eye_score = (ear < EAR_THRESH)
head_score = abs(head_angle) > ANGLE_THRESH
cnn_score = cnn_liveness(aligned_face) > 0.5
# 加权投票
votes = eye_score + head_score + cnn_score
is_live = votes >= 2
cv2.putText(frame, f'Live: {is_live}', (10,60), ...)
关键函数解析:
- 头动估计:用
OpenCV SolvePnP根据2D-3D关键点计算头部欧拉角 TFLite Interpreter:在CPU上快速执行轻量级活体检测模型- 投票融合:融合多信号减少单一算法误判,提升鲁棒性
小结
本节系统介绍了基于 OpenCV 构建端到端人脸识别系统的完整流程。首先通过 DNN 模型与 KCF 跟踪器融合,实现高效稳定的人脸检测与关键点定位;接着利用 Torch 加载轻量级 FaceNet 网络,对齐人脸并提取归一化特征编码,实现欧氏距离与余弦相似度的人脸精确比对;最后引入眨眼检测、头部姿态估计和轻量级 CNN 模型,构建多模态融合的活体检测机制,有效防御照片与视频伪造攻击。
系列链接
OpenCV计算机视觉实战(1)------计算机视觉简介
OpenCV计算机视觉实战(2)------环境搭建与OpenCV简介
OpenCV计算机视觉实战(3)------计算机图像处理基础
OpenCV计算机视觉实战(4)------计算机视觉核心技术全解析
OpenCV计算机视觉实战(5)------图像基础操作全解析
OpenCV计算机视觉实战(6)------经典计算机视觉算法
OpenCV计算机视觉实战(7)------色彩空间详解
OpenCV计算机视觉实战(8)------图像滤波详解
OpenCV计算机视觉实战(9)------阈值化技术详解
OpenCV计算机视觉实战(10)------形态学操作详解
OpenCV计算机视觉实战(11)------边缘检测详解
OpenCV计算机视觉实战(12)------图像金字塔与特征缩放
OpenCV计算机视觉实战(13)------轮廓检测详解
OpenCV计算机视觉实战(14)------直方图均衡化
OpenCV计算机视觉实战(15)------霍夫变换详解
OpenCV计算机视觉实战(16)------图像分割技术
OpenCV计算机视觉实战(17)------特征点检测详解
OpenCV计算机视觉实战(18)------视频处理详解
OpenCV计算机视觉实战(19)------特征描述符详解
OpenCV计算机视觉实战(20)------光流法运动分析
OpenCV计算机视觉实战(21)------模板匹配详解
OpenCV计算机视觉实战(22)------图像拼接详解
OpenCV计算机视觉实战(23)------目标检测详解
OpenCV计算机视觉实战(24)------目标追踪算法
OpenCV计算机视觉实战(25)------立体视觉详解
OpenCV计算机视觉实战(26)------OpenCV与机器学习
OpenCV计算机视觉实战(27)------深度学习与卷积神经网络
OpenCV计算机视觉实战(28)------深度学习初体验
OpenCV计算机视觉实战(29)------OpenCV DNN模块
OpenCV计算机视觉实战(30)------图像分类模型