原标题:500行Python代码打造刷脸考勤系统
作者 | 月小水长(某 985 计算机学院大三在校生,熟悉 C++、Java、Python等多种语言,有大型软件项目开发经验,致力于安卓、计算机视觉、爬虫、数据可视化开发,也是业余的前端爱好者)
需求分析
我们希望达到的目标是:
考勤时,须满足三个条件:面部信息已录入、在打卡时间段内、未重复打卡,只有打卡成功,打卡人姓名及工号、打卡日期及时间才会被当成一行记录保存到数据库并在控制台输出打卡成功信息,否则会在控制台输出失败及其原因信息。
总而言之:我们的设计目标是规范化、人性化。
总体设计
为了完成上述目标一,程序的界面初始化分为三部分,第一部分初始化菜单栏,第二部分初始化左边控制台,第三部分初始化右边展示面板,使这三部分相互独立;数据逻辑部分的初始化分为两部分,第一部分是数据库部分的初始化,如果数据库/表不存在就新建,存在则加载相关数据,第二部分是初始化一些需要循环使用的变量,比如新建录入时的员工姓名、工号、截图数目计数器等,每当完成录入时这些数据都应该被重置成初始化以待下一次录入,把这些初始化语句写成一个函数可以提高代码复用度。
上述目标二主要是一些限制性条件,可以通过添加判断语句来实现,比如对输入id的合法性检验:
whileself.id == ID_WORKER_UNAVIABLE:
self.id = wx. GetNumberFromUser(message= "请输入您的工号(-1不可用)",prompt= "工号", caption= "温馨提示", value= ID_WORKER_UNAVIABLE, parent= self.bmp, max= 100000000, min= ID_WORKER_UNAVIABLE)
forknew_id inself.knew_id:
ifknew_id == self.id:
self.id = ID_WORKER_UNAVIABLE
wx. MessageBox(message= "工号已存在,请重新输入", caption= "警告")
再比如对拒绝多张人脸时、只处理距离屏幕最近的员工的面部信息:
iflen(dets) != 0:
biggest_face= dets[0]
#取占比最大的脸
maxArea= 0
fordet in dets:
w= det.right - det.left
h= det.top-det.bottom
ifw*h > maxArea:
biggest_face= det
maxArea= w*h
dets是侦测到的所有面部数组,biggest_face是距离屏幕最近的面部。
程序框图:
注:图片如看不清也可在线预览
https://www.processon.com/view/link/5bbcc953e4b08faf8c7324a1
本程序的设计思想大致可分为以下几个方面
面向对象的原则,整个程序的主体就是一个WAS(WorkAttendanceSystem)类,所有的实现都围绕这个类展开。
界面和数据逻辑分离的原则,WAS类的初始化过程包括界面的初始化和数据初始化,两者相互独立。
代码封装原则,多次调用的语句集写成接口供调用,没有冗余的代码。
接口隔离原则:使用多个专门的接口,而不是使用单一的总接口。
函数清单
注:所有类内的函数的第一个参数为self,表明该函数属于该类,后面不再赘述
def __init__(self) WAS类的构造函数,主要是完成一些初始化操作,如初始化菜单、信息打印面板、主展示面板以及初始化加载数据库、初始化循环使用的变量。
def initMenu(self): 完成菜单的初始化显示,点击事件绑定。
def initInfoText(self): 完成左边信息提示面板的初始化显示。
def initGallery(self): 完成右边主展示面板的初始化显示。
def initDatabase(self): 数据库的初始化,建立数据库连接(如果数据库inspurer.db不存在则先新建),如果数据库中不存在员工信息worker_info和考勤logcat这两个表,则依次创建。
def loadDataBase(self,type): 该模块函数完成从数据库读取数据的操作,包括读取员工信息和考勤信息,第二个参数type用于标识是加载员工信息还是考勤信息,一方面,可以统一接口,打开数据库和得到游标、关闭连接是一样的,将两个读取接口合二为一,提高代码复用度;另一方面,可以减少加载的工作量,减少IO,提高程序运行速度;最后,因为读取信息前对上一次读取的信息列表做了清空处理,用type标识可以避免读取一个表时对另一个表造成的误操作。
def insertARow(self,Row,type): 该模块函数完成写数据库操作,第二个参数为准备写的一条记录,第三个参数type表示要对哪一个表进行写操作。
def adapt_array(self,arr): 将提取的人脸特征信息(列表)压缩,入口参数就是待压缩的数据,出口参数是压缩后的数据,用于写入数据库。
def convert_array(self,text): 将读取出来的数据解压缩成人脸特征信息,入口参数是待解压的数据,出口参数是解压后的数据。
def return_euclidean_distance(feature_1, feature_2): 计算两个人脸的欧式距离,入口参数是两个人脸的特征数据,出口参数是判定的结果,欧式距离大于0.4判为不同,不大于判为相同。
def OnNewRegisterClicked(self,event): 见名知义,菜单新建录入的监听事件,参数event为事件信息,其他几个菜单的(OnFinishRegisterClicked,OnStartPunchCardClicked, OnEndPunchCardClicked, OnOpenLogcatClicked,OnCloseLogcatClicked)类似,在此不再赘述。
def getDateAndTime(self): 得到当前日期和时间,并组装成特定格式作为出口参数返回。
函数调用关系:箭头指向被调用者
在线预览地址:
https://www.processon.com/view/link/5bbe0b0de4b0534c9bfbecb4
程序运行结果
程序主界面
新建录入
我们看到,信息栏有人脸数据重复警告,本次录入取消。
于是我们把数据库数据删了重来。
下面是打印的日志信息(为保证格式,复制到记事本中截的图)
看到已经录入成功了。
开始打卡
提示信息打印如下
迟到与否的临界时间是9:00
展示日志
只有已经录入且第一次成功签到才会写进到数据库,无论迟到与否。
源代码:
https://github.com/inspurer/WorkAttendanceSystem