【项目实训】基于人脸识别的课堂签到管理系统(python+qt5+sqlite3+百度智能云)

一.环境介绍

编写工具:Pycharm,Pyqt5designer
语言:python 版本:python3.7.4
数据库:sqlite3
代码:【项目实训】 基于人脸识别的课堂签到管理系统.zip

二.签到功能

签到功能是该项目最核心的功能,将会着重介绍几个核心函数

2.1 启动签到

首先先看下启动签到,该功能所需要调用的函数。然后对重要函数进行分析,以及代码展示

    def on_actionopen(self):
        list = self.getlist()
        self.group_id, ret = QInputDialog.getText(self, "请输入所在用户组", "用户组信息\n" + str(list['result']['group_id_list']))
        if self.group_id == '':
            QMessageBox.about(self, "签到失败", "用户组不能为空\n")
            return
        group_status = 0
        for i in list['result']['group_id_list']:
            if i == self.group_id:
                group_status =1
                break
        if group_status == 0:
            QMessageBox.about(self, "签到失败", "该用户组不存在\n")
            return
        #启动摄像头
        self.cameravideo = camera()
        self.camera_status = True
        #启动定时器,进行定时,每个多长时间进行一次获取摄像头数据进行显示,用作流畅显示画面
        self.timeshow = QTimer(self)
        self.timeshow.start(10)
        #10ms的定时器启动,每到10ms就会产生一个信号timeout,信号没有()
        self.timeshow.timeout.connect(self.show_cameradata)
        #self.timeshow.timeout().connect(self.show_cameradata)
        # self.show_cameradata()
        #创建检测线程
        self.create_thread()
        # self.group_id.emit(str(group_id))
        # self.group_id.connect(self.detectThread.get_group_id)
        #当开启启动签到时,创建定时器,500ms,用作获取要检测的画面
        # facedetecttime定时器设置检测画面获取
        self.facedetecttime = QTimer(self)
        self.facedetecttime.start(500)
        self.facedetecttime.timeout.connect(self.get_cameradata)

        self.detect_data_signal.connect(self.detectThread.get_base64)
        self.detectThread.transmit_data.connect(self.get_detectdata)
        self.detectThread.search_data.connect(self.get_search_data)

首先是调用getlist函数,该函数是用来获取学生所在的用户组(可以理解为班级),通过输入所在班级,在该班级搜寻该学生人脸,同时通过if条件语句判断是否为空,或者错误班级,防止程序卡死

在这里插入图片描述接着是启动摄像头,通过设置一个定时器,来不断获取摄像头一帧画面
在这里插入图片描述为了使界面显示画面流畅,这里采用多线程方式,每500ms发送一次,将学生图片发送给另一个线程
在这里插入图片描述多线程类就接受学生人脸图片以及相关参数,并将其上传至百度智能云,进行人脸检测

    def detect_face(self,base64_image):
        '''
        #对话框获取图片
        #获取一张图片(一帧画面)
        #getOpenFileName通过对话框的形式获取一个图片(.JPG)路径
        path,ret = QFileDialog.getOpenFileName(self,"open picture",".","图片格式(*.jpg)")
        #把图片转换成base64编码格式
        fp = open(path,'rb')
        base64_imag = base64.b64encode(fp.read())
        print(base64_imag)
        '''
        # 摄像头获取画面
        # camera_data = self.cameravideo.read_camera()
        # # 把摄像头画面转换成图片,然后设置编码base64编码格式数据
        # _, enc = cv2.imencode('.jpg', camera_data)
        # base64_image = base64.b64encode(enc.tobytes())
        # 发送请求的地址
        request_url = "https://aip.baidubce.com/rest/2.0/face/v3/detect"
        # 请求参数是一个字典,在字典中存储,百度AI要识别的图片信息,属性内容
        params = {
            "image": base64_image,  # 图片信息字符串
            "image_type": "BASE64",  # 图片信息格式
            "face_field": "gender,age,beauty,expression,face_shape,glasses,emotion,mask",  # 请求识别人脸的属性, 各个属性在字符串中用,逗号隔开
            "max_face_num": 1
        }
        # 访问令牌
        access_token = self.access_token
        # 把请求地址和访问令牌组成可用的网络请求
        request_url = request_url + "?access_token=" + access_token
        # 参数:设置请求的格式体
        headers = {'content-type': 'application/json'}
        # 发送网络post请求,请求百度AI进行人脸检测,返回检测结果
        # 发送网络请求,就会存在一定的等待时间,程序就在这里阻塞执行,所以会存在卡顿现象
        response = requests.post(request_url, data=params, headers=headers)
        if response:
            data = response.json()
            if data['error_code'] != 0:
                self.transmit_data.emit(data)
                self.search_data.emit(data['error_msg'])
                return

            if data['result']['face_num'] > 0:
                #data是请求数据的结果,需要进行解析,单独拿出所需的数据内容,分开
                self.transmit_data.emit(dict(data))
                self.face_search(self.group_id)

