当前位置:   article > 正文

毕业设计:基于python人脸识别系统 LBPH算法 sqlite数据库 (源码)✅_人脸识别与sqlite交互

人脸识别与sqlite交互

博主介绍:✌全网粉丝10W+,前互联网大厂软件研发、集结硕博英豪成立工作室。专注于计算机相关专业毕业设计项目实战6年之久,选择我们就是选择放心、选择安心毕业✌感兴趣的可以先收藏起来,点赞、关注不迷路✌

毕业设计:2023-2024年计算机毕业设计1000套(建议收藏)

毕业设计:2023-2024年最新最全计算机专业毕业设计选题汇总

1、项目介绍

技术栈:
Python语言、 opencv库、 LBPH算法 pyqt5、sqlite数据库

2、项目界面

(1)系统首页

在这里插入图片描述

(2)人脸录入
在这里插入图片描述

(3)人脸识别
在这里插入图片描述

(4)数据库管理
在这里插入图片描述

3、项目说明

LBPH算法是一种基于局部二值模式的人脸识别算法。它是一种简单且高效的算法,常用于人脸识别、人脸表情识别、行人检测等领域。

原理
LBPH算法将人脸图像分为许多小的局部区域,对每个局部区域进行特征提取,然后将每个局部区域的特征串联成一个整体特征向量,最终完成相似度计算。

LBPH算法不是深度学习算法,
它是一种基于图像纹理特征的传统机器学习算法。
LBPH算法用于人脸识别,它通过将图像划分为小区域,并计算每个区域的局部二值模式,
然后使用直方图统计这些局部二值模式的分布来表示图像特征。与深度学习相比,
LBPH算法不需要大量的标注数据和复杂的网络结构,但在某些场景下仍然具有一定的应用价值。

人脸识别系统是一种基于计算机视觉技术的应用程序,能够在图像或视频中识别和验证人脸。其主要包括两个部分:人脸检测和人脸识别。

人脸检测是指在图像或视频中寻找可能是人脸的区域,并将其框出来。常用的方法有Haar级联检测器、基于深度学习的方法等。

人脸识别是指在检测到人脸的基础上,对人脸进行特征提取和比对,实现人脸的自动识别和验证。常用的算法有Eigenface、Fisherface、LBPH等。

LBPH算法是一种基于局部二值模式(Local Binary Pattern)的特征提取算法,它将人脸图像划分成若干个小区域,对每个区域进行二值化处理,然后提取局部二值模式特征。LBPH算法具有简单易实现、鲁棒性强等优点,在小样本情况下也表现良好,因此得到了广泛应用。

Python语言和OpenCV库是构建人脸识别系统的关键工具,PyQt5则可以用来构建用户界面,SQLite数据库则可用于存储人脸特征数据和识别结果等信息。

4、核心代码



import sys,os
from PyQt5.QtWidgets import QApplication,QWidget,QPushButton,QLabel,QLineEdit,QVBoxLayout,QGroupBox,QHBoxLayout,QRadioButton,QMessageBox
from PyQt5 import QtCore, QtGui, QtWidgets,uic
from PyQt5.QtCore import *
from PyQt5.QtGui import QImage, QIcon, QPixmap
from datetime import datetime
import time
import glob
from model.SSD import FaceMaskDetection
from model.FACENET import InceptionResnetV1
from model.Facecnn import FaceCNN
import torch
from PIL import Image,ImageDraw,ImageFont
import tqdm
import resource
import numpy as np
from statistics import mode
#调用opencv2
import cv2
import dlib
import imutils
from imutils import face_utils
from scipy.spatial import distance as dist
from jishiqi import jishi




# Create arrays of known face encodings and their names



class MyWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        # ###################### 窗口初始化 ######################
        self.ui=uic.loadUi("./ui/MainUI.ui")


        #print(self.ui.__init__)
        #显示所有控件
        #print(self.ui.__dict__)
        # for k,v in self.ui.__dict__.items():
        #     print(k)
        # print(self.ui.label_riqi.text())

        # 创建一个定时器
        timer = QTimer(self)
        # 每隔1000ms刷新一次
        timer.setInterval(1000)
        # 连接定时器信号到槽函数
        timer.timeout.connect(self.showTime)
        # 启动定时器
        timer.start()

        # ####################### 初始数据 ######################





        # ####################### 人脸参数 ######################
        # 加载检测模型
        face_mask_model_path = r'weights/SSD/face_mask_detection.pb'
        self.ssd_detector = FaceMaskDetection(face_mask_model_path, margin=0, GPU_ratio=0.1)

        # 加载识别模型
        self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
        # 实例化
        self.facenet = InceptionResnetV1(is_train=False, embedding_length=128, num_classes=14575).to(self.device)
        # 从训练文件中加载
        self.facenet.load_state_dict(
            torch.load(r'./weights/4.Chinese_CASIA_ALL_AG_epoch/facenet_best.pt', map_location=self.device))
        self.facenet.eval()

        # 加载目标人的特征
        # name_list支持:恩培,恩培_1,,恩培_2形式,与known_embedding对应
        self.name_list, self.known_embedding = self.loadFaceFeats()
        # 增加一个未知人员
        self.name_list.append('未知')
        # 生成每个人的名称PNG图片(以解决中文显示问题)
        self.name_png_list = self.getNamePngs(self.name_list)
        # 加载佩戴和未佩戴标志
        self.mask_class_overlay = self.getMaskClassPngs()




        # ###################### 摄像头初始化 ######################
        # 初始化摄像头,默认调用第一个摄像头
        # self.url = 0
        # 如果要调用笔记本外接USB摄像头,则设置为1,默认为0
        self.url = 0
        self.cap = cv2.VideoCapture()
        # self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 500)
        # self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 400)
        # self.cap.set(cv2.CAP_PROP_FPS, 20)


        # ###################### 按键的槽函数 ######################
        # 设置摄像头按键连接函数
        self.ui.bt_open_camera.clicked.connect(self.open_camera)
        self.ui.bt_close_camera.clicked.connect(self.close_camera)
        #检测ID
        self.ui.bt_start_check.clicked.connect(self.open_camera1)
        #检测表情
        self.ui.bt_gathering.clicked.connect(self.open_camera2)
        #疲劳检测
        self.ui.bt_pilao.clicked.connect(self.open_camera3)
        # 训练
        self.ui.bt_generator.clicked.connect(self.loadFaceFeats)
        self.ui.pushButton_3.clicked.connect(self.tuichu)

        # ###################### 数据库相关操作 ######################
        # 初始化需要记录的人名
        self.record_name = []
        # 设置更新人脸数据库的按键连接函数
        self.js = jishi()

    # 进入考勤模式,通过switch_bt进行控制的函数
    def auto_control(self):
        self.check_time_set = self.format_check_time_set()
        print( self.check_time_set)
        self.ui.label_start_time.setText("当前考勤开始时间为:"+str(self.check_time_set))
        # if self.check_time_set == '':
        #     QMessageBox.warning(self, "Warning", "请先设定考勤时间(例 08:00)!", QMessageBox.Ok)
        # else:
        if self.cap.isOpened():
            if self.switch_bt == 0:
                self.switch_bt = 1
                self.ui.bt_start_check.setText(u'退出考勤')
                self.show_camera()
            elif self.switch_bt == 1:
                self.switch_bt = 0
                self.ui.bt_start_check.setText(u'开始考勤')
                self.show_camera()
            else:
                print("[Error] The value of self.switch_bt must be zero or one!")
        else:
            QMessageBox.information(self, "提示", "请先打开摄像头!", QMessageBox.Ok)


    def open_camera(self):
        # 判断摄像头是否打开,如果打开则为true,反之为false
        print(self.cap.isOpened())
        if not self.cap.isOpened():
        #     # 默认打开Windows系统笔记本自带的摄像头,如果是外接USB,可将0改成1
            self.cap.open(self.url)
            self.ui.textBrowser_log.append("相机已打开...")
            self.show_camera1()

        else:
            #self.cap.release()
            #self.ui.label_logo.clear()
            print("相机已打开")
            self.ui.textBrowser_log.append("相机已打开...")
            #self.ui.label_camera.clear()
            #self.ui.bt_open_camera.setText(u'打开相机')

    def open_camera1(self):
        # 判断摄像头是否打开,如果打开则为true,反之为false
        print(self.cap.isOpened())
        if not self.cap.isOpened():
        #     # 默认打开Windows系统笔记本自带的摄像头,如果是外接USB,可将0改成1
            self.cap.open(self.url)
            self.ui.textBrowser_log.append("相机已打开...")
            self.show_camera()

        else:
            #self.cap.release()
            #self.ui.label_logo.clear()
            print("相机已打开")
            self.ui.textBrowser_log.append("相机已打开...")
            #self.ui.label_camera.clear()
            #self.ui.bt_open_camera.setText(u'打开相机')

    def open_camera2(self):
        # 判断摄像头是否打开,如果打开则为true,反之为false
        print(self.cap.isOpened())
        if not self.cap.isOpened():
            #     # 默认打开Windows系统笔记本自带的摄像头,如果是外接USB,可将0改成1
            self.cap.open(self.url)
            self.ui.textBrowser_log.append("相机已打开...")
            self.show_biaoqing()

        else:
            # self.cap.release()
            # self.ui.label_logo.clear()
            print("相机已打开")
            self.ui.textBrowser_log.append("相机已打开...")
            # self.ui.label_camera.clear()
            # self.ui.bt_open_camera.setText(u'打开相机')


    def open_camera3(self):
        # 判断摄像头是否打开,如果打开则为true,反之为false
        print(self.cap.isOpened())
        if not self.cap.isOpened():
            #     # 默认打开Windows系统笔记本自带的摄像头,如果是外接USB,可将0改成1
            self.cap.open(self.url)
            self.ui.textBrowser_log.append("相机已打开...")
            self.show_pilao()

        else:
            # self.cap.release()
            # self.ui.label_logo.clear()
            print("相机已打开")
            self.ui.textBrowser_log.append("相机已打开...")
            # self.ui.label_camera.clear()
            # self.ui.bt_open_camera.setText(u'打开相机')

    def getMaskClassPngs(self):
        '''
        加载佩戴和未佩戴标志
        '''
        labels = ['masked','without_mask']
        overlay_list = []
        for label in labels:
            fileName = './images/%s.png' % (label)
            overlay = cv2.imread(fileName,cv2.COLOR_RGB2BGR)
            overlay = cv2.resize(overlay,(0,0), fx=0.2, fy=0.2)
            overlay_list.append(overlay)
        return overlay_list

    def close_camera(self):
        if self.cap.isOpened():
            self.cap.release()
            self.ui.textBrowser_log.append("相机已关闭...")
        else:
            print("相机已关闭")
            self.ui.textBrowser_log.append("相机已关闭...")


    def readPngFile(self,fileName):
        '''
        读取PNG图片
        '''
        # 解决中文路径问题
        png_img = cv2.imdecode(np.fromfile(fileName,dtype=np.uint8),-1)
        # 转为BGR,变成3通道
        png_img = cv2.cvtColor(png_img,cv2.COLOR_RGB2BGR)
        png_img = cv2.resize(png_img,(0,0), fx=0.4, fy=0.4)
        return png_img

    def getNamePngs(self, name_list):
        '''
        生成每个人的名称PNG图片(以解决中文显示问题)
        '''
        # 先将['恩培','恩培_1','恩培_2','小明','小明_1','小明_2']变成['恩培','小明']
        real_name_list = []
        for name in name_list:
            real_name = name.split('_')[0]
            if real_name not in real_name_list:
                real_name_list.append(real_name)

        pngs_list = {}
        for name in tqdm.tqdm(real_name_list, desc='生成人脸标签PNG...'):

            filename = './images/name_png/' + name + '.png'
            # 如果存在,直接读取
            if os.path.exists(filename):
                png_img = self.readPngFile(filename)
                pngs_list[name] = png_img
                continue

            # 如果不存在,先生成
            # 背景
            bg = Image.new("RGBA", (400, 100), (0, 0, 0, 0))
            # 添加文字
            d = ImageDraw.Draw(bg)
            font = ImageFont.truetype('./fonts/MSYH.ttc', 80, encoding="utf-8")

            if name == '未知':
                color = (0, 0, 255, 255)
            else:
                color = (0, 255, 0, 255)

            d.text((0, 0), name, font=font, fill=color)
            # 保存
            bg.save(filename)
            # 再次检查
            if os.path.exists(filename):
                png_img = self.readPngFile(filename)
                pngs_list[name] = png_img

        return pngs_list

    def loadFaceFeats(self):
        '''
        加载目标人的特征
        '''
        # 记录名字
        name_list = []
        # 输入网络的所有人脸图片
        known_faces_input = []
        # 遍历
        known_face_list = glob.glob('./images/origin/*')
        for face in tqdm.tqdm(known_face_list, desc='处理目标人脸...'):
            name = face.split('\\')[-1].split('.')[0]
            name_list.append(name)
            # 裁剪人脸
            croped_face = self.getCropedFaceFromFile(face)
            if croped_face is None:
                print('图片:{} 未检测到人脸,跳过'.format(face))
                continue
            # 预处理
            img_input = self.imgPreprocess(croped_face)
            known_faces_input.append(img_input)
        # 转为Nummpy
        faces_input = np.array(known_faces_input)
        # 转tensor并放到GPU
        tensor_input = torch.from_numpy(faces_input).to(self.device)
        # 得到所有的embedding,转numpy
        known_embedding = self.facenet(tensor_input).detach().cpu().numpy()

        return name_list, known_embedding


    def getCropedFaceFromFile(self, img_file, conf_thresh=0.5):

        # 读取图片
        # 解决中文路径问题
        img_ori = cv2.imdecode(np.fromfile(img_file, dtype=np.uint8), -1)

        if img_ori is None:
            return None
        # 转RGB
        img = cv2.cvtColor(img_ori, cv2.COLOR_BGR2RGB)
        # 缩放
        img = cv2.resize(img, self.ssd_detector.img_size)
        # 转float32
        img = img.astype(np.float32)
        # 归一
        img /= 255
        # 增加维度
        img_4d = np.expand_dims(img, axis=0)
        # 原始高度和宽度
        ori_h, ori_w = img_ori.shape[:2]
        bboxes, re_confidence, re_classes, re_mask_id = self.ssd_detector.inference(img_4d, ori_h, ori_w)
        for index, bbox in enumerate(bboxes):
            class_id = re_mask_id[index]
            l, t, r, b = bbox[0], bbox[1], bbox[0] + bbox[2], bbox[1] + bbox[3]

            croped_face = img_ori[t:b, l:r]
            return croped_face

        # 都不满足
        return None

    def imgPreprocess(self, img):
        # 转为float32
        img = img.astype(np.float32)
        # 缩放
        img = cv2.resize(img, (112, 112))
        # BGR 2 RGB
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        # h,w,c 2 c,h,w
        img = img.transpose((2, 0, 1))
        # 归一化[0,255] 转 [-1,1]
        img = (img - 127.5) / 127.5
        # 增加维度
        # img = np.expand_dims(img,0)

        return img
    def show_camera1(self):
        cap = cv2.VideoCapture(0)
        while self.cap.isOpened():

            ret, frame = cap.read()
            # 告诉QT处理来处理任何没有被处理的事件,并且将控制权返回给调用者,让代码变的没有那么卡
            QApplication.processEvents()

            show = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)  # 这里指的是显示原图

            # opencv 读取图片的样式,不能通过Qlabel进行显示,需要转换为Qimage QImage(uchar * data, int width,
            self.showImage = QImage(show.data, show.shape[1], show.shape[0], QImage.Format_RGB888)
            self.ui.label_camera.setPixmap(QPixmap.fromImage(self.showImage))


    def show_camera(self):
        # Initialize some variables


        print("isOpened",self.cap.isOpened())
        #self.ui.bt_open_camera.setText('关闭相机')

        threshold=1
        cap = cv2.VideoCapture(0)
        frame_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
        frame_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        while self.cap.isOpened():
            start_time = time.time()
            # 以BGR格式读取图像

            ret,frame = cap.read()
            #print(len(frame))
            frame = cv2.flip(frame, 1)
            # 转RGB
            img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            # 缩放
            img = cv2.resize(img, self.ssd_detector.img_size)
            # 转float32
            img = img.astype(np.float32)
            # 归一
            img /= 255
            # 增加维度
            img_4d = np.expand_dims(img, axis=0)
            bboxes, re_confidence, re_classes, re_mask_id = self.ssd_detector.inference(img_4d, frame_h, frame_w)

            for index, bbox in enumerate(bboxes):
                class_id = re_mask_id[index]
                conf = re_confidence[index]

                if class_id == 0:
                    color = (0, 255, 0)  # 戴口罩
                elif class_id == 1:
                    color = (0, 0, 255)  # 没带口罩

                l, t, r, b = bbox[0], bbox[1], bbox[0] + bbox[2], bbox[1] + bbox[3]

                # cv2.putText(frame,str(round(conf,2)),(l,t-10),cv2.FONT_ITALIC,1,(0,255,0),1)

                # 裁剪人脸
                crop_face = frame[t:b, l:r]

                # 人脸识别

                # 转为float32
                img = crop_face.astype(np.float32)
                # 缩放
                img = cv2.resize(img, (112, 112))
                # BGR 2 RGB
                img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
                # h,w,c 2 c,h,w
                img = img.transpose((2, 0, 1))
                # 归一化[0,255] 转 [-1,1]
                img = (img - 127.5) / 127.5
                # 扩展维度
                img_input = np.expand_dims(img, 0)
                # C连续特性
                # img_input = np.ascontiguousarray(img_input)
                # 转tensor并放到GPU
                tensor_input = torch.from_numpy(img_input).to(self.device)
                # 得到embedding
                embedding = self.facenet(tensor_input)
                embedding = embedding.detach().cpu().numpy()
                # print(embedding)
                # 计算距离
                dist_list = np.linalg.norm((embedding - self.known_embedding), axis=1)
                # 最小距离索引
                min_index = np.argmin(dist_list)
                # 识别人名与距离
                pred_name = self.name_list[min_index]
                # 最短距离
                min_dist = dist_list[min_index]

                if min_dist < threshold:
                    # 识别到人
                    # 人名png
                    real_name = pred_name.split('_')[0]
                    name_overlay = self.name_png_list[real_name]
                else:
                    # 未识别到,加载未知
                    name_overlay = self.name_png_list['未知']

                # √和×标志
                class_overlay = self.mask_class_overlay[class_id]

                # 拼接两个PNG
                overlay = np.zeros((40, 210, 3), np.uint8)
                overlay[:40, :40] = class_overlay
                overlay[:40, 50:210] = name_overlay

                # 覆盖显示
                overlay_h, overlay_w = overlay.shape[:2]
                # 覆盖范围
                overlay_l, overlay_t = l, (t - overlay_h - 20)
                overlay_r, overlay_b = (l + overlay_w), (overlay_t + overlay_h)
                # 判断边界
                if overlay_t > 0 and overlay_r < frame_w:
                    overlay_copy = cv2.addWeighted(frame[overlay_t:overlay_b, overlay_l:overlay_r], 1, overlay, 20,
                                                   0)

                    frame[overlay_t:overlay_b, overlay_l:overlay_r] = overlay_copy

                print(pred_name, min_dist)

                cv2.rectangle(frame, (l, t), (r, b), color, 2)

            fps = 1 / (time.time() - start_time)
            cv2.putText(frame, str(round(fps, 2)), (50, 50), cv2.FONT_ITALIC, 1, (0, 255, 0), 2)

            # 告诉QT处理来处理任何没有被处理的事件,并且将控制权返回给调用者,让代码变的没有那么卡
            QApplication.processEvents()

            show = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)  # 这里指的是显示原图

            # opencv 读取图片的样式,不能通过Qlabel进行显示,需要转换为Qimage QImage(uchar * data, int width,
            self.showImage = QImage(show.data, show.shape[1], show.shape[0], QImage.Format_RGB888)
            self.ui.label_camera.setPixmap(QPixmap.fromImage(self.showImage))


            # 因为最后会存留一张图像在lable上,需要对lable进行清理
        self.ui.label_camera.clear()

    def preprocess_input(self,images):
        """ preprocess input by substracting the train mean
        # Arguments: images or image of any shape
        # Returns: images or image with substracted train mean (129)
        """
        images = images / 255.0
        return images



    def cv2AddChineseText(self,img, text, position, textColor=(0, 255, 0), textSize=30):
        if (isinstance(img, np.ndarray)):  # 判断是否OpenCV图片类型
            img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
        # 创建一个可以在给定图像上绘图的对象
        draw = ImageDraw.Draw(img)
        # 字体的格式
        fontStyle = ImageFont.truetype(
            "./font/simsun.ttc", textSize, encoding="utf-8")
        # 绘制文本
        draw.text(position, text, textColor, font=fontStyle)
        # 转换回OpenCV格式
        return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)

    def show_biaoqing(self):

        # ####################### 表情参数 ######################

        detection_model_path = './model/haarcascade_frontalface_default.xml'
        classification_model_path = './model/model_net.pkl'
        frame_window = 10

        emotion_labels = {0: '生气', 1: '厌恶', 2: '正常', 3: '高兴', 4: '悲哀', 5: '惊喜', 6: '正常'}

        # 加载人脸检测模型
        face_detection = cv2.CascadeClassifier(detection_model_path)

        # 加载表情识别
        emotion_classifier = torch.load(classification_model_path)
        emotion_window = []
        #font = cv2.FONT_HERSHEY_SIMPLEX



        cap = cv2.VideoCapture(0)
        while self.cap.isOpened():

            # 读取一帧
            _, frame = cap.read()
            # 获得灰度图,并且在内存中创建一个图像对象
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            # 获取当前帧中的全部人脸
            # 其中gray为要检测的灰度图像,1.3为每次图像尺寸减小的比例,5为minNeighbors
            faces = face_detection.detectMultiScale(gray, 1.3, 5)
            # 对于所有发现的人脸
            for (x, y, w, h) in faces:
                # 在脸周围画一个矩形框,(255,0,0)是颜色,2是线宽
                cv2.rectangle(frame, (x, y), (x + w, y + h), (84, 255, 159), 2)

                # 获取人脸图像
                face = gray[y:y + h, x:x + w]

                try:
                    # shape变为(48,48)
                    face = cv2.resize(face, (48, 48))
                except:
                    continue

                # 扩充维度,shape变为(1,48,48,1)
                # 将(1,48,48,1)转换成为(1,1,48,48)
                face = np.expand_dims(face, 0)
                face = np.expand_dims(face, 0)
                # 人脸数据归一化,将像素值从0-255映射到0-1之间
                face = self.preprocess_input(face)
                new_face = torch.from_numpy(face)
                new_new_face = new_face.float().requires_grad_(False)

                # 调用我们训练好的表情识别模型,预测分类
                emotion_arg = np.argmax(emotion_classifier.forward(new_new_face).detach().numpy())
                emotion = emotion_labels[emotion_arg]

                emotion_window.append(emotion)

                if len(emotion_window) >= frame_window:
                    emotion_window.pop(0)

                try:
                    # 获得出现次数最多的分类
                    emotion_mode = mode(emotion_window)
                except:
                    continue

                # 在矩形框上部,输出分类文字
                #cv2.putText(frame, emotion_mode, (x, y - 30), font, .7, (255, 0, 0), 1, cv2.LINE_AA)
                frame = self.cv2AddChineseText(frame, emotion_mode, (x, y-30), (0, 255, 0), 30)

            # 告诉QT处理来处理任何没有被处理的事件,并且将控制权返回给调用者,让代码变的没有那么卡
            QApplication.processEvents()

            show = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)  # 这里指的是显示原图

            # opencv 读取图片的样式,不能通过Qlabel进行显示,需要转换为Qimage QImage(uchar * data, int width,
            self.showImage = QImage(show.data, show.shape[1], show.shape[0], QImage.Format_RGB888)
            self.ui.label_camera.setPixmap(QPixmap.fromImage(self.showImage))
    def show_pilao(self):

        # 定义开始时间
        self.kaishishijian = datetime.now()
        # 眼睛长宽比
        # 闪烁阈值
        EYE_AR_THRESH = 0.2
        EYE_AR_CONSEC_FRAMES = 3
        # 初始化帧计数器和眨眼总数
        COUNTER = 0
        TOTAL = 0

        # 初始化DLIB的人脸检测器(HOG),然后创建面部标志物预测

        self.ui.textBrowser_log.append("加载面部特征检测器")
        # 第一步:使用dlib.get_frontal_face_detector() 获得脸部位置检测器
        face_detector = dlib.get_frontal_face_detector()
        # 第二步:使用dlib.shape_predictor获得脸部特征位置检测器
        predictor = dlib.shape_predictor('./model/shape_predictor_68_face_landmarks.dat')
        # 第三步:分别获取左右眼面部标志的索引
        (lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS["left_eye"]
        (rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS["right_eye"]
        # 初始化循环次数,比如统计10帧中人脸的数量,取最大值进行考勤
        loop_num = 0



        while self.cap.isOpened():
            loop_num += 1
            # 从线程视频流中抓取帧
            ret, frame = self.cap.read()
            QApplication.processEvents()

            # 调整框架的大小以使其宽度为900像素(同时保持纵横比),然后抓取图像尺寸
            frame = imutils.resize(frame, width=700)
            (h, w) = frame.shape[:2]
            # 从图像构造一个blob, 缩放为 300 x 300 x 3 像素的图像,为了符合ResNet-SSD的输入尺寸
            # OpenCV Blog的使用可参考:https://www.pyimagesearch.com/2017/11/06/deep-learning-opencvs-blobfromimage-works/
            image_blob = cv2.dnn.blobFromImage(
                cv2.resize(frame, (300, 300)), 1.0, (300, 300),
                (104.0, 177.0, 123.0), swapRB=False, crop=False)



            # 在检测结果中循环检测
            # 注意:这里detection为ResNet-SSD网络的输出,与阈值的设置有关,具体可以参考prototxt文件的输出层,输出shape为[1, 1, 200, 7]
            # 7 表示的含义分别为 [batch Id, class Id, confidence, left, top, right, bottom]
            # 200 表示检测到的目标数量,具体可参考SSD的论文,针对每幅图像,SSD最终会预测8000多个边界框,通过NMS过滤掉IOU小于0.45的框,剩余200个。


                # 图片做维度扩大,并进灰度化
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            # 使用detector(gray, 0) 进行脸部位置检测
            rects = face_detector(gray, 0)

            # 第七步:循环脸部位置信息,使用predictor(gray, rect)获得脸部特征位置的信息
            # print("rects",rects)
            if len(rects) > 0:

                for rect in rects:
                    shape = predictor(gray, rect)

                    # 第八步:将脸部特征信息转换为数组array的格式
                    shape = face_utils.shape_to_np(shape)

                    # 第九步:提取左眼和右眼坐标
                    leftEye = shape[lStart:lEnd]
                    rightEye = shape[rStart:rEnd]

                    # 第十步:构造函数计算左右眼的EAR值,使用平均值作为最终的EAR
                    leftEAR = self.eye_aspect_ratio(leftEye)
                    rightEAR = self.eye_aspect_ratio(rightEye)
                    ear = (leftEAR + rightEAR) / 2.0

                    # 第十一步:使用cv2.convexHull获得凸包位置,使用drawContours画出轮廓位置进行画图操作
                    leftEyeHull = cv2.convexHull(leftEye)
                    rightEyeHull = cv2.convexHull(rightEye)
                    cv2.drawContours(frame, [leftEyeHull], -1, (0, 255, 0), 1)
                    cv2.drawContours(frame, [rightEyeHull], -1, (0, 255, 0), 1)

                    # 第十二步:进行画图操作,用矩形框标注人脸
                    left = rect.left()
                    top = rect.top()
                    right = rect.right()
                    bottom = rect.bottom()
                    cv2.rectangle(frame, (left, top), (right, bottom), (0, 255, 0), 3)

                    '''
                        分别计算左眼和右眼的评分求平均作为最终的评分,如果小于阈值,则加1,如果连续3次都小于阈值,则表示进行了一次眨眼活动
                    '''
                    # 第十三步:循环,满足条件的,眨眼次数+1
                    if ear < EYE_AR_THRESH:  # 眼睛长宽比:0.2
                        COUNTER += 1

                    else:
                        # 如果连续3次都小于阈值,则表示进行了一次眨眼活动
                        if COUNTER >= EYE_AR_CONSEC_FRAMES:  # 阈值:3
                            TOTAL += 1

                        # 重置眼帧计数器
                        COUNTER = 0

                    # 第十四步:进行画图操作,68个特征点标识
                    for (x, y) in shape:
                        cv2.circle(frame, (x, y), 1, (0, 0, 255), -1)

                    # 第十五步:进行画图操作,同时使用cv2.putText将眨眼次数进行显示
                    cv2.putText(frame, "Faces: {}".format(len(rects)), (10, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.7,
                                (0, 0, 255), 2)
                    cv2.putText(frame, "Blinks: {}".format(TOTAL), (150, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.7,
                                (0, 0, 255), 2)
                    cv2.putText(frame, "COUNTER: {}".format(COUNTER), (300, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.7,
                                (0, 0, 255), 2)
                    cv2.putText(frame, "EAR: {:.2f}".format(ear), (450, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255),
                                2)

                print('眼睛实时长宽比:{:.2f} '.format(ear))
                if TOTAL >= 10:
                    cv2.putText(frame, "tired!!!", (200, 200), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)
                    self.js.start()
                else:
                    self.js.stop()
            else:
                cv2.putText(frame, "Shake head!!!", (10, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.7,
                            (0, 0, 255), 2)
                self.js.start()

                # 窗口显示 show with opencv
                # cv2.imshow("Frame", frame)



            # 显示输出框架
            show_video = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)  # 这里指的是显示原图
            # opencv读取图片的样式,不能通过Qlabel进行显示,需要转换为Qimage。
            # QImage(uchar * data, int width, int height, int bytesPerLine, Format format)
            self.showImage = QImage(show_video.data, show_video.shape[1], show_video.shape[0],
                                    QImage.Format_RGB888)
            self.ui.label_camera.setPixmap(QPixmap.fromImage(self.showImage))


    def eye_aspect_ratio(self,eye):
        # 垂直眼标志(X,Y)坐标
        A = dist.euclidean(eye[1], eye[5])  # 计算两个集合之间的欧式距离
        B = dist.euclidean(eye[2], eye[4])
        # 计算水平之间的欧几里得距离
        # 水平眼标志(X,Y)坐标
        C = dist.euclidean(eye[0], eye[3])
        # 眼睛长宽比的计算
        ear = (A + B) / (2.0 * C)
        # 返回眼睛的长宽比
        return ear


    def tuichu(self):
        self.box = QMessageBox(QMessageBox.Warning, "系统提示信息", "是否退出系统?")
        qyes = self.box.addButton(self.tr("是"), QMessageBox.YesRole)
        qno = self.box.addButton(self.tr("否"), QMessageBox.NoRole)
        print(qyes, qno)
        self.box.exec_()
        if self.box.clickedButton() == qyes:
            if not self.cap.isOpened():
                sys.exit().accept()
            else:
                self.cap.release()
                sys.exit().accept()
        else:
            return

    def showTime(self):
        # # 获取当前时间并转换为字符串
        # currentTime = QDateTime.currentDateTime().toString('yyyy-MM-dd hh:mm:ss')
        # # 在标签上显示当前时间
        # self.ui.label_riqi.setText(currentTime)

        # 设置宽度
        #self.ui.label_riqi.setFixedWidth(200)
        # 设置显示文本格式
        # self.ui.label_riqi.setStyleSheet(
        #     # "QLabel{background:white;}" 此处设置背景色
        #     "QLabel{color:rgb(0, 0, 0); font-size:14px; font-weight:bold; font-family:宋体;}"
        #     "QLabel{font-size:14px; font-weight:bold; font-family:宋体;}")

        current_datetime = QDateTime.currentDateTime().toString("yyyy-MM-dd hh:mm:ss dddd")
        #print(current_datetime)
        self.ui.label_riqi.setText("" + current_datetime)

        # 格式化设定的考勤时间

    def format_check_time_set(self):
        """
        格式化考勤时间,方便比较
        :return: datetime.datetime格式,相见之后为timedelta格式,具有seconds,hours,minutes,days属性
        """
        # 获取完整的时间格式
        now = datetime.now()
        # 分别获取当前的年,月,日,时,分,秒,均为int类型
        judg_time = now
        now_y = judg_time.year
        now_m = judg_time.month
        now_d = judg_time.day

        original_hour = str(self.ui.spinBox_time_hour.text())


        # 格式化考勤时间
        att_time = datetime.strptime(f'{now_y}-{now_m}-{now_d} {original_hour}:00', '%Y-%m-%d %H:%M:%S')

        return att_time










if __name__=='__main__':
    app=QApplication(sys.argv)

    #创建Qwidget子类
    w=MyWindow()
    w.ui.show()

    #app.exec_()
    sys.exit(app.exec_())

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 445
  • 446
  • 447
  • 448
  • 449
  • 450
  • 451
  • 452
  • 453
  • 454
  • 455
  • 456
  • 457
  • 458
  • 459
  • 460
  • 461
  • 462
  • 463
  • 464
  • 465
  • 466
  • 467
  • 468
  • 469
  • 470
  • 471
  • 472
  • 473
  • 474
  • 475
  • 476
  • 477
  • 478
  • 479
  • 480
  • 481
  • 482
  • 483
  • 484
  • 485
  • 486
  • 487
  • 488
  • 489
  • 490
  • 491
  • 492
  • 493
  • 494
  • 495
  • 496
  • 497
  • 498
  • 499
  • 500
  • 501
  • 502
  • 503
  • 504
  • 505
  • 506
  • 507
  • 508
  • 509
  • 510
  • 511
  • 512
  • 513
  • 514
  • 515
  • 516
  • 517
  • 518
  • 519
  • 520
  • 521
  • 522
  • 523
  • 524
  • 525
  • 526
  • 527
  • 528
  • 529
  • 530
  • 531
  • 532
  • 533
  • 534
  • 535
  • 536
  • 537
  • 538
  • 539
  • 540
  • 541
  • 542
  • 543
  • 544
  • 545
  • 546
  • 547
  • 548
  • 549
  • 550
  • 551
  • 552
  • 553
  • 554
  • 555
  • 556
  • 557
  • 558
  • 559
  • 560
  • 561
  • 562
  • 563
  • 564
  • 565
  • 566
  • 567
  • 568
  • 569
  • 570
  • 571
  • 572
  • 573
  • 574
  • 575
  • 576
  • 577
  • 578
  • 579
  • 580
  • 581
  • 582
  • 583
  • 584
  • 585
  • 586
  • 587
  • 588
  • 589
  • 590
  • 591
  • 592
  • 593
  • 594
  • 595
  • 596
  • 597
  • 598
  • 599
  • 600
  • 601
  • 602
  • 603
  • 604
  • 605
  • 606
  • 607
  • 608
  • 609
  • 610
  • 611
  • 612
  • 613
  • 614
  • 615
  • 616
  • 617
  • 618
  • 619
  • 620
  • 621
  • 622
  • 623
  • 624
  • 625
  • 626
  • 627
  • 628
  • 629
  • 630
  • 631
  • 632
  • 633
  • 634
  • 635
  • 636
  • 637
  • 638
  • 639
  • 640
  • 641
  • 642
  • 643
  • 644
  • 645
  • 646
  • 647
  • 648
  • 649
  • 650
  • 651
  • 652
  • 653
  • 654
  • 655
  • 656
  • 657
  • 658
  • 659
  • 660
  • 661
  • 662
  • 663
  • 664
  • 665
  • 666
  • 667
  • 668
  • 669
  • 670
  • 671
  • 672
  • 673
  • 674
  • 675
  • 676
  • 677
  • 678
  • 679
  • 680
  • 681
  • 682
  • 683
  • 684
  • 685
  • 686
  • 687
  • 688
  • 689
  • 690
  • 691
  • 692
  • 693
  • 694
  • 695
  • 696
  • 697
  • 698
  • 699
  • 700
  • 701
  • 702
  • 703
  • 704
  • 705
  • 706
  • 707
  • 708
  • 709
  • 710
  • 711
  • 712
  • 713
  • 714
  • 715
  • 716
  • 717
  • 718
  • 719
  • 720
  • 721
  • 722
  • 723
  • 724
  • 725
  • 726
  • 727
  • 728
  • 729
  • 730
  • 731
  • 732
  • 733
  • 734
  • 735
  • 736
  • 737
  • 738
  • 739
  • 740
  • 741
  • 742
  • 743
  • 744
  • 745
  • 746
  • 747
  • 748
  • 749
  • 750
  • 751
  • 752
  • 753
  • 754
  • 755
  • 756
  • 757
  • 758
  • 759
  • 760
  • 761
  • 762
  • 763
  • 764
  • 765
  • 766
  • 767
  • 768
  • 769
  • 770
  • 771
  • 772
  • 773
  • 774
  • 775
  • 776
  • 777
  • 778
  • 779
  • 780
  • 781
  • 782
  • 783
  • 784
  • 785
  • 786
  • 787
  • 788
  • 789
  • 790
  • 791
  • 792
  • 793
  • 794
  • 795
  • 796
  • 797
  • 798
  • 799
  • 800
  • 801
  • 802
  • 803
  • 804
  • 805
  • 806
  • 807
  • 808
  • 809
  • 810
  • 811
  • 812
  • 813
  • 814
  • 815
  • 816
  • 817
  • 818
  • 819
  • 820
  • 821
  • 822
  • 823
  • 824
  • 825
  • 826
  • 827
  • 828
  • 829
  • 830
  • 831
  • 832
  • 833
  • 834
  • 835
  • 836
  • 837
  • 838
  • 839
  • 840
  • 841
  • 842

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/606574
推荐阅读
相关标签