python给人脸带上口罩(简单版)

导读

因为目前公开的口罩人脸数据比较少,如果想训练一个口罩人脸识别模型,必须依赖大量的人脸数据。为了收集到更多的口罩人脸数据,我们只能利用已有的公开人脸数据上通过程序来模拟人脸带口罩。这篇文章向大家介绍一个简单版本使用python来给正常人脸带上口罩的思路和代码

原理介绍

想要给人脸戴口罩,就必须要用到人脸关键点的信息,通过关键点信息,我们就能知道应该将口罩放在哪个位置

  • 人脸关键点检测

这里我们使用的是68个人脸关键点,关键点的位置在人脸上的分布如下图所示
在这里插入图片描述
后面我们只需要用到其中四个关键点,用来计算口罩的位置,鼻梁第28点下巴的第2、9、16点

  • 根据关键点计算口罩的size

我们需要根据上面提到的四个关键点来计算口罩的widthheight口罩的宽利用点2和点16的欧式距离,口罩的高用点28和点9的欧式距离

实际使用中,建议将口罩分为左右两部分,因为有时候头部会向左或右偏转,分成两部分计算效果会好些。口罩的高度计算不变,计算左半部分口罩的宽利用点2直线(由点28和点9组成)的距离,同理可知右半部分口罩的宽计算。

  • 口罩角度的调整

因为人脸可能是歪的,利用点28和点9组成的直线结合垂直线计算偏转角度(人脸是正的,点28和点9组成的直线是垂直水平线),通过偏转角度来选择口罩

  • 口罩位置的确定

利用点28和点9来确定口罩所处位置的中心点,针对头会左右旋转的问题,需要对口罩的中心位置的确定进行微调,具体细节实现可以参考代码。

代码实现
环境准备
  • python版本:3.7
  • 第三方库安装
#安装OpenCV
pip install opencv-python
#安装numpy
pip install numpy
#安装PIL 
pip install pillow
#安装pytorch,pytorch (>=1.0)
#建议安装cuda版本的pytorch
#安装教程:https://pytorch.org/get-started/previous-versions/
#安装人脸关键点检测face_alignment
pip install face-alignment
  • 关键点模型下载不了的问题

如果在使用face-alignment的时候无法下载模型文件,可以直接从下面的地址进行下载:
https://www.adrianbulat.com/downloads/python-fan/2DFAN-4.pth.tar
https://www.adrianbulat.com/downloads/python-fan/3DFAN-4.pth.tar
https://www.adrianbulat.com/downloads/python-fan/depth.pth.tar
将下载好的模型解压后得到pth模型文件,对linux系统,将解压后得到的pth文件放到~/.face_alignment/data目录下

口罩mask的提取

需要注意的是,准备的口罩mask最好是png(RGBA)透明背景的图片。否则在将口罩mask复制到人脸上时会遮盖多余的部分,影响效果。
我这里实现了一个从纯色背景的口罩jpg图片中,提取出一个透明背景的png口罩图片,代码如下

import cv2
import numpy as np

