Python | 人脸识别系统 — 活体检测

news2024/11/24 17:11:32

本博客为人脸识别系统的活体检测代码解释

人脸识别系统博客汇总:人脸识别系统-博客索引

项目GitHub地址:

注意:阅读本博客前请先参考以下博客

工具安装、环境配置:人脸识别系统-简介

UI界面设计:人脸识别系统-UI界面设计

UI事件处理:人脸识别系统-UI事件处理

摄像头画面展示:人脸识别系统-摄像头画面展示

阅读完本博客后可以继续阅读:

用户端逻辑:

  • 人脸识别:Python | 人脸识别系统 — 人脸识别
  • 背景模糊:Python | 人脸识别系统 — 背景模糊
  • 姿态检测:Python | 人脸识别系统 — 姿态检测
  • 人脸比对:Python | 人脸识别系统 — 人脸比对
  • 用户操作:Python | 人脸识别系统 — 用户操作

管理员端逻辑:

  • 管理员操作:
  • 用户操作:

一、基本思路

代码使用 静默活体检测+交互活体检测 结合判断。

        静默活体检测使用百度API,通过接口返回的置信度,判断是否通过。

        交互活体检测通过要求用户完成一定动作,判断是否通过。

二、初始化

        初始化 isFaceRecognition_flag 标志判断当前人脸识别状态。

        按钮绑定人脸识别判断器 recognize_face_judge 方法。

        其余属性会在后面讲到。

    def __init__(self, parent=None):
        super(UserMainWindow, self).__init__(parent)
        self.setupUi(self)

        self.isFaceDetection_flag = False  # 是否打开活体检测标志
        self.biopsy_testing_button.clicked.connect(self.detect_face_judge)  # 活体检测

        self.detector = None  # 人脸检测器
        self.predictor = None  # 特征点检测器
        # 闪烁阈值
        self.EAR_THRESH = None
        self.MOUTH_THRESH = None
        # 总闪烁次数
        self.eye_flash_counter = None
        self.mouth_open_counter = None
        self.turn_left_counter = None
        self.turn_right_counter = None
        # 连续帧数阈值
        self.EAR_CONSTANT_FRAMES = None
        self.MOUTH_CONSTANT_FRAMES = None
        self.LEFT_CONSTANT_FRAMES = None
        self.RIGHT_CONSTANT_FRAMES = None
        # 连续帧计数器
        self.eye_flash_continuous_frame = 0
        self.mouth_open_continuous_frame = 0
        self.turn_left_continuous_frame = 0
        self.turn_right_continuous_frame = 0
        # 字体颜色
        self.text_color = (255, 0, 0)
        # 百度API
        self.api = BaiduApiUtil

三、判断器

    # 活体检测判断器
    def detect_face_judge(self):
        if not self.cap.isOpened():
            QMessageBox.information(self, "提示", self.tr("请先打开摄像头"))
        else:
            if not self.isFaceDetection_flag:
                self.isFaceDetection_flag = True
                self.biopsy_testing_button.setText("关闭活体检测")
                self.detect_face()
                self.biopsy_testing_button.setText("活体检测")
                self.isFaceDetection_flag = False
            elif self.isFaceDetection_flag:
                self.isFaceDetection_flag = False
                self.remind_label.setText("")
                self.biopsy_testing_button.setText("活体检测")
                self.show_camera()

四、检测器

        首先判断当前环境是否联网(联网检测代码在BaiduApiUtil工具类中,工具类的代码在下面),联网进行静默活体检测+交互活体检测(联网检测),否则进行单独的交互活体检测(本地检测)。

    # 百度API
    self.api = BaiduApiUtil

    ... ...

    # 整体活体检测
    def detect_face(self):
        if self.api.network_connect_judge():
            if not self.detect_face_network():
                return False
        if not self.detect_face_local():
            return False
        return True


    # 联网活体检测
    def detect_face_network(self):
        ... ... 


    # 本地活体检测
    def detect_face_local(self):
        ... ...

1、静默活体检测

        静默活体检测用到了百度智能云接口,我们创建一个工具类 BaiduApiUtil,在工具类中编写网络连接、请求、解析结果等的代码。然后在用户界面逻辑代码中进行使用。

接口详情请参考 百度智能云-接口详情

代码示例请参考 百度智能云-代码示例

注意:使用前需要注册百度智能云账号,申请接口(接口免费),获取自己的API_KEY以及SECRET_KEY

        (1)工具类 BaiduApiUtil

                 a.联网判断

def network_connect_judge():
    """
    联网判断
    :return: 是否联网
    """
    ret = os.system("ping baidu.com -n 1")
    return True if ret == 0 else False

               b. 获取访问令牌

将申请百度接口的API_KEY等参数保存到.conf配置文件中(配置文件在当前项目的conf目录下),然后在使用ConfigParser读取并使用。

