ARTICLE DETAIL

资讯详情

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

Python+OpenCV人脸识别考勤签到系统开发实战

Python+OpenCV人脸识别考勤签到系统开发实战 简介一套基于Python、OpenCV与PyQt5构建的人脸识别考勤签到系统源码面向课程设计与毕业设计场景覆盖人脸检测、特征提取、签到记录与查询管理的完整流程。资源共44个文件包含12个Python脚本、16个pyc缓存、10个OpenCV级联分类器XML和2个PyQt5界面UI文件压缩包整体仅681KB目录结构清晰便于按界面层、功能层和数据层分段研读其中py文件承载核心逻辑xml为预训练的人脸检测模型ui为可视化窗口布局定义。源码注释详尽从Haar级联人脸检测、LBPH特征提取到PyQt5信号与槽交互均有落地实现并配有数据库/文件存储、日期时间处理与异常捕获等模块能帮助开发者掌握考勤系统的工程化写法。目前已有266人学习下载非常适合正在准备课程设计或毕业设计、希望系统学习OpenCV人脸识别与PyQt5桌面应用开发的初学者作为实战参考。1. 基于 PythonOpenCV 的人脸考勤这源码解决的是什么场景把摄像头往门口一放员工靠近屏幕系统喊出名字并自动写一条打卡记录——这就是基于 pythonopencv 人脸识别考勤签到系统最朴素的使用场景。这类项目在 opencv 图像处理里属于非常典型的落地练习既要处理实时视频流又要跑人脸检测与识别还得用 pyqt5 把结果以桌面界面的方式展示给操作员。它解决的是不想买昂贵门禁机、又希望签到这个动作尽量无人值守的需求特别适合中小团队、实验室和毕业设计成本就是一部 USB 摄像头加一台普通电脑。适合有一定 Python 基础想改识别逻辑、接自有数据库、把界面换成自己公司风格的开发者。我下面直接按“环境跑通 → 核心代码 → 考勤逻辑 → 踩坑 → 如何验证”的顺序来讲新手能跟完熟手也能找到可调的参数。2. 把项目跑起来之前环境、依赖与目录结构2.1 安装 Python 与 OpenCV、PyQt5版本组合和常见安装失败这类项目对 Python 版本其实很敏感。我见过太多人按最新的 python 安装教程装完 3.12转头pip install opencv-python发现装上了却 import 不了或者 PyQt5 装完打开窗口直接闪退。最稳的组合是 Python 3.8 或 3.9搭配 opencv-contrib-python 4.5.5 和 PyQt5 5.15.7。3.10 也能用但 3.12 容易踩到轮子还没跟上来的坑不建议新手上来就挑战。# 建议先建一个虚拟环境避免污染系统 Python python -m venv venv # Windows 激活命令Linux/macOS 用 source venv/bin/activate venv\Scripts\activate # 核心依赖 pip install numpy pip install opencv-contrib-python4.5.5.64 pip install pyqt55.15.7 pip install pillow这里最容易翻车的点是 OpenCV 的包名。很多人只装了opencv-python结果代码里写cv2.face.LBPHFaceRecognizer_create()时直接报No module named cv2.face。因为opencv-python是精简版不带face模块人脸识别要用opencv-contrib-python。另外不要同时装这两个包它们会互相覆盖最后 import cv2 时版本都对不上。如果pip install慢到怀疑人生加一个国内镜像源就行pip install -i https://pypi.tuna.tsinghua.edu.cn/simple opencv-contrib-python。装完之后用一条命令验证环境python -c import cv2; print(cv2.getBuildInformation())。能打印出来就说明 cv2 没问题如果明明 pip show 里有 opencvpython 却说找不到那大概率是虚拟环境没激活pip 装到了另一个 Python 解释器里。这种“opencv 安装成功却找不到 cv2”的情况本质不是安装失败而是解释器路径错位。2.2 源码目录应该怎么摆样本、模型和三层结构拿到一个考勤签到系统源码不要急着直接双击 main.py。这类项目一般都有三块职责界面UI、识别核心core、数据存储db。混在一个文件里后患无穷因为摄像头线程要和界面线程通信识别逻辑又要和数据库耦合一旦写成一坨调一个打卡时间逻辑都可能牵动整个界面。常见的目录结构是这样的attendance/ ├─ main.py # 程序入口只负责启动 QApplication ├─ requirements.txt # 依赖清单 ├─ core/ │ ├─ detector.py # 人脸检测封装 Haar Cascade │ ├─ recognizer.py # 训练和预测封装 LBPH │ └─ database.py # SQLite 读写负责签到记录 ├─ ui/ │ ├─ main_window.py # PyQt5 主窗口按钮和表格 │ └─ camera_thread.py # 摄像头 QThread不停发画面和识别结果 ├─ data/ │ ├─ samples/ │ │ ├─ 10001/ # 每个工号一个文件夹 │ │ │ ├─ 0.jpg │ │ │ └─ 1.jpg │ └─ models/ │ └─ face_model.yml # 训练出来的人脸模型 └─ attendance.db每个目录下面的代码都应该带上详细注释。我一般会把函数说明写在 docstring 里尤其是detector.py里的detect_all_faces()和recognizer.py里的predict()因为参数含义不写在注释里换一个人维护时基本只能靠猜。数据库文件不要放在项目根目录建议放data/下备份时长整目录拷走就行。2.3 第一次启动从注册人脸到完成一次签到建议按“注册人脸 → 训练模型 → 启动签到”三步走不要跳步。先把人脸样本准备好再训练模型最后才开界面。很多人拿到代码后直接启动界面结果识别框里全是 0或者所有人都被识别成同一个人都是因为模型只有一个空架子。第一步是采集人脸样本。下面的代码会打开摄像头检测到人脸后裁切、缩放、保存为灰度图每个工号存 30 到 50 张# collect_faces.py import cv2 emp_id input(输入工号用于创建文件夹: ).strip() detector cv2.CascadeClassifier(haarcascade_frontalface_default.xml) cap cv2.VideoCapture(0) count 0 while count 30: ok, frame cap.read() if not ok: continue gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces detector.detectMultiScale(gray, 1.1, 5, minSize(100, 100)) for (x, y, w, h) in faces: face gray[y:y h, x:x w] face cv2.resize(face, (160, 160)) cv2.imwrite(fdata/samples/{emp_id}/{count}.jpg, face) count 1 print(f已采集 {count}/30)这里有两个设计点保存灰度图是因为 LBPH 识别器只吃单通道彩色图进去反而会多出无效信息缩放成 160×160 是为了让训练和预测都固定输入尺寸避免模型对分辨率过敏感。注意要预先创建data/samples/{emp_id}这个文件夹否则imwrite会因为路径不存在而静默失败这一步很容易被忽略。第二步是训练模型。把data/samples下所有照片读进来打上标签交给 LBPH 训练最后写成一个 yml 文件# train.py import cv2 import glob import os import numpy as np recognizer cv2.face.LBPHFaceRecognizer_create(radius1, neighbors8, grid_x8, grid_y8) faces, labels [], [] for path in glob.glob(data/samples/*/*.jpg): label int(path.split(os.sep)[-2]) # 文件夹名就是员工 ID face cv2.imread(path, cv2.IMREAD_GRAYSCALE) face cv2.resize(face, (160, 160)) faces.append(face) labels.append(label) labels np.array(labels) recognizer.train(faces, labels) recognizer.write(data/models/face_model.yml) print(f训练完成共 {len(faces)} 张样本)这样训练出来的模型就可以被主界面加载。跑通这两个脚本之后再启动 main.py选择 USB 摄像头就能在 PyQt5 窗口里看到实时画面和人名框了。第一次启动如果画面里有人名但考勤记录没有写入多半是还没有走到“写入数据库”的判定逻辑这一块我在第 4 章展开。3. 核心代码逐个拆人脸检测、特征训练与 PyQt5 显示3.1 用 Haar 级联检测人脸detectMultiScale 的参数与返回结果OpenCV 里的人脸检测最常见做法是 Haar Cascade它在 OpenCV 4.x 里依然是默认选择。不是因为它最强而是胜在不需要额外下载模型、CPU 也能跑得动。识别考勤这种场景Haar 的误检率是能接受的。# core/detector.py import cv2 class FaceDetector: def __init__(self, cascade_pathhaarcascade_frontalface_default.xml): self.cascade cv2.CascadeClassifier(cascade_path) def detect(self, gray, scale_factor1.1, min_neighbors5, min_size(80, 80)): faces self.cascade.detectMultiScale( gray, scaleFactorscale_factor, minNeighborsmin_neighbors, minSizemin_size, flagscv2.CASCADE_SCALE_IMAGE ) return faces # 返回 [(x, y, w, h), ...]detectMultiScale里有三个参数需要重点理解。scaleFactor控制图像缩放步长1.1 表示每轮把图像缩小 10%值越小越精确但速度越慢值太大容易把远处的小人脸漏掉。minNeighbors表示每个候选框需要被附近多少个检测框“投票”才能保留5 到 6 是比较折中的值设成 1 或 2 误报会暴增背景里的插线板都可能被当成人脸。minSize是检测框最小尺寸如果你的摄像头离人脸比较远可以设成 80×80 或更小否则识别模块拿到的人脸区域太小LBPH 几乎不可能给出稳定结果。另外Haar 对侧脸和大角度俯仰确实不行。如果你发现考勤时员工需要正对摄像头才能签到这不是代码 bug是 Haar 的固有局限。想减少这种“玄学”失败可以在检测之前对灰度图做一次直方图均衡化把逆光造成的暗部细节拉回来gray cv2.equalizeHist(gray)别小看这一行很多“十次识别八次失败”的翻车现场加完这一行之后成功率明显上升因为 LBPH 对光照变化没那么鲁棒先做直方图均衡化相当于帮它把对比度统一了。3.2 LBPH 训练与预测radius、neighbors、grid 与置信度阈值考勤系统里人脸识别部分最常用的是 OpenCV 自带的 LBPHLocal Binary Patterns Histograms。它不依赖 GPU训练速度以秒计几百张样本就能见到效果。对比动不动就要跑深度模型的方案LBPH 是“入门首选、可解释性强”的那一类。# core/recognizer.py import cv2 class FaceRecognizer: def __init__(self, model_pathNone, threshold80): self.recognizer cv2.face.LBPHFaceRecognizer_create( radius1, neighbors8, grid_x8, grid_y8 ) self.recognizer.setThreshold(threshold) if model_path: self.recognizer.read(model_path) def train(self, faces, labels): self.recognizer.train(faces, labels) def predict(self, face): label, confidence self.recognizer.predict(face) if confidence self.recognizer.getThreshold(): return -1, confidence return label, confidencepredict()返回两个值label 是整数标签confidence 是置信度距离。对 LBPH 来说距离越小表示越匹配。所以threshold的语义是“超过这个距离就认为不认识”默认值通常取 80 到 100。如果想让系统更严格防止陌生人刷脸成功就应该降低阈值到 60 左右如果房间里光线差、识别总失败可以提高阈值到 110但代价是有可能把两个人判成同一个人。识别器参数里radius1表示计算 LBP 特征时采样半径neighbors8表示使用 8 个采样点这也是 OpenCV 官方示例值grid_x8, grid_y8把图像切分成 8×8 的块计算直方图。切块越多空间位置信息保留得越好但特征维度也会变大。实测下来 8×8 是速度和精度的平衡点grid_x11, grid_y11在小数据集上并不会明显更好反而容易过拟合。训练之后模型里的 label 是文件夹名解析出来的整数。如果你把工号“001”当成字符串保存读回来时int(001)会变成 1这个细节在训练脚本里要注意否则工号对不上人。我一般会在模型旁边配一个employees.json把整数 label 和真实工号、姓名对应起来。3.3 PyQt5 界面怎么接摄像头BGR 转 RGB、QImage 与 QThreadPyQt5 界面设计里最容易出问题的是把摄像头读取放到主线程。QTimer一秒钟刷新 30 次每次cap.read()加检测再加预测整个 UI 会卡到没法看窗口拖都拖不动。正确做法是把摄像头采集放到 QThread 里识别结果通过信号发回主窗口。# ui/camera_thread.py import cv2 import time from PyQt5.QtCore import QThread, pyqtSignal from PyQt5.QtGui import QImage class CameraThread(QThread): frame_ready pyqtSignal(QImage) face_detected pyqtSignal(int, float) # label, confidence def __init__(self): super().__init__() self._run_flag True def run(self): cap cv2.VideoCapture(0) while self._run_flag: ok, frame cap.read() if ok: gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces detector.detect(gray) # 对每个人脸框画矩形并展示 for (x, y, w, h) in faces: cv2.rectangle(frame, (x, y), (xw, yh), (0, 255, 0), 2) rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape qimg QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) self.frame_ready.emit(qimg.copy()) time.sleep(0.03) # 30 帧左右 def stop(self): self._run_flag False self.wait()这段代码里必须解释三个细节。第一cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)不能省OpenCV 读进来是 BGRPyQt5 的 QImage 却按 RGB 解析不转换的话人脸会整体偏蓝偏暗。第二qimg.copy()一定要加因为QImage构造时引用的是rgb.data的内存一旦frame被下一次cap.read()覆盖那块内存就被改掉了界面显示的就是残影和花屏。第三time.sleep(0.03)是为了给 UI 线程留呼吸时间不让线程空转占满 CPU如果识别很慢把间隔调成 0.05 或 0.06画面上每秒帧数低一点但不会卡死。信号face_detected在实际项目中还要带上识别结果比如self.face_detected.emit(label, confidence)。主窗口收到后把 label 翻译成姓名再显示到状态栏同时把签到记录写进 SQLite。这个流程放到第 4 章继续讲。4. 考勤逻辑签到记录、时间窗与重复签到去重4.1 数据库表设计员工表、签到表和时间字段人脸识别只是把“是谁”这个问题回答完了考勤系统还要回答“什么时候来的、算不算迟到、今天打了几次卡”。这部分最好用 SQLite标准库自带不需要额外启数据库服务。CREATE TABLE employees ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, department TEXT ); CREATE TABLE attendance ( id INTEGER PRIMARY KEY AUTOINCREMENT, employee_id INTEGER NOT NULL, check_time TEXT NOT NULL, status TEXT DEFAULT 正常, UNIQUE(employee_id, check_time), FOREIGN KEY (employee_id) REFERENCES employees(id) );attendance表里我特意加了UNIQUE(employee_id, check_time)防止同一秒插入两条重复记录。实际代码里签到时间用datetime.now().strftime(%Y-%m-%d %H:%M:%S)存储字符串格式化之后后面判断迟到只需要比较串的前后顺序即可。如果你担心请假、加班等场景可以再加一个attendance_type字段日常考勤用不上就先不加避免把表结构搞复杂。“迟到”的判断不能写在 SQL 里SQL 只负责存和查业务规则放在 Python 层更清晰。常见做法是把考勤时间上限比如 9:30作为常量写在配置里界面上的“统计考勤”按钮再统一跑一遍计算。4.2 签到去重与迟到判断同一个工号一天只打一次卡考勤签到系统的第一原则是“一天只能有一个有效签到记录”。如果每识别到一次人脸就 insert 一条某人坐工位上被摄像头扫到十次就要删十条脏数据。我一般会在写入之前先查当天是否已有记录没有才写入。# core/database.py import sqlite3 from datetime import datetime def check_in(emp_id, name): today datetime.now().strftime(%Y-%m-%d) now datetime.now().strftime(%Y-%m-%d %H:%M:%S) conn sqlite3.connect(data/attendance.db) cur conn.cursor() cur.execute( SELECT id FROM attendance WHERE employee_id? AND check_time LIKE ?, (emp_id, today %) ) if cur.fetchone() is not None: conn.close() return already_checked_in cur.execute( INSERT INTO attendance (employee_id, check_time, status) VALUES (?, ?, ?), (emp_id, now, 正常) ) conn.commit() conn.close() return ok这段代码的巧妙处在于用LIKE做日期前缀匹配比DATE(check_time) ?可读性好一些也避免了格式化日期时遇到2025-05-01和2025-5-1不一致的麻烦。注意check_time的格式必须是四位年份、两位月份、两位日期、两位小时字符串比较才可靠。如果项目用的是 24 小时制之外的显示格式最好在写入时统一成 ISO 格式后面所有统计都不用再做字符串转换。迟到的判断如果公司是 9:00 上班我的建议是 9:00 之前正常9:00 到 9:30 显示“迟到”9:30 之后还没来那就别写签到直接按缺勤处理。这一块和“只写一次”是配合的第一次写入时就把状态算好之后不再变更。4.3 多帧确认减少误签同一人脸连续多帧命中才写入单帧识别结果直接写库会被偶然的侧脸、遮挡、表情变化坑到。人脸识别器偶尔会把 A 识别成 B置信度距离在阈值边缘抖动是常态。更稳的姿势是做一个滑动窗口在最近 20 帧里同一 label 出现足够多的次数才触发签到。# core/vote_recognizer.py from collections import deque class VoteRecognizer: def __init__(self, vote_threshold15, window_size20): self.window deque(maxlenwindow_size) self.vote_threshold vote_threshold def feed(self, label, confidence, conf_threshold80): if confidence conf_threshold: self.window.append(label) else: self.window.append(-1) if len(self.window) self.window.maxlen: return -1 hits self.window.count(label) if hits self.vote_threshold: self.window.clear() # 触发后清空防止连续重复写库 return label return -1这个滑动窗口解决了一个很隐蔽的问题如果员工在人脸识别器前站了 3 秒每秒 10 帧每一帧都返回同一个 label那 30 次识别结果就会触发 30 次check_in()。虽然数据库有去重条件但每次都查一次库也会拖慢界面响应。更可怕的是当员工离开后另一个人出现在镜头里如果滑动窗口没清空旧 label 的票数可能还够导致二次误签。所以触发调用check_in()之前必须先清空窗口。我建议把“写入考勤”这个动作单独放到vote_recognizer.feed()返回 label 之后不要放在识别线程里。界面主线程收到 signal再发起check_in()这样线程和数据库之间是单向传递排查问题也容易。如果你想让签到更严格可以把窗口扩大到 30 帧、阈值设为 25如果考勤点人流量大、排队打卡窗口太大反而会让后面的人迟迟签不上8 帧里命中 6 帧也是可用的配置。5. 避坑指南六个真实翻车现场与排查方法5.1 ModuleNotFoundError: No module named cv2或安装后仍报错现象是执行import cv2时报ModuleNotFoundError: No module named cv2但用 pip list 却能看到 opencv。原因无非两种当前 Python 环境不是安装 opencv 的环境或者装的是opencv-python包名对应模块确实是 cv2不会报这个错。解决时先执行python -c import sys; print(sys.executable)看解释器路径再执行pip -V看 pip 指向哪里。如果路径不一致激活正确的虚拟环境或者用python -m pip install opencv-contrib-python强制装到当前解释器。另外有些 IDE 自己虚拟了一个环境终端里能 importIDE 里不能这种情况直接在 IDE 的终端里重装一次即可。5.2 cv2.error: OpenCV(4.4.0) 窗口卡死或摄像头打不开现象是程序崩溃报一段cv2.error: OpenCV(4.4.0) C:\Users\...常见于摄像头被其他程序占用的场景也有可能是cap.read()返回False但代码没有处理。原因是 OpenCV 的 VideoCapture 在摄像头被占用时不会给友好提示而是把错误堆栈抛出来。解决方法是启动摄像头前先cap.isOpened()判断并且给摄像头释放加finallycap cv2.VideoCapture(0) if not cap.isOpened(): raise RuntimeError(摄像头打开失败请检查是否有其他程序占用)如果镜头画面是黑的检查电脑摄像头权限Windows 的“隐私设置”里经常会默认关闭摄像头访问。5.3 界面卡死成白屏摄像头循环放到了主线程现象是 PyQt5 窗口能打开但一启动识别就白屏拖不动任务管理器里 CPU 飙到 100%。原因是把cap.read()、人脸检测、预测全写在了 UI 线程的循环里。解决方法是把所有耗时操作搬进QThread主线程只负责接收信号画界面。经验是UI 线程里不能有任何超过 50ms 的操作否则用户感觉到的就是卡顿。改成线程后还要注意一个细节在线程run()里不要直接操作 QLabel 或 QTableWidget这些控件不是线程安全的必须通过pyqtSignal把结果传回主线程再更新。5.4 全员识别成同一个人训练样本太少或误把背景当人脸现象是无论谁站到摄像头前识别结果全部显示 A 的姓名或者几乎每次都返回 -1。原因大概率有两个样本量不够每个员工只存了 5 张照片LBPH 把所有人往同一类上拟合还有一种可能是采集样本时把非人脸区域也保存了训练集里大量“空脸”模型被污染。解决方法是每个工号至少 30 张且保证图像里有完整的眉毛、眼睛、鼻子区域训练前在采集脚本里打印检测框的宽高如果框明显小于真实人脸说明minSize设小了把背景框进来是常见误操作。训练完成后立刻对着摄像头做一次自测把 confidence 打出来低于 80 才算基本合格。5.5 中文路径读不到样本cv2.imread 的编码问题现象是程序在 Linux 上跑得好好的换到 Windows 后cv2.imread(data/样本/10001/1.jpg)直接返回 None。原因是 OpenCV 的imread在 Windows 下对中文路径支持很差底层用的是系统编码。解决办法是用np.fromfile读取字节再交给cv2.imdecode写文件时用cv2.imencodedef imread_unicode(path): data np.fromfile(path, dtypenp.uint8) return cv2.imdecode(data, cv2.IMREAD_COLOR) def imwrite_unicode(path, img): ext os.path.splitext(path)[1] ok, buffer cv2.imencode(ext, img) if ok: buffer.tofile(path)考勤系统里员工姓名经常就是中文如果数据库和文件夹都用工号问题不大但一旦涉及“按姓名导出考勤表”文件路径里的中文名就会触发这个坑。我建议整个项目绝对路径下不要有任何中文目录所有员工文件夹只用数字工号姓名只存在数据库和界面显示层。5.6 模型文件读取后 predict 报错OpenCV 版本或标签类型不匹配现象是训练完成后写出的face_model.yml换台电脑读取时报TypeError或cv2.error。原因可能是训练时标签数组用了 Python 列表而不是 numpy 数组LBPH 在 OpenCV 4.x 要求标签必须是np.int32。训练脚本里必须写labels np.array(labels, dtypenp.int32)。另外recognizer.read(model_path)之后最好调用一次recognizer.setThreshold(80)因为 yml 文件里不一定保存了 threshold不设置的话会用默认阈值预测结果可能完全不是你想要的效果。模型跟随源码一起分发时要注意 OpenCV 的大版本必须一致4.5 训练出来的 yml 在 4.4 上有时可以读有时会报 “The NodeType” 之类的错误最好在 requirements.txt 里锁定版本号。6. 从能签到到考勤可用准确率验证与三个实用改进6.1 先给识别器出一份成绩单在测试集上算准确率不要凭“感觉挺准”就上线。我会先从每个员工手里额外采集 10 张不参与训练的照片单独放到data/test/工号/目录然后跑一段验证脚本import glob, cv2, numpy as np recognizer cv2.face.LBPHFaceRecognizer_create() recognizer.read(data/models/face_model.yml) recognizer.setThreshold(80) correct 0 total 0 for path in glob.glob(data/test/*/*.jpg): true_label int(path.split(/)[-2]) img cv2.imread(path, cv2.IMREAD_GRAYSCALE) img cv2.resize(img, (160, 160)) pred_label, conf recognizer.predict(img) total 1 if pred_label true_label and conf 80: correct 1 print(f准确率: {correct / total:.2f})准确率低于 0.90 时别急着改界面先把样本数和光照条件补起来。我还会打印所有预测失败的 confidence 分布如果失败样本的 confidence 普遍在 100 到 120 之间就说明阈值 80 太严可以放松到 95如果失败的样本 confidence 只有 60 却依然预测错说明模型本身就分不清两个员工加样本比调阈值更管用。6.2 三个低成本改进换识别器、导出 Excel、增量训练想让识别效果向人脸识别门禁机靠近最直接的办法是把 LBPH 换成face_recognition库或 ArcFace 的 Python SDK。LBPH 在正脸、均匀光照下够用但侧脸、遮挡、暗光环境确实不稳定。face_recognition底层是深度学习模型使用门槛不高只是人脸向量维度更高、比对耗时稍长。对考勤这种低频场景完全可以承担。如果追求更高精度再考虑 ArcFace不过那就需要 GPU 或者至少 CPU 推理库了。导出 Excel 是让行政人员愿意用这个系统的关键。考勤数据躺在 SQLite 里很多人不会写 SQL。加一个按钮用 pandas 读取attendance表按月透视成表格import pandas as pd df pd.read_sql(SELECT * FROM attendance, sqlite3.connect(data/attendance.db)) pivot df.pivot_table(indexemployee_id, columnscheck_time, valuesstatus, aggfuncfirst) pivot.to_excel(考勤统计.xlsx)增量训练解决的是“老员工模型里没有新员工”的问题。新员工入职只采集他的样本然后加载已有模型继续train()最后write()回同一个文件而不是把所有老照片重新训练。注意 OpenCV 的 LBPH 在同一实例上连续train()会累积新数据但 OpenCV 4.5 有个坑新训练的数据和旧模型合并时会降低旧员工的特征权重所以新员工样本最好一次采集 50 张以上。6.3 我最后的习惯先看日志再调阈值最后改界面最后一次给同事做考勤系统时我把阈值默认设成了 150结果所有人互相串脸场面非常尴尬。后来我养成了一个习惯所有识别结果先写入日志包括 label、confidence、时间、摄像头帧号跑一天再统计日志里的 distribution。这样调参数时有数据依据而不是靠肉眼“好像准了”。阈值、滑动窗口大小、检测的minNeighbors每改一个参数就重新跑一遍测试集。界面永远放最后改因为数据不稳定的系统界面再好看也没用。做这类基于 pythonopencv 人脸识别的考勤签到系统最大的成就感不是它识别得多准而是它稳定地跑了三个月没人抱怨打卡记录对不上。希望你也能先跑通最小闭环再盯准参数和日志把它做成真正敢交给行政用的工具。希望帮到你。本文还有配套的精品资源点击获取
返回列表