10150-深度学习遮挡下的人脸识别详细设计说明书+源代码+教学视频)

请添加图片描述

功能需求:
在面部遮挡情况下的人脸识别。

资料摘要:
随着深度学习卷积神经网络在图像处理领域的广泛应用,人脸识别技术成为学术界和工业界关注的焦点。然而,近年来,全球范围内的突发公共卫生事件导致了口罩的普遍佩戴,给传统人脸识别技术带来了新的挑战。本论文深入研究了口罩下的人脸识别问题,并提出了一种解决方案,旨在提高在口罩遮挡情境下的人脸识别精度。传统的人脸识别技术在准确率方面表现出色,然而,口罩导致了人脸大面积遮挡,传统方法因此变得不再适用。为了克服这一问题,本文采用了一种基于改进型激活函数LeakyReLU的ResNet18残差神经网络方法。该方法通过解决传统神经网络中的梯度消失和网络退化问题,以及专门针对口罩遮挡下的人脸进行优化,从而有效提高了识别精度。
为验证提出方法的有效性,我使用了Python语言在PyTorch框架下构建了ResNet18残差神经网络模型,并在开源数据集上进行了训练和测试。测试结果显示,在两轮训练集训练后,相较于使用传统ReLU函数的模型,采用改进型激活函数LeakyReLU的模型识别精确度提高了3%。实验证明,该算法能够较好地解决面具遮挡条件下的人脸识别问题。
算法原理
基于人脸识别的口罩识别算法主要利用了人脸识别技术和图像处理技术。首先,通过人脸识别技术,算法能够快速准确地检测出人脸的位置和特征;然后,通过图像处理技术,算法对人脸图像进行分析,判断是否佩戴口罩。具体来说,算法会提取人脸图像中的特征点,如眼睛、鼻子、嘴巴等,并对其进行比对。如果这些特征点在人脸图像中存在且与数据库中的特征点匹配,则判断为人脸;反之,如果这些特征点在人脸图像中不存在或者不匹配,则判断为人脸未佩戴口罩。

实现步骤
人脸检测:利用人脸识别技术,算法能够快速准确地检测出人脸的位置和特征。这一步通常使用开源的人脸识别库,如OpenCV和Dlib等。
特征提取:通过提取人脸图像中的特征点,如眼睛、鼻子、嘴巴等,并对其进行比对。这一步通常使用深度学习模型,如CNN等。
口罩判断:根据比对结果,判断是否佩戴口罩。如果特征点匹配成功,则认为人脸佩戴了口罩;反之,则认为人脸未佩戴口罩。
结果输出:将判断结果输出到控制台或者保存到数据库中,以便后续处理和应用。

资料包含:
1、源代码工程文件
2、详细设计说明书-13042字
3、教学视频教你如何运行

请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述

import argparse
import time
from pathlib import Path
import threading
import sys
import os
import torch
#print('detect.py', torch.__version__)
#print('detect.py', torch.__path__)
import cv2
import numpy as np
import glob
import time
import tqdm
from PIL import Image, ImageDraw, ImageFont
import os

from argparse import ArgumentParser
import torch.backends.cudnn as cudnn
from numpy import random
import numpy as np
import shutil


from model.SSD import FaceMaskDetection
from model.FACENET import InceptionResnetV1

import PyQt5.QtCore
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from window_dancheng import Ui_Win_traffic