[baidu_config]
app_id = XXXXXXXXXXXXXXXXXXXXXXXX
secret_key = XXXXXXXXXXXXXXXXXXXXXXXX
def get_access_token():
    """
    获取访问令牌
    :return: 访问令牌
    """
    conf = ConfigParser()
    path = os.path.join(os.path.dirname(__file__))
    conf.read(path[:path.rindex('util')] + "conf\\setting.conf", encoding='gbk')

    API_KEY = conf.get('baidu_config', 'app_id')
    SECRET_KEY = conf.get('baidu_config', 'secret_key')

    url = "https://aip.baidubce.com/oauth/2.0/token"
    params = {"grant_type": "client_credentials", "client_id": API_KEY, "client_secret": SECRET_KEY}
    return str(requests.post(url, params=params).json().get("access_token"))

                c.接口调用

注意:进行API请求时,上传的图片格式为base64格式,我们传入的图片为jpg格式,故需要进行格式转换。通过base64.b64encode()方法进行转换

def face_api_invoke(path):
    """
    人脸 API 调用
    :param path: 待检测的图片路径
    :return: 是否通过静默人脸识别
    """
    with open(path, 'rb') as f:
        img_data = f.read()
        base64_data = base64.b64encode(img_data)
        base64_str = base64_data.decode('utf-8')
    url = "https://aip.baidubce.com/rest/2.0/face/v3/faceverify?access_token=" + get_access_token()
    headers = {'Content-Type': 'application/json'}
    payload = json.dumps(([{
        "image": base64_str,
        "image_type": "BASE64"
    }]))
    response = requests.request("POST", url, headers=headers, data=payload)
    print(response)
    result = json.loads(response.text)
    if result["error_msg"] == "SUCCESS":
        frr_1e_4 = result["result"]["thresholds"]["frr_1e-4"]
        frr_1e_3 = result["result"]["thresholds"]["frr_1e-3"]
        frr_1e_2 = result["result"]["thresholds"]["frr_1e-2"]
        face_liveness = result["result"]["face_liveness"]

        if face_liveness >= frr_1e_2:
            return True
        elif frr_1e_3 <= face_liveness <= frr_1e_2:
            return True
        elif face_liveness <= frr_1e_4:
            return False

         (2)用户主界面逻辑调用

    # 文件目录
    curPath = os.path.abspath(os.path.dirname(__file__))
    # 项目根路径
    rootPath = curPath[:curPath.rindex('logic')] # logic为存放用户界面逻辑代码的文件夹名
    # 配置文件夹路径
    CONF_FOLDER_PATH = rootPath + 'conf\\'
    # 图片文件夹路径
    PHOTO_FOLDER_PATH = rootPath + 'photo\\'
    # 数据文件夹路径
    DATA_FOLDER_PATH = rootPath + 'data\\'

    ... ...

    # 联网活体检测
    def detect_face_network(self):
        while self.cap.isOpened():
            ret, frame = self.cap.read()
            frame_location = face_recognition.face_locations(frame)
            if len(frame_location) == 0:
                QApplication.processEvents()
                self.remind_label.setText("未检测到人脸")
            else:
                global PHOTO_FOLDER_PATH
                shot_path = PHOTO_FOLDER_PATH + datetime.now().strftime("%Y%m%d%H%M%S") + ".jpg"
                self.show_image.save(shot_path)
                QApplication.processEvents()
                self.remind_label.setText("正在初始化\n请稍后")
                # 百度API进行活体检测
                QApplication.processEvents()
                if not self.api.face_api_invoke(shot_path):
                    os.remove(shot_path)
                    QMessageBox.about(self, '警告', '未通过活体检测')
                    self.remind_label.setText("")
                    return False
                else:
                    os.remove(shot_path)
                    return True

            show_video = cv2.cvtColor(cv2.resize(frame, (self.WIN_WIDTH, self.WIN_HEIGHT)), cv2.COLOR_BGR2RGB)
            self.show_image = QImage(show_video.data, show_video.shape[1], show_video.shape[0], QImage.Format_RGB888)
            self.camera_label.setPixmap(QPixmap.fromImage(self.show_image))