读取百度智能云返回主线程相关的人脸信息,若人脸数大于0,则执行人脸识别功能

# 人脸识别检测,只检测一个人
    def face_search(self,group_id):
        request_url = "https://aip.baidubce.com/rest/2.0/face/v3/search"
        params = {
            "image": self.base64_image,
            "image_type": "BASE64",
            "group_id_list": group_id,
        }
        access_token = self.access_token
        request_url = request_url + "?access_token=" + access_token
        headers = {'content-type': 'application/json'}
        response = requests.post(request_url, data=params, headers=headers)
        if response:
            data = response.json()
            if data['error_code'] == 0:
                if data['result']['user_list'][0]['score'] > 90:
                    #存储要保存的签到数据,方便显示
                    del(data['result']['user_list'][0]['score'])
                    datetime = QDateTime.currentDateTime()
                    datetime = datetime.toString()
                    data['result']['user_list'][0]['datetime'] = datetime
                    key = data['result']['user_list'][0]['group_id']+data['result']['user_list'][0]['user_id']
                    if key not in self.sign_list.keys():
                        self.sign_list[key] = data['result']['user_list'][0]
                    self.search_data.emit("学生签到成功\n学生信息:"+data['result']['user_list'][0]['user_info'])
                    stu_data = data['result']['user_list'][0]['user_info']
                    info = stu_data.split('\n')
                    _, info_name = info[0].split(':')
                    _, info_class = info[1].split(':')
                    id = data['result']['user_list'][0]['user_id']
                    # self.add_sqlite(id, info_name, info_class, datetime)
                    self.search_sqlite(id)
                    for i in self.values:
                        search_id = i[0]
                    if search_id == id:
                        self.update_sqlite(id,info_name,info_class,datetime)
                    else:
                        self.add_sqlite(id,info_name,info_class,datetime)
                else:
                    self.search_data.emit("学生签到失败,找不到对应学生")

当人脸匹配可信度大于90时,将学生签到信息添加到数据库,下面代码是sqlite3数据库相关操作

#创建数据库
    def create_sqlite(self):
        con = sqlite3.connect(r"stu_data.db")
        c = con.cursor()
        c.execute("create table student(id primary key ,name ,stu_class,datetime)")
        print("创建成功")

    #添加学生数据到数据库
    def add_sqlite(self,id,name,stu_class,datetime):
        con = sqlite3.connect(r"stu_data.db")
        c = con.cursor()
        value = (id,name,stu_class,datetime)
        sql = "insert into student(id,name,stu_class,datetime) values(?,?,?,?)"
        c.execute(sql,value)
        # 提交
        con.commit()

    #更新学生数据库信息
    def update_sqlite(self,id,name,stu_class,datetime):
        con = sqlite3.connect(r"stu_data.db")
        c = con.cursor()
        # value = (name,stu_class,datetime,id)
        sql = "update student set name=?,stu_class=?,datetime=? where id =?"
        c.execute(sql,(name,stu_class,datetime,id))
        con.commit()

    #查询学生数据库信息
    def search_sqlite(self,id):
        con = sqlite3.connect(r"stu_data.db")
        c = con.cursor()
        sql = "select * from student where id=?"
        self.values = c.execute(sql,(id,))

当线程完成人脸识别后,就会将学生信息和签到信息返回,这时就检测数据,并打印到主窗口

