搞视觉开发的朋友,几乎都绕不开人脸识别。但说起人脸识别,很多人第一反应是深度学习、训练大模型,一上来就整 TensorFlow 或 PyTorch,照搬一个 ResNet 网络,结果发现环境装半天、数据集不够、显卡跑不动,最后连门都没进去。其实 OpenCV 加上 Python,完全可以直接跑通一条“训练 + 识别”的完整链路,而且效果不比花里胡哨的方案差太多。这个项目最核心的价值,不是“识别率比别人高几个点”,而是用几百行代码,让你把人脸识别整个流程彻底吃透:检测、对齐、特征提取、比对、实时调用,每个环节都能亲手摸一遍。
这篇文章是给两类人准备的:一类是刚接触计算机视觉,想用一个可落地的项目来理解 OpenCV 核心 API 的;另一类是自己动手做过简单图像处理,但没碰过“模型训练”和“实时视频识别”,想搞懂整套工程链路怎么串的。全文不绕弯子,直接讲项目结构、核心原理、实操代码和排查经验,只要照着做,本机就能跑起来。
1. 项目拆解:一次人脸识别到底做了什么
1.1 人脸识别不是“一个功能”,而是四个环节
很多人以为人脸识别就是一个函数,摄像头一拍,返回是不是某人。实际拆开看,一套完整的识别流程至少分成四步:人脸检测、人脸对齐、特征提取、特征比对。这四个环节各干各的活,任何一个掉链子,最后结果都会拉胯。
人脸检测的任务,是在一张图或一帧视频里找到人脸所在的矩形区域。OpenCV 里常见的做法是 Haar Cascade,用训练好的级联分类器快速扫一遍图像;另一种是用 OpenCV 的 DNN 人脸检测器,基于回归的方式直接输出人脸框,精度更高但计算量也更大。检测的关键不是把框画得好看,而是框的紧致程度——框偏大了会把头发、脖子、背景都包进来,后续特征提取时就会混入大量无用信息。
人脸对齐,是把检测到的人脸摆正,消除倾斜和尺度差异。OpenCV 的 API 里一般不会单独把这个过程独立出来,但训练和识别时,我们用眼垂线或者固定的关键点信息把脸旋转到同一方向,这是很有用的前置步骤。因为同一个人的脸,如果角度差个十五度,LBPH 这种传统特征算出来的直方图特征可能就完全对不上了。
特征提取,是整个人脸识别最核心的环节。传统方案里常用的是 LBPH(Local Binary Patterns Histograms),它把图像划分成多个小区域,在每个区域内计算局部二值模式直方图,然后把所有小区域的直方图拼接成一个特征向量。这个特征的思路很简朴:不管肤色深浅,只看局部纹理结构,相当于把图像转成了一张“纹理指纹”。深度学习方案则是让卷积网络自己学出高维特征向量,比如 OpenCV 自带的 FaceRecognizerSF,或者把图像喂给训练好的模型提取 128 维或 512 维的嵌入向量。
特征比对,就是将待识别的人脸特征和库里已知的人脸特征做相似度计算。LBPH 用的比对方式是直方图距离,这个距离值也叫置信度,距离越小越像自己,超过一定阈值就认定为陌生人。深度学习方案一般用余弦相似度,越接近 1 越匹配。
1.2 为什么用 OpenCV 而不是直接上深度学习框架
这个问题的本质是“项目目标”决定工具选型。你想做的如果是手机人脸解锁、门禁闸机这种需要抗复杂光线、大角度姿态的高鲁棒性系统,那当然要上深度模型,甚至需要自己采集上万张样本来微调。但如果目标是快速做一个人脸识别 Demo,或者验证一个算法思路,或者把一套业务流程跑通,OpenCV 就是最合适的那把刀。
OpenCV 的优势有三点。第一,功能覆盖全,检测、特征提取、模型存取、摄像头采集、图像显示都齐了,不需要拼装多个库。第二,部署简单,不需要 GPU,纯 CPU 就能跑,LBPH 模型大小也就几 MB,一台普通笔记本就能扛住实时识别。第三,代码直观,每一行都能看懂在干什么,这对理解技术原理非常有帮助。我自己早期做项目时,用 OpenCV 跑通一条完整流程之后,再去看深度学习的面部识别论文,很多概念就能对号入座,因为检测、对齐、特征、比对这些抽象名词已经变成我手底下真实存在的模块了。
当然,用 OpenCV 的代价也很明显:传统特征对姿态和光照的鲁棒性一般。这也恰恰是好的——你只有先被光照和角度坑过一遍,才会真正理解为什么深度学习要搞数据增强、为什么人脸对齐是刚需。踩坑本身就是经验积累的一部分。
2. 环境准备与基础选型
2.1 版本选择的几个“坑”
项目开始前,先把环境搞定。直接说结论:Python 3.8 到 3.11 都可以,OpenCV 用opencv-python和opencv-contrib-python两个包,二选一。人脸识别模块在 OpenCV 的 contrib 版本里,所以如果你要用 LBPH,就必须装opencv-contrib-python,普通版是不带face模块的。
pip install opencv-contrib-python注意不要opencv-python和opencv-contrib-python混着装,两个包会产生 DLL 冲突,import cv2 时直接给你弹个错误。装库之前,最好先开一个干净的虚拟环境:
python -m venv face_env # Windows face_env\Scripts\activate # Linux / macOS source face_env/bin/activate pip install opencv-contrib-python numpy Pillow如果你只是为了测试,装个opencv-contrib-python就够;但如果还需要读取不同类型的图片格式,顺手装一个 Pillow 模块会省很多事,因为 OpenCV 对某些少见格式的支持不太友好。NumPy 是 OpenCV 的底层依赖,装好即可,不用管它。
2.2 人脸检测器选择的取舍
OpenCV 里有两套主流的人脸检测方案,项目里建议两套都跑一遍,因为都很容易踩坑,体验完全不同。
第一套是 Haar Cascade。它是 OpenCV 里最老牌的检测器,原理是基于 Haar-like 特征加 AdaBoost 级联分类器。训练好的模型以 XML 文件形式随 OpenCV 一起分发,调用方式固定:
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")这套方案的好处是速度快,单张 640x480 的图上,检测一帧只需要十几毫秒;坏处是对侧脸、暗光、遮挡比较敏感,而且容易出现误检——比如把褶皱的窗帘、墙上的阴影当成脸。另外它检测到的框普遍偏大,需要自己调整参数。
另一套是 OpenCV 的 DNN 人脸检测器。它使用一个预训练的深度学习模型(基于 ResNet 实现的人脸检测网络),通过cv2.dnn模块加载,精度和鲁棒性比 Haar 高一个量级,副作用是速度慢一些。在 CPU 上跑,一帧大约要 50 到 150 毫秒,但检测框紧贴人脸,并且对侧脸的支持好很多。以下是 DNN 检测器的加载方式:
net = cv2.dnn.readNetFromCaffe("deploy.prototxt", "res10_300x300_ssd_iter_140000.caffemodel")这里的 prototxt 和 caffemodel 就是 OpenCV 提供的公开人脸检测模型文件,网上可以直接找到。不过我提醒一句,DNN 检测器虽然精度高,但模型文件需要单独下载,不像 Haar 一样自带。第一次跑项目时,建议先用 Haar 保证流程通,后面再换成 DNN 提升效果。
2.3 摄像头索引的经验
调用摄像头时,绝大多数人第一次都会卡在这个位置:
cap = cv2.VideoCapture(0)如果cap.isOpened()返回 False,说明摄像头没有打开成功。常见原因是索引不对:笔记本自带摄像头一般是 0,USB 外接摄像头可能是 1 或 2。可以写一个遍历脚本,从 0 到 3 逐个尝试打开并 Promise 显示画面,找到好用的那个索引再写进正式代码里。另外,有些轻薄本有摄像头硬件开关或者隐私遮蔽挡片,系统里根本枚举不到设备,这个不是代码能解决的,得先排除硬件问题。
3. 核心实现一:人脸检测与关键区域提取
3.1 Haar Cascade 检测的代码与参数逻辑
先从最基础的 Haar Cascade 开始。一个完整的检测代码块差不多长这样:
import cv2 face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml") def detect_faces(image): gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale( gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30), flags=cv2.CASCADE_SCALE_IMAGE ) return faces这里有几个参数需要解释清楚,因为它们决定了检测效果。
scaleFactor是每次缩放图像的比例。检测器不是只在一张固定尺寸的图上扫,它会像金字塔一样把图像一层一层缩小,小的图像对应检测大的脸,这样就能找到不同大小的人脸。scaleFactor 越大,缩放步长越大,计算速度越快,但可能漏掉某些尺寸的人脸;越小越精细,但速度会明显减慢。经验值 1.1 到 1.2 比较稳。
minNeighbors是每个候选区域至少需要被检测到多少次才能被确认是人脸。级联分类器在检测时会对同一位置反复验证,这个值设得越大,误检越少,但也可能把真正的人脸过滤掉。我用下来的经验:正脸照 5 就够,侧脸或模糊视频建议降到 3。
minSize是人脸最小尺寸。这个参数经常被忽略,但实际很关键。如果你知道摄像头拍摄距离远、人脸在画面里占幅小,就把 minSize 调小;如果只在近距离用,minSize 设大一点能明显减少误检。像 30 x 30 是一个比较通用的起点,在 1080p 的摄像头画面里实际够用。
改写一下 Haars Cascade 的实际运行方式:它是在灰度图上检测的,所以记得先将 BGR 图像转为灰度图。这一步不要省略,因为级联分类器的训练数据是灰度图,直接在彩色图上检测不仅慢结果也不对。如果图像本身比较暗,可以再加一步直方图均衡化:
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) gray = cv2.equalizeHist(gray)均衡化之后,检测率在很多暗光场景下有肉眼可见的提升。
3.2 DNN 人脸检测器的用法
如果想用 DNN 检测器,代码会有一些变化,但整体思路不变:
import cv2 import numpy as np net = cv2.dnn.readNetFromCaffe("deploy.prototxt", "res10_300x300_ssd_iter_140000.caffemodel") def detect_faces_dnn(image): h, w = image.shape[:2] blob = cv2.dnn.blobFromImage(image, 1.0, (300, 300), (104.0, 177.0, 123.0)) net.setInput(blob) detections = net.forward() faces = [] for i in range(detections.shape[2]): confidence = detections[0, 0, i, 2] if confidence > 0.7: box = detections[0, 0, i, 3:7] * np.array([w, h, w, h]) (x1, y1, x2, y2) = box.astype("int") faces.append((x1, y1, x2 - x1, y2 - y1)) return facesblobFromImage这里做的事情,是把图像预处理成模型要求的形式:缩放到 300x300、减均值、调整通道顺序。这一步对新手来说最容易忽略,因为不预处理模型也能跑,但结果基本上全是错的,或者检测不到任何对象。
DNN 输出的detections是一个 4D 数组,其中第二维的第一个元素是检测到的目标数量,每个目标包含 7 个值:序号、类别、置信度、以及归一化后的边界框坐标。最关键的就是把归一化坐标乘回原始图像尺寸,这一点别忘了。
跑了一遍之后,你会发现 DNN 检测器在很多场景下明显更稳:侧脸、戴眼镜、复杂背景,都有比较好的表现。不过它的速度确实感人,在 1080p 摄像头画面上,每次检测可能需要上百毫秒。如果要做实时识别,建议把传入检测器的图像先缩放一半再处理:
frame = cv2.resize(frame, (0, 0), fx=0.5, fy=0.5)这样速度提升非常明显,检测框坐标换算回去也只是乘 2 的问题。
3.3 ROI 提取:把脸从图里“抠”出来
检测到人脸框之后,最重要的一步就是把区域抠出来。这一步的代码很简单,但方向千万别搞反:
x, y, w, h = face face_roi = gray[y:y+h, x:x+w]注意坐标系里 x 是列方向,y 是行方向,所以切片是[y:y+h, x:x+w],这个顺序一写反,截图就歪。很多新手在这个愚蠢的地方浪费了半小时,排查时对着一块扭曲的色块发呆。
另外,抠 ROI 的时候可以稍微扩大一点边界,把额头和下巴带进去一点,但不要扩太多。扩太少会丢掉脸部边缘纹理,扩太多会混入背景噪声。我的经验是按照人脸框的 10% 到 15% 进行扩展。
margin = int(0.1 * w) x1, y1 = max(0, x - margin), max(0, y - margin) x2, y2 = min(image.shape[1], x + w + margin), min(image.shape[0], y + h + margin) face_roi = gray[y1:y2, x1:x2]抠出来的人脸区域,后续要么直接喂给识别器,要么先做对齐和缩放。如果你直接拿不同比例的图去训练或识别,效果多半不稳定,因为人脸区域必须缩放成固定尺寸才能放进训练器。这个尺寸的选择,下一节详细讲。
4. 核心实现二:训练自己的人脸识别模型
4.1 LBPH 算法:不靠“长相”靠“纹理”
LBPH 的原理听起来有点绕,但拆开很直观。LBP(Local Binary Pattern)的核心思想,是对图像里每个像素点,拿它和周围一圈邻居比大小。比如取一个像素和它周围 8 个邻居做比较,如果邻居比中心像素大,记为 1,否则记为 0,这样一圈下来得到一个 8 位二进制数,转换成十进制就是这个像素的局部纹理编码。
把一整张脸切成若干小格子,每个格子内统计这些纹理编码的直方图,然后把所有格子的直方图拼起来,就得到了一整张人脸的特征指纹。这个指纹不会因为肤色深浅、整体亮度高低而发生剧烈变化,因为它是局部相对关系,不是绝对像素值。这也是 LBPH 能在传统特征里扛起大梁的原因。
OpenCV 里使用 LBPH 很简单:
recognizer = cv2.face.LBPHFaceRecognizer_create()为了让人脸的纹理特征更稳定,建议训练前把 ROI 缩放到统一大小并做直方图均衡化。这一步能抹平很多光照差异。
4.2 训练数据准备的正确姿势
识别效果最大的影响因素不是算法,而是训练数据质量。这是很多人没意识到的:你给模型看 20 张几乎没有变化的正面照,结果换个角度就识别不出来了,这不是算法的问题,是数据本身没有覆盖到各种情况。
准备数据的正规流程是:为每个人建立一个单独的文件夹,命名约定为“名字_编号”格式,例如zhangsan_001.jpg、zhangsan_002.jpg,方便程序解析标签。每人准备 20 到 30 张图片,这里面要在不同角度(正脸、左右各偏 15 度、轻微俯仰)、不同光照(室内灯、自然光、暗光)、不同表情(不笑、微笑、严肃)之间做平衡。这不是为了多,而是为了把真实使用时可能遇到的情况提前“喂”给模型。
采集方式可以分两种:一种是直接拿现有照片做预处理,裁剪出人脸区域;另一种是写一个采集脚本,打开摄像头,每按一次空格存一帧人脸。我个人更推荐后者,因为采集的数据和真实运行环境的一致性更高。
数据收集完后,写一个脚本来统一预处理:
import cv2 import os import glob def prepare_training_data(data_folder_path): subjects = [] labels = [] dirs = os.listdir(data_folder_path) for dir_name in dirs: if not os.path.isdir(os.path.join(data_folder_path, dir_name)): continue label = int(dir_name) subjects.append(dir_name) image_paths = glob.glob(os.path.join(data_folder_path, dir_name, "*.jpg")) for image_path in image_paths: image = cv2.imread(image_path) gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=5) if len(faces) == 0: continue (x, y, w, h) = faces[0] roi = gray[y:y+w, x:x+h] roi = cv2.resize(roi, (200, 200)) labels.append(label) subjects.append(roi) return subjects, labels这里有个细节,labels.append(label)和subjects.append(roi)的顺序必须一一对应,否则训练时标签和样本错位,模型会学得一团糟。另外,训练用的 ROI 尺寸需要和之后识别时保持一致,比如统一缩放到 200x200,不然特征向量维度对不上。
4.3 模型训练与参数验证
准备好样本和标签之后,训练代码本身非常简单:
faces, labels = prepare_training_data("training_data") recognizer = cv2.face.LBPHFaceRecognizer_create(radius=1, neighbors=8, grid_x=8, grid_y=8) recognizer.train(faces, np.array(labels)) recognizer.save("face_model.yml")LBPH 的几个参数值得讲一下。radius是中心像素取邻居的半径,半径越大能看到大范围的纹理结构,但太大会丧失局部细节;neighbors是采样邻居数量,8 是常用值;grid_x和grid_y是分格子数量,格子越多特征越细,但也越容易受噪声影响。我实测下来,radius=1, neighbors=8, grid_x=8, grid_y=8是各方面都均衡的组合,识别稳定且速度极快。
训练完成后,模型文件里保存的是学到的直方图特征、标签映射表、训练样本尺寸。识别时加载模型,就能把新的人脸 ROI 转换成同样的特征,并计算与已知样本的距离:
recognizer = cv2.face.LBPHFaceRecognizer_create() recognizer.read("face_model.yml") label, confidence = recognizer.predict(face_roi)这里的label是整数,confidence是距离值,注意 LBPH 的置信度是一 个“越小越像”的指标。我自己的经验是:confidence 小于 50 时基本可以认为是同一个人;50 到 80 之间比较模糊,需要结合现场情况判断;大于 80 基本就是陌生人了。这个阈值不是死的,可以在自己的测试集上多跑几组,画出不同阈值的误识率变化,然后选一个折中值。
5. 核心实现三:实时视频流识别
5.1 循环读取与性能优化
摄像头识别的代码结构其实就是一个 while 循环:读一帧,检测人脸,对每个人脸做预测,画框显示。框架大概长这样:
cap = cv2.VideoCapture(0) recognizer = cv2.face.LBPHFaceRecognizer_create() recognizer.read("face_model.yml") face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml") while True: ret, frame = cap.read() if not ret: break gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) gray = cv2.equalizeHist(gray) faces = face_cascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=5, minSize=(50, 50)) for (x, y, w, h) in faces: roi = gray[y:y+h, x:x+w] roi = cv2.resize(roi, (200, 200)) label, confidence = recognizer.predict(roi) if confidence < 60: name = "Person_" + str(label) else: name = "Unknown" cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2) cv2.putText(frame, name, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.imshow("Face Recognition", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows()这里面有几个工程化的小优化点。
第一个是跳帧检测。检测器如果用的 DNN,一帧可能要上百毫秒,实时循环里帧率会掉到个位数。一个简单的策略是:设定一个计数器,每 3 帧只做一次检测,但预测和画框每帧都做。因为视频流的连续性很强,人脸位置短时间不会跳到太远,跳帧对体验损失很小,但帧率提升明显。
第二个是检测区域的裁剪。如果人脸在画面里总是占据中间区域,可以先手动裁剪出一个“关注区域”,只在这个区域里做人脸检测,能省掉不少计算量。比如:
roi_area = frame[100:400, 150:500]这个优化适用于固定摄像头的场景,比如考勤机、办公室入口,因为人基本都是从固定位置出现的。
第三个是输出信息的格式化。predict返回的置信度是浮点数,如果想显示在画面上,最好保留一位小数:
cv2.putText(frame, f"{name} {confidence:.1f}", (x, y+h+25), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)这行代码放的位置要注意,别放到 ROI 抠图之前,不然画框和文字会对不上。顺序上永远是先检测、再预测、最后画框和文字。
5.2 连续识别的逻辑设计
实时识别和单张图片识别的最大区别,是“判断结果不能只看一帧”。因为单帧画面可能出现模糊、瞬间低头、光线闪动,直接导致某帧识别失败。我在实际项目里会把识别结果做成一个“滑动窗口”:连续 N 帧的结果一致,才算最终判定结果。比如连续 10 帧里,有 8 帧认为该人是“张三”,那就判定为张三。这个机制能大幅减少误判,也让识别结果更平滑。
具体实现可以维护一个结果缓存队列:
from collections import deque result_buffer = deque(maxlen=15) # 在循环里 result_buffer.append((x, y, label)) # 统计最近15帧中label众数 from collections import Counter counter = Counter([item[2] for item in result_buffer]) stable_label, count = counter.most_common(1)[0] if count >= 10: final_name = "Person_" + str(stable_label) else: final_name = "Unknown"这里 deque 的 maxlen 就是窗口大小,句子中的窗口大小决定了判定的稳定性和响应速度之间的平衡。窗口越大判定越稳,但结果切换会有延迟,这个参数需要按自己场景调。
5.3 多目标识别与边界框优化
当画面里同时出现多张人脸,OpenCV 的检测器会返回多个框,循环处理每个框即可,不需要额外逻辑。但这里会暴露一个问题:LBPH 对多人同时出现时,框与框之间的距离较近,容易发生误判。一种可行的优化是做人脸比对时把多个 ROI 提取出来,分别送入识别器,然后统一做阈值过滤。代码上和单个目标没有本质区别,就是 for 循环多跑几次。
还有一个细节:有些 ROI 可能出现在画面边缘,比如人只露出一半脸,检测框直接贴边。这种框虽然检测出来,但特征严重不全,直接送进识别器大概率误判。处理的办法是去掉那些过于靠近画面边缘的框:
# 跳过太靠近边缘的人脸框 if x < 10 or y < 10 or x + w > frame.shape[1] - 10 or y + h > frame.shape[0] - 10: continue这个小过滤条件能减少很多没意义的误判。
6. 常见问题速查与避坑心得
6.1 问题排查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| import cv2 报错 | opencv-python 和 opencv-contrib-python 都装了 | 重装环境,只保留 opencv-contrib-python |
| 训练时报错“train() missing labels” | labels 是列表且未转成 numpy 数组 | 先np.array(labels)再传入 train |
| 程序能打开摄像头但画面黑屏 | 笔记本物理隐私挡片开启,或摄像头被其他程序占用 | 先关闭其他摄像头调用程序,再检查硬件 |
| 检测不到人脸 | minSize 设置过大,或图像过于暗 | 调小 minSize,增加直方图均衡化 |
| 识别置信度很高但仍乱标 | 训练数据太少或太多重复角度 | 增加每个样本的角度和光照多样性,不要只拍正脸 |
| 识别速度很慢 | Haar 检测在 1080p 原图下全图扫描 | 先缩放图像到一半再进行检测,或改跳帧检测 |
| 模型文件加载失败 | OpenCV 版本不对应 | 确认装了 contrib 版本,重新训练并保存模型 |
| 画面里有框但名字总是 Unknown | 置信度阈值设置过严,或 ROI 尺寸和训练尺寸不一致 | 放宽阈值到 70-80,检查 roi 是否 resize 到统一尺寸 |
6.2 实操中容易忽略的细节
第一个细节,直方图均衡化不是万能的。它在光照偏暗或偏亮时效果明显,但如果图像本身对比度很高,均衡化反而会破坏纹理信息。所以我在使用时会先做一个简单的灰度方差判断,方差异常低就均衡化,正常范围就跳过。代码也简单,但能省掉不少无用计算。
第二个细节,ROI 的缩放方式。训练和识别时必须用同样的插值方式,推荐的写法是cv2.resize(roi, (200, 200), interpolation=cv2.INTER_LINEAR),如果训练代码里用了 INTER_CUBIC 或 INTER_AREA,识别时也要保持一致。这个细节没人提醒的话,你可能永远不知道为什么识别率对不上。
第三个细节,模型文件的备份。LBPH 模型文件是 YAML 格式,文本编辑器能直接打开。训练完成后把模型文件复制一份放到项目目录外,防止目录被清掉。模型里存的是训练时的直方图,重新训练会覆盖它,有了备份至少不会全军覆没。
6.3 后续还能扩展什么
跑通这个项目之后,你会发现整个人脸识别链路已经完整了,接下来能扩展的方向很多。比如可以用 OpenCV 的 FaceRecognizerSF 替换 LBPH,它是 OpenCV 4.5 之后集成的深度学习人脸识别器,支持提取高维特征并做相似度比对,精度比 LBPH 高不少。再比如可以把采集的图像定期离线续训,实现“越用越准”——每隔几天用自动抓拍的新样本重新训练一遍模型参数。
我在实际项目里也试过用这个人脸识别做简单的陌生人告警:在门口放一个小摄像头,检测到人脸后先识别,如果是库中人就不管,如果是陌生人就把这一帧画面存下来,并推一条消息到手机。整个系统跑下来,一台树莓派级别的设备就能胜任,CPU 占用不到 30%。
人脸识别这个项目之所以适合拿来练手,就是因为它麻雀虽小五脏俱全:从检测到训练到部署,每个环节都有实际工程问题可以踩。踩过一轮坑之后,你再去看任何一套人脸识别系统,都能从模块化的角度理解它内部到底在做什么。这种能力,比背诵几个 API 重要得多。