2、交互活体检测

        (1)基本原理

        采用开源框架dlib的shape_predictor_68_face_landmarks模型,对人脸的68个特征点进行检测定位。本系统活体检测主要检测人脸左摇头、右摇头、眨眼、张嘴、点头等多个动作,故需要鼻子[32,36]、左眼[37,42]、右眼[43,48]、上嘴唇内边缘[66,68]等多个部分的特征点集合。

        眨眼检测的基本原理是计算眼睛长宽比EAR(Eye Aspect Ratio)值。当人眼睁开时,EAR在某个值上下波动。当人眼闭合时,EAR将迅速下降,理论上接近于零,实际上一般波动于0.25上下,故本系统设置阈值在0.25。

        EAR的计算公式如下:

                 其中,p1~p5为当前眼睛的6个标记点,图示如下:

        (2)实现原理

        不断读取摄像头传回的每一帧,对该帧的EAR值进行计算。当EAR低于阈值时,自动将当前帧计数加一。当帧连续计数超过2帧后,EAR值大于阈值,则将该次动作视为一次眨眼

        同理,对张嘴、左摇头、右摇头的处理也是类似的。首先通过dlib获取当前器官的标记点,计算其长宽比,与系统预先指定的阈值进行比较。当长宽比小于阈值时,连续帧计数器自动加一。当连续帧计数器值超过指定值时,判断本次动作为一次有效的动作,进行记录。

        由于需要用户进行各种动作的完成,纸质或者电子照片基本上无法再通过本次活体检测。

        但对于视频,攻击者有可能使用预先录制的完成一定顺序动作的视频,以此欺骗系统。对于该情况,本团队的应对措施如下:

        系统需要用户完成左摇头、右摇头、眨眼、张嘴动作,其中张嘴以及眨眼指定的次数为指定数目。系统对上述动作进行随机打乱,并且张嘴以及眨眼指定的次数也为随机数。

        通过以上方式,用户在进行每一次的交互活体检测时,需要完成的方案都是完全不相同的,且完成的张嘴、眨眼次数也是不同的。当用户超过系统的指定时间未完成检测,则自动判断为活体检测失效。当用户超过3次进行登录的活体检测失败,系统将判断当前用户存在风险,并锁死当前登录的用户。被锁死的用户需要经过管理员通过管理员系统方可以解除锁定。

        通过以上的方式,对视频的欺骗攻击,本系统也有能力进行抵御阻挡。

        (3)代码详解

        a.初始化

        需要初始化的参数包括:特征点检测器 self.predictor、self.detector、闪烁阈值、总闪烁次数、连续帧数阈值、连续帧计数器、当前总帧数、检测随机值、面部特征点索引。

        特征点检测器:通过dlib的shape_predictor_68_face_landmarks模型,对人脸的68个特征点进行检测定位,首先需要进行模型的加载。由于模型加载时间较长,设置逻辑判断。当不是第一次使用活体检测时,使用已经加载好的属性,提高初始化时间。

        面部特征点索引:当前用户面部特征点的索引序号

        闪烁阈值、连续帧计数器:设置眨眼、张嘴的EAR、MAR阈值,当前帧用户的动作超过阈值时连续帧计数器加一。

       连续帧数阈值:当帧连续计数超过阈值设置的帧数后,EAR值大于阈值,则将该次动作视为一次眨眼或张嘴动作。

       总闪烁次数:用户需要完成的动作的次数。

       当前总帧数:从开始到当前时间 活体检测的帧数,超过系统指定帧数时则判断活体检测失败。

       检测随机值:包括随机次数的眨眼、张嘴次数,以及随机的动作集合,如(右转头-眨眼-张嘴-左转头)、(右转头-眨眼-左转头-张嘴)、(眨眼-张嘴-右转头-左转头)等

项目结构如下:

 其中 shape_predictor_68_face_landmarks.dat 文件在项目的data目录下。

