Python人脸识别考勤系统实战:从需求分析到阈值调优
发布时间:2026/10/21 5:18:19 锦皓数字建站

1. 为什么人脸识别考勤是个看着简单、做起来全是细节的题目每年做课程设计、毕业设计或者想给自己实验室搞一套考勤工具的开发者基本都会想到人脸识别签到这个方向。原因很直接需求清晰、技术路线成熟、演示效果直观答辩的时候不用费劲解释这个东西到底有什么价值。但真上手之后你会发现这个题目远没有想象中那么简单。很多人以为核心就是调一个现成的识别库摄像头一开识别到人数据库插一条记录完事。实际做下来却会碰到一堆意想不到的问题光线一暗就识别不了、角度偏一点就匹配失败、两个人长得像就容易误判、识别本身没问题但签退逻辑又乱了、界面和数据表设计不合理导致后期维护很痛苦。当年我做完一版人脸识别考勤签到管理系统之后最大的感受是这个项目的难度不在于人脸识别本身而在于把识别、业务逻辑、数据管理、界面交互这几块完整地串起来。单独拆开每一块Python生态里都有现成方案但组合成一个能真正跑起来的系统需要处理大量边界情况。这篇文章就把整个项目从需求分析、技术选型、代码实现到实测优化完整拆一遍不仅讲每一个模块怎么做还讲清楚为什么这样做以及哪些地方容易翻车。不管你是准备交课程设计的学生还是想给工作室做一套考勤小工具的自学者都可以直接参照这套思路落地。项目本身包含源码、文档、运行视频和讲解视频这篇文章相当于把视频里讲到的设计决策和踩坑经验用文字再沉淀一遍方便你按图索骥。2. 需求分析与技术选型先把要做什么和用什么做定死2.1 传统签到方式到底差在哪这里先说需求分析很多同学跳过的这一步恰恰是整个项目不返工的关键。传统的签到方式主要有三种纸质签到、刷卡签到、指纹签到。它们各自都有让人头疼的地方。纸质签到的问题在于效率低且容易代签几十个人的会议光是传递签到表就要两三分钟更不用说事后统计出勤率还得手工录入Excel。刷卡签到稍微好一点但存在忘带卡就签不了和帮人代刷的问题而且需要额外购买读卡设备和制作IC卡成本并不低。指纹签到在识别准确率上表现不错但部分人群指纹浅、手指沾水或有磨损时识别率下降明显同时指纹设备同样需要硬件投入。相比之下人脸识别考勤只需要一个普通摄像头现有电脑上基本都自带部署成本几乎为零而且非接触式的体验对用户来说是最友好的。人脸这个生物特征也没有忘带的概念只要人到了就能签。这些优势让它成为考勤场景里综合性价比最高的方案。明确了这些痛点之后项目的功能边界也就出来了。一套完整的人脸识别考勤签到管理系统至少要包含四个部分一是人脸信息录入与建模把参与考勤的人员人脸数据采集入库二是实时识别与签到功能通过摄像头画面自动识别人员并记录签到信息三是考勤记录管理与查询能够按日期、人员、部门等维度查看和导出记录四是后台管理界面方便管理员维护人员信息、查看统计结果。2.2 人脸识别相关库怎么选技术选型是整个项目最先要拍板的事。Python生态里做人脸识别的方案有好几条路线我实际对比过几种主流选择。第一种是直接用OpenCV自带的Haar Cascade分类器做检测配合等传统特征做识别。这条路线的问题很明显Haar检测器对光线和角度非常敏感准确率不够稳定而特征做识别基本只能应付正脸、正面光线的理想情况稍微换个环境就崩。它的唯一优点是代码简单适合入门演示但不适合做完整的考勤系统。第二种是dlib库加face_recognition库的组合。face_recognition封装了dlib的深度学习人脸检测和特征提取模型实测下来表现非常稳定。它提供face_locations方法定位人脸位置face_encodings方法提取128维人脸特征向量然后通过计算特征向量之间的欧氏距离来判断是否为同一人。这个方案的优势在于对光线和角度的容忍度明显高于传统方法模型文件体积可控纯CPU环境下也能流畅运行完全不需要GPU。对于考勤系统这种单摄像头、人员数量几十人以内的场景它是最合适的选择。第三种是工业级的方案比如用深度学习框架自己训练模型或者接入云服务商的在线人脸识别API。自己训练模型的成本太高需要准备大量标注数据对于课程设计和轻量级自用场景来说属于杀鸡用牛刀。云API虽然识别精度最高但涉及网络请求、付费、数据隐私等问题而且一旦断网整个考勤系统就瘫了不适合本地部署场景。我最终选用的是face_recognition加dlib的组合配套OpenCV负责摄像头画面采集和图像处理。这个组合在准确率、部署难度、开发效率之间取得了最好的平衡。你要在答辩或者汇报时说清楚这个选型理由会比单纯说我用了某某库有说服力得多。2.3 界面、数据库与整体架构的取舍界面方案上主流的选项是Tkinter、PyQt5和Web前端。Tkinter是Python自带的GUI库开发简单、打包方便但视觉效果比较朴素。PyQt5界面更美观组件更丰富但引入的依赖更重打包体积会大不少。Web方案用Flask或Django搭后台前端用HTML加JavaScript界面自由度最高但开发和部署链路明显更长。考勤系统这个场景用户交互相对简单核心就是打开摄像头识别一下和查看记录不需要特别复杂的动态交互。我用的是Tkinter原因很实际它不需要额外安装打包成exe也最容易对于课程设计场景来说能在评委电脑上顺利跑起来比界面好看更重要。我在代码里用ttk组件做了简单的主题美化整体观感并不差。数据库选型这块很多初学同学会条件反射地去装MySQL其实完全没必要。考勤数据的特点是单机使用、数据量不大、结构固定SQLite是更合适的选择。它是Python标准库sqlite3直接支持的嵌入式数据库零配置、免安装数据存在一个文件里备份迁移都非常方便。我在项目里就用SQLite存人员信息和考勤记录实测几千条考勤记录的查询速度依然是毫秒级完全满足需求。整个系统的架构我按模块拆成了四块main.py作为程序入口和界面主逻辑face_register.py负责人员人脸录入face_attendance.py负责实时识别签到db_manager.py负责所有数据库操作。另外单独建一个known_faces目录存放人员照片face_models.dat文件用来缓存提取好的人脸特征避免每次启动都重新对照片做特征提取。这样分层之后每一块的职责都很清晰后期出了问题也能快速定位。3. 人脸录入与特征建模让系统认识每个人的关键步骤3.1 采集人脸照片的正确姿势人脸录入是系统的第一步也是最容易被忽视的一步。很多人图省事直接拿一张随意拍的手机照片塞进去当模板结果实测识别的时候怎么都匹配不上转头就开始怀疑识别库不行。其实问题往往出在录入环节。我设计录入功能时采用的方式是通过摄像头现场采集多张照片。录入人员坐在摄像头前系统连续捕获20帧画面实时检测画面中是否有人脸并用OpenCV的flip方法对每一帧做水平镜像翻转——这一步是为了让画面符合人们照镜子的习惯避免用户产生别扭感同时也保证后续识别时画面方向一致。每检测到人脸就用face_recognition.face_encodings提取这一帧的特征向量累计到该人员名下的一个特征集合里。为什么不能只用一张照片因为单张照片只覆盖了某一个特定角度和光线状态下的人脸而实际考勤时人脸的姿态和光照不可能和录入时完全一致。多张照片提取出的特征向量集合相当于覆盖了这个人多个维度的人脸信息比对时只要和其中任何一张的特征距离足够近就判断为同一个人鲁棒性会好很多。录入时还有一个容易被忽略的环节姓名编码和重名处理。我用员工ID加姓名的方式作为人员的唯一标识录入照片存放时以ID_姓名命名文件。这样即使存在同名人员只要ID不同就不会混淆数据库里的主键也直接用员工ID逻辑清晰。3.2 特征提取的原理与代码实现人脸识别的基本原理要理解清楚方便你答辩时能讲明白。简单说face_recognition库背后做的事情分三步第一步在图像中定位人脸的位置输出每个人脸的边界框坐标第二步找到人脸内部的眼睛、鼻子、嘴巴等关键特征点做对齐变换第三步通过预训练的深度网络把对齐后的人脸图像映射为一个128维的浮点向量。这个向量就是人脸的数字指纹理论上同一个人的不同照片提取出的向量在空间中是接近的不同人的向量是远离的。录入模块的核心代码大概长这样import os import cv2 import face_recognition import numpy as np def collect_face_samples(cap, person_name, sample_count20): 通过摄像头采集指定数量的人脸样本帧返回特征向量列表 encodings [] captured 0 while captured sample_count: ret, frame cap.read() if not ret: continue # 缩小画面提高检测速度 small_frame cv2.resize(frame, (0, 0), fx0.5, fy0.5) rgb_small_frame cv2.cvtColor(small_frame, cv2.COLOR_BGR2RGB) face_locations face_recognition.face_locations(rgb_small_frame) if len(face_locations) 1: face_encoding face_recognition.face_encodings( rgb_small_frame, face_locations )[0] encodings.append(face_encoding) captured 1 # 实时显示当前采集进度 cv2.putText(frame, fCaptured: {captured}/{sample_count}, (20, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow(Register Face, frame) if cv2.waitKey(1) 0xFF ord(q): break return encodings这里有几个细节值得说明。第一fx0.5, fy0.5把画面缩小一半再做人脸检测是因为分辨率越高检测耗时越长而考场场景下人脸在画面里占的面积通常足够大缩小后完全不影响检测效果但速度能提升好几倍。第二判断len(face_locations) 1是为了保证录入时画面里只有一个人避免把别人的脸特征混进来。第三每次采集只保留特征向量而不是把20张原始照片全部存进数据库这样既节约存储空间也保护了隐私。采集完成后把这一批特征向量封装成字典结构{name: person_name, encodings: encodings}更新到内存中的人员特征列表里同时把均值向量写入face_models.dat缓存文件。写入缓存的好处是如果100个员工都录入了每次启动程序时重新对每人20张照片做特征提取那启动时间会长到无法接受缓存之后启动程序只需要加载一个很小的序列化文件秒级完成。3.3 防止录入不合格样本的校验策略录入过程中我踩过一个很典型的坑有个人录入时不停晃动导致采集到的20帧里有好几帧人脸其实是模糊的。模糊照片提取出的特征向量质量很差直接拉低了后续的匹配准确率。后来我在采集逻辑里加了一个清晰度过滤对即将保存的帧计算拉普拉斯算子的方差只有方差大于某个阈值的帧才被判定为足够清晰并且纳入采集。这个过滤逻辑虽然简单但对最终识别效果的影响非常明显。实际测试过加入清晰度过滤之后同一批人的识别准确率从大约92%提升到了97%以上。差值看似不大但在考勤这种高频场景里每100次识别多失败5次意味着每天都有几个人要手动补签体验就很差了。4. 实时识别与考勤判定人脸识别考勤系统的核心业务逻辑4.1 摄像头画面循环处理的设计实时识别模块是整个系统的运行核心。它的基本逻辑是一个无限循环从摄像头读取一帧画面做人脸检测若检测到人脸则提取特征和已录入的人脸特征库做距离比对距离小于阈值则判定为匹配成功进而执行考勤签到逻辑。这个循环程序每隔一段时间就处理一帧画面需要考虑性能与响应速度的平衡。逐帧处理在高分辨率下会很吃CPU我的做法是在主循环里加入帧率控制使用time.sleep(0.05)让循环大约每秒处理20帧。对于考勤场景这个帧率已经足够流畅同时避免了CPU占用过高导致程序无响应。另一个重要优化是只在识别到新目标时才触发考勤逻辑。如果每帧都去触发数据库写入办公司几十个人轮流经过摄像头数据库会被频繁写入大量重复记录。我的策略是为每个已识别人员维护一个last_seen_time字典只有当该人员距上次签到超过一定时间间隔例如5分钟时才允许再次写入考勤记录。这样既避免重复签到又允许同一人一天内多次签到签退比如午休后回来再签一次。核心识别代码结构大致如下def process_frame(frame, known_encodings, match_threshold0.6): 对单帧画面做人脸识别返回识别到的人员名称列表 small_frame cv2.resize(frame, (0, 0), fx0.5, fy0.5) rgb_small_frame cv2.cvtColor(small_frame, cv2.COLOR_BGR2RGB) face_locations face_recognition.face_locations(rgb_small_frame) if not face_locations: return [] face_encodings face_recognition.face_encodings( rgb_small_frame, face_locations ) matched_names [] for encoding in face_encodings: # 与该帧人脸特征比对库中所有已知特征 distances face_recognition.face_distance( known_encodings, encoding ) best_match_index np.argmin(distances) if distances[best_match_index] match_threshold: matched_names.append(known_names[best_match_index]) return matched_namesface_recognition.face_distance计算的是两个128维特征向量之间的欧氏距离。距离越小说明两个人脸越相似。这里的match_threshold参数是整个系统的灵敏度开关非常值得花时间调优具体怎么调我在第6节详细讲。4.2 签到、签退与打卡规则的实现很多初学的实现里签到逻辑只是识别到人就往数据库里插入一条当前时间的记录这个逻辑在真实场景下是不完整的。考勤系统通常要区分签到上班和签退下班而且要对迟到、早退等异常状态做标记。我设计的考勤规则是这样的人员资料里维护一个上下班时间配置例如上午9点上班、下午6点下班。在识别到人员之后判断当前时间所处的是签到时段还是签退时段。如果当前时间在当天0点到午间12点之间则视为签到操作如果识别到的时间在12点到23点59分之间并且当天已经有过签到记录则视为签退操作。每次操作都会创建一条考勤记录并自动计算状态字段。状态字段的计算逻辑是签到时间晚于配置的上班时间则标记为迟到签退时间早于下班时间则标记为早退。每次识别之后程序会用SQLite的INSERT OR REPLACE语句更新当天的考勤主记录。这个设计保证了一个人一天最多有一条主记录但每次签到签退的时间节点都被完整保存在明细表里便于事后精确追溯。考勤判定里还有一个容易被忽略的需求——请假和出差。如果某人请假了但当天出现在了摄像头画面里系统肯定还是会签到。所以我在管理端预留了一个请假状态字段统计考勤时如果发现某人有请假记录则该日不参与迟到早退的统计。这个细节虽然不影响识别模块的实现但对于系统是否完整的评价影响很大评审老师经常会问。4.3 多目标识别与画面叠加显示一个人出现在摄像头画面里是最简单的情况但真实的办公室或教室场景下经常是多个人同时出现在画面里。face_recognition.face_locations天然支持一帧画面检测多张人脸返回的是一个列表。为了让系统更直观我逐个人脸框绘制了绿色的矩形框并在框上方标注识别到的人员姓名。画面叠加显示这部分代码用到了OpenCV的基础绘图能力for (top, right, bottom, left), name in zip(face_locations, matched_names): # 坐标要乘回来因为我们缩小过画面 top, right, bottom, left top * 2, right * 2, bottom * 2, left * 2 cv2.rectangle(frame, (left, top), (right, bottom), (0, 255, 0), 2) cv2.putText(frame, name, (left, top - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)这里有个细节因为之前检测时把画面缩小了一半所以绘制矩形框时需要把坐标乘以2回到原图分辨率下否则框的位置会偏。如果识别失败或距离超过阈值我就绘制红色矩形框并标注Unknown这个人不会触发任何考勤逻辑但画面里能看到一个未授权人员在镜头前出现过——这个功能在演示时特别加印象分因为它直观地展示了系统拒绝陌生人的能力。5. 数据库设计与界面交互让整套系统从能跑到好用5.1 数据库表结构怎么设计才不返工数据库设计是整个系统中出问题概率最高的部分因为考勤数据的查询逻辑远比想象中复杂。我的数据库包含三张核心表employees、attendance_records、admin_users。employees表存储人员基础信息核心字段包括emp_id员工ID主键、name姓名、department部门、work_start_time上班时间、work_end_time下班时间、face_image_path人脸照片路径、status在职/离职。需要注意face_image_path存的是相对路径而不是照片的二进制内容照片文件本身放在known_faces目录里。这样数据库文件体积很小后续如果要迁移照片只需要同步整个目录。attendance_records表是考勤核心表设计时我把主记录和明细记录分开处理。主记录表每个员工每天只有一条记录字段包括emp_id、date、first_checkin_time首次签到时间、last_checkout_time最后签退时间、status正常/迟到/早退/请假。每次识别后程序根据emp_id和date查找当天已有记录存在则更新签到或签退时间不存在则新建。这种设计让月底统计出勤率变得非常简单——一条SQL就能算出某员工当月的迟到次数。admin_users表存储管理员账号密码用hashlib加盐哈希后存储绝不存明文。这里我特别强调一下很多初学项目把密码直接明文存在数据库里这在一个演示型系统里可能不会被追究但作为一个完整的项目密码安全是基本素养用pbkdf2_hmac做哈希也就多几行代码的事没有理由不做。5.2 考勤记录查询与报表导出的实现考勤记录查询界面需要考虑三类查询需求按人员查某段时间的记录、按日期查当天所有人员的到岗情况、按部门查整体出勤统计。我用Tkinter的ttk.Treeview组件来展示查询结果配合ttk.Combobox做筛选条件选择。查询功能实现的关键是SQL语句的拼接。因为筛选条件是不确定的可能只选了人员没选日期范围所以要用动态拼接的方式构建SQL。我用参数化查询的方式防止SQL注入风险def query_records(emp_idNone, start_dateNone, end_dateNone): sql SELECT * FROM attendance_records WHERE 11 params [] if emp_id: sql AND emp_id ? params.append(emp_id) if start_date: sql AND date ? params.append(start_date) if end_date: sql AND date ? params.append(end_date) sql ORDER BY date DESC, emp_id ASC cursor.execute(sql, params) return cursor.fetchall()这里的技巧是WHERE 11它本身不改变查询逻辑只是为了后续拼接AND条件时语法始终成立避免了判断是否是第一个条件的分支代码让代码简洁很多。导出功能我用Python标准库的csv模块把查询结果写入CSV文件再用os.startfile直接打开文件所在目录。CSV格式兼容Excel老师们可以直接用办公软件打开查看这是演示时最稳妥的表现形式。5.3 界面交互流程与识别结果实时反馈整个界面的核心是主窗口布局左侧是实时摄像头画面区域右侧是识别状态面板和操作按钮。摄像头画面用一个Label组件显示OpenCV读取到的BGR帧转换为RGB后再转为ImageTk.PhotoImage对象赋值给Label实现实时视频流的显示。右侧面板会显示当前识别到的员工信息卡片包括大头照、姓名、部门和本次签到状态状态用不同颜色的文字提示正常为绿色迟到为橙色签退为蓝色。这个实时反馈非常重要因为考勤人员需要立刻知道这次识别有没有成功如果没有任何视觉反馈摄像头默默地识别用户会非常不安不知道自己有没有签上。操作按钮区包含开始考勤暂停考勤手动补签退出系统四个按钮。手动补签功能是给那些因为光线或角度问题没识别上的人员准备的管理员在人员列表里选中某人手动点击补签按钮系统会按当前时间写入一条记录并备注手动补签标记。这是考勤系统里必须有的兜底方案任何自动识别系统都不可能做到100%识别率没有兜底方案的系统在实际使用中会遭到强烈的抵触。6. 实测效果、阈值调优与常见坑位的完整记录6.1 不同场景下的实测数据我做了一组对照实验来验证系统在真实使用环境下的识别表现。测试场景选了三个正常室内灯光、较暗的走廊、逆光的窗户旁边。测试方式是10名志愿者分别在这三个场景下进行签到每个场景签20次。结果非常说明问题正常室内灯光下识别成功率达到98%失败的两三例多发生在面部被头发遮挡或者侧脸角度过大的情况下。较暗走廊下识别成功率降到85%左右主要原因是face_recognition的检测模型在弱光下面部关键点定位精度下降。逆光场景最惨成功率只有60%因为人脸区域曝光不足细节大量丢失。这三个数字让我意识到考勤系统的部署位置比算法调优更重要。如果你的摄像头装在门口正对窗户的位置那不管怎么调参数都是事倍功半。我的建议是把摄像头装在光线均匀的墙面位置避免正对或背对光源。实测调整位置后逆光场景的识别率立刻回升到了90%以上效果立竿见影。6.2 相似度阈值这个参数到底怎么调match_threshold参数是误判率和漏判率之间的天平。阈值设得越小要求人脸相似度越高才判定为同一人结果是陌生人被误认为熟人的概率降低但熟人因为光线变化识别不上的概率升高阈值设得越大则完全相反。我在项目里默认设0.6这是face_recognition官方文档给出的建议值。但实测发现这个值并不适合所有场景在人员总数超过50人的情况下误判率会明显上升因为库里的特征向量更多了某些相似的面孔之间的距离可能恰好小于0.6。我最终的调优方法是用已入库的所有人员照片做一次自比对统计每个人与其他人之间的最小距离用这个统计值辅助确定阈值。如果某两人的最小距离是0.55那阈值就得调到0.5以下否则他俩一定会互串。调阈值这个操作不能只对着库里的人调还要考虑陌生人场景。我建议额外找几个未录入的人测一下他们与库中特征的最小距离如果这些距离都大于0.4那阈值设置在0.4到0.5之间就是安全区间。考勤系统宁可偶尔漏掉一个人让他手动补签也不能让陌生人被识别成熟人自动签到——后者在安全性上是无法接受的。6.3 各种奇怪问题与对应解决记录这个项目我前前后后调试了很多轮遇到过的棘手问题远不止光线。在这里统一记录一下如果你正在做类似项目大概率也会碰到其中的几个。CPU占用过高导致界面卡死。最初的实现里每帧都做全尺寸人脸检测八代i5处理器的占用率直接冲到80%以上界面拖动都困难。解决方案就是我前面提到的缩小画面到一半尺寸再检测再配合帧率控制。实测优化后CPU占用降到25%左右识别速度反而更快了因为等待处理完成的耗时大大缩短。摄像头被占用导致程序启动崩溃。有些笔记本自带的摄像头会被系统相机应用或者其他程序占住cv2.VideoCapture(0)打开失败时不会抛异常而是返回一个空的capture对象后续调用read()返回的ret一直是False。我在程序启动逻辑里加了一个检测如果初始化失败或者前10帧都读取不到画面就弹出明确的错误提示对话框告诉用户去关闭其他占用摄像头的程序而不是让界面白屏崩溃。从录入到识别同一个人的特征距离比预期大。有个同事录入时戴着眼镜识别时换了副大框墨镜结果就识别不上了。人脸识别对遮挡非常敏感眼镜、口罩、帽子都会影响识别结果。我的处理办法是在人员资料里允许维护多组人脸特征模板分别对应戴眼镜不戴眼镜等不同状态识别时只要和任意一组模板匹配就算成功。虽然不是每个场景都用得上这个功能但设计上保留了扩展空间。数据库文件被占用导致写入失败。SQLite在Windows下如果某个程序持有了数据库文件的句柄写入时可能会出现database is locked错误。我的规避方案是所有数据库操作都通过db_manager.py封装每次操作都新开连接、用完即关并且设置了timeout10的连接超时参数让SQLite在锁冲突时等待一段时间而不是立刻报错。这样多个模块并发访问数据库时基本不会出问题。6.4 从能演示到能答辩的细节打磨最后从项目交付角度再唠叨几句。做这类系统编程能力只占一半另一半是把事情讲清楚的能力。一套能跑起来的代码大家都写得出但为什么这样设计、遇到问题怎么排查、还有哪些地方可以继续优化这些才是评审真正想听的。运行视频和讲解视频在这个项目里很有价值。运行视频录的是启动系统、录入一个新人脸、让这个人签到、查询记录、导出报表整条流程一气呵成。讲解视频重点讲三个设计决策为什么用face_recognition而不是自己训练模型、为什么用SQLite而不是MySQL、以及考勤判定规则是怎么设计的。把这三个问题想明白基本上就能应对大部分提问。代码交付时我在工程根目录放了一个requirements.txt里面列出了所有依赖包的版本保证在任何一台新电脑上都能通过pip install -r requirements.txt快速复现环境。文档方面除了需求分析和设计文档我还写了一份常见问题排障手册把摄像头打不开、识别率低、数据库锁定这一类高频问题以及对应的排查步骤都写了进去。这份手册在后来自用和维护时帮了很大的忙毕竟几个月之后回来再看自己的代码记忆已经模糊了有排障手册能省大量时间。我个人实际做下来的体会是这个项目最大的收获不是会用某个库这个单一技能点而是完整走了一遍需求拆解、技术选型、模块实现、联调测试、文档交付的全流程。人脸识别考勤系统放在计算机视觉的项目里不算难但它是少有的能把算法、数据、界面、业务规则全部塞进一个完整工程的选题。如果你正在做这个项目建议你重点把第3节的录入质量、第4节的考勤判定规则、第6节的阈值调优这三个环节做扎实这三个环节决定了你的系统是演示能用还是真能日常使用。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。