ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用Python和dlib自建人脸识别系统:从录入到比对全流程解析

用Python和dlib自建人脸识别系统:从录入到比对全流程解析 简介这是一套基于Python与OpenCV的人脸录入与识别项目核心借助dlib机器学习库完成人脸特征建模并配套Tkinter图形界面可录入人脸及中英文姓名信息。资源面向有一定Python基础、希望快速上手人脸识别实战的开发者适合考勤打卡、实验教学、个人身份验证等场景代码按采集、训练、识别模块拆分便于阅读和二次改造。包内共25个文件容量约96MB主体为9个Python脚本与9张示例图片另有2个dlib预训练数据文件、字体、说明文档、演示PPT等辅助内容脚本覆盖摄像头采集人脸、特征提取、特征库导出CSV、实时识别、单张人脸识别等完整步骤目录结构清晰。当前已有234人学习下载。通过该资源可获得可直接运行的交互式界面代码、模型文件与配套样例沿示例逐步改造即可搭建一套可演示的人脸识别应用并理解dlib人脸检测与特征比对的关键思路。1. 为什么自己搭一套Python人脸识别门禁机背后的黑匣子值得打开人脸识别门禁机、考勤机到处都在用但多数人只把它当成一个“黑匣子”不知道它内部怎么录入、怎么比对、阈值设了多少、数据存到哪里。用Python、OpenCV和dlib自己搭一套人脸录入与识别系统半小时就能跑通一个可用原型而且整条链路——人脸检测、关键点对齐、128维特征提取、距离比对——每个环节都握在自己手里。dlib是老牌机器学习库用它不需要自己训练模型下载官方预训练模型就能干活OpenCV负责摄像头读取和图像处理各管一段。这套方案特别适合想做门禁/考勤原型的工程师、做课程设计的学生以及想把“人脸识别”四个字真正落地成代码的入门者。2. 环境搭建Python、OpenCV、dlib的版本搭配与两个模型文件2.1 选型理由OpenCV管图像dlib管人脸算法先回答一个绕不开的问题OpenCV自己就带人脸检测为什么不直接用它OpenCV内置的Haar Cascade和LBP检测器对正脸效果尚可但角度稍偏、光线一变就漏检而且它不提供人脸关键点也不提供特征描述子。也就是说OpenCV可以帮你“看到”画面但很难帮你“认出”人。dlib在这个方案里干两件大事一是用HOG或CNN检测器做人脸检测——HOG版本在CPU上也能跑得动实时性比OpenCV自带的级联检测器更稳二是提供68点关键点模型和128维人脸特征模型。前者标记眼睛、鼻子、嘴巴的位置后者把一张人脸压缩成128个浮点数同一人的特征向量距离近不同人的距离远。两者的分工很清楚OpenCV处理图像输入输出和绘制结果dlib负责“认脸”这一层。2.2 最小安装组合虚拟环境、pip命令与编译失败时的退路dlib在Windows上经常需要源码编译如果编译环境不齐会卡很长时间甚至报错。我的习惯是先用Python 3.8或3.9创建独立虚拟环境这两个版本对dlib预编译包的支持最稳。Python 3.10以上或最新版本dlib的wheel往往还没跟上就可能被迫走源码编译老路。# 创建虚拟环境Python 3.9 是我用下来最稳的组合 python -m venv face_env # Windows 激活虚拟环境 face_env\Scripts\activate # Linux / macOS 激活虚拟环境 source face_env/bin/activate # 安装两个核心库 pip install opencv-python pip install dlib代码解释python -m venv face_env创建独立环境避免把依赖装进系统Python把环境搞乱。Windows和Linux的激活命令不同激活成功后命令行提示符前面会出现(face_env)字样看到这个再执行pip安装。opencv-python是OpenCV的官方Pypi包包含cv2模块dlib在多数平台会优先安装预编译wheel安装过程几秒到几十秒不等。如果它开始“Building wheel for dlib”说明正在源码编译这时去吃个饭都未必编完。如果编译失败常见退路是找与你Python版本和操作系统匹配的dlib预编译whl文件用pip直接安装# 文件名里的 cp39 对应 Python 3.9请按自己的实际版本选择 pip install dlib-xxx.whl参数说明whl文件必须和Python版本、系统位数严格对应装错版本会报“not supported on this platform”。装完后立刻验证环境别急着跑业务代码。2.3 验证环节加载两个模型文件并跑通第一张人脸检测dlib需要两个预训练模型文件官方发布页可以找到下载后建议统一放在models/目录。这两个文件是整套系统的“算法黑匣子”它们决定检测和特征提取的精度模型文件用途核心输出shape_predictor_68_face_landmarks.dat定位人脸68个关键点眼睛、鼻子、嘴巴坐标dlib_face_recognition_resnet_model_v1.dat提取128维人脸特征同一人距离近不同人距离远环境验证脚本如下能同时确认OpenCV、dlib、两个模型文件是否都正常import cv2 import dlib print(OpenCV version:, cv2.__version__) print(dlib version:, dlib.__version__) detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(models/shape_predictor_68_face_landmarks.dat) face_rec dlib.face_recognition_model_v1( models/dlib_face_recognition_resnet_model_v1.dat) print(模型加载成功)这段代码干了三件事创建HOG人脸检测器、加载关键点模型、加载特征提取模型。如果某个模型文件路径不对或下载不完整第三行就会抛异常。注意模型文件有几十MB如果下载下来的文件只有几KB加载时一定会报错别怀疑自己的代码先去重新下载模型文件。3. 人脸录入摄像头采集、68点对齐与128维特征入库3.1 录入流程拆解选帧、裁剪、质量控制三步录入是整个识别系统的地基。识别阶段能不能认出人很大程度上取决于录入阶段的样本质量。录入不是随便按几下快门而是要从摄像头画面中挑出合格的人脸提取成特征向量再存进库里。整个过程分三步选帧、裁剪、提取特征。选帧时评估人脸框大小和清晰度人脸太小的直接丢弃裁剪时只保留人脸区域减少背景干扰提取特征用dlib的128维模型把图像变成一组数字。这里有一个容易被忽视的点重复样本问题。如果连续几帧拍到的是同一姿势录进去的特征几乎一模一样对提升识别鲁棒性没有帮助。我的做法是每帧最多保存一张并且每录一张换一个姿势或角度让人脸特征库尽量覆盖不同视角。还有一个“后悔药”技巧录入时同时保存原始人脸图和对应的.npy特征文件后续如果发现样本质量差可以直接删掉对应文件重新录不用动整个库。3.2 关键点定位与对齐68个点让系统知道眼睛在哪dlib的关键点模型输出68个人脸坐标点其中36和45分别是左眼外眼角和右眼外眼角这两点计算人脸倾斜角度最可靠。关键点的价值不只是画图好看而是让特征提取模型拿到一个“正”的人脸。先看一段可视化代码确认模型工作正常def draw_landmarks(frame, shape): # 把68个关键点画在图像上方便调试 for i in range(68): point shape.part(i) cv2.circle(frame, (point.x, point.y), 1, (0, 255, 0), -1) # 标出左眼外眼角36和右眼外眼角45 cv2.circle(frame, (shape.part(36).x, shape.part(36).y), 3, (0, 0, 255), -1) cv2.circle(frame, (shape.part(45).x, shape.part(45).y), 3, (0, 0, 255), -1) return frame这段代码把预测出来的人脸关键点逐一点在画面上红色标记的是两只眼睛的外眼角用来直观判断关键点定位准不准。提示dlib的compute_face_descriptor在提取特征时会利用这些关键点对人脸做内部归一化对齐。所以录入和识别代码里不一定非要手动做仿射变换直接把关键点传给特征提取函数即可dlib会完成姿态纠正。手动对齐不是必须的但如果你想把采集到的人脸图片存档或者后续要重新训练模型建议在保存前先把人脸旋转到水平。用两眼连线的角度做旋转是最轻量可靠的做法OpenCV的仿射变换函数就能完成。3.3 特征提取与存储把128维向量写进CSV录入代码要一次性完成“采集样本提取特征保存结果”。下面的代码打开摄像头连续采集20张合格人脸每张都提取128维特征并保存import os import cv2 import dlib import numpy as np detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(models/shape_predictor_68_face_landmarks.dat) face_rec dlib.face_recognition_model_v1( models/dlib_face_recognition_resnet_model_v1.dat) person_name zhangsan save_dir os.path.join(dataset, person_name) os.makedirs(save_dir, exist_okTrue) cap cv2.VideoCapture(0) count 0 while count 20: ret, frame cap.read() if not ret: break # dlib 内部按 RGB 顺序处理图像OpenCV 默认读进来是 BGR rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) faces detector(rgb, 1) # 每帧最多录入一张脸 if len(faces) 0: continue face faces[0] if face.width() 80: continue shape predictor(rgb, face) descriptor face_rec.compute_face_descriptor(rgb, shape) feature np.array(descriptor) crop frame[face.top():face.bottom(), face.left():face.right()] cv2.imwrite(os.path.join(save_dir, f{count}.jpg), crop) np.save(os.path.join(save_dir, f{count}.npy), feature) count 1 cv2.putText(frame, fcaptured: {count}/20, (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow(capture, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()代码逻辑说明每帧先转成RGB再送入检测器因为OpenCV默认读入是BGR而dlib按RGB顺序处理不转换的话特征会跑偏。face.width() 80过滤掉太远太小的人脸这个值按你的摄像头分辨率调整。compute_face_descriptor返回的是dlib的向量对象转成numpy数组方便后续计算距离。每张图的特征存成.npy同时存一张裁剪图方便你事后检查录入的样本长什么样。20张是起步数量实际门禁入库我一般建议30到50张覆盖正面、左右转头、抬头低头几个角度。采集完成后把散落的npy文件汇总成一个CSV识别时加载这个文件就行import glob import csv import os import numpy as np def save_features_to_csv(csv_pathface_db.csv): rows [] # dataset/人名/xxx.npy 的结构 for npy_path in glob.glob(os.path.join(dataset, *, *.npy)): person os.path.basename(os.path.dirname(npy_path)) feature np.load(npy_path) row ,.join([f{v:.6f} for v in feature]) rows.append(f{person},{row}) with open(csv_path, w, encodingutf-8) as f: f.write(\n.join(rows)) print(f已写入 {len(rows)} 条特征) def load_face_db(csv_pathface_db.csv): db {} with open(csv_path, encodingutf-8) as f: for line in f: parts line.strip().split(,) name parts[0] feature np.array([float(x) for x in parts[1:]]) db.setdefault(name, []).append(feature) return db说明CSV里每行是一个人脸特征第一列是人名后面128个浮点数用逗号分隔。加载时按人名聚合成列表因为有人录了多张样本识别时可以和这个人的所有样本都比一遍。选择CSV而不是SQLite是因为人脸样本量通常只有几十到几百条CSV简单直观出错时用文本编辑器就能改。4. 识别系统特征距离比对、阈值设定与实时主循环4.1 识别原理欧氏距离和0.5阈值背后的权衡人脸识别的本质不是“图像匹配”而是“特征向量距离判断”。dlib的128维特征把一张人脸映射到高维空间同一个人在不同表情、不同角度下特征距离小不同人的特征距离明显大。判断标准是欧氏距离distance np.linalg.norm(feature_a - feature_b)距离小于阈值判为同一人大于阈值判为陌生人。阈值怎么选直接影响系统的实用体验阈值取值误识率别人被认成你拒识率你没被认出来适用场景0.4低高保密门禁宁可拒也不放错0.5中等中等dlib官方示例常用值0.6高低考勤机优先保证识别通过在正式使用前不要迷信任何默认阈值。每台摄像头的安装角度、光线条件、人脸距离都不一样阈值要拿到现场调。最简单的方法是录一批“本人”和“非本人”样本分别算出距离看两组数据的分布区间。如果本人距离普遍在0.35左右、非本人在0.7左右0.5就是合适的中间点。如果两组数据有重叠说明录入样本质量不够先去补样本而不是继续调阈值。4.2 识别主循环代码摄像头读帧、比对、画框、输出识别部分是一个实时循环持续从摄像头读帧检测人脸、提取特征、和库里所有特征比对然后把结果画在画面上import cv2 import dlib import numpy as np # 模型加载 detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(models/shape_predictor_68_face_landmarks.dat) face_rec dlib.face_recognition_model_v1( models/dlib_face_recognition_resnet_model_v1.dat) # db 由上一章的 load_face_db(face_db.csv) 得到 db load_face_db(face_db.csv) def find_match(feature, db, threshold0.5): best_name unknown best_dist float(inf) for name, feats in db.items(): for feat in feats: dist np.linalg.norm(feature - feat) if dist best_dist: best_dist dist best_name name return best_name if best_dist threshold else unknown, best_dist cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) faces detector(rgb, 1) for face in faces: shape predictor(rgb, face) descriptor face_rec.compute_face_descriptor(rgb, shape) feature np.array(descriptor) name, dist find_match(feature, db, threshold0.5) x1, y1, x2, y2 face.left(), face.top(), face.right(), face.bottom() cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, f{name} {dist:.2f}, (x1, y1 - 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()逻辑说明find_match函数遍历库里所有人的所有样本特征取距离最小的那个作为候选结果如果最小距离仍大于阈值就判为unknown。dist输出的数值可以用来观察系统当前的判断余量如果某个人每次识别距离都在0.3以下说明样本充足如果总是贴着0.48徘徊识别就很危险应该重新录入。参数说明detector(rgb, 1)的第二个参数表示上采样次数1表示把图像放大一倍再检测小脸检测率会提高但速度也明显变慢。摄像头画面里人脸本来就大时用0即可省下性能。threshold0.5是默认值实际要按4.1的方法现场调整。4.3 工程化补丁结果平滑、多人并列与FPS统计实时识别跑起来后你会发现三个工程问题名字跳变、多人漏检、帧率没有感知。名字跳变是最常见的——同一个用户正对着摄像头名字偶尔从“zhangsan”跳成“unknown”过一帧又跳回来。原因是某一帧的角度或特征提取波动让距离恰好越过了阈值。解决办法是加一个滑动窗口投票from collections import deque, Counter vote_queue deque(maxlen5) # 每帧得到 name 之后执行投票 vote_queue.append(name) if len(vote_queue) 5: final_name, cnt Counter(vote_queue).most_common(1)[0] # 连续5帧里至少3帧判定同一人才输出最终结果 if cnt 3: print(识别结果:, final_name)说明窗口长度5、投票阈值3是经验值。这个机制能吸收单帧波动代价是输出会延迟几帧考勤场景完全够用。多人并列识别不需要改架构第二层for face in faces已经遍历了画面里所有检测到的人脸只需把每个人的结果分别显示即可。FPS统计用cv2.getTickCount()计算每帧耗时当画面开始明显卡顿优先把检测上采样次数从1改成0再把图像分辨率调低识别速度会立刻改善。5. 避坑与排查dlib编译失败、cv2导入报错等5个真实翻车记录5.1 pip install dlib 卡在源码编译现象执行pip install dlib后命令行停在“Building wheel for dlib”持续十几分钟不长进度最后报CMake错误错误路径里能看到pip-req-build这样的临时目录。原因你的Python版本没有对应预编译wheelpip只好走源码编译。源码编译dlib需要完整的C编译环境Windows上缺Visual Studio Build Tools最典型。解决Windows安装Visual Studio Build Tools 2019安装时勾选“使用C的桌面开发”工作负载然后重试。不想装编译环境的话直接换Python 3.8或3.9再创建虚拟环境这两个版本几乎总能找到现成的dlib wheel安装秒级完成。5.2 import cv2 报 No module named cv2现象明明执行过pip install opencv-python但运行脚本时提示ModuleNotFoundError: No module named cv2。原因最常见的是机器上装了多个Python。pip命令用的是Python A的site-packages运行脚本用的却是Python B或者虚拟环境没激活就执行了安装。解决不要再用pip install改成python -m pip install opencv-python这样装包和运行脚本能锁定同一个Python解释器。装完立刻检查python -c import cv2; print(cv2.__version__)能打印版本就说明对上了。5.3 识别时名字跳变同一人一会儿A一会儿unknown现象识别结果不稳定同一张脸几秒内名字反复变化画面里能看到距离值在0.47和0.52之间来回跳而阈值是0.5。原因阈值设在了距离波动区间中间任何微小的光线或表情变化都会让结果越过阈值。另外一个隐藏因素是录入样本太少只有1到2张特征覆盖不够。解决先补录入样本到20张以上再按4.3的方式加滑动窗口投票。投票比单纯调阈值更有效因为它利用了时间连续性单帧抖动不会直接改变最终输出。5.4 侧脸一歪就识别失败现象正脸对准摄像头能识别头稍微转一下就变成unknown灵敏度很差。原因录入阶段只采了正脸样本识别阶段一来角度大的侧脸特征距离就偏离了原有特征空间。这不是dlib模型的问题是训练集和识别条件不匹配。解决录入时让用户左右转头、抬头低头各拍几轮保证样本覆盖角度。识别端确认传入了关键点让dlib做姿态归一化。如果侧脸依然失效最可靠的办法是装两个朝向不同的摄像头识别时选择角度更正的画面。单摄像头硬件限制下不要指望算法能逆天改命。5.5 模型加载报错提示dat文件无效现象加载shape_predictor_68_face_landmarks.dat时抛出异常提示文件格式错误或者文件尾被截断。原因模型文件没有下载完整。这两个dat文件体积不小下载中断会生成残缺文件而dlib加载时对文件头有校验残缺文件必然报错。解决先看文件大小。形状关键点模型约几十MB特征模型约20MB左右如果下载下来的文件只有几KB基本就是下载不完整。删掉重新下载下载后先运行2.3的验证脚本确认能加载再进入录入流程。模型文件和.py代码放同一目录时要注意工作目录路径用相对路径models/xxx.dat时确保启动脚本的位置在models目录的上一级。5.6 补充BGR和RGB顺序颠倒导致特征异常现象识别能跑通但同一个人的特征距离很大不同人的距离反而很小识别结果完全混乱。原因OpenCV的cv2.imread和cv2.VideoCapture读到的图像默认是BGR三通道顺序而dlib的模型按RGB顺序训练和推理。不转换就直接送入compute_face_descriptor特征会整体偏移。解决在调用dlib检测和特征提取前统一执行rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)。注意绘制结果时仍用原来的BGR图像因为OpenCV的画框和显示函数默认按BGR工作混用会看到颜色诡异但不报错这类问题排查起来比异常报错更费时间。6. 把识别阈值调到最优EER、活体检测与模型替换的下一步最值得花时间调校的参数就是阈值0.5。它不是官方给出的真值只是dlib在标准数据集上的推荐起点。换到你的摄像头和现场光线最优阈值可能在0.45也可能在0.6。调阈值不能靠肉眼感觉要按数据说话录一组“本人”在不同角度、不同光线下的特征再录一组“非本人”的特征分别两两算出欧氏距离。本人距离构成一条分布曲线非本人距离构成另一条两条曲线交叉点对应的距离就是等错误率点EER。把阈值设在EER点系统误识和拒识的概率最接近。这个调校过程看起来费时实际半小时能完成但比起拍脑袋调参能明显减少实景翻车概率。活体检测是当前方案的一大短板。这套系统只区分“这张脸是不是库里的这张脸”分不出“这是真人还是手机屏幕上的照片”。课程设计或实验室内部场景可以加一个“请眨眼”交互检测到人脸后提示用户眨眼用68个关键点里眼睛高宽比的变化判断是否有人眼闭合动作连续几帧没有眨眼动作就不放行。商用门禁则建议走近红外双目或结构光方案那已经超出dlib的能力范围属于硬件选型范畴。如果未来想继续提升识别精度可以考虑把dlib的128维特征换成arcface框架产出的特征。arcface在难度更高的数据集上精度更好但引入它意味着要换一套模型格式、预处理和推理代码工程代价比想象中大。我的建议是只有当dlib在你自己的数据分布上明显不够用时再换不要在demo阶段折腾模型替换先把数据流程跑顺。我自己写这套系统时最深的教训是在录入阶段太省事每个用户只录了五张后来夜间光线一变识别率掉得惨不忍睹补样本又花了一晚上。记住录入阶段偷的懒都会在识别阶段加倍还回来。希望这篇笔记能帮你少走一段弯路也欢迎你带着自己的踩坑经验回来互相印证。本文还有配套的精品资源点击获取
返回列表