img = cv2.imread("mask_images/mask.jpg")
#将图片分割为三个通道
b_channel,g_channel,r_channel = cv2.split(img)
alpha_channel = np.zeros(b_channel.shape,dtype=b_channel.dtype)
gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
blur_img = cv2.GaussianBlur(gray_img,(3,3),cv2.BORDER_DEFAULT)
canny_img = cv2.Canny(blur_img,50,150)
contours,hierarchy = cv2.findContours(canny_img,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
#获取面积最大的轮廓
contour = max(contours,key=cv2.contourArea)
#获取最大轮廓的凸包
hull = cv2.convexHull(contour)
#初始化一个alpha通道
alpha_channel = cv2.drawContours(alpha_channel,[hull],-1,255,-1)
png_img = cv2.merge((b_channel,g_channel,r_channel,alpha_channel))
#获取最大轮廓的最小外接矩形
rect = cv2.minAreaRect(contour)
#获取矩形四个顶点的坐标
box = cv2.boxPoints(rect)
#将矩形转换为水平的矩形
x_min = int(np.min(box[:,0]))
y_min = int(np.min(box[:,1]))
x_max = int(np.max(box[:,0]))
y_max = int(np.max(box[:,1]))
png_img = png_img[y_min:y_max,x_min:x_max,:]
cv2.imwrite("mask_images/mask.png",png_img)

在这里插入图片描述

给人脸添加口罩
import os,face_alignment
from PIL import Image
import numpy as np


class WearFaceMask(object):
    def __init__(self,face_path,mask_path,save_path,enlarge_ratio=0.9,use_gpu=True,show=False):
        self.face_path = face_path
        self.mask_path = mask_path
        self.save_path = save_path
        self.enlarge_ratio = enlarge_ratio
        self.use_gpu = use_gpu
        self.show = show
        if use_gpu:
            self.fa=face_alignment.FaceAlignment(
                face_alignment.LandmarksType._2D, device='cuda')
        else:
            self.fa = face_alignment.FaceAlignment(
                face_alignment.LandmarksType._2D, device='cpu')

    def get_key_landmarks(self,face_landmarks):
        """从68个关键点中获取4个关键点的位置
        用来定口罩佩戴的位置
        :param face_landmarks:人脸68个关键点
        :return:
        """
        #获取下巴右边佩戴口罩的位置(关键点2)
        self.left_chin_point = face_landmarks[1]
        #获取鼻梁佩戴口罩的位置(关键点28)
        self.nose_point = face_landmarks[27]
        #获取下巴左边佩戴口罩的位置(关键点16)
        self.right_chin_point = face_landmarks[15]
        #获取下巴最下面佩戴口罩的位置(关键点9)
        self.bottom_chin_point = face_landmarks[8]

    @staticmethod
    def cal_point_to_line_dist(point,line_point1,line_point2):
        """计算点到直线的距离
        :param point: 点的坐标
        :param line_point1: 直线上第一点的坐标
        :param line_point2: 直线上另一点的坐标
        :return: 点到直线的距离
        """
        #计算点和直线上点组成的向量
        vec1 = line_point1 - point
        vec2 = line_point2 - point
        dist = abs(np.cross(vec1,vec2)) / np.linalg.norm(line_point2 - line_point1)
        return dist

    def wear_face_mask(self):
        self._face_img = Image.open(self.face_path)
        self._mask_img = Image.open(self.mask_path)
        face_landmarks = self.fa.get_landmarks(np.asarray(self._face_img))[0].astype(np.int32)
        #获取需要的关键点信息
        self.get_key_landmarks(face_landmarks)

        #获取口罩的宽和高
        mask_width = self._mask_img.width
        mask_height = self._mask_img.height
        #计算口罩适应人脸后和高度
        new_mask_height = int(np.linalg.norm(self.bottom_chin_point - self.nose_point))

        #将口罩分割为左右两部分用来适配人脸
        #左边口罩人脸
        mask_left_img = self._mask_img.crop((0,0,mask_width//2,mask_height))
        mask_left_width = self.cal_point_to_line_dist(self.left_chin_point,
                                      self.nose_point,self.bottom_chin_point)
        mask_left_width = int(mask_left_width * self.enlarge_ratio)
        mask_left_img = mask_left_img.resize((mask_left_width,new_mask_height))

        #右边口罩人脸
        mask_right_img = self._mask_img.crop((mask_width//2,0,mask_width,mask_height))
        mask_right_width = self.cal_point_to_line_dist(self.right_chin_point,
                                       self.nose_point,self.bottom_chin_point)
        mask_right_width = int(mask_right_width * self.enlarge_ratio)
        mask_right_img = mask_right_img.resize((mask_right_width,new_mask_height))

        #合并口罩
        size = (mask_left_width+mask_right_width,new_mask_height)
        mask_img = Image.new("RGBA",size)
        mask_img.paste(mask_left_img,(0,0),mask_left_img)
        mask_img.paste(mask_right_img,(mask_left_width,0),mask_right_img)

        #计算人脸的旋转角度
        angle = np.arctan2(self.bottom_chin_point[1]-self.nose_point[1],
                           self.bottom_chin_point[0]-self.nose_point[0])
        #旋转口罩
        rotated_mask_img = mask_img.rotate(angle,expand=True)

        #计算mask的位置
        mask_center_x = (self.nose_point[0]+self.bottom_chin_point[0]) // 2
        mask_center_y = (self.nose_point[1]+self.bottom_chin_point[1]) // 2
        offset = mask_img.width // 2 - mask_left_width
        #将弧度转换为角度
        radian = angle * np.pi / 180
        #对口罩的位置进行微调
        box_x = mask_center_x + int(offset * np.cos(radian)) - rotated_mask_img.width // 2
        box_y = mask_center_y + int(offset * np.sin(radian)) - rotated_mask_img.height // 2

        self._face_img.paste(mask_img,(box_x,box_y),mask_img)
        self.save()

    def save(self):
        self._face_img.save(self.save_path)
        print(f'Save to {self.save_path}')


face_path="imgs/test.jpg"
mask_path="mask_images/mask.png"
save_path="imgs/test_mask.jpg"
WearFaceMask(face_path,mask_path,save_path).wear_face_mask()

接下来我们试试给最不喜欢戴口罩的特没谱同志带上口罩,看看效果如何。感觉好像效果还是差了那么点,下篇文章介绍另一种方法来提升一下这个效果。
在这里插入图片描述
代码我已经上传到gitee:https://gitee.com/pragmaticAgile/wear_face_mask.git

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

修炼之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值