#槽函数,获取检测数据
    def get_detectdata(self,data):
        if data['error_code'] != 0:
            self.plainTextEdit_2.setPlainText(data['error_msg'])
            return
        elif data['error_msg'] == 'SUCCESS':
            # 在data字典中,键为'result'对应的值才是返回的检查结果
            # data['result']才是检测结果
            # 人脸数目
            self.plainTextEdit_2.clear()
            face_num = data['result']['face_num']
            if face_num == 0:
                self.plainTextEdit_2.appendPlainText("未测到检人脸")
                return
            else:
                self.plainTextEdit_2.appendPlainText("测到检人脸")
            # 人脸信息data['result']['face_list'],是列表,每个数据就是一个人脸信息,需要取出每个列表数据
            # 每个人脸信息:data['result']['face_list'][0~i]人脸信息字典
            for i in range(face_num):
                # 通过for循环,分别取出列表的每一个数据
                # data['result']['face_list'][i],就是一个人脸信息的字典

                age = data['result']['face_list'][i]['age']
                beauty = data['result']['face_list'][i]['beauty']
                gender = data['result']['face_list'][i]['gender']['type']
                expression = data['result']['face_list'][i]['expression']['type']
                face_shape = data['result']['face_list'][i]['face_shape']['type']
                glasses = data['result']['face_list'][i]['glasses']['type']
                emotion = data['result']['face_list'][i]['emotion']['type']
                mask = data['result']['face_list'][i]['mask']['type']
                # 往窗口中添加文本,参数就是需要的文本信息
                self.plainTextEdit_2.appendPlainText("-----------------")
                self.plainTextEdit_2.appendPlainText("第" + str(i + 1) + "个学生信息:")
                self.plainTextEdit_2.appendPlainText("-----------------")
                self.plainTextEdit_2.appendPlainText("年龄: " + str(age))
                self.plainTextEdit_2.appendPlainText("颜值分数: " + str(beauty))
                self.plainTextEdit_2.appendPlainText("性别: " + str(gender))
                self.plainTextEdit_2.appendPlainText("表情: " + str(expression))
                self.plainTextEdit_2.appendPlainText("脸型: " + str(face_shape))
                self.plainTextEdit_2.appendPlainText("是否佩戴眼镜: " + str(glasses))
                self.plainTextEdit_2.appendPlainText("情绪: " + str(emotion))
                if mask == 0:
                    mask = "否"
                else:
                    mask = "是"
                self.plainTextEdit_2.appendPlainText("是否佩戴口罩: " + str(mask))
                self.plainTextEdit_2.appendPlainText("-----------------")
    def get_search_data(self,data):
        self.plainTextEdit.setPlainText(data)

这样启动签到功能就基本实现了

2.2 结束签到

接着是结束签到,该功能所需要调用的函数。然后对重要函数进行分析,以及代码展示

    def on_actionclose(self):
        # 清除学生人脸信息(False)
        # self.plainTextEdit_2.setPlainText("  ")
        #关闭定时器,不再设置检测画面获取
        self.facedetecttime.stop()
        #self.facedetecttime.timeout.disconnect(self.get_cameradata)
        #self.detect_data_signal.disconnect(self.detectThread.get_base64)
        #self.detectThread.transmit_data.connect(self.get_detectdata)
        #关闭检测线程
        self.detectThread.OK = False
        self.detectThread.quit()
        self.detectThread.wait()
        print(self.detectThread.isRunning())
        # 关闭定时器,不再去获取摄像头的数据
        self.timeshow.stop()
        self.timeshow.timeout.disconnect(self.show_cameradata)
        # 关闭摄像头
        self.cameravideo.close_camera()
        self.camera_status = False
        print("1")
        #显示本次签到情况
        self.signdata = sign_data(self.detectThread.sign_list,self)
        self.signdata.exec_()
        if self.timeshow.isActive() == False and self.facedetecttime.isActive() == False:
            # 画面设置为初始状态
            self.label.setPixmap(QPixmap("1.jpg"))
            self.plainTextEdit.clear()
            self.plainTextEdit_2.clear()
        else:
            QMessageBox.about(self, "错误", "关闭签到失败\n")

