
简介一套基于计算机视觉与深度学习的人脸识别签到系统项目采用Python语言开发适合有编程基础并正在完成课程设计或需要快速搭建课堂、会议自动签到场景的开发者也可作为入门级考勤方案的设计蓝本。压缩包整体仅1.93MB共23个文件含有源代码、界面文件、配置文件、图标素材、说明文档以及样例数据等多种类型目录组织清晰能够直接导入开发工具运行调试。当前已有1483人学习下载。项目实现了从摄像头实时取流、图像灰度化与去噪等预处理到人脸检测、特征提取和身份匹配的完整流程同时具备签到时间记录、考勤时长统计和数据导出功能界面部分采用可视化控件并考虑了数据安全与隐私保护的基本策略。读者可以从中理解人脸识别考勤系统的整体架构、关键算法衔接和工程实现要点适合毕业设计、实训教学或轻量级应用的二次开发起点。1. 人脸识别签到从“扫码打卡”到“刷脸打卡”的最小落地路径很多人以为人脸识别签到必须自己训练深度学习模型或者要买一台几千块的人脸识别门禁机。实际上用 Python 加一个开源人脸识别库在普通办公电脑上配一个 USB 摄像头就能把签到流程跑起来。这篇文章要讲的就是这条最小落地路径怎么录入人脸、怎么识别签到、怎么避免重复打卡以及我踩过的那些坑。适合正在做企业内部考勤工具、实验室点名系统或者想给工作室做个轻量签到程序的开发者。读完后你能得到一套可复现的代码和一组靠谱的参数而不是一个黑匣子。2. 技术选型与原理为什么我用 face_recognition 打通签到主链路2.1 三种常见实现路线对比做 Python 人脸识别签到我见过三种主流路线。第一种是直接用 OpenCV 的 Haar Cascade 或 LBPH 人脸识别器。这条路入门门槛低代码量也小但 LBPH 对姿态和光线极其敏感稍微侧脸就认不出多人场景基本不可用。第二种是自己用 PyTorch 或 TensorFlow 训练一个分类网络比如用 MobileNet 微调。这条路效果上限高但需要准备大量标注数据、设计训练流程、处理模型部署做一个签到系统至少要多花两三周时间。第三种是使用 face_recognition 库它封装了 dlib 的人脸检测和特征提取能力不需要训练只需给每个人提取一次 128 维特征向量之后拿这个向量做比对就行。我一般会选第三种。原因很直接签到场景不需要识别出“这个人是谁”这个开放集合问题只需要在已经录好的人脸库里做 1:N 匹配。face_recognition 正是为此设计的它把最复杂的检测和特征提取变成了几行 API 调用。而且它对 CPU 的占用可控在 Intel i5 级别的机器上一次检测加特征提取大约 0.5 到 1 秒对于签到这种低频操作完全够用。网上那些“Python人脸识别签到源码大全”里绝大多数也是基于这个库改的但很多都只写了识别部分没有考虑防重复、日志落库和异常恢复这才是实际运维中的大头。2.2 face_recognition 的核心原理人脸图像如何变成 128 维向量理解这个库的关键在于它内部做两件事先做人脸检测再做人脸特征编码。检测阶段用的是 HOG方向梯度直方图或 CNN 模型它会从图像中找出包含人脸的矩形框。找到框之后库会把框内的人脸区域对齐并缩放然后送进一个预训练的深度残差网络输出一个 128 维的浮点向量。这个向量的含义不是年龄、性别这类属性而是把“这个人长什么样”压缩成了空间中的一个点。比对时系统计算两张人脸向量的欧氏距离。距离越小越可能是同一个人。face_recognition 的compare_faces函数里有一个tolerance参数默认是 0.6意思就是距离小于等于 0.6 判定为同一人。这个 0.6 是原作者在 LFW 数据集上调试出的经验值但它在真实办公场景下不一定合适。比如同一个人的两张照片如果光线差异很大距离可能超过 0.6而两个长得像的同事距离可能只有 0.5 左右。所以这个参数必须根据现场人脸库实际调不能直接默认跑。这里还有一个关键认知特征向量不是“固定”的。同一个人在不同光照、角度、表情下提取出的向量会有波动。签到系统要做的不是追求零误差而是把误识率和拒识率平衡到“一天里不需要人工干预”的程度。我后面会专门讲怎么调这个阈值。2.3 环境准备Python 版本、依赖安装的注意事项face_recognition 的安装比普通库麻烦一点因为它依赖 dlib而 dlib 需要 C 编译环境。在 Windows 上我建议直接用 Python 3.8 或 3.9 的 64 位版本然后先安装 Visual Studio Build Tools 里的 C 桌面开发组件再执行pip install dlib face_recognition opencv-python。如果不想折腾编译可以找dlib的预编译 wheel或者直接使用 Anacondaconda 会帮你处理编译好的二进制。Linux 上则要先装cmake和build-essential否则 pip 会在编译 dlib 时卡很久。一个常见翻车点是装了 OpenCV 后摄像头编号不对。笔记本自带摄像头通常是0外接 USB 摄像头可能是1但某些设备上顺序会颠倒。我习惯写一个小脚本先枚举所有可用摄像头而不是写死VideoCapture(0)。下面这段代码可以列出当前机器上能打开的摄像头索引import cv2 for index in range(5): cap cv2.VideoCapture(index) if cap.isOpened(): ret, frame cap.read() if ret: print(f摄像头 {index} 可用画面尺寸: {frame.shape}) cap.release()这段代码的逻辑是依次尝试 0 到 4 号设备能读到帧就说明该索引可用。实际部署时我会把可用索引写进配置文件而不是在代码里写死。另一个容易忽略的是摄像头权限Linux 下如果用opencv-python打开设备需要确认当前用户属于video组否则会报ioctl(VIDIOC_STREAMON)错误。Windows 下则要注意杀毒软件可能会拦截摄像头访问我在两台办公电脑上遇到过程序第一次运行能打开摄像头第二次就被安全软件静默拦截的情况表现是cap.read()一直返回 False没有任何报错。3. 人脸库搭建采集、特征提取与存储的完整脚本3.1 用摄像头采集人脸照片兼顾清晰度和角度的采集脚本签到系统的第一步是录入人脸。很多人直接让员工对着摄像头拍一张然后提取特征保存。这种做法在两天内就会暴露问题上班时光线和录入时不一样识别率立刻下降。正确的做法是每人采集 3 到 5 张不同角度、不同光线下的照片比如正面、轻微左侧、轻微右侧、抬头一点、低头一点。这能让后面的特征提取得到一个更稳定的“平均脸”。我写过一个采集脚本核心逻辑是实时显示摄像头预览按下空格键保存当前帧按下q退出。保存时需要自动裁出人脸区域这样能避免把背景也存进去减少后续特征提取时的干扰。下面是一个可运行的版本import cv2 # 采集脚本按空格键保存人脸按 q 退出 cap cv2.VideoCapture(0) save_count 0 person_name input(请输入姓名用作文件名前缀: ) while True: ret, frame cap.read() if not ret: print(无法读取摄像头画面) break # 显示实时画面便于员工调整位置 cv2.imshow(capture, frame) key cv2.waitKey(1) 0xFF if key ord( ): # 保存整帧后续再用人脸检测裁剪 filename fraw_{person_name}_{save_count}.jpg cv2.imwrite(filename, frame) print(f已保存 {filename}) save_count 1 elif key ord(q): break cap.release() cv2.destroyAllWindows()这段代码保存的是原始帧。我的习惯是之后统一用 face_recognition 检测人脸并裁剪入库而不是在采集时就裁剪因为采集时人脸框可能还没稳定保存原帧更保险。参数说明waitKey(1)的 1 毫秒是刷新间隔调大可以减少画面闪烁但会降低响应速度。保存的 JPG 质量默认是 95%对签到来说足够。实际运行时我会让员工每次录入前先看一眼画面确认没有被逆光或遮挡再按空格。3.2 提取特征并保存pickle 序列化与标签管理采集完照片后下一步是把每张照片变成 128 维向量并连同姓名标签一起保存。这里有两个细节。第一要检测照片中是否真的包含人脸如果检测不到或者检测到多张脸这张照片就应该被丢弃或人工复核。第二保存格式我建议用 pickle因为它可以很方便地把{name: 张三, encoding: [float, ...]}这样的字典序列化加载也快。当然如果你需要跨语言调用可以改用 NumPy 的.npz格式或者 JSON但后者的浮点数存储会浪费不少空间。下面是一段批量提取特征的脚本它遍历一个文件夹下所有 JPG 图每张图只取第一大的人脸即面积最大的那个框做编码然后保存到face_library.pklimport face_recognition import pickle import os import glob # 遍历 raw_images 目录下的所有 jpg 图片 known_encodings [] known_names [] for image_path in glob.glob(raw_images/*.jpg): # 从文件名中提取姓名例如 raw_张三_0.jpg - 张三 basename os.path.basename(image_path) name basename.split(_)[1] # 加载图片并检测人脸位置使用 HOG 模型速度快 image face_recognition.load_image_file(image_path) face_locations face_recognition.face_locations(image, modelhog) if len(face_locations) ! 1: print(f{image_path} 检测到 {len(face_locations)} 张人脸跳过) continue # 提取第一个人的 128 维特征向量 encoding face_recognition.face_encodings(image, face_locations)[0] known_encodings.append(encoding) known_names.append(name) print(f已处理 {image_path}) # 保存到 pickle 文件 library {encodings: known_encodings, names: known_names} with open(face_library.pkl, wb) as f: pickle.dump(library, f) print(f人脸库保存完成共 {len(known_names)} 条特征)这段代码里load_image_file是 face_recognition 提供的便捷方法它用imageio读取图片并转为 RGB 格式。这里有个容易踩坑的点face_recognition的 API 期望 RGB 输入而 OpenCV 读出来的是 BGR如果你先用cv2.imread读图再传给face_encodings颜色通道是反的虽然人脸检测通常不受影响但特征向量的质量会明显变差。所以要么用load_image_file要么在传给库之前用cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)转换。3.3 参数说明face_locations 的模型选择与 upsample 次数face_locations函数有两个关键参数一个是model另一个是upsample_times。model支持hog和cnnhog在 CPU 上运行快但小脸或者侧脸容易漏检cnn更准但如果没装 CUDA 版 dlib速度会慢到每帧 3 到 5 秒不适合实时签到。我的实际经验是签到场景下摄像头离人一般 0.5 到 2 米脸在画面里不算小用hog足够。如果你把摄像头装在门口人脸会偏小这时候可以把upsample_times从默认的 1 改成 2它表示对图像放大几次后再检测小脸检测率会提升但耗时也会变长。另一个容易被忽略的是face_encodings里的num_jitters参数默认是 1。这个值可以理解为对同一个人脸采样多次后取平均用来平滑微小变化。录入人脸库时我建议把num_jitters设为 5因为离线处理不赶时间能显著提升特征向量的稳定性但在实时签到的识别循环里num_jitters保持 1 就好否则每帧等待时间会翻好几倍。人脸库文件的管理我也要提醒一句。pickle 文件里存的是 Python 对象如果人脸库里的姓名有中文在 Windows 下打开文件时要注意编码。写文件时最好指定encodingutf-8我见过有人直接open(face_library.pkl, wb)然后加载时在 Linux 上正常在 Windows 上偶发UnicodeDecodeError。虽然 pickle 本身存的是字节但字符串的内部表示在不同 Python 版本下有差异。稳妥的做法是在保存前把字符串统一转成 UTF-8或者在加载时也保持相同的环境。另外pickle 不适合跨 Python 大版本移植升级 Python 后建议重新生成一次人脸库。4. 签到主程序实现识别、防重与考勤落库4.1 签到流程的状态机待识别 → 已识别 → 防重复签到程序不是一个简单的“循环识别”就能跑起来的。一个可用的签到系统有两个隐藏要求一是同一个人在一段时间内不能重复签到二是人离开再回来后要能再次签到。这就需要一个状态机。我给每个员工维护一个“最近签到时间”字典当识别到某人后先判断当前时间减去该员工上次签到时间是否超过设定的间隔比如 10 分钟。如果没超过就忽略这次识别如果超过了才写入考勤记录并更新该员工的时间戳。这个设计能挡住一个常见问题员工站在摄像头前聊天系统在几秒内连续识别出同一张脸结果产生十几条签到记录。更合理的做法是识别成功后程序进入一个“冷却”状态比如 3 秒内不对同一人脸做重复处理同时把画面上的姓名和签到时间叠加显示让员工看到自己打卡成功。下面是一个简化版的主循环框架import face_recognition import cv2 import pickle import time import datetime # 加载人脸库 with open(face_library.pkl, rb) as f: library pickle.load(f) known_names library[names] known_encodings library[encodings] # 记录每个人上次签到的时间戳 last_sign_in {} # 防重复识别的冷却期秒避免连续帧重复打卡 cooldown_time 3 cap cv2.VideoCapture(0) print(签到系统启动按 q 退出) while True: ret, frame cap.read() if not ret: print(摄像头读取失败尝试重连...) time.sleep(2) cap cv2.VideoCapture(0) continue # 转为 RGB因为 face_recognition 需要 RGB 输入 rgb_frame cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # 检测当前画面中的人脸位置 face_locations face_recognition.face_locations(rgb_frame, modelhog) # 提取当前所有人脸的特征向量 face_encodings face_recognition.face_encodings(rgb_frame, face_locations) for face_encoding in face_encodings: # 与人脸库比对tolerance 设为 0.5比默认更严格 matches face_recognition.compare_faces(known_encodings, face_encoding, tolerance0.5) name 未知人员 if True in matches: # 取第一个匹配到的名字也可以根据距离选最近的 matched_index matches.index(True) name known_names[matched_index] # 检查是否在冷却期内 now time.time() last last_sign_in.get(name, 0) if name ! 未知人员 and now - last cooldown_time: last_sign_in[name] now sign_time datetime.datetime.now().strftime(%Y-%m-%d %H:%M:%S) print(f{name} 签到成功: {sign_time}) # 这里调用写入考勤记录的函数 # 在画面上标注人脸框和姓名 for (top, right, bottom, left), n in zip(face_locations, [name]): cv2.rectangle(frame, (left, top), (right, bottom), (0, 255, 0), 2) cv2.putText(frame, n, (left, top - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) cv2.imshow(sign-in, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码有几个细节需要说明。第一compare_faces返回的是布尔列表matches.index(True)只取第一个匹配项。如果人脸库里有重名或者有特征距离非常接近的两个不同人这个逻辑可能会选错。更稳的做法是计算所有已知人脸与当前向量的距离然后取最小距离的人名并判断最小距离是否小于tolerance。这个我在第 5 章避坑里细说。第二cooldown_time是防重复的关键参数。如果设成 0员工站在摄像头前就会被反复打卡如果设得太大比如 10 分钟那么员工临时离开工位再回来就会被禁止签到。我一般设 3 到 5 秒既能防止连续帧刷屏又不影响正常离座返回。第三摄像头读取失败后的重连逻辑不能省略。USB 摄像头偶尔会因为供电不足或驱动问题断开cap.read()会连续返回 False。如果不做重连程序会一直空转。我用的策略是连续 10 次读取失败就释放并重新VideoCapture并加入 2 秒等待避免高频重试把接口占死。4.2 识别核心代码face_encodings 与 compare_faces 的阈值逻辑上面那版代码虽然能跑但compare_faces的布尔匹配方式不够精细。我实际使用的是另一种写法手动计算距离并且只接受最小距离小于阈值的候选。这样既避免了“第一个匹配”带来的误判又能让我随时打印出距离值做观察。下面是一段替换逻辑import numpy as np # 计算当前人脸与库中所有人脸的距离 distances face_recognition.face_distance(known_encodings, face_encoding) # 取最小距离及其对应的索引 min_distance np.min(distances) min_index np.argmin(distances) # 阈值这里用 0.5小于阈值才认为是库中的人 if min_distance 0.5: name known_names[min_index] confidence 1 - min_distance # 只是一个相对的置信度参考 else: name 未知人员这段逻辑才是生产环境中更常见的写法。face_distance返回的是一个 NumPy 数组长度等于人脸库人数所以直接用np.min和np.argmin找到最相似的人。阈值 0.5 是我在大多数普通室内光线下调试出的一个折中值。如果你的人脸库很大比如超过 100 人不同员工之间的特征距离可能会更接近这时候 0.5 可能不够安全需要下降到 0.4 或 0.45代价是一个员工稍微背光就可能识别失败。这个阈值没有银弹必须实际测试。在签到日志里我会把min_distance也记下来。这样万一出现误签翻日志就能看到当时的距离值方便判断是阈值问题还是人脸库问题。这是黑匣子调试的第一步不要只记录“谁签到了”要记录“当时匹配到的距离是多少”。4.3 考勤记录落库CSV 与 SQLite 两种方案的选择签到结果往哪写最偷懒的是写进 CSV 文件一行一条记录用csv.writer追加写。这种方法的好处是直观Excel 能直接打开坏处是并发和异常恢复差。如果程序在写一半时崩溃CSV 可能出现半行数据如果多人同时签到Python 的多线程里如果没有加锁两条记录可能互相穿插。对于少于 20 人的小团队CSV 完全够用只要每次打开文件用with语句写入后立即关闭就能规避大部分问题。如果团队人数超过 50或者你想按日期、员工号查询出勤我建议用 SQLite。它是单文件数据库不需要安装额外服务Python 自带sqlite3。下面是一个标准的签到落库函数import sqlite3 import datetime def init_db(db_pathattendance.db): # 创建表结构date 字段用于按天查询 conn sqlite3.connect(db_path) conn.execute( CREATE TABLE IF NOT EXISTS sign_in ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, sign_time TEXT NOT NULL, distance REAL ) ) conn.commit() conn.close() def save_sign_in(name, distance, db_pathattendance.db): # 使用 with 语句自动提交和关闭连接 sign_time datetime.datetime.now().strftime(%Y-%m-%d %H:%M:%S) conn sqlite3.connect(db_path, timeout10) with conn: conn.execute( INSERT INTO sign_in (name, sign_time, distance) VALUES (?, ?, ?), (name, sign_time, distance) ) conn.close()这里的关键点有两个。第一sqlite3.connect的timeout参数要设置默认是 5 秒如果签到瞬间有多个线程写入数据库文件会被锁定timeout太短会直接抛异常。第二with conn会自动提交事务不需要手动commit()但要注意如果conn.execute失败with只保证回滚不保证连接释放所以还是要手动close()。我见过有人只写conn.execute不提交结果数据一直没落盘重启程序后全部丢失这属于签到系统最基础的翻车原因。4.4 无人值守运行定时重启与日志会议室门口或者公司前台的签到电脑不可能每天有人去手动启动脚本。所以主程序需要能无人值守跑。我一般在 Linux 下用systemd服务托管在 Windows 下用计划任务开机启动。比起简单的python sign_in.py我还会加两层保护第一层是程序内部捕获所有异常并写日志第二层是让脚本每隔 24 小时自动重启一次。自动重启的理由很实际OpenCV 的摄像头对象在长时间运行后会越来越不稳定尤其是 USB 摄像头可能连续运行 48 小时后read()返回的帧率会掉到个位数或者画面卡在最后一帧。常见的做法是让主循环自己计数运行超过max_run_seconds后主动退出然后由一个外层脚本重新拉起。如果你用的是 systemdRestartalways可以保证进程退出后 2 秒内重新启动。Windows 计划任务里则要勾选“如果任务失败重新启动”。日志系统也需要简单而有效。不要把print当日志程序崩溃时缓冲区里的内容会丢。我习惯用 Python 的logging模块同时输出到控制台和文件。日志文件按日期滚动这样排查问题时能直接看当天的签到记录和错误堆栈。上面说的签到信息我除了写进 SQLite也会用logging.info打一条方便快速 grep。5. 避坑排查识别不准、重复签到与摄像头掉线的实战清单5.1 现象陌生人被识别成熟人这是最让人头疼的问题。新来一个同事没录入人脸结果系统把他识别成了张三还打了卡。我遇到过一次原因是当时tolerance用的是默认值 0.6而两个人脸特征距离刚好是 0.58小于 0.6就被判成同一人。解决方法是把阈值降到 0.45 到 0.5同时在后端加一道人工审核机制第一次识别的新标签先标为“待确认”管理员在后台看到照片后确认无误再正式计入考勤。这里要记住人脸识别不是精确匹配它只是一个概率判断。阈值越低陌生人误识率越低但本人因为光线变化被拒识的概率也越高。我最后把阈值定在 0.48并在签到屏幕上显示识别距离让员工自己看到分数接近阈值时就会主动调整一下角度。5.2 现象同一个人有时识别失败有时秒过同一个同事早上能签到中午休息后回来就识别失败。这类问题几乎都和光线、角度有关。人脸特征向量对光照很敏感同一个人的脸在左侧光和右侧光下提取出的向量可能相差很大。我的解决方案是录入人脸库时不要只拍一张而是让员工在正常工位光线下分别向左、向右、向前各拍一张提取出来的三个向量都加入人脸库这样相当于用多个视觉点覆盖同一个人的变化范围。另一个细节是摄像头的位置不能太矮或者太高仰拍和俯拍都会改变脸部几何特征我一般把摄像头固定在离地 1.5 米左右略高于人脸这样匹配更稳定。5.3 现象程序运行几天后内存暴涨、画面卡顿这是个很隐蔽的问题。cap.read()每次都会分配一个新帧对象如果循环里没有及时释放引用Python 的垃圾回收可能跟不上。尤其是当你用cv2.imshow显示画面却没有调用cv2.waitKey的合适间隔时GUI 的事件队列会被慢慢塞满。我的经验是每处理完一帧手动del frame不是必须的但一定要确保没有把每一帧都存进列表或缓存里。另外OpenCV 在某些版本下会有内存泄漏升级到 4.5 以上会好很多。如果内存还是持续上涨终极办法就是每天凌晨自动重启进程这招简单粗暴但非常有效。5.4 现象好几个人同时路过签到了不该签的人摄像头视野内只要出现一张脸程序就会识别。如果两个人并肩走过来系统可能同时识别出两个人甚至因为前面的人遮挡了后面的人把后面的人漏掉。这里的问题不在于识别算法而在于业务规则。我做的处理是只在画面中出现一个人脸时才执行签到如果超过一个人脸就只显示名字但不写入记录。这样做的理由是单人靠近摄像头是主动签到动作多人同时出现时无法判断谁是来签到的。另一个更严格的方案是检测人脸框的宽度如果人脸太小说明离摄像头太远不可能是来签到的直接忽略。5.5 现象SQLite 报 database is locked当我从识别线程和 GUI 线程同时往 SQLite 里写数据时会出现锁冲突。解决方法是使用 WAL 模式它允许读和写并发。设置方法很简单每次连接后执行一行 SQLconn.execute(PRAGMA journal_modeWAL)WAL 模式能明显减少锁等待但不能完全消除写锁冲突。我还会在写操作外包一层重试装饰器遇到锁错误就等 0.2 秒重试最多重试 3 次。对于签到这种低频写入场景这个方案已经非常稳了。6. 让签到更可靠再谈阈值调优和日常维护的一个习惯6.1 阈值调优的验证方法用已有人脸做误识率测试调阈值不能拍脑袋。我通常做一个小批量测试从人脸库中随机选 20 个人每人拿 5 张未参与入库的照片然后用不同的tolerance值跑一遍识别统计两个指标——误识率别人的照片被认成库里的人和拒识率本人的照片没认出来。画一条曲线取两个率都比较低的点。比如我的场景里0.48 时误识率是 0.2%拒识率是 1.5%这就够用了。如果你没有第二组照片最简单的方法是把现有照片切一半用来入库另一半用来测试不要用同一批数据既入库又测阈值那样结果虚高。6.2 日常维护定期更新人脸库清理失效特征人脸库不是录一次用一年的。员工会减肥、留胡子、换发型这些都会让特征向量偏移。我建议每季度重新采集一次人脸或者至少在员工外貌变化较大时重新录入。另外离职员工的特征要及时从库里删除否则一个已离职的人还会收到“签到成功”的提示虽然不影响考勤统计但对现场体验很不好。删除操作最好通过一个管理脚本完成而不是直接编辑 pickle 文件。6.3 我的一个习惯开机后先跑一遍自检我最终养成的习惯是签到程序每次启动后先自检摄像头和人脸库。自检逻辑很简单打开摄像头读一帧确认画面不是全黑加载人脸库确认特征数量大于 0然后对着摄像头拍一张当前画面用库里的第一个人脸做一次“干跑”识别只要能产生一个距离值就算正常。自检不通过就直接在屏幕上给出错误提示而不是等员工过来打卡时才发现程序是坏的。这个自检动作花不到 3 秒却能把最常出现的“摄像头没插好”和“pkl 文件被误删”两个问题挡在上班之前。这套 Python 人脸识别签到方案我已经在三个不同场景里跑过一个 30 人小公司的前门考勤一个实验室的机房上机签到还有一个工作室的日常出勤。每次都会遇到新问题但核心流程始终未变人脸库要建得稳阈值要调得准程序要能自动恢复。希望今天写下来的这些参数和踩坑记录能帮你少走几段弯路。如果你正在做类似的项目不妨先按本文的步骤搭一个最小原型跑通后再根据实际环境调阈值这样投入的成本最低返工的余地也最大。希望帮到你。本文还有配套的精品资源点击获取