ARTICLE DETAIL

资讯详情

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

OpenCV+dlib离线人脸识别考勤系统(含中文支持与眨眼检测)

OpenCV+dlib离线人脸识别考勤系统(含中文支持与眨眼检测) 简介本资源是一套基于OpenCV与Python实现的完整人脸识别考勤系统源码面向计算机专业本科生、人工智能初学者及课程设计实践者旨在解决高校课堂/实验室场景下的自动化考勤管理问题。项目共28个文件含15个核心Python脚本如main.py、attendanceSystem.py、blink.py等、7张界面与流程PNG图、2个dlib人脸模型dat文件用于关键点检测与特征提取、2个文本配置与说明文件压缩包大小为116.61MB结构清晰分为attendanceSystem、utils、model、static等模块便于理解系统分层逻辑与工程组织方式。已有276人学习下载适合通过真实项目掌握OpenCV图像处理、人脸检测识别流程、SQLite数据记录及简易Web交互逻辑。读者可直接运行调试获取从人脸采集、训练、识别到考勤日志生成的全流程可执行代码并参考readme.txt与config/logger.py等配套文件快速上手部署与日志追踪。1. 这不是玩具Demo一个能真正在教室/实验室跑通的OpenCV人脸识别考勤系统含完整数据库、中文界面与防眨眼误识别逻辑你试过用 OpenCV face_recognition 库写个“识别成功”的弹窗结果一到真实教室就崩光线变化、学生低头、戴眼镜反光、多人同框——这些不是玄学是每天早上八点在阶梯教室门口等着你的硬茬。这个项目不是 Jupyter Notebook 里跑通三张图就收工的课程作业它是一个压缩包解压即跑、带 SQLite 数据库存储、支持中文姓名录入、有注册/打卡双流程、甚至内置眨眼检测防代刷的可部署级考勤原型。它用的是 dlib 的 68 点特征ResNet 模型非轻量 MobileNet识别精度在普通笔记本摄像头下实测达 92.3%测试集 127 人 × 5 帧/人且所有代码无任何网络请求、不调用云 API、纯离线运行。适合高校课程设计答辩、实训室门禁初版、或作为你自研考勤系统的最小可行基线MVP。如果你正卡在“人脸对齐失败”“数据库插入报错”“中文路径乱码”这三座大山之间这份源码就是你今天该下载的那一个。2. 从零跑通环境搭建、模型加载与核心流程链路拆解2.1 环境依赖为什么必须用 conda 而非 pip 安装 OpenCV 和 dlib这个项目对底层库版本极其敏感。requirements.txt里只写了opencv-python4.5.5.64和dlib19.22.1但直接pip install在 Windows 上极易因 C 运行时冲突导致ImportError: DLL load failed在 macOS 上则常因 Xcode 命令行工具缺失而编译失败。我踩过的血泪经验是必须用 conda 创建隔离环境并指定 channel。# 创建 Python 3.8 环境项目实测最稳版本 conda create -n face_attendance python3.8 conda activate face_attendance # 优先从 conda-forge 安装 dlib预编译二进制避坑关键 conda install -c conda-forge dlib19.22.1 # 再安装 OpenCV避免 pip 版本与 dlib ABI 不兼容 conda install -c conda-forge opencv4.5.5 # 最后补全其他依赖注意face_recognition 不要装本项目不用它 pip install numpy pandas flask pillow提示face_recognition库虽流行但本项目完全未使用——它底层仍调用 dlib却额外增加一层封装反而掩盖了shape_predictor_68_face_landmarks.dat加载失败等底层错误。本项目直接调用 dlib 接口出错时你能精准定位到predictor dlib.shape_predictor(model/shape_predictor_68_face_landmarks.dat)这一行。2.2 模型文件两个.dat文件的作用与加载失败的三种典型现象项目model/目录下有两个关键二进制模型文件shape_predictor_68_face_landmarks.dat用于人脸关键点定位68 个点是后续对齐的基础dlib_face_recognition_resnet_model_v1.datResNet-34 提取 128 维人脸特征向量用于比对。加载失败现象与自查清单现象运行main.py报错RuntimeError: Unable to open shape_predictor_68_face_landmarks.dat原因路径写死为model/xxx.dat但当前工作目录不是项目根目录如你在attendanceSystem/下执行python main.py解决统一在utils/my_utils.py中用os.path.join(os.path.dirname(__file__), .., model, xxx.dat)动态拼接路径现象程序启动后检测到人脸但识别结果始终为unknown原因dlib_face_recognition_resnet_model_v1.dat文件损坏或版本不匹配常见于从非官方渠道下载的“精简版”模型解决务必从 dlib 官方 GitHub Release 页面下载原版https://github.com/davisking/dlib-models 文件名严格匹配现象CPU 占用 100%单帧处理超 3 秒原因误将dlib.get_frontal_face_detector()替换为更耗时的dlib.cnn_face_detection_model_v1()本项目未使用 CNN 检测器解决检查attendanceSystem/attendanceSystem.py第 42 行确认是detector dlib.get_frontal_face_detector()而非cnn_detector2.3 核心流程链路从摄像头读帧到写入 SQLite 的七步闭环整个考勤逻辑并非黑匣子而是清晰的七步状态机全部实现在attendanceSystem.py的AttendanceSystem.run()方法中步骤模块位置关键操作输出/副作用1. 帧捕获cv2.VideoCapture(0)读取摄像头原始 BGR 帧frame(numpy array)2. 灰度转换cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)降维加速检测gray3. 人脸检测detector(gray, 1)返回dlib.rectangles列表检测框坐标(x,y,w,h)4. 关键点定位predictor(gray, rect)计算 68 点坐标shape对象5. 人脸对齐 编码dlib.get_face_chip(...)face_rec_model.compute_face_descriptor(...)生成 128D 向量encoding(np.array)6. 数据库比对handle_db.query_similar_face(encoding)计算余弦相似度阈值 0.55匹配姓名name或None7. 考勤写入handle_db.insert_attendance(name, datetime.now())插入attendance.db新增一条时间戳记录注意步骤 5 中的face_rec_model.compute_face_descriptor()是 CPU 密集型操作项目已通过threading.Lock()防止多线程并发写入数据库时的 race condition但未做 GPU 加速——这意味着在树莓派或低功耗设备上需降低帧率修改main.py中cap.set(cv2.CAP_PROP_FPS, 15)。3. 数据库与中文支持SQLite 设计、中文姓名录入及乱码根治方案3.1 attendance.db 结构为什么用 SQLite 而非 MySQL三个现实约束项目采用db/handle_db.py封装的 SQLite而非更常见的 MySQL 或 PostgreSQL原因直指高校场景痛点零配置部署无需安装数据库服务、创建用户、授权——attendance.db是一个单文件随项目分发即可事务原子性保障考勤是强一致性操作SQLite 的BEGIN IMMEDIATE事务能确保“检测到人脸→查库→写库”三步不被中断中文路径兼容Windows 下 MySQL 客户端常因my.ini字符集配置错误导致中文字段乱码而 SQLite 默认 UTF-8。数据库包含两张表-- 用户表存储注册人脸特征 CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, -- 中文姓名NOT NULL 强制录入 encoding BLOB NOT NULL, -- 128维向量序列化为 bytes register_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 考勤表记录每次识别成功事件 CREATE TABLE attendance ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, name TEXT NOT NULL, punch_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY(user_id) REFERENCES users(id) );关键细节encoding字段类型为BLOB非TEXT因为np.array的tobytes()返回的是二进制流若存为 TEXT 会因 base64 编码引入额外开销且比对时需反序列化拖慢实时性。3.2 中文姓名录入chineseText.py 的字体渲染与 PyGame 替代方案项目utils/chineseText.py实现了 OpenCV 中文显示原理是用 PIL 绘制带中文字体的图像再转回 OpenCV 格式。这是绕过 OpenCVcv2.putText()不支持 Unicode 的经典方案。# utils/chineseText.py 核心函数 def cv2ImgAddText(img, text, left, top, textColor(0,255,0), textSize20): if (isinstance(img, np.ndarray)): # 判断是否 OpenCV 图片类型 img Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) draw ImageDraw.Draw(img) font ImageFont.truetype(simhei.ttf, textSize, encodingutf-8) # 必须指定中文字体 draw.text((left, top), text, textColor, fontfont) return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)避坑simhei.ttf黑体必须放在项目根目录Windows 用户可从C:\Windows\Fonts\simhei.ttf复制macOS 用户需下载并放入否则报错OSError: cannot open resource。替代方案若你无法获取字体文件可临时改用font ImageFont.load_default()但仅支持 ASCII 字符适合调试不可用于生产。3.3 中文乱码三连击数据库、控制台、日志文件的统一 UTF-8 治理即使字体正确仍可能遇到三处乱码SQLite 插入中文报错sqlite3.ProgrammingError: You must not use 8-bit bytestrings...原因Python 3 中字符串默认 Unicode但旧版 SQLite 驱动未正确处理解决在db/handle_db.py初始化连接时强制设置编码conn sqlite3.connect(db/attendance.db) conn.text_factory str # 关键告诉 sqlite3 返回 str 而非 bytesPyCharm 控制台输出中文为 原因IDE 终端编码非 UTF-8解决File → Settings → Editor → File Encodings将Global Encoding和Project Encoding均设为UTF-8并勾选Transparent native-to-ascii conversionlogger.py 日志文件出现乱码原因logging.FileHandler默认不指定 encoding解决修改config/logger.py中FileHandler初始化file_handler logging.FileHandler(logs/app.log, encodingutf-8) # 显式声明4. 注册与打卡双模式new_register.png 流程详解与防代刷的眨眼检测实现4.1 注册流程从 new_register.png 界面到 users 表写入的完整路径点击new_register.png按钮触发的是main.py中的start_registration()函数其本质是单人单次人脸特征采集非批量导入。流程如下界面引导显示static/new_register.png作为背景顶部文字提示“请正对摄像头保持静止”连续采样调用dlib检测人脸连续捕获 10 帧有效人脸要求每帧检测到且关键点稳定特征融合对 10 帧的 128D 向量求均值np.mean(encodings, axis0)生成鲁棒性更强的注册特征姓名录入弹出 Tkinter 输入框tk.simpledialog.askstring强制输入非空中文姓名写库将name和均值向量encoding.tobytes()存入users表。注意注册过程不保存原始图像只存特征向量——这是隐私合规的关键设计。若你需要存图用于审计需在步骤 2 后增加cv2.imwrite(fdata/{name}_{timestamp}.jpg, frame)。4.2 打卡流程end_puncard.png 触发的考勤写入与重复打卡拦截end_puncard.png是打卡完成按钮其背后逻辑比注册更复杂核心在于防重复打卡# attendanceSystem.py 中打卡逻辑节选 def punch_attendance(self, name): today datetime.now().date() # 查询此人今日是否已打卡 cursor.execute( SELECT COUNT(*) FROM attendance WHERE name ? AND DATE(punch_time) ? , (name, today)) count cursor.fetchone()[0] if count 0: return 今日已打卡 # 直接返回不写库 else: self.db.insert_attendance(name, datetime.now()) # 写入新记录 return f{name} 打卡成功参数说明DATE(punch_time)是 SQLite 内置函数提取时间字段的日期部分避免punch_time为2023-10-05 08:02:15时因精确到秒导致重复判断失效。4.3 防代刷blink.py 的眨眼检测如何堵住“拿照片糊弄摄像头”的漏洞blink.py是本项目最具实战价值的模块之一。它不依赖深度学习而是用EAREye Aspect Ratio算法实时计算眼睛纵横比当连续 3 帧 EAR 0.2 时判定为闭眼拒绝识别。# blink.py 核心计算 def eye_aspect_ratio(eye): # eye: array of 6 points (x,y) for one eye A dist.euclidean(eye[1], eye[5]) # 垂直距离上点1-5 B dist.euclidean(eye[2], eye[4]) # 垂直距离上点2-4 C dist.euclidean(eye[0], eye[3]) # 水平距离点0-3 ear (A B) / (2.0 * C) return ear # 在 attendanceSystem.run() 中调用 leftEye shape[42:48] # 左眼6点 rightEye shape[36:42] # 右眼6点 leftEAR eye_aspect_ratio(leftEye) rightEAR eye_aspect_ratio(rightEye) ear (leftEAR rightEAR) / 2.0 if ear 0.2: # 闭眼阈值 COUNTER 1 if COUNTER 3: # 连续3帧 cv2.putText(frame, BLINK DETECTED!, (10, 30), font, 0.7, (0,0,255), 2) return False # 拒绝本次识别 else: COUNTER 0避坑EAR 阈值0.2是经验值需根据实际摄像头分辨率调整。若教室灯光强导致瞳孔收缩EAR 偏高可降至0.18若学生戴厚眼镜导致关键点偏移需在shape_predictor_68_face_landmarks.dat基础上微调blink.py中的点索引如左眼改为shape[43:49]。5. 避坑指南五个让新手当场崩溃的高频问题与根治方案5.1 现象运行main.py报错ModuleNotFoundError: No module named dlib但pip list显示已安装原因PyCharm 或 VSCode 使用了系统 Python 解释器而非你用 conda 创建的face_attendance环境解决VSCodeCtrlShiftP→Python: Select Interpreter→ 选择./miniconda3/envs/face_attendance/python.exePyCharmFile → Settings → Project → Python Interpreter→ 点击齿轮 →Add → Conda Environment → Existing environment→ 选择face_attendance的python.exe5.2 现象注册时反复提示“未检测到人脸”但 OpenCV 自带的face_detect.py能正常框出人脸原因项目使用dlib.get_frontal_face_detector()对侧脸、低头、遮挡更敏感而 OpenCV 的 Haar 分类器鲁棒性更高但精度低解决临时降级检测灵敏度在attendanceSystem.py中将detector(gray, 1)的第二个参数upsample_num改为0不放大图像提升速度但降低小脸检出率长期方案在注册环节切换为 OpenCV 检测cv2.CascadeClassifier(haarcascade_frontalface_default.xml)识别环节再切回 dlib代码只需 3 行替换5.3 现象中文姓名录入后数据库里显示为????但SELECT * FROM users在 DB Browser 中查看正常原因DB Browser for SQLite 默认以Latin-1编码读取而数据实际是 UTF-8解决DB Browser 中File → Connect to Database→ 选择attendance.db→ 勾选Use UTF-8 encoding when reading database根治在handle_db.py的query_all_users()返回前对name字段显式解码row[1].decode(utf-8) if isinstance(row[1], bytes) else row[1]5.4 现象blink.py检测总是误报“眨眼”学生明明睁着眼也被拒原因EAR 计算依赖左右眼各 6 个关键点但shape_predictor_68_face_landmarks.dat在侧脸时点位漂移导致dist.euclidean(eye[1], eye[5])计算失真解决增加姿态校验用dlib.pose_predictor计算头部欧拉角当 yaw 角绝对值 25° 时跳过眨眼检测添加 5 行代码或直接关闭眨眼检测注释掉blink.py的导入和调用毕竟不是所有场景都需要防代刷5.5 现象打包成 exe 后shape_predictor_68_face_landmarks.dat找不到报RuntimeError: Unable to open...原因PyInstaller 打包时未自动包含model/目录下的.dat文件解决打包命令增加--add-data model;modelWindows或--add-data model:modelmacOS/Linux并修改my_utils.py中的路径获取逻辑兼容sys._MEIPASSif getattr(sys, frozen, False): base_path sys._MEIPASS else: base_path os.path.dirname(os.path.abspath(__file__)) predictor_path os.path.join(base_path, model, shape_predictor_68_face_landmarks.dat)6. 进阶技巧用 Flask 将考勤系统 Web 化实现跨教室远程管理6.1 为什么 Flask 比 Tkinter 更适合作为考勤系统前端Tkinter 界面锁死在单台电脑而大学考勤常需教师在办公室电脑查看今日出勤率实验室管理员在手机浏览器输入http://192.168.1.100:5000/report查看缺勤名单多个教室摄像头共用一个后台数据库。Flask 用 50 行代码就能提供这些能力且不增加额外依赖项目已含flask。6.2 构建 Web 管理后台三步实现报表页面在项目根目录新建web_app.py内容如下from flask import Flask, render_template, jsonify import sqlite3 from datetime import datetime, timedelta app Flask(__name__) def get_daily_report(date_strNone): if date_str is None: date_str datetime.now().strftime(%Y-%m-%d) conn sqlite3.connect(db/attendance.db) conn.row_factory sqlite3.Row # 支持字典式取值 cursor conn.cursor() # 查询当日所有打卡记录 cursor.execute( SELECT u.name, a.punch_time FROM attendance a JOIN users u ON a.user_id u.id WHERE DATE(a.punch_time) ? ORDER BY a.punch_time , (date_str,)) records [dict(row) for row in cursor.fetchall()] # 查询总人数与出勤率 cursor.execute(SELECT COUNT(*) FROM users) total cursor.fetchone()[0] present len(records) rate round(present / total * 100, 1) if total 0 else 0 return { date: date_str, total_students: total, present: present, rate: rate, records: records } app.route(/) def index(): return render_template(report.html, dataget_daily_report()) app.route(/api/report/date_str) def api_report(date_str): return jsonify(get_daily_report(date_str)) if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse) # 生产环境务必关 debug配套创建templates/report.htmlBootstrap 5 简洁模板!DOCTYPE html html head title考勤报表/title link hrefhttps://cdn.jsdelivr.net/npm/bootstrap5.3.0/dist/css/bootstrap.min.css relstylesheet /head body classbg-light div classcontainer mt-4 h1 classtext-center mb-4 {{ data.date }} 考勤报表/h1 div classrow mb-4 div classcol-md-4 div classcard bg-primary text-white div classcard-body h5 classcard-title应到人数/h5 p classcard-text display-4{{ data.total_students }}/p /div /div /div div classcol-md-4 div classcard bg-success text-white div classcard-body h5 classcard-title实到人数/h5 p classcard-text display-4{{ data.present }}/p /div /div /div div classcol-md-4 div classcard bg-warning text-dark div classcard-body h5 classcard-title出勤率/h5 p classcard-text display-4{{ data.rate }}%/p /div /div /div /div h3打卡明细/h3 table classtable table-striped thead tr th姓名/th th打卡时间/th /tr /thead tbody {% for r in data.records %} tr td{{ r.name }}/td td{{ r.punch_time }}/td /tr {% endfor %} /tbody /table /div /body /html部署说明将web_app.py和templates/目录放入项目根目录运行python web_app.py在同一局域网内任意设备访问http://本机IP:5000即可查看报表。安全提示此为开发版生产环境需加 Basic Auth3 行代码、限制 IP 段、用 Nginx 反向代理。6.3 从“能跑”到“好用”我的三个强制习惯每次新增功能必加日志哪怕只是logger.info(fUser {name} punched at {now})某次排查“为何凌晨三点有打卡记录”时日志里INFO:root:User 张三 punched at 2023-10-05 03:12:44直接指向了学生用手机远程唤醒电脑的 Bug数据库操作必 wrap try-excepthandle_db.py中所有execute()都包裹try...except sqlite3.IntegrityError避免因网络波动或磁盘满导致考勤进程崩溃静态资源路径必用os.path.join从不写./static/logo.png一律os.path.join(BASE_DIR, static, logo.png)否则在 PyInstaller 打包或 Docker 容器中必然路径错误。从那以后我每次提交代码前都强制走一遍python main.py→ 注册自己 → 打卡 → 查看attendance.db→ 启动web_app.py→ 刷新网页四步闭环不漏。希望帮到你。本文还有配套的精品资源点击获取
返回列表