python 提取最小外接矩形_python给人脸带上口罩(简单版)

导读

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

原理介绍

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

  • 人脸关键点检测

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

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

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

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

  • 口罩角度的调整

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

  • 口罩位置的确定

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

代码实现
环境准备
  • python版本:3.7

  • 第三方库安装

#安装OpenCVpip install opencv-python#安装numpypip install numpy#安装PIL pip install pillow#安装pytorch,pytorch (>=1.0)#建议安装cuda版本的pytorch#安装教程:https://pytorch.org/get-started/previous-versions/#安装人脸关键点检测face_alignmentpip install face-alignment
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 关键点模型下载不了的问题

如果在使用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 cv2import numpy as npimg = 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)

70e5856ac504f480221de308991a87d3.png

给人脸添加口罩
import os,face_alignmentfrom PIL import Imageimport numpy as npclass 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()

接下来我们试试给最不喜欢戴口罩的特没谱同志带上口罩,看看效果如何。感觉好像效果还是差了那么点,下篇文章介绍另一种方法来提升一下这个效果。

90aa87b10c1b6e196032091e4d7608f0.png

代码我已经上传到gitee:https://gitee.com/pragmaticAgile/wear_face_mask.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值