关闭定时器,不再设置检测画面获取
在这里插入图片描述关闭检测线程
在这里插入图片描述
关闭定时器,不再去获取摄像头的数据
在这里插入图片描述
关闭摄像头
在这里插入图片描述
显示本次签到情况,这里会弹出一个窗口,将学生签到情况显示在窗口,并提供两个按钮,取消和导出。导出能够将学生签到信息导出成TXT文件

在这n'chu入图片描述

        #设置窗口内容不能被修改
        self.signdata = signdata
        self.tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
        for i in signdata.values():
            info = i['user_info'].split('\n')
            rowcount = self.tableWidget.rowCount()
            self.tableWidget.insertRow(rowcount)
            info_name = info[0].split(':')
            self.tableWidget.setItem(rowcount, 0, QTableWidgetItem(info_name[1]))
            info_class = info[1].split(':')
            self.tableWidget.setItem(rowcount, 1, QTableWidgetItem(info_class[1]))
            self.tableWidget.setItem(rowcount, 2, QTableWidgetItem(i['user_id']))
            self.tableWidget.setItem(rowcount, 3, QTableWidgetItem(i['datetime']))
        #导出按钮
        self.pushButton.clicked.connect(self.save_data)
        #取消按钮
        self.pushButton_2.clicked.connect(self.close_window)
    def close_window(self):
        self.reject()

    def save_data(self):
        #打开对话框,获取要导出的数据文件名
        filename,ret = QFileDialog.getSaveFileName(self,"导出数据",".","TXT(*.txt)")
        f = open(filename,"w")
        for i in self.signdata.values():
            info = i['user_info'].split('\n')
            _,info_name = info[0].split(':')
            _,info_class = info[1].split(':')
            f.write(str(info_name+"  "+info_class+"  "+i['user_id']+"  "+i['datetime'] ))
        f.close()
        self.accept()

三.用户组操作

3.1 添加用户组

添加用户组实现代码

 def add_group(self):
        #打开对话框,进行输入用户组
        group,ret = QInputDialog.getText(self,"添加用户组","请输入用户组(由数字、字母、下划线组成)")

        request_url = "https://aip.baidubce.com/rest/2.0/face/v3/faceset/group/add"
        params = {
            "group_id":group
        }
        access_token = self.access_token
        request_url = request_url + "?access_token=" + access_token
        headers = {'content-type': 'application/json'}
        response = requests.post(request_url, data=params, headers=headers)
        if response:
            message = response.json()
            if message['error_msg'] == 'SUCCESS':
                QMessageBox.about(self,"用户组创建结果","用户组创建成功")
            else:
                QMessageBox.about(self,"用户组创建结果","用户组创建失败\n"+message['error_msg'])  

3.2 删除用户组

删除用户组实现代码

   def del_group(self):
        request_url = "https://aip.baidubce.com/rest/2.0/face/v3/faceset/group/delete"
        list = self.getlist()
        group, ret = QInputDialog.getText(self, "用户组列表", "用户组信息\n" + str(list['result']['group_id_list']))
        # 删除,需要知道那些组
        params = {
            "group_id": group  # 要删除的用户组的id
        }
        access_token = self.access_token
        request_url = request_url + "?access_token=" + access_token
        headers = {'content-type': 'application/json'}
        response = requests.post(request_url, data=params, headers=headers)
        if response:
            data = response.json()
            if data['error_msg'] == 'SUCCESS':
                QMessageBox.about(self, "用户组删除结果", "用户组删除成功")
            else:
                QMessageBox.about(self, "用户组删除结果", "用户组删除失败\n" + data['error_msg'])

3.3 查询用户组

查询用户组实现代码

#获取用户组
    def getlist(self):
        request_url = "https://aip.baidubce.com/rest/2.0/face/v3/faceset/group/getlist"

        params = {
            "start":0,"length":100
        }
        access_token = self.access_token
        request_url = request_url + "?access_token=" + access_token
        headers = {'content-type': 'application/json'}
        response = requests.post(request_url, data=params, headers=headers)
        if response:
            data = response.json()
            if data['error_msg'] == 'SUCCESS':
                return data
            else:
                QMessageBox.about(self, "获取用户组结果", "获取用户组失败\n" + data['error_msg'])

