简介这份资源是面向高校学生与Python初学者的人脸识别考勤系统完整项目源码适合用作课程设计、期末大作业或OpenCV与dlib入门实战参考。项目围绕考勤管理场景实现了用户注册登录、人脸检测与识别、打卡记录及数据查询等核心功能并借助dlib预训练模型完成特征提取与比对。压缩包共28个文件以15个Python源码文件为主体另含7张PNG界面截图、2个dat模型数据文件、2个txt说明文档及gitignore等配置项整体约116.61MB目录按attendanceSystem、utils、db、config等模块划分结构清晰便于二次开发。目前已有276人学习下载。读者可从中获得一套可直接运行的人脸考勤实现方案理解人脸检测、特征比对与数据库记录之间的协作流程并参考其模块划分与依赖配置快速搭建自己的考勤应用或在此基础上扩展功能。1. 从一张考勤表说起OpenCV 人脸识别考勤系统到底解决什么问题公司行政每个月月底最头疼的事就是对着打卡机导出的 Excel 一行行核对谁迟到了、谁忘打卡、谁找人代打。传统指纹机怕脱皮、怕出汗IC 卡怕代刷而一套基于 OpenCV 的 Python 人脸识别考勤系统恰好卡在「成本可控」和「体验够用」之间——一台普通 USB 摄像头加一台常年开机的迷你主机就能把「刷脸即打卡」跑起来。这个标题拆开看是四件事OpenCV 负责图像采集与预处理Python 负责把识别、比对、记录串成流水线人脸识别是核心算法环节考勤系统是最终落地形态。它适合两类人一是想拿它当课程设计或毕业设计的同学二是想给中小团队做一套轻量打卡工具的一线开发者。整套方案不依赖昂贵的专用硬件也不要求你从零训练模型用现成的人脸检测器和特征提取器就能跑通。下面我按「先跑通最小闭环再补工程细节」的顺序把这条链路讲透。2. 环境搭建与依赖选型别让 ModuleNotFoundError 卡在第一步2.1 Python 版本与 OpenCV 安装的三条路径新手最容易翻车的地方不是算法而是环境。热词里高频出现的ModuleNotFoundError: No module named opencv九成是装错了包名或装错了环境。OpenCV 在 Python 里的包名是opencv-python不是cv2也不是opencv。我一般推荐三条路径按你的场景选路径适用场景安装命令优缺点pip 直装快速验证、纯 CPUpip install opencv-python最省事但不含 contrib 模块conda 安装已有 Anaconda 环境conda install -c conda-forge opencv依赖自动解决体积大源码编译需要 CUDA 加速或自定义模块cmake 编译耗时长但可控性最强对绝大多数考勤场景CPU 版本足够。人脸检测在 640×480 分辨率下单帧耗时约 3080ms一秒处理十几帧完全够用。只有当你需要同时处理多路摄像头或做实时视频流分析时才考虑 CUDA 版本。# 推荐用虚拟环境隔离避免污染系统 Python python -m venv attendance_env # Linux/macOS 激活 source attendance_env/bin/activate # Windows 激活 attendance_env\Scripts\activate # 安装核心依赖 pip install opencv-python numpy pillow # 如果需要人脸识别专用库含特征提取 pip install opencv-contrib-python face-recognition逻辑说明虚拟环境是为了让这套考勤系统的依赖和你机器上其他项目隔离避免版本冲突。opencv-contrib-python比基础版多了face模块里面包含 LBPH 人脸识别器这是后面做识别比对的关键。face-recognition库封装了 dlib 的深度学习模型精度更高但安装时可能需要编译 dlibWindows 上建议先装好 CMake 和 Visual Studio Build Tools。参数说明opencv-python和opencv-contrib-python不要同时装否则会出现函数冲突。如果你已经装了基础版先pip uninstall opencv-python再装 contrib 版。提示在 Anaconda Prompt 里执行conda list看不到 opencv不代表没装可能是装在了 pip 的子环境里。用python -c import cv2; print(cv2.__version__)验证最可靠。2.2 摄像头采集与第一帧人脸检测环境通了之后先别急着写考勤逻辑用最小代码验证摄像头和人脸检测能不能跑通。这一步是整个系统的地基。import cv2 # 加载 OpenCV 自带的人脸检测器Haar 级联 # 文件路径在 cv2.data.haarcascades 下跨平台通用 cascade_path cv2.data.haarcascades haarcascade_frontalface_default.xml face_cascade cv2.CascadeClassifier(cascade_path) # 打开默认摄像头参数 0 表示第一个摄像头 cap cv2.VideoCapture(0) if not cap.isOpened(): raise RuntimeError(摄像头打开失败检查设备索引或权限) while True: ret, frame cap.read() if not ret: break # 转灰度Haar 检测器只接受单通道图 gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # detectMultiScale 参数图像、缩放步长、最小邻居数、最小人脸尺寸 faces face_cascade.detectMultiScale( gray, scaleFactor1.1, minNeighbors5, minSize(80, 80) ) for (x, y, w, h) in faces: cv2.rectangle(frame, (x, y), (x w, y h), (0, 255, 0), 2) cv2.imshow(Face Detection, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明detectMultiScale是滑动窗口检测scaleFactor1.1表示每次把图像缩小 10% 再检测值越小检测越细但越慢。minNeighbors5控制误检值越大越严格漏检也越多。minSize(80,80)过滤掉太小的区域避免把远处背景误判成人脸。参数说明如果你在 Linux 上跑摄像头权限问题很常见把当前用户加入video组即可sudo usermod -aG video $USER重新登录生效。Windows 上如果摄像头被其他程序占用cap.isOpened()会返回 False关掉占用程序再试。3. 人脸识别核心链路从检测到比对再到打卡记录3.1 三种识别方案选型HaarLBPH、Dlib、深度学习检测到人脸只是第一步考勤系统真正要做的是「这张脸是谁」。常见做法有三条路线我按落地难度和精度排一下方案核心原理精度速度适合场景Haar LBPH纹理特征直方图比对中快课程设计、小团队Dlib ResNet128 维特征向量欧氏距离高中正式考勤、门禁深度学习端到端人脸 embedding 分类很高慢大规模、高安全LBPHLocal Binary Patterns Histograms是 OpenCV contrib 里自带的识别器原理是把人脸切成小格每格算局部二值模式直方图最后拼接成特征向量做比对。它的好处是训练快、样本需求少每人 1020 张就能跑缺点是光照变化大时精度掉得厉害。Dlib 的方案是把人脸映射成 128 维向量同一个人的向量距离近不同人距离远。这个方案对光照和角度鲁棒得多但需要face-recognition库和 dlib 编译环境。我一般建议课程设计用 LBPH 快速出效果正式上线用 Dlib。下面重点讲 LBPH 的完整实现因为它的依赖最少、复现门槛最低。3.2 人脸样本采集与 LBPH 训练脚本考勤系统需要先「录入」员工人脸。我一般写一个采集脚本按人建文件夹每人采 30 张不同角度和表情的图。import cv2 import os # 员工姓名作为文件夹名方便后续训练时读取标签 person_name input(输入员工姓名拼音如 zhangsan: ) save_dir os.path.join(dataset, person_name) os.makedirs(save_dir, exist_okTrue) face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) cap cv2.VideoCapture(0) count 0 max_samples 30 while count max_samples: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, 1.1, 5, minSize(80, 80)) for (x, y, w, h) in faces: count 1 # 统一裁剪成 200x200减少后续训练尺寸不一致的问题 face_roi cv2.resize(gray[y:yh, x:xw], (200, 200)) cv2.imwrite(f{save_dir}/{count}.jpg, face_roi) cv2.rectangle(frame, (x, y), (xw, yh), (0, 255, 0), 2) cv2.putText(frame, f{count}/{max_samples}, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow(Collecting, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows() print(f{person_name} 采集完成共 {count} 张)逻辑说明采集时统一转灰度并 resize 到 200×200是为了让训练阶段所有样本尺寸一致。LBPH 对尺寸不敏感但统一尺寸能减少噪声。文件名用数字递增训练时按文件夹名映射标签。参数说明max_samples30是经验值太少泛化差太多采集耗时。采集时让员工稍微转动头部、换几个表情能显著提升识别率。如果检测不到人脸检查minSize是否设得太大或者光线是否太暗。训练脚本更简单import cv2 import numpy as np import os recognizer cv2.face.LBPHFaceRecognizer_create() face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) faces [] labels [] label_map {} # 姓名 - 数字标签 current_label 0 dataset_path dataset for person_name in os.listdir(dataset_path): person_dir os.path.join(dataset_path, person_name) if not os.path.isdir(person_dir): continue label_map[current_label] person_name for img_name in os.listdir(person_dir): img_path os.path.join(person_dir, img_name) img cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) if img is None: continue faces.append(img) labels.append(current_label) current_label 1 recognizer.train(faces, np.array(labels)) recognizer.save(trainer.yml) # 保存标签映射识别时要用 import json with open(label_map.json, w, encodingutf-8) as f: json.dump(label_map, f, ensure_asciiFalse) print(f训练完成共 {len(faces)} 张样本{len(label_map)} 个人)逻辑说明LBPHFaceRecognizer_create()创建识别器train()接收图像列表和标签数组。标签映射单独存 JSON是因为识别器只输出数字需要靠映射还原姓名。参数说明trainer.yml是模型文件换机器部署时一起拷贝。如果样本量很大几千张训练可能耗时几分钟属正常。3.3 实时识别与考勤记录写入识别阶段把检测、比对、记录串起来。核心是predict()返回的置信度值越小表示越像。import cv2 import json import csv from datetime import datetime recognizer cv2.face.LBPHFaceRecognizer_create() recognizer.read(trainer.yml) face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) with open(label_map.json, r, encodingutf-8) as f: label_map {int(k): v for k, v in json.load(f).items()} # 记录已打卡的人避免同一人反复写入 checked_in set() CONFIDENCE_THRESHOLD 70 # 置信度阈值越小越严格 def mark_attendance(name): today datetime.now().strftime(%Y-%m-%d) now datetime.now().strftime(%H:%M:%S) with open(attendance.csv, a, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([today, now, name]) cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, 1.1, 5, minSize(80, 80)) for (x, y, w, h) in faces: face_roi cv2.resize(gray[y:yh, x:xw], (200, 200)) label, confidence recognizer.predict(face_roi) if confidence CONFIDENCE_THRESHOLD: name label_map.get(label, unknown) if name not in checked_in: mark_attendance(name) checked_in.add(name) color (0, 255, 0) text f{name} {confidence:.1f} else: color (0, 0, 255) text funknown {confidence:.1f} cv2.rectangle(frame, (x, y), (xw, yh), color, 2) cv2.putText(frame, text, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, color, 2) cv2.imshow(Attendance, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明predict()返回标签和置信度置信度低于阈值才认为是同一个人。checked_in集合防止同一人站在摄像头前被反复记录。考勤数据追加写入 CSV方便后续用 Excel 或 pandas 统计。参数说明CONFIDENCE_THRESHOLD70是 LBPH 的经验值实际项目里要拿真人测试调。设太低会认错人设太高会频繁 unknown。建议先用 10 个人各测 20 次统计误识率和拒识率再定。4. 避坑与排查那些让我加班到凌晨的细节4.1 摄像头打开失败或画面全黑现象cap.isOpened()返回 True但read()一直返回 False或者画面全黑。原因Linux 下摄像头设备被其他进程占用或者设备索引不是 0。Windows 下常见于摄像头被会议软件后台占用。解决先用ls /dev/video*确认设备号把VideoCapture(0)改成对应索引。Windows 下在任务管理器里结束占用摄像头的进程。如果用的是虚拟机检查 USB 设备是否直通。4.2 识别率忽高忽低同一个人有时认不出现象同一个人正面能识别稍微侧脸或换个房间就报 unknown。原因LBPH 对光照和角度敏感训练样本太单一。另外detectMultiScale的minSize设得太大侧脸时检测框偏小被过滤。解决采集样本时覆盖不同光照和角度每人至少 30 张。把minSize降到(60,60)scaleFactor调到1.05提高检测密度。如果还不行换 Dlib 方案它的特征向量对角度鲁棒得多。4.3 置信度阈值怎么调都不对现象阈值设 70熟人认不出设 100陌生人也能认成员工。原因LBPH 的置信度不是概率不同数据集分布差异大没有通用阈值。解决写一个测试脚本让每个员工在摄像头前测 20 次记录每次的 confidence 值取「本人最大值」和「陌生人最小值」的中间值作为阈值。如果两个分布重叠严重说明样本质量不够回去补采。4.4 考勤 CSV 出现重复记录或时间错乱现象同一个人一分钟内被记录多次或者时间戳和实际不符。原因checked_in集合只在内存里程序重启就清空。时间戳用的是系统时间如果机器时区不对就会错。解决把checked_in按日期持久化比如每天一个 JSON 文件启动时加载。时间统一用datetime.now()并确保系统时区正确。生产环境建议加一个「打卡间隔」逻辑同一人 5 分钟内只记一次。4.5 多线程下 OpenCV 卡死现象把识别放到子线程后程序偶尔卡死或报GIL相关错误。原因OpenCV 的 GUI 函数imshow、waitKey必须在主线程调用子线程里调用会出问题。解决主线程负责imshow和waitKey子线程只做detectMultiScale和predict通过队列传递结果。或者干脆不用多线程CPU 版本单线程跑 640×480 已经够用。5. 从能跑到好用三个让考勤系统真正落地的技巧5.1 用「人脸 活体检测」挡住照片打卡LBPH 方案最大的漏洞是拿一张照片也能识别通过。我一般加一个轻量活体检测连续采集 10 帧计算人脸区域的帧间差异如果差异低于阈值说明是静态照片。import cv2 import numpy as np def is_live(prev_gray, curr_gray, face_box, threshold8): x, y, w, h face_box prev_roi prev_gray[y:yh, x:xw] curr_roi curr_gray[y:yh, x:xw] if prev_roi.shape ! curr_roi.shape: return True # 计算帧间绝对差均值 diff np.mean(cv2.absdiff(prev_roi, curr_roi)) return diff threshold逻辑说明真人脸部会有微小的肌肉抖动和呼吸起伏帧间差异通常大于 8打印照片或手机屏幕静止时差异接近 0。这个阈值要在实际光照下测光线太暗时噪声大可能误判。参数说明threshold8是经验值光线充足时可用 510暗光环境要调高到 15 以上。更稳的做法是结合眨眼检测但那就需要 dlib 的关键点模型了。5.2 考勤数据自动统计与异常标记CSV 只是原始记录行政要的是「谁迟到、谁缺勤」。用 pandas 几行就能出日报import pandas as pd from datetime import datetime df pd.read_csv(attendance.csv, names[date, time, name]) today datetime.now().strftime(%Y-%m-%d) today_df df[df[date] today] # 设定上班时间 09:00晚于即迟到 today_df[is_late] today_df[time] 09:00:00 print(today_df.groupby(name)[is_late].max())逻辑说明按日期过滤后用字符串比较判断迟到前提是时间格式统一为 HH:MM:SS。groupby按人聚合max()表示只要有一次迟到就算迟到。参数说明上班时间、迟到判定规则按公司制度改。如果要统计工时还需要下班打卡记录那就得在 CSV 里加一列「类型」区分上班和下班。5.3 模型更新与增量训练新员工入职、老员工换发型都需要更新模型。LBPH 不支持增量训练只能全量重训。我的习惯是dataset文件夹按人组织新员工加文件夹离职员工删文件夹然后重跑训练脚本。整个过程不到一分钟比维护增量逻辑省心。如果样本量到了几千张重训变慢可以改用 Dlib 方案特征提取和分类分离新增人脸只需提取新样本的 128 维向量追加到数据库不用重训整个模型。这也是我最终在生产环境选 Dlib 的原因。最后说个血泪经验别在周五下午部署考勤系统。我踩过一次周一早上全公司站在摄像头前排队结果因为周末有人动了摄像头角度识别率暴跌。后来我养成了习惯——每次调整摄像头位置后先拿测试账号跑 20 次验证确认阈值稳定再通知大家用。希望帮到你。本文还有配套的精品资源点击获取