十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

基于OpenCV与dlib的人脸识别系统:环境搭建、特征提取与实时识别实战

基于OpenCV与dlib的人脸识别系统:环境搭建、特征提取与实时识别实战 简介这份基于OpenCV与dlib库实现的Python人脸识别系统毕业设计项目面向计算机、通信、人工智能、自动化等专业的学生、教师或从业者既可作为期末课程设计、课程大作业与毕业设计的参考范例也适合刚入门的小白从零到一掌握人脸检测、关键点定位、特征提取与比对的完整流程。压缩包共118个文件大小约121MB除Python源码外还包含dlib人脸关键点模型与识别模型、多组人员的人脸样本图片、对应的特征CSV数据以及演示视频、markdown说明文档等目录层次清晰方便对照学习、修改与二次开发。项目答辩评审分为98分所有代码均经过调试测试可稳定运行。当前已有283人在此学习浏览。借助源码中的注释与配套说明读者能够快速理解基于OpenCVdlib的人脸识别实现逻辑替换或扩充自带数据集后还能应用于考勤打卡、门禁识别等真实场景整体借鉴价值较高。1. 基于 OpenCV、dlib 和 Python 的人脸识别系统是毕业设计里出现频率最高、也最容易翻车的选型之一这套技术栈不依赖 GPU 和深度学习框架一台普通笔记本就能跑完注册人脸、实时识别、输出姓名的完整闭环。但它的难点不在模型深而在三个库的衔接OpenCV 负责图像读取、摄像头采集、画框和坐标变换dlib 负责检测人脸、定位 68 点关键点、提取 128 维特征Python 做胶水把检测—对齐—提特征—比对串成一条流水线。任何一个环节的参数没对齐识别率就会从能用掉到看命。下面按评审老师会追问的技术纵深拆开讲环境怎么装、检测参数怎么设、对齐为什么比模型更影响准确率、0.6 这个阈值到底该不该信。新手可以照着命令一步步复现熟手可以直接看第 5 章的细节。2. dlib 安装是第一道坎Python 环境、版本组合与 OpenCV 联调排错这类毕设压缩包解压之后代码本身通常没什么悬念真正的第一个崩溃点永远是pip install dlib。dlib 是一个 C 库pip 安装它本质上是在替你的机器编译十多万行 C 代码所以它比 opencv、numpy 这些带预编译 wheel 的包更容易失败。多年的常见做法是先定 Python 版本再谈安装最后用一条命令验证三个库同时可用。2.1 推荐版本组合Python 3.8~3.11 dlib 19.24 OpenCV 4.8组件推荐版本说明Python3.8 ~ 3.11覆盖绝大多数 dlib 预编译 wheel3.12/3.13 经常要现场编译numpy1.26.4dlib 19.24 的 wheel 按 numpy 1.x ABI 编译numpy 2.x 可能触发 DLL 加载失败dlib19.24.0同时提供 HOG 检测器、68 点关键点模型和 ResNet 特征模型opencv-python4.8 及以上只装 opencv-python不要与 opencv-contrib-python 混装二者互相覆盖选 Python 3.10 是当前最稳的组合。Python 安装教程里最容易漏掉的一步是勾选 Add Python to PATH漏勾之后pip命令会直接提示找不到。装完后在终端跑一次python --version确认解释器路径再继续下一步。2.2 两种安装路径pip 预编译与源码编译python -m venv venv # Windows venv\Scripts\activate # Linux / macOS # source venv/bin/activate pip install --upgrade pip setuptools wheel pip install numpy1.26.4 pip install opencv-python pip install dlib先建虚拟环境再装依赖是我一直建议的隔离方式避免这台机器上多个项目把 opencv 或 numpy 互相升级成不兼容的版本。在 Windows 上如果 dlib 有对应 Python 版本的官方 wheelpip 会静默装完一旦输出里出现Building wheel for dlib说明没找到 wheel正在进入源码编译。源码编译需要两个前置CMake 和 C 编译器。Windows 上安装 Visual Studio Build Tools勾选使用 C 的桌面开发工作负载Linux 上确保装了 g 和 libx11-dev。不想装编译器的可以先换国内镜像源再试一次 wheel镜像 CI 里通常有更多可用轮子pip install dlib -i https://pypi.tuna.tsinghua.edu.cn/simple提示如果电脑里装过多个 Python 版本先运行python --version确认当前 venv 用的解释器避免依赖装进另一个解释器后 import 还是报 ModuleNotFoundError。2.3 验证环境一条命令点亮三个库并检查模型文件import cv2 import dlib import numpy as np from pathlib import Path print(OpenCV:, cv2.__version__) print(dlib:, dlib.__version__) detector dlib.get_frontal_face_detector() img dlib.load_rgb_image(test.jpg) # dlib 自带读图返回 RGB 顺序 faces detector(img, 0) print(检测到人脸:, len(faces)) for f in [shape_predictor_68_face_landmarks.dat, dlib_face_recognition_resnet_model_v1.dat]: print(f, 存在 if Path(f).exists() else 缺失)这段脚本把三件事一次验证掉opencv 能否 import、dlib 能否 import 并真实检测、两个模型文件是否在手。detector(img, 0)的第二个参数是 upsample 次数测试阶段用 0 就够。模型文件从 dlib 官方 model zoo 下载关键点模型约 100MB特征模型约 5MB下载后放在脚本同级的 models 目录下。两个文件缺失时 detector 还能跑但 shape_predictor 和 face_recognition_model_v1 的构造函数会直接抛异常这是前面好好的突然崩的最常见原因。报错直接原因处理ModuleNotFoundError: No module named cv2opencv 未装或装错环境确认 venv 已激活重跑 pip install opencv-pythonMicrosoft Visual C 14.0 or greater is required源码编译缺编译器装 VS Build Tools或换 Python 版本拿 wheelCMake was not found编译期缺 CMakepip install cmake 后再装 dlibImportError: DLL load failed while importing dlibnumpy 2.x ABI 不兼容降级 numpy 到 1.26.x重启 Python 进程3. 人脸检测与对齐OpenCV 图像预处理、dlib 68 点关键点与仿射校正环境跑通后流水线的第一站不是识别而是在图像里把脸找出来并摆正。检测这一层 dlib 用的是 HOG 特征加线性 SVM不是深度学习所以速度极快、无需显卡代价是对模糊、大角度侧脸和极端光线的容错不如深度模型。对齐则是整个系统里最容易被忽略、却对最终准确率影响最大的一步特征模型看到的是对齐后的人脸 chip而不是原图里的任意矩形。3.1 OpenCV 读图与预处理灰度转换、尺寸缩放与颜色顺序纪律import cv2 img cv2.imread(known/zhang.jpg) # OpenCV 读出的是 BGR 顺序 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 灰度图供 dlib 检测器使用 h, w gray.shape if max(h, w) 800: # 大图先降采样检测时间与像素数正相关 scale 800.0 / max(h, w) gray cv2.resize(gray, (int(w * scale), int(h * scale))) print(缩放后尺寸:, gray.shape)cvtColor的第二个参数是颜色空间转换码BGR 转灰度用COLOR_BGR2GRAY如果图是 RGB 读进来的就要用COLOR_RGB2GRAY用错不会报错只是灰度值分布不同。把长边压到 800 像素以内能显著加快 HOG 检测对后续 68 点定位的精度损失可以忽略。注意dlib.load_rgb_image返回 RGB 顺序cv2.imread返回 BGR 顺序。混用不会立刻报错但会让 ResNet 特征整体偏移。我一般会在代码开头定一条纪律读图统一用 cv2进 dlib 之前统一转 RGB画框时再转回 BGR。3.2 dlib 正面检测器detector(gray, 0) 里的 upsample 与 run() 阈值detector dlib.get_frontal_face_detector() faces detector(gray, 0) # 0 表示不对图像做放大适合摄像头常见距离 for r in faces: x0, y0, x1, y1 r.left(), r.top(), r.right(), r.bottom() print(f人脸框: ({x0}, {y0}) - ({x1}, {y1}), 宽 {x1 - x0}px)detector 返回一个 dlib.rectangle 列表按置信度从高到低排列。第二个参数 upsample 每加 1dlib 会把图像放大一倍再检测小脸漏检率下降但计算量约翻 4 倍。桌面摄像头距离下用 0班级合影或监控画面用 1 就够再大只会拖慢速度。如果想更精细地控制多严才算一张脸用run方法而不是直接调用 detectorrects, scores, idx detector.run(gray, 0, -0.5) # 第三个参数是阈值调整量第三个参数 adjust_threshold 默认 0调大则要求更高的置信度可以压掉误检调成负值则放水能找回弱检出的脸。scores 列表里是每个框的置信度分数调参时先打印出来看看分布再决定阈值而不是凭感觉改。3.3 68 点关键点与最小对齐get_face_chip 与手工仿射两种做法predictor dlib.shape_predictor(models/shape_predictor_68_face_landmarks.dat) shape predictor(gray, faces[0]) # faces[0] 是上一节检测到的 rectangle pts np.array([[p.x, p.y] for p in shape.parts()]) print(pts.shape) # (68, 2) # 标准做法dlib 一步完成转正 居中裁剪 缩放到 150x150 chip dlib.get_face_chip(img, shape, size150) # 返回 RGB 图像68 点模型的索引有固定语义整理如下答辩时经常被问到区域索引范围下巴轮廓0 ~ 16左眉 / 右眉17 ~ 21 / 22 ~ 26鼻梁与鼻尖27 ~ 35左眼 / 右眼36 ~ 41 / 42 ~ 47外嘴唇 / 内嘴唇48 ~ 59 / 60 ~ 67如果想跟评审解释对齐原理是什么手工仿射是最短路径取左右眼中心连线与水平线的夹角把整张图绕左眼中心旋转回去。left_eye pts[36:42].mean(axis0) right_eye pts[42:48].mean(axis0) angle np.degrees(np.arctan2(right_eye[1] - left_eye[1], right_eye[0] - left_eye[0])) M cv2.getRotationMatrix2D(tuple(left_eye), angle, 1.0) rotated cv2.warpAffine(img, M, (img.shape[1], img.shape[0]))这段代码演示了 get_face_chip 内部最核心的一步摆正。除此之外它还会以两眼为基准做正方形裁剪并缩放到 150×150。为什么对齐这么重要ResNet 特征模型是在正脸 chip 上训练的输入带倾角的人脸会让 128 维向量整体漂移。实测中同一个人的两张照片如果头部倾角差 10°~15°不经过对齐的距离可能从 0.3 拉到 0.7直接跨过阈值变成误判对齐后能稳定压回 0.4 以内。4. 人脸识别核心dlib 的 128 维特征向量、欧氏距离与 0.6 阈值标定检测和对齐解决了脸在哪里、脸正不正这一章回答框里是谁。dlib 的识别不自己做分类器和训练而是用一个预训练的 ResNet 模型把对齐后的人脸压缩成一个 128 维浮点向量。两个人的向量距离越近就越可能是同一个人。整个识别阶段没有训练过程只有提特征和查表两步。4.1 加载 ResNet 模型把一张脸压成 128 个浮点数rec_model dlib.face_recognition_model_v1( models/dlib_face_recognition_resnet_model_v1.dat) vec rec_model.compute_face_descriptor( imgrgb_img, shapeshape, num_jitters0) emb np.array(vec) # shape: (128,)特征描述子 print(emb[:8]) # 打印前 8 维肉眼确认不是全 0compute_face_descriptor 内部会再做一次对齐输入rgb_img必须和提取shape用的是同一张图且尺寸一致否则关键点坐标对不上出来的向量就是废的。num_jitters是对同一张 chip 做多次微扰动采样后取平均能压低摄像头噪声带来的波动数值越大越稳、越慢。注册照片用 10实时识别帧用 0 或 1。dlib 官方在 LFW 人脸基准上给这个模型的准确率大约是 99.38%但那是高质量照片 良好对齐条件下的数字。自采数据集、手机自拍、教室摄像头这三种来源的分布完全不同所以毕设里这个数字只能当宣传用不能当验收标准。4.2 距离度量与阈值默认 0.6 不是免检参数def face_dist(v1, v2): return float(np.linalg.norm(v1 - v2))这个 128 维空间是 dlib 用三元组损失训练出来的判别语义天然就是欧氏距离。余弦相似度虽然也能排序但它和欧氏距离的阈值语义不同混用容易在答辩时被问住。毕设里我建议只用欧氏距离跟模型作者的设计意图保持一致。欧氏距离判断建议 0.4同一个人高置信0.4 ~ 0.55大概率同一人可放行0.55 ~ 0.7灰区取决于注册照质量 0.7判为不同人0.6 是社区流传最广的默认值也是很多开源项目写死的数字但它不是免检参数。正确标定方法是用自己的数据集测两组分布同一人不同照片互比不同人两两互比然后把阈值放在两组距离分布的中间且偏严的位置。灰区样本多就说明注册照质量和数量有问题而不是换阈值能解决的。4.3 注册与识别流程用 pickle 建最小人脸特征库from pathlib import Path import pickle 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) rec_model dlib.face_recognition_model_v1( models/dlib_face_recognition_resnet_model_v1.dat) def encode_image(path): img dlib.load_rgb_image(str(path)) rects detector(img, 1) if len(rects) ! 1: raise ValueError(f{path.name}: 检测到 {len(rects)} 张人脸) shape predictor(img, rects[0]) return np.array(rec_model.compute_face_descriptor( imgimg, shapeshape, num_jitters10)) def build_db(folderknown/): db {} for f in Path(folder).glob(*.*): if f.suffix.lower() not in (.jpg, .jpeg, .png): continue try: db[f.stem] encode_image(f) # 文件名叫什么识别结果就是什么 except ValueError as e: print(e) with open(faces.pkl, wb) as fh: pickle.dump(db, fh) return db def recognize(path, db, threshold0.6): vec encode_image(path) best_name, best_dist unknown, float(inf) for name, ref in db.items(): d face_dist(vec, ref) if d best_dist: best_name, best_dist name, d return (best_name, best_dist) if best_dist threshold else (unknown, best_dist)这个最小实现把注册和识别都收敛到同一个 encode_image规则是known 目录下一个文件对应一个人文件名叫什么识别结果就是什么。注册用 num_jitters10 取平均等于给模板降噪识别时用单次提取的向量去和库里每个模板算距离取最近邻。pickle 适合几十人规模的特征库超过一百人时线性扫描开始有延迟再换 SQLite 或向量检索。5. 摄像头实时识别与特征库体检三个翻车细节和一处加分技巧到这一章离线识别的流程已经完整。剩下的是把系统搬到摄像头前以及确保答辩演示不出丑。实时识别和离线识别只差两件事帧率和坐标映射。5.1 VideoCapture 实时识别降采样、跳帧与坐标还原cap cv2.VideoCapture(0) if not cap.isOpened(): raise SystemExit(摄像头打开失败) frame_idx 0 while True: ok, frame cap.read() frame_idx 1 if not ok: break if frame_idx % 3 ! 0: # 每 3 帧检测一次 cv2.imshow(face rec, frame) if cv2.waitKey(1) 0xFF ord(q): break continue small cv2.resize(frame, (0, 0), fx0.5, fy0.5) gray cv2.cvtColor(small, cv2.COLOR_BGR2GRAY) rgb cv2.cvtColor(small, cv2.COLOR_BGR2RGB) for r in detector(gray, 0): x0, y0, x1, y1 r.left()*2, r.top()*2, r.right()*2, r.bottom()*2 cv2.rectangle(frame, (x0, y0), (x1, y1), (0, 255, 0), 2) shape predictor(gray, r) vec np.array(rec_model.compute_face_descriptor( imgrgb, shapeshape, num_jitters1)) # 这里把 vec 交给 4.3 的 recognize() 最近邻逻辑即可 cv2.imshow(face rec, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()VideoCapture 的 read() 是 grab retrieve 两步的封装底层在 Windows 上走 DirectShow、Linux 上走 V4L2驱动会缓存最近一帧如果处理速度跟不上采集速度读到的就是旧帧表现是画面粘滞。所以实时系统的常见做法是降采样和跳帧二选一把图像缩一半HOG 计算量降到四分之一每 3 帧检测一次把帧率不确定性让位给稳定性。注意检测框和关键点坐标都来自缩小后的图画到原始 frame 上必须乘回缩放系数这是实时模块里最常见的坐标 bug。5.2 特征库体检导出关键点叠加图和 face chip与其答辩现场赌运气不如提前给每张注册照做一次体检def inspect_registration(path, out_prefix): img dlib.load_rgb_image(str(path)) rects detector(img, 1) if len(rects) ! 1: print(skip:, path) return shape predictor(img, rects[0]) chip dlib.get_face_chip(img, shape, size150) cv2.imwrite(out_prefix _chip.jpg, cv2.cvtColor(chip, cv2.COLOR_RGB2BGR)) overlay img.copy() for p in shape.parts(): cv2.circle(overlay, (p.x, p.y), 1, (0, 0, 255), -1) cv2.imwrite(out_prefix _landmarks.jpg, cv2.cvtColor(overlay, cv2.COLOR_RGB2BGR))体检标准就两条chip 里的人脸是否居中正脸68 点是否贴合五官轮廓。chip 歪了、关键点飘了说明这张注册照质量不合格后面阈值怎么调都白搭。所有注册图导出后人工扫一遍比任何指标都直观。5.3 三个翻车细节颜色空间、模型路径、注册照数量第一颜色空间。cv2 读出的是 BGRdlib 模型按 RGB 训练全流程只允许一个顺序进 dlib 前转 RGB出 dlib 后转回 BGR。混用最隐蔽的症状是单机自拍能认换一台电脑的摄像头就认不出。第二模型路径。用相对路径加载 .dat 文件时基准是当前工作目录而不是脚本目录PyCharm 运行、终端运行、双击运行三个场景的 CWD 可能完全不同。常见做法是统一写成str(Path(__file__).parent / models / shape_predictor_68_face_landmarks.dat)一次写对到处不炸。第三注册照数量。每个注册人一张照片是最小可行方案但最容易踩 0.55~0.7 的灰区。常见做法是每人留 2~3 张不同角度和光线的照片分别编码后取向量平均作为模板同类距离通常能压低 0.05~0.1同时必须用一张绝对不同的人的照片验证不会误放行。演示前把阈值写进配置文件演示时只换测试照片、不调阈值这样系统看起来才像一个有确定行为的系统而不是现场表演参数调优。本文还有配套的精品资源点击获取
返回列表