四.用户操作

4.1 添加用户

添加用户实现代码

    def add_user(self):
        request_url = "https://aip.baidubce.com/rest/2.0/face/v3/faceset/user/add"
        if self.camera_status:
            QMessageBox.about(self,"摄像头状态","摄像头已打开,正在进行人脸签到\n请关闭签到,再添加用户")
            return
        list = self.getlist()
        #创建一个窗口来选择这些内容
        window = adduserwindow(list['result']['group_id_list'],self)
        #新创建窗口,通过exec()函数一直执行,阻塞执行,窗口不进行关闭
        #exec()函数不会退出,关闭窗口才会结束
        window_status = window.exec_()
        #进行判断,判断是否点击确定进行关闭
        if window_status != 1:
            return
        #请求参数,需要获取人脸:转换人脸编码,添加的组id,添加的用户id,新用户的id信息
        params = {
            "image":window.base64_image,#人脸图片
            "image_type":"BASE64",#人脸图片编码
            "group_id":window.group_id,#组id
            "user_id":window.user_id,#新用户id
            "user_info":'姓名:'+window.msg_name+'\n'
                        +'班级:'+window.msg_class#用户信息
        }
        access_token = self.access_token
        request_url = request_url + "?access_token=" + access_token
        headers = {'content-type': 'application/json'}
        response = requests.post(request_url, data=params, headers=headers)
        if response:
            data = response.json()
            if data['error_msg'] == 'SUCCESS':
                QMessageBox.about(self, "人脸创建结果", "人脸创建成功")
            else:
                QMessageBox.about(self, "人脸创建结果", "用户组创建失败\n"+ data['error_msg'])

4.2 删除用户

删除用户实现代码

 def del_face_token(self,group,user,face_token):
        request_url = "https://aip.baidubce.com/rest/2.0/face/v3/faceset/face/delete"

        params = {
            "user_id": user,
            "group_id": group,
            "face_token":face_token
        }
        access_token = self.access_token
        request_url = request_url + "?access_token=" + access_token
        headers = {'content-type': 'application/json'}
        response = requests.post(request_url, data=params, headers=headers)
        if response:
            data = response.json()
            if data['error_msg'] == 'SUCCESS':
                QMessageBox.about(self, "人脸删除结果", "人脸删除成功")
            else:
                QMessageBox.about(self, "人脸删除结果", "用户组删除失败\n" + data['error_msg'])

    def del_user(self):
        #查询用户人脸信息(face_token)
        #获取用户组
        list = self.getlist()
        group,ret = QInputDialog.getText(self,"用户组获取","用户组信息\n"+str(list['result']['group_id_list']))
        group_status = 0
        if self.group_id == '':
            QMessageBox.about(self, "删除失败", "用户组不能为空\n")
            return
        for i in list['result']['group_id_list']:
            if i == group:
                group_status = 1
                break
        if group_status == 0:
            QMessageBox.about(self, "删除失败", "该用户组不存在\n")
            return
        #获取用户
        userlist = self.get_userlist(group)
        user,ret = QInputDialog.getText(self,"用户获取","用户信息\n"+str(userlist['result']['user_id_list']))
        user_status = 0
        if user == '':
            QMessageBox.about(self, "删除失败", "用户不能为空\n")
            return
        for i in userlist['result']['user_id_list']:
            if i == user:
                user_status = 1
                break
        if user_status == 0:
            QMessageBox.about(self, "删除失败", "该用户不存在\n")
            return
        #获取用户人脸列表
        face_list = self.user_face_list(group,user)
        for i in face_list['result']['face_list']:
            self.del_face_token(group,user,i['face_token'])

4.3 更新用户