class MainWindow(Ui_Win_traffic, QMainWindow):
    # 基本配置不动,然后只动第三个界面
    def __init__(self):
        # 初始化界面
        super().__init__()
        self.setObjectName("mainWindow")
        self.setStyleSheet("#mainWindow{border-image:url(bg.jpg)}")
        self.del_file("images/tmp")
        self.del_file("images/name_png")

        # # 初始化视频读取线程
        self.vid_source = '0'  # 初始设置为摄像头
        self.output_size = 640
        self.tk = False
        self.save_flag = False
        self.stopEvent = threading.Event()
        self.stopEvent.clear()

        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.setupUi(self)

        # -----------页面切换按钮-----------
        self.pushButton.clicked.connect(self.show_photo)
        self.pushButton_2.clicked.connect(self.update_db)
        self.pushButton_3.clicked.connect(self.show_camera)
        # -----------页面切换按钮-----------

        # -----------功能按钮-----------

        self.open_pic_btn.clicked.connect(self.upload_img)
        self.tk_pic_btn.clicked.connect(self.tk_photo)
        self.save_pic_btn.clicked.connect(self.save_pic)
        # self.pic_det_btn.clicked.connect(self.detect_infer)
        # self.mp4_detection_btn.clicked.connect(self.open_mp4)
        # self.vid_stop_btn.clicked.connect(self.close_vid)

        self.webcam_detection_btn.clicked.connect(self.detect_cam)
        self.vid_stop_btn_cma.clicked.connect(self.close_vid)

        # -----------功能按钮-----------
    def update_db(self):
        # 加载目标人的特征
        # 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()

    def show_photo(self):
        self.show_picture_page.setCurrentIndex(0)

    def show_video(self):
        # self.stopEvent.set()
        self.show_picture_page.setCurrentIndex(1)

    def show_camera(self):
        # self.stopEvent2.set()
        self.show_picture_page.setCurrentIndex(2)

    # 关闭页面
    def closeEvent(self, event):
        reply = QMessageBox.question(self,
                                     'quit',
                                     "Are you sure?",
                                     QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.tk = True

            self.close_vid()
            self.close()
            event.accept()
        else:
            event.ignore()

    def upload_img(self):
        # 选择录像文件进行读取
        fileName, fileType = QFileDialog.getOpenFileName(
            self, 'Choose file', '', '*.jpg *.png *.tif *.jpeg')
        if fileName:
            suffix = fileName.split(".")[-1]
            save_path = os.path.join("images/tmp", "tmp_upload." + suffix)
            shutil.copy(fileName, save_path)
            # 应该调整一下图片的大小,然后统一防在一起
            im0 = cv2.imread(save_path)
            resize_scale = self.output_size / im0.shape[0]
            im0 = cv2.resize(im0, (0, 0), fx=resize_scale, fy=resize_scale)
            cv2.imwrite("images/tmp/upload_show_result.jpg", im0)
            self.label.setPixmap(QPixmap("images/tmp/upload_show_result.jpg"))
            # self.label.setPixmap(QPixmap(fileName))

            self.label.setAlignment(Qt.AlignCenter)

            self.vid_source = fileName
            self.save_flag = True

            # self.left_img.setPixmap(QPixmap("images/tmp/upload_show_result.jpg"))a
            # todo 上传图片之后右侧的图片重置,
            # self.right_img.setPixmap(QPixmap("images/UI/right.jpeg"))
    def del_file(self, path_data):
        '''
            删除指定文件夹中的文件, 只删除第一级文件
        '''
        for i in os.listdir(path_data):
            file_data = path_data + "\\" + i
            if os.path.isfile(file_data) == True:
                os.remove(file_data)

    def tk_camera(self):
        self.tk = False
        cap = cv2.VideoCapture(0)

        while True:
            ret, frame = cap.read()
            # img = cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)
            img = cv2.flip(frame, 1)
            cv2.imwrite("images/tmp/test_camera.jpg", img)
            self.label.setPixmap(QPixmap("images/tmp/test_camera.jpg"))
            if self.tk == True:
                break
        self.stopEvent.clear()

    def tk_photo(self):
        # self.stopEvent.set()
        self.vid_source = "tkp"
        self.save_flag = True
        th = threading.Thread(target=self.tk_camera)
        th.start()

    def md_camera(self):
        self.webcam_detection_btn.setEnabled(False)
        self.vid_stop_btn.setEnabled(True)
        th = threading.Thread(target=self.detect_cam)
        th.start()

    def save_pic(self):
        # 人脸识别距离阈值,越低越准确
        threshold = 1
        if self.save_flag:
            name = self.lineEdit.text()
            if name == "":
                reply = QMessageBox.warning(
                    self, ' ', "请输入姓名!!", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
            elif " " in name:
                reply = QMessageBox.warning(
                    self, ' ', "请勿使用空格", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
            else:
                if self.vid_source == "tkp":
                    self.tk = True
                    im = cv2.imread("images/tmp/test_camera.jpg")
                    cv2.imwrite(f"images/origin/{name}.jpg", im)
                    self.vid_source = '0'
                    reply = QMessageBox.information(
                        self, ' ', "人脸图像已保存", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)

                    self.label.setPixmap(QPixmap(""))
                    self.label.setText("Face Here")
                    self.label.setAlignment(Qt.AlignCenter)
                else:
                    save_path = "images/tmp/upload_show_result.jpg"
                    im0 = cv2.imread(save_path)
                    cv2.imencode('.jpg', im0)[1].tofile(f"images/origin/{name}.jpg")
                    # cv2.imwrite(f"images/origin/{name}.jpg", im0)
                    reply = QMessageBox.information(
                        self, ' ', "人脸图像已保存", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
                    self.label.setPixmap(QPixmap(""))
                    self.label.setText("Face Here")
                    self.label.setAlignment(Qt.AlignCenter)
                self.save_flag = False

        else:
            reply = QMessageBox.warning(
                self, ' ', "未获取人像数据!", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)

    def detect_cam(self):
        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))

        png_index = 0

        while True:
            start_time = time.time()

            ret, frame = cap.read()
            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
                if pred_name != "" and class_id == 0:
                    self.data_log.append( " 佩戴口罩")
                elif pred_name != "" and class_id == 1:
                    self.data_log.append( " 未佩戴口罩!")

                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)
            cv2.imwrite("images/tmp/camera_result.jpg", frame)
            self.cam_img.setPixmap(
                QPixmap("images/tmp/camera_result.jpg"))
            self.cam_img.setAlignment(Qt.AlignCenter)
            if cv2.waitKey(25) & self.stopEvent.is_set() == True:
                cap.release()
                self.stopEvent.clear()
                self.mp4_detection_btn.setEnabled(True)
                self.reset_vid()
                break

    def reset_vid(self):
        self.webcam_detection_btn.setEnabled(True)
        self.cam_img.setPixmap(QPixmap(""))
        self.cam_img.setText("Camera Here")
        self.vid_source = '0'

    def close_vid(self):
        self.stopEvent.set()
        self.reset_vid()

        # pass

    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 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


if __name__ == "__main__":
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