简介:这是一套基于OpenCV实现的人脸识别考勤系统完整项目,面向计算机相关专业正在做毕业设计的学生,以及需要项目实战练习、课程设计或期末大作业的学习者。项目围绕图像采集、人脸检测、特征提取与人脸匹配四个核心环节展开,涉及灰度化、直方图均衡化等预处理手段,以及Eigenfaces、Fisherfaces、LBPH等经典特征提取算法,适合作为入门计算机视觉与机器学习应用的实践参考。压缩包共123个文件,以jpg人脸样本图像、xml分类器与配置数据、py源码脚本为主,另含exe可执行程序、bat启动脚本及md说明文档,整体约1.88MB,目录结构清晰,便于按模块查阅。项目经过严格调试,可直接运行,文档详细解释各功能模块的设计与实现原理,并给出开发环境搭建、编译运行指南及异常处理思路。目前已有48人学习关注,适合希望深入理解人脸识别流程并积累项目经验的开发者。
1. 从一张打卡照片说起:这套 OpenCV 人脸考勤系统到底能跑出什么
上周帮一个做企业内训的朋友看他们新装的考勤机,行政抱怨说员工戴着口罩站在门口反复摘戴,队伍排到电梯口。我顺手翻了翻他们后台,发现识别逻辑其实很粗糙——固定阈值、单帧比对、没有活体判断,光线一变就翻车。这让我想起手头这份基于 OpenCV 的人脸识别考勤系统源码,它解决的正是这类场景:用普通 USB 摄像头或笔记本自带镜头,配合 Python 和 OpenCV 完成人脸采集、训练、识别、打卡记录一条龙,不需要专用硬件,也不需要联网调云端接口。
这套资源适合三类人:一是做课程设计或毕业设计的学生,需要一份结构完整、能跑通、有文档可写的项目;二是想快速搭个内部考勤原型的开发者,拿它当脚手架改;三是刚学完 OpenCV 基础、想找个综合案例把 Haar 级联、LBPH 识别器、GUI 串起来的新手。它不追求工业级精度,但胜在链路完整、依赖干净、代码可读,你能清楚看到每一帧图像从摄像头到考勤表到底经历了什么。下面我按实际拆包和复现的顺序,把这份源码的用法、参数和坑一次讲透。
2. 拆开源码包:模块划分与 OpenCV 人脸识别链路
2.1 目录结构与各文件职责
拿到压缩包解压后,常见做法是看到类似这样的结构(不同版本文件名可能略有差异,但职责基本一致):
face_attendance/ ├── dataset/ # 存放采集到的人脸灰度图,按人名分文件夹 ├── trainer/ # 训练后生成的 .yml 模型文件 ├── attendance/ # 每日考勤 CSV 记录 ├── haarcascade_frontalface_default.xml # OpenCV 自带的人脸检测器 ├── capture_faces.py # 人脸采集脚本 ├── train_model.py # 模型训练脚本 ├── recognize_attendance.py # 实时识别与打卡脚本 ├── requirements.txt # 依赖清单 └── docs/ # 详细文档,含流程图与参数说明dataset里每个人一个子目录,比如dataset/zhangsan/下是1.jpg、2.jpg……这种按人名分文件夹的设计很关键,训练时 OpenCV 的 LBPH 识别器会直接读取目录名作为标签,省去手写映射表。trainer目录存训练产物,attendance按日期生成 CSV,方便后续用 Excel 或脚本统计。
2.2 人脸检测:Haar 级联为什么还在用
很多人一提到人脸检测就想到深度学习,但这份源码用的是 Haar 级联分类器。原因很实际:OpenCV 安装后自带haarcascade_frontalface_default.xml,零额外下载,CPU 上跑几十毫秒一帧,对考勤这种正脸、近距离、光照可控的场景足够用。它的原理是滑动窗口加积分图,快速排除非人脸区域,再用级联的弱分类器逐层筛选。
调用时核心就一行:
faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(100, 100))scaleFactor=1.1表示每次图像尺寸缩小 10%,值越小检测越细但越慢;minNeighbors=5控制误检,调高会减少误报但可能漏掉侧脸;minSize限制最小人脸像素,低于 100×100 的基本是噪点。我一般会把minNeighbors设到 6,因为考勤场景宁可漏检重试,也不要误识别成别人。
2.3 识别环节:LBPH 的输入输出与阈值逻辑
识别用的是cv2.face.LBPHFaceRecognizer_create()。LBPH 把每张人脸切成小区域,计算局部二值模式直方图,再比对直方图距离。它的好处是训练快、样本需求少(每人 20~30 张就够),对光照变化有一定容忍度。
训练脚本里典型写法:
recognizer = cv2.face.LBPHFaceRecognizer_create() recognizer.train(faces, np.array(ids)) recognizer.write('trainer/trainer.yml')faces是灰度图列表,ids是每个人对应的整数标签。识别时:
id_, confidence = recognizer.predict(gray_face) if confidence < 70: name = names[id_] else: name = "unknown"注意这里的confidence其实是距离,越小越可信。70 这个阈值是经验值,不同摄像头和光照下要微调。文档里如果写了固定 50,你直接拿去用很可能一半人识别不出来,这是最常见的翻车点之一。
3. 从零跑通:环境安装、采集、训练、识别四步实操
3.1 环境准备与 OpenCV 安装避坑
先确认 Python 版本,建议 3.8~3.10,太新的版本有时opencv-contrib-python轮子不全。安装命令:
pip install opencv-python==4.5.5.64 pip install opencv-contrib-python==4.5.5.64 pip install numpy pillow pandas必须装opencv-contrib-python,因为cv2.face模块只在 contrib 包里。很多人只装了opencv-python,运行到cv2.face就报module 'cv2' has no attribute 'face',这就是热词里常搜的modulenotfounderror: no named 'opencv'的变种。两个包版本要一致,否则会冲突。
提示:如果 pip 安装慢,换国内镜像源加
-i参数,不要同时装opencv-python-headless,它不含 GUI 功能,imshow会报错。
3.2 人脸采集脚本:样本数量与命名规范
采集脚本的核心逻辑是打开摄像头,检测人脸,保存灰度图并递增编号:
import cv2 import os cam = cv2.VideoCapture(0) face_detector = cv2.CascadeClassifier('haarcascade_frontalface_default.xml') name = input("输入姓名拼音: ") os.makedirs(f'dataset/{name}', exist_ok=True) count = 0 while True: ret, frame = cam.read() gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces = face_detector.detectMultiScale(gray, 1.1, 5, minSize=(100, 100)) for (x, y, w, h) in faces: count += 1 cv2.imwrite(f'dataset/{name}/{count}.jpg', gray[y:y+h, x:x+w]) cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2) cv2.imshow('capture', frame) if cv2.waitKey(1) == 27 or count >= 30: break cam.release() cv2.destroyAllWindows()每人采集 30 张左右比较稳。采集时注意:不要只在一个角度拍,稍微左右转头、换点表情,否则训练出来的模型只认正脸。保存的是灰度图且只存人脸区域,这样训练时不用再检测一遍,省时间。count >= 30是硬上限,实际可以按s键手动停,文档里如果有说明就按文档来。
3.3 训练脚本:标签映射与模型输出
训练脚本要遍历dataset下所有子目录,把每个人名映射成整数:
import cv2 import numpy as np import os recognizer = cv2.face.LBPHFaceRecognizer_create() detector = cv2.CascadeClassifier('haarcascade_frontalface_default.xml') faces, ids = [], [] names = {} current_id = 0 for root, dirs, files in os.walk('dataset'): for file in files: if file.endswith('jpg') or file.endswith('png'): path = os.path.join(root, file) name = os.path.basename(root) if name not in names: names[name] = current_id current_id += 1 img = cv2.imread(path, cv2.IMREAD_GRAYSCALE) faces.append(img) ids.append(names[name]) recognizer.train(faces, np.array(ids)) recognizer.write('trainer/trainer.yml') np.save('trainer/names.npy', names) print(f"训练完成,共 {len(names)} 人,{len(faces)} 张样本")这里把names字典存成names.npy,识别脚本再加载回来,避免每次重新遍历目录。current_id从 0 开始递增,LBPH 内部就是靠这个整数区分不同人。如果新增了人,必须重新训练整个模型,LBPH 不支持增量更新,这是它的硬限制。
3.4 识别与打卡:置信度阈值与 CSV 写入
识别脚本把上面两步串起来,加上打卡记录:
import cv2 import numpy as np import pandas as pd from datetime import datetime recognizer = cv2.face.LBPHFaceRecognizer_create() recognizer.read('trainer/trainer.yml') names = np.load('trainer/names.npy', allow_pickle=True).item() detector = cv2.CascadeClassifier('haarcascade_frontalface_default.xml') cam = cv2.VideoCapture(0) marked = set() while True: ret, frame = cam.read() gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces = detector.detectMultiScale(gray, 1.1, 6, minSize=(100, 100)) for (x, y, w, h) in faces: id_, conf = recognizer.predict(gray[y:y+h, x:x+w]) if conf < 70: name = [k for k, v in names.items() if v == id_][0] if name not in marked: marked.add(name) now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') df = pd.DataFrame([[name, now]], columns=['姓名', '时间']) df.to_csv(f'attendance/{datetime.now().date()}.csv', mode='a', header=False, index=False) cv2.putText(frame, f'{name} {conf:.1f}', (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) else: cv2.putText(frame, 'unknown', (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2) cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2) cv2.imshow('attendance', frame) if cv2.waitKey(1) == 27: break cam.release() cv2.destroyAllWindows()marked集合保证同一个人当天只写一次,避免站在镜头前反复刷记录。CSV 用追加模式,表头在第一次写入时手动加或提前建好。conf < 70这个阈值要按实际调整,光线暗的办公室可以放宽到 80,但误识率会上升。
4. 避坑与排查:识别率低、报错、摄像头异常的常见原因
4.1 现象:识别总是 unknown,置信度普遍偏高
原因通常是训练样本太少或角度单一,LBPH 对没见过的姿态泛化差。解决:每人补采到 50 张,覆盖左右转 15 度、戴不戴眼镜两种状态;同时把minNeighbors从 5 降到 4,让检测框更稳。如果还是不行,检查采集时保存的是否为灰度图,彩色图直接喂给 LBPH 会报维度错误或识别率骤降。
4.2 现象:运行报module 'cv2' has no attribute 'face'
原因只装了opencv-python,没装opencv-contrib-python。解决:pip uninstall opencv-python后重装 contrib 版本,两个包不要共存。如果用的是 conda,conda install -c conda-forge opencv通常自带 contrib。
4.3 现象:摄像头打开黑屏或VideoCapture(0)返回 False
原因可能是摄像头被其他程序占用,或索引不是 0。解决:关掉微信、钉钉等可能调用摄像头的软件;把0换成1或-1试;Linux 下检查/dev/video*权限,必要时sudo chmod 666 /dev/video0。
4.4 现象:训练时报Assertion failed !_src.empty()
原因是有图片路径读进来是空的,常见于dataset里混入了非图片文件或中文路径。解决:确保dataset下只有 jpg/png,路径全用英文;在cv2.imread后加if img is None: continue跳过坏图。
4.5 现象:打卡 CSV 中文乱码
原因to_csv默认编码不是 utf-8-sig。解决:写入时加encoding='utf-8-sig',Excel 打开就正常。这个坑很小但很烦,文档里如果没提,第一次用大概率中招。
5. 进阶技巧:把识别阈值调稳、把考勤数据用起来
5.1 用滑动窗口平滑置信度
单帧判断容易受表情和光线抖动影响,我一般会维护一个长度为 5 的队列,取中位数再和阈值比:
from collections import deque conf_queue = deque(maxlen=5) id_, conf = recognizer.predict(gray_face) conf_queue.append(conf) smooth_conf = sorted(conf_queue)[len(conf_queue)//2] if smooth_conf < 70: name = names_inv[id_]这样偶尔一帧跳到 90 也不会立刻判成 unknown,打卡体验顺很多。代价是响应慢半拍,考勤场景完全能接受。
5.2 阈值标定:用已知样本反推
不要凭感觉设 70。拿 10 张已知人的图跑predict,记录置信度分布,取最大值再加 10 作为阈值。比如张三的置信度在 35~58 之间,李四在 40~62,那阈值设 72 比较安全。文档里如果给了固定值,你也要用自己的数据复核一遍。
5.3 考勤数据后处理
attendance目录下每天一个 CSV,月底用 pandas 合并统计:
import pandas as pd import glob files = glob.glob('attendance/*.csv') df = pd.concat([pd.read_csv(f, names=['姓名', '时间']) for f in files]) df['日期'] = pd.to_datetime(df['时间']).dt.date summary = df.groupby(['姓名', '日期']).size().unstack(fill_value=0) summary.to_excel('月度考勤汇总.xlsx')这样迟到、缺勤一目了然。如果要做迟到判断,把时间列转成datetime后和 9:00 比较即可,逻辑简单但实用。
5.4 从 LBPH 换到深度学习识别器的边界
如果场景光线复杂、人数上百,LBPH 会吃力。常见做法是换成cv2.dnn加载 OpenCV 官方的人脸识别模型(如openface或facenet),或者用face_recognition库。但换之前想清楚:依赖变重、训练数据需求变大、CPU 推理变慢。考勤这种几十人、固定点位的场景,LBPH 调好阈值完全够用,没必要为了技术而技术。
从那以后我每次拿到这类源码,都强制先用自己的脸跑一遍采集、训练、识别全流程,再去看文档里写的参数是否对得上。这份 OpenCV 人脸考勤系统源码的价值不在于算法多先进,而在于它把一条完整链路摊开给你看,改起来有抓手。希望帮到你。
本文还有配套的精品资源,点击获取