更新用户实现代码

 #更新用户人脸
    def update_user(self):
        request_url = "https://aip.baidubce.com/rest/2.0/face/v3/faceset/user/update"
        if self.camera_status:
            QMessageBox.about(self,"摄像头状态","摄像头已打开,正在进行人脸签到\n请关闭签到,再添加用户")
            return
        list = self.getlist()
        #创建一个窗口来选择这些内容
        window = adduserwindow(list['result']['group_id_list'],self)
        #新创建窗口,通过exec()函数一直执行,阻塞执行,窗口不进行关闭
        #exec()函数不会退出,关闭窗口才会结束
        window_status = window.exec_()
        #进行判断,判断是否点击确定进行关闭
        if window_status != 1:
            return
        #请求参数,需要获取人脸:转换人脸编码,添加的组id,添加的用户id,新用户的id信息
        params = {
            "image":window.base64_image,#人脸图片
            "image_type":"BASE64",#人脸图片编码
            "group_id":window.group_id,#组id
            "user_id":window.user_id,#新用户id
            "user_info":'姓名:'+window.msg_name+'\n'
                        +'班级:'+window.msg_class#用户信息
        }
        access_token = self.access_token
        request_url = request_url + "?access_token=" + access_token
        headers = {'content-type': 'application/json'}
        response = requests.post(request_url, data=params, headers=headers)
        if response:
            data = response.json()
            if data['error_msg'] == 'SUCCESS':
                QMessageBox.about(self, "人脸更新结果", "人脸更新成功")
            else:
                QMessageBox.about(self, "人脸更新结果", "用户组更新失败\n" + data['error_msg'])

五.摄像头相关设置

摄像头类代码

import cv2
import numpy as np
from  PyQt5.QtGui import QPixmap,QImage
'''
摄像头操作:创建类对象完成摄像头操作,所以可以把打开摄像头与创建类对象操作合并
    __init__函数完成摄像头的配置打开
'''

class camera():
    def __init__(self):
        #VideoCapture类对视频或调用摄像头进行读取操作
        #参数 filename;device
        #0表示默认的摄像头进行打开
        #self.capture表示打开的摄像头对象
        self.capture = cv2.VideoCapture(0)
        #isOpened函数返回一个布尔值,来判断是否摄像头初始化成功
        # if self.capture.isOpened():
        #     print("isOpened")
        #定义一个多维数组,存取画面
        self.currentframe = np.array([])

    #读取摄像头数据
    def read_camera(self):
        #ret是否成功,pic_data数据
        ret,data = self.capture.read()
        if not ret:
            print("获取摄像头数据失败")
            return None
        return data

    #数据转换成界面能显示的数据格式
    def camera_to_pic(self):
        pic = self.read_camera()
        #摄像头是BGR方式存储,首先要转换成RGB
        self.currentframe = cv2.cvtColor(pic,cv2.COLOR_BGR2RGB)
        #设置宽高
        #self.currentframe = cv2.cvtColor(self.currentframe,(640,480))

        #转换格式(界面能够显示的格式)
        #获取画面的宽度和高度
        height,width = self.currentframe.shape[:2]
        #先转换成QImage类型的图片(画面),创建QImage类对象,使用摄像头的画面
        #QImage (data, width, height , format)创建:数据,宽度,高度,格式
        qimg = QImage(self.currentframe,width,height,QImage.Format_RGB888)
        qpixmap = QPixmap.fromImage(qimg)
        return qpixmap

    def close_camera(self):
        self.capture.release()

六.相关链接

代码下载:【项目实训】 基于人脸识别的课堂签到管理系统.zip
详细教程:
基于人脸识别的课堂签到管理系统(一)—环境设置以及简单的QT界面设计
基于人脸识别的课堂签到管理系统(二)—摄像头显示
基于人脸识别的课堂签到管理系统(三)—实时时间显示以及百度AI人脸识别
基于人脸识别的课堂签到管理系统(四)—摄像头上传实时数据,百度AI读取并返回信息以及多线程操作
基于人脸识别的课堂签到管理系统(五)—启动/结束签到,以及在百度智能云创建用户组
基于人脸识别的课堂签到管理系统(六)—删除,查询用户组以及人脸的添加,删除,更新
基于人脸识别的课堂签到管理系统(七)—实现人脸搜索,完善签到功能

  • 10
    点赞
  • 86
    收藏
    觉得还不错? 一键收藏
  • 18
    评论
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值