# 本地活体检测
    def detect_face_local(self):
        self.detect_start_time = time()

        QApplication.processEvents()
        self.remind_label.setText("正在初始化\n请稍后")
        # 特征点检测器首次加载比较慢,通过判断减少后面加载的速度
        if self.detector is None:
            self.detector = dlib.get_frontal_face_detector()
        if self.predictor is None:
            self.predictor = dlib.shape_predictor('../data/shape_predictor_68_face_landmarks.dat')

        # 闪烁阈值
        self.EAR_THRESH = 0.25
        self.MOUTH_THRESH = 0.7

        # 总闪烁次数
        self.eye_flash_counter = 0
        self.mouth_open_counter = 0
        self.turn_left_counter = 0
        self.turn_right_counter = 0

        # 连续帧数阈值
        self.EAR_CONSTANT_FRAMES = 2
        self.MOUTH_CONSTANT_FRAMES = 2
        self.LEFT_CONSTANT_FRAMES = 4
        self.RIGHT_CONSTANT_FRAMES = 4

        # 连续帧计数器
        self.eye_flash_continuous_frame = 0
        self.mouth_open_continuous_frame = 0
        self.turn_left_continuous_frame = 0
        self.turn_right_continuous_frame = 0

        print("活体检测 初始化时间:", time() - self.detect_start_time)

        # 当前总帧数
        total_frame_counter = 0

        # 设置随机值
        now_flag = 0
        random_type = [0, 1, 2, 3]
        random.shuffle(random_type)

        random_eye_flash_number = random.randint(4, 6)
        random_mouth_open_number = random.randint(2, 4)
        QMessageBox.about(self, '提示', '请按照指示执行相关动作')
        self.remind_label.setText("")

        # 抓取面部特征点的索引
        (lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS["left_eye"]
        (rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS["right_eye"]
        (mStart, mEnd) = face_utils.FACIAL_LANDMARKS_IDXS["mouth"]

              b.EAR、MAR等值的计算

                以眼睛为例,获取眼睛的,套用计算EAR值的公式,计算得到EAR值。

    # 计算眼长宽比例 EAR值
    @staticmethod
    def count_EAR(eye):
        A = dist.euclidean(eye[1], eye[5])
        B = dist.euclidean(eye[2], eye[4])
        C = dist.euclidean(eye[0], eye[3])
        EAR = (A + B) / (2.0 * C)
        return EAR

    # 计算嘴长宽比例 MAR值
    @staticmethod
    def count_MAR(mouth):
        A = dist.euclidean(mouth[1], mouth[11])
        B = dist.euclidean(mouth[2], mouth[10])
        C = dist.euclidean(mouth[3], mouth[9])
        D = dist.euclidean(mouth[4], mouth[8])
        E = dist.euclidean(mouth[5], mouth[7])
        F = dist.euclidean(mouth[0], mouth[6])  # 水平欧几里德距离
        ratio = (A + B + C + D + E) / (5.0 * F)
        return ratio

    # 计算左右脸转动比例 FR值
    @staticmethod
    def count_FR(face):
        rightA = dist.euclidean(face[0], face[27])
        rightB = dist.euclidean(face[2], face[30])
        rightC = dist.euclidean(face[4], face[48])
        leftA = dist.euclidean(face[16], face[27])
        leftB = dist.euclidean(face[14], face[30])
        leftC = dist.euclidean(face[12], face[54])
        ratioA = rightA / leftA
        ratioB = rightB / leftB
        ratioC = rightC / leftC
        ratio = (ratioA + ratioB + ratioC) / 3
        return ratio

        c.用户动作判断

    def check_eye_flash(self, average_EAR):
        if average_EAR < self.EAR_THRESH:
            self.eye_flash_continuous_frame += 1
        else:
            if self.eye_flash_continuous_frame >= self.EAR_CONSTANT_FRAMES:
                self.eye_flash_counter += 1
            self.eye_flash_continuous_frame = 0

    def check_mouth_open(self, mouth_MAR):
        if mouth_MAR > self.MOUTH_THRESH:
            self.mouth_open_continuous_frame += 1
        else:
            if self.mouth_open_continuous_frame >= self.MOUTH_CONSTANT_FRAMES:
                self.mouth_open_counter += 1
            self.mouth_open_continuous_frame = 0

    def check_right_turn(self, leftRight_FR):
        if leftRight_FR <= 0.5:
            self.turn_right_continuous_frame += 1
        else:
            if self.turn_right_continuous_frame >= self.RIGHT_CONSTANT_FRAMES:
                self.turn_right_counter += 1
            self.turn_right_continuous_frame = 0

    def check_left_turn(self, leftRight_FR):
        if leftRight_FR >= 2.0:
            self.turn_left_continuous_frame += 1
        else:
            if self.turn_left_continuous_frame >= self.LEFT_CONSTANT_FRAMES:
                self.turn_left_counter += 1
            self.turn_left_continuous_frame = 0

        d.活体检测判断

        当摄像头打开时,进行活体检测判断。当用户 活体检测成功 或者 超时 时才退出循环。

        while self.cap.isOpened():
            ret, frame = self.cap.read()
            total_frame_counter += 1
            frame = imutils.resize(frame)
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            rects = self.detector(gray, 0)

            if len(rects) == 1:
                QApplication.processEvents()
                shape = self.predictor(gray, rects[0])
                shape = face_utils.shape_to_np(shape)

                # 提取面部坐标
                left_eye = shape[lStart:lEnd]
                right_eye = shape[rStart:rEnd]
                mouth = shape[mStart:mEnd]

                # 计算长宽比
                left_EAR = self.count_EAR(left_eye)
                right_EAR = self.count_EAR(right_eye)
                mouth_MAR = self.count_MAR(mouth)
                leftRight_FR = self.count_FR(shape)
                average_EAR = (left_EAR + right_EAR) / 2.0

                # 计算左眼、右眼、嘴巴的凸包
                left_eye_hull = cv2.convexHull(left_eye)
                right_eye_hull = cv2.convexHull(right_eye)
                mouth_hull = cv2.convexHull(mouth)

                # 可视化
                cv2.drawContours(frame, [left_eye_hull], -1, (0, 255, 0), 1)
                cv2.drawContours(frame, [right_eye_hull], -1, (0, 255, 0), 1)
                cv2.drawContours(frame, [mouth_hull], -1, (0, 255, 0), 1)

                if now_flag >= 4:
                    self.remind_label.setText("")
                    QMessageBox.about(self, '提示', '已通过活体检测')
                    self.turn_right_counter = 0
                    self.mouth_open_counter = 0
                    self.eye_flash_counter = 0
                    return True

                if random_type[now_flag] == 0:
                    if self.turn_left_counter > 0:
                        now_flag += 1
                    else:
                        self.remind_label.setText("请向左摇头")
                        self.check_left_turn(leftRight_FR)
                        self.turn_right_counter = 0
                        self.mouth_open_counter = 0
                        self.eye_flash_counter = 0

                elif random_type[now_flag] == 1:
                    if self.turn_right_counter > 0:
                        now_flag += 1
                    else:
                        self.remind_label.setText("请向右摇头")
                        self.check_right_turn(leftRight_FR)
                        self.turn_left_counter = 0
                        self.mouth_open_counter = 0
                        self.eye_flash_counter = 0

                elif random_type[now_flag] == 2:
                    if self.mouth_open_counter >= random_mouth_open_number:
                        now_flag += 1

                    else:
                        self.remind_label.setText("已张嘴{}次\n还需张嘴{}次".format(self.mouth_open_counter, (
                                random_mouth_open_number - self.mouth_open_counter)))
                        self.check_mouth_open(mouth_MAR)
                        self.turn_right_counter = 0
                        self.turn_left_counter = 0
                        self.eye_flash_counter = 0

                elif random_type[now_flag] == 3:
                    if self.eye_flash_counter >= random_eye_flash_number:
                        now_flag += 1
                    else:
                        self.remind_label.setText("已眨眼{}次\n还需眨眼{}次".format(self.eye_flash_counter, (
                                random_eye_flash_number - self.eye_flash_counter)))
                        self.check_eye_flash(average_EAR)
                        self.turn_right_counter = 0
                        self.turn_left_counter = 0
                        self.mouth_open_counter = 0

            elif len(rects) == 0:
                QApplication.processEvents()
                self.remind_label.setText("没有检测到人脸!")

            elif len(rects) > 1:
                QApplication.processEvents()
                self.remind_label.setText("检测到超过一张人脸!")

            show_video = cv2.cvtColor(cv2.resize(frame, (self.WIN_WIDTH, self.WIN_HEIGHT)), cv2.COLOR_BGR2RGB)
            self.show_image = QImage(show_video.data, show_video.shape[1], show_video.shape[0], QImage.Format_RGB888)
            self.camera_label.setPixmap(QPixmap.fromImage(self.show_image))

            if total_frame_counter >= 1000.0:
                QMessageBox.about(self, '警告', '已超时,未通过活体检测')
                self.remind_label.setText("")
                return False

        (4)全部代码

    # 计算眼长宽比例 EAR值
    @staticmethod
    def count_EAR(eye):
        A = dist.euclidean(eye[1], eye[5])
        B = dist.euclidean(eye[2], eye[4])
        C = dist.euclidean(eye[0], eye[3])
        EAR = (A + B) / (2.0 * C)
        return EAR

    # 计算嘴长宽比例 MAR值
    @staticmethod
    def count_MAR(mouth):
        A = dist.euclidean(mouth[1], mouth[11])
        B = dist.euclidean(mouth[2], mouth[10])
        C = dist.euclidean(mouth[3], mouth[9])
        D = dist.euclidean(mouth[4], mouth[8])
        E = dist.euclidean(mouth[5], mouth[7])
        F = dist.euclidean(mouth[0], mouth[6])  # 水平欧几里德距离
        ratio = (A + B + C + D + E) / (5.0 * F)
        return ratio

    # 计算左右脸转动比例 FR值
    @staticmethod
    def count_FR(face):
        rightA = dist.euclidean(face[0], face[27])
        rightB = dist.euclidean(face[2], face[30])
        rightC = dist.euclidean(face[4], face[48])
        leftA = dist.euclidean(face[16], face[27])
        leftB = dist.euclidean(face[14], face[30])
        leftC = dist.euclidean(face[12], face[54])
        ratioA = rightA / leftA
        ratioB = rightB / leftB
        ratioC = rightC / leftC
        ratio = (ratioA + ratioB + ratioC) / 3
        return ratio

    # 本地活体检测
    def detect_face_local(self):
        self.detect_start_time = time()

        QApplication.processEvents()
        self.remind_label.setText("正在初始化\n请稍后")
        # 特征点检测器首次加载比较慢,通过判断减少后面加载的速度
        if self.detector is None:
            self.detector = dlib.get_frontal_face_detector()
        if self.predictor is None:
            global DATA_FOLDER_PATH
            self.predictor = dlib.shape_predictor('../data/shape_predictor_68_face_landmarks.dat')

        # 闪烁阈值
        self.EAR_THRESH = 0.25
        self.MOUTH_THRESH = 0.7

        # 总闪烁次数
        self.eye_flash_counter = 0
        self.mouth_open_counter = 0
        self.turn_left_counter = 0
        self.turn_right_counter = 0

        # 连续帧数阈值
        self.EAR_CONSTANT_FRAMES = 2
        self.MOUTH_CONSTANT_FRAMES = 2
        self.LEFT_CONSTANT_FRAMES = 4
        self.RIGHT_CONSTANT_FRAMES = 4

        # 连续帧计数器
        self.eye_flash_continuous_frame = 0
        self.mouth_open_continuous_frame = 0
        self.turn_left_continuous_frame = 0
        self.turn_right_continuous_frame = 0

        print("活体检测 初始化时间:", time() - self.detect_start_time)

        # 当前总帧数
        total_frame_counter = 0

        # 设置随机值
        now_flag = 0
        random_type = [0, 1, 2, 3]
        random.shuffle(random_type)

        random_eye_flash_number = random.randint(4, 6)
        random_mouth_open_number = random.randint(2, 4)
        QMessageBox.about(self, '提示', '请按照指示执行相关动作')
        self.remind_label.setText("")

        # 抓取面部特征点的索引
        (lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS["left_eye"]
        (rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS["right_eye"]
        (mStart, mEnd) = face_utils.FACIAL_LANDMARKS_IDXS["mouth"]

        while self.cap.isOpened():
            ret, frame = self.cap.read()
            total_frame_counter += 1
            frame = imutils.resize(frame)
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            rects = self.detector(gray, 0)

            if len(rects) == 1:
                QApplication.processEvents()
                shape = self.predictor(gray, rects[0])
                shape = face_utils.shape_to_np(shape)

                # 提取面部坐标
                left_eye = shape[lStart:lEnd]
                right_eye = shape[rStart:rEnd]
                mouth = shape[mStart:mEnd]

                # 计算长宽比
                left_EAR = self.count_EAR(left_eye)
                right_EAR = self.count_EAR(right_eye)
                mouth_MAR = self.count_MAR(mouth)
                leftRight_FR = self.count_FR(shape)
                average_EAR = (left_EAR + right_EAR) / 2.0

                # 计算左眼、右眼、嘴巴的凸包
                left_eye_hull = cv2.convexHull(left_eye)
                right_eye_hull = cv2.convexHull(right_eye)
                mouth_hull = cv2.convexHull(mouth)

                # 可视化
                cv2.drawContours(frame, [left_eye_hull], -1, (0, 255, 0), 1)
                cv2.drawContours(frame, [right_eye_hull], -1, (0, 255, 0), 1)
                cv2.drawContours(frame, [mouth_hull], -1, (0, 255, 0), 1)

                if now_flag >= 4:
                    self.remind_label.setText("")
                    QMessageBox.about(self, '提示', '已通过活体检测')
                    self.turn_right_counter = 0
                    self.mouth_open_counter = 0
                    self.eye_flash_counter = 0
                    return True

                if random_type[now_flag] == 0:
                    if self.turn_left_counter > 0:
                        now_flag += 1
                    else:
                        self.remind_label.setText("请向左摇头")
                        self.check_left_turn(leftRight_FR)
                        self.turn_right_counter = 0
                        self.mouth_open_counter = 0
                        self.eye_flash_counter = 0

                elif random_type[now_flag] == 1:
                    if self.turn_right_counter > 0:
                        now_flag += 1
                    else:
                        self.remind_label.setText("请向右摇头")
                        self.check_right_turn(leftRight_FR)
                        self.turn_left_counter = 0
                        self.mouth_open_counter = 0
                        self.eye_flash_counter = 0

                elif random_type[now_flag] == 2:
                    if self.mouth_open_counter >= random_mouth_open_number:
                        now_flag += 1

                    else:
                        self.remind_label.setText("已张嘴{}次\n还需张嘴{}次".format(self.mouth_open_counter, (
                                random_mouth_open_number - self.mouth_open_counter)))
                        self.check_mouth_open(mouth_MAR)
                        self.turn_right_counter = 0
                        self.turn_left_counter = 0
                        self.eye_flash_counter = 0

                elif random_type[now_flag] == 3:
                    if self.eye_flash_counter >= random_eye_flash_number:
                        now_flag += 1
                    else:
                        self.remind_label.setText("已眨眼{}次\n还需眨眼{}次".format(self.eye_flash_counter, (
                                random_eye_flash_number - self.eye_flash_counter)))
                        self.check_eye_flash(average_EAR)
                        self.turn_right_counter = 0
                        self.turn_left_counter = 0
                        self.mouth_open_counter = 0

            elif len(rects) == 0:
                QApplication.processEvents()
                self.remind_label.setText("没有检测到人脸!")

            elif len(rects) > 1:
                QApplication.processEvents()
                self.remind_label.setText("检测到超过一张人脸!")

            show_video = cv2.cvtColor(cv2.resize(frame, (self.WIN_WIDTH, self.WIN_HEIGHT)), cv2.COLOR_BGR2RGB)
            self.show_image = QImage(show_video.data, show_video.shape[1], show_video.shape[0], QImage.Format_RGB888)
            self.camera_label.setPixmap(QPixmap.fromImage(self.show_image))

            if total_frame_counter >= 1000.0:
                QMessageBox.about(self, '警告', '已超时,未通过活体检测')
                self.remind_label.setText("")
                return False

    def check_eye_flash(self, average_EAR):
        if average_EAR < self.EAR_THRESH:
            self.eye_flash_continuous_frame += 1
        else:
            if self.eye_flash_continuous_frame >= self.EAR_CONSTANT_FRAMES:
                self.eye_flash_counter += 1
            self.eye_flash_continuous_frame = 0

    def check_mouth_open(self, mouth_MAR):
        if mouth_MAR > self.MOUTH_THRESH:
            self.mouth_open_continuous_frame += 1
        else:
            if self.mouth_open_continuous_frame >= self.MOUTH_CONSTANT_FRAMES:
                self.mouth_open_counter += 1
            self.mouth_open_continuous_frame = 0

    def check_right_turn(self, leftRight_FR):
        if leftRight_FR <= 0.5:
            self.turn_right_continuous_frame += 1
        else:
            if self.turn_right_continuous_frame >= self.RIGHT_CONSTANT_FRAMES:
                self.turn_right_counter += 1
            self.turn_right_continuous_frame = 0

    def check_left_turn(self, leftRight_FR):
        if leftRight_FR >= 2.0:
            self.turn_left_continuous_frame += 1
        else:
            if self.turn_left_continuous_frame >= self.LEFT_CONSTANT_FRAMES:
                self.turn_left_counter += 1
            self.turn_left_continuous_frame = 0

阅读完本博客后可以继续阅读:

用户端逻辑:

  • 人脸识别:Python | 人脸识别系统 — 人脸识别
  • 背景模糊:Python | 人脸识别系统 — 背景模糊
  • 姿态检测:Python | 人脸识别系统 — 姿态检测
  • 人脸比对:Python | 人脸识别系统 — 人脸比对
  • 用户操作:Python | 人脸识别系统 — 用户操作

管理员端逻辑:

  • 管理员操作:
  • 用户操作:

注:以上代码仅为参考,若需要运行,请参考项目GitHub完整源代码:  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/489567.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

6---N字形变化

将一个给定字符串 s 根据给定的行数 numRows &#xff0c;以从上往下、从左到右进行 Z 字形排列。 比如输入字符串为 "PAYPALISHIRING" 行数为 3 时&#xff0c;排列如下&#xff1a; P A H N A P L S I I G Y I R 之后&#xff0c;你的输出需要从左往右逐…

JavaScrpit—数据类型转换

目录 1、起 源 理 念 2、特 点 框 架 AngularJS框架 WebSocket协议 3、书 写 位 置 注 释 浏览器调试js代码 4、变量作用 全局变量 局部变量 常量 5、数据类型 数 组 严格检查数据类型 字符串 6、类型转换 字符串转数字 转bool值 能力判断 7、编码方式 …

Spring IOC相关注解运用——上篇

目录 前言 一、Component 二、Repository、Service、Controller 三、Scope 四、Autowired 五、Qualifier 六、Value 1. 直接设置固定的属性值 2. 获取配置文件中的属性值 3. 测试结果 往期专栏&文章相关导读 1. Maven系列专栏文章 2. Mybatis系列专栏文章 3.…

记录一次Linux下ChatGLM部署过程

前言 本地化的GPT就是香&#xff0c;就是有点费钱。 项目地址&#xff1a;https://github.com/THUDM/ChatGLM-6B 前期准备 服务器&#xff08;本机的跳过&#xff09; 由于本地电脑显卡都不行&#xff0c;所以我租了AutoDL的一台算力服务器。Tesla T4 16G显存&#xff0c;…

自供电-测力刀柄资料整理

自供电-测力刀柄资料整理 2. 相关专利2.1 实时测量铣削过程中床主轴温度装置【1】2.2 一种基于应变片的测力系统【2】 3. 相关商业化产品3.1 spike 测力刀柄【3】3.2 瑞士奇石乐&#xff08;Kistler&#xff09;旋转切削测力仪【4】3.3 kistler的通用型压电式切削力测量系统3.4…

SPSS如何进行聚类分析之案例实训?

文章目录 0.引言1.快速聚类分析2.分层聚类分析3.两阶段聚类分析 0.引言 因科研等多场景需要进行绘图处理&#xff0c;笔者对SPSS进行了学习&#xff0c;本文通过《SPSS统计分析从入门到精通》及其配套素材结合网上相关资料进行学习笔记总结&#xff0c;本文对聚类分析进行阐述。…

【软考高项笔记】第1章 信息化发展1.5 数字化转型与元宇宙

1.5 数字化转型与元宇宙 元宇宙本质上是对现实世界的虚拟化、数字化过程&#xff0c;需要对内容生产、经济系统、用户体验以及实体世界内容等进行大量改造1.5.1 数字化转型 新建一个富有活力的数字化商业模式 组织对业务进行彻底重新定义&#xff08;大洗牌&#xff09;之后才…

浅谈明日方舟游戏系统

主要玩法&#xff1a;敌方阵营从敌方初始点进入战斗并且沿着怪物前进路线行驶到己方保护目标。玩家可以通过部署干员守护己方保护目标&#xff0c;防止敌方阵营进入&#xff1b;当保护目标的生命值为0时&#xff0c;则战斗失败&#xff0c;任务结束。 1 干员系统 1.1 职业分支…

linux(stat-readdir-dup2)04-虚拟地址空间,stat函数,文件,目录,errno说明,dup2和dup

01 学习目标 1.掌握stat/lstat函数的使用 2.了解文件属性相关的函数使用 3.了解目录操作相关的函数的使用 4.掌握目录遍历相关函数的使用 5.掌握dup,dup2函数的使用 6.掌握fcntl函数的使用 02 虚拟地址空间 03 打开最大文件数量 openmax.c #include<stdio.h> #include&…

Redo log详解

WAL&#xff08;Write-Ahead Logging&#xff09;机制 WAL 的全称是 Write-Ahead Logging&#xff0c;中文称预写式日志(日志先行)&#xff0c;是一种数据安全写入机制。就是先写日志&#xff0c;然后再写入磁盘&#xff0c;这样既能提高性能又可以保证数据的安全性。Mysql中的…

华为MPLS跨域C2方式RR场景(数据不经过RR)实验配置

目录 配置BGP邻居的建立 配置MPLS LDP 配置RR之间的MP-BGP邻居 配置通过ASBR学习PE路由并为PE分配标签&#xff08;实现Vpnv4路由传递和数据传输&#xff09; 根据图配置接口的IP地址和IGP协议 配置BGP邻居的建立 PE1和RR1建立MP-IBGP邻居&#xff08;RR1传给PE1的Vpnv4路…

进程/线程 状态模型详解

前言&#xff1a;最近操作系统复习到线程的状态模型&#xff08;也可以说进程的状态模型&#xff0c;本文直接用线程来说&#xff09;时候&#xff0c;网上查阅资料&#xff0c;发现很多文章都说的很不一样&#xff0c;有五状态模型、六状态模型、七状态模型.......虽然都是对的…

【1024杯】web

文章目录 1024_fastapi信息收集SSTI 1024_柏拉图信息收集phar反序列化 1024_WEB签到1024_图片代理信息收集gopherus|fastcgi 1024_hello_world信息收集ssti盲注 1024_fastapi 信息收集 FastAPI 是一个高性能 Web 框架&#xff0c;用于构建 API。 主要特性&#xff1a; 快速&am…

【头歌】完整汇编语言程序设计

摘自头歌实训​​​​​​ 目录 相关知识 1.1 RISC-V 汇编语言程序基本结构 1.2 RISC-V 汇编语言程序主要元素 1.2.1 汇编指令 1.2.2 标签 1.2.3 汇编指示语句 1.3 RISC-V 汇编语言程序示例 相关知识 RISC-V 操作数类型、基本调用约定等已在前序关卡中介绍&#xff0c;…

云渲染是您3D项目的最佳选择吗?

市面上很多优秀的大型动画电影&#xff0c;一般都是由实力较大的视觉特效团队制作的&#xff0c;而且大部分使用的是基于云的渲染服务来进行最终渲染。像《哪吒之魔童降世》这样 IMAX 3D 首部国产动画电影、并且刷新了中国动画电影票房纪录的作品&#xff0c;就是现代3D动画提供…

ChatGPT做PPT方案,10组提示词方案!

今天我们要搞定的PPT内容是&#xff1a; 活动类型&#xff1a;节日活动、会员活动、新品活动分析类型&#xff1a;用户分析、新品立项、项目汇报内容类型&#xff1a;内容规划、品牌策划 用到的工具&#xff1a; mindshow 邀请码 6509097ChatGPT传送门&#xff08;免费使用…

《Linux0.11源码解读》理解(二) 加载setup、加载system

现在CPU开始执行bootsect&#xff0c;它的作用是把第二部分、第三部分程序陆续加载到内存中。把放到合理的内存位置需要先对内存进行规划。 根据上一节&#xff0c;boostsect当前所在内存位置是0x07c0&#xff0c;大小为512byte&#xff0c;现在要将其挪动到内存的0x9000&…

无代码时代来了,程序员会失业吗?不,程序员又不够用了!

有人问我无代码时代来了&#xff0c;程序员会失业吗&#xff1f;太难了&#xff0c;秃了头就算了&#xff0c;连工作也保不住了&#xff1f; 先说观点&#xff1a;并不会 因为&#xff0c;无代码不是真正意义上的无代码。 无代码开发的使用对象是编程小白&#xff08;我猿是…

Redis的命令以及数据类型

1.Redis的通用命令 KEYS&#xff1a;查看符合模板的所有key&#xff0c;不建议在生产环境设备上使用 DEL&#xff1a;删除一个指定的key EXISTS&#xff1a;判断key是否存在 TTL&#xff1a;查看一个KEY的剩余有效期 2.String类型 String类型&#xff0c;也就是字符串类型…

【Halcon】 Halcon 22.11 安装详细教程

文章目录 1安装2 获取许可证 license2.1 license下载2.2 激活 license放置在相应文件夹下 3 DLT 安装 1安装 1.解压安装包 2.打开运行 exe 程序 跳转至页面 点击“可获得的”&#xff0c;并安装 选择&#xff1a; AVAILABLE ->INSTALL 可获得的 ->安装 5. 等待安装 6…