python实现面部特效_【AI美颜算法】300行Python实现基于人脸特征的美颜算法
先上效果图:
AI美颜
人类一直是一个看脸的物种,人人都希望可以变得更美是无可争议的,而美颜类应用的出现拯救了所有人,从此人类进入了美(照)颜(骗)时代。
。。。。
每次写技术blog都要写一堆废话引言,现在懒得写,大概就是美颜很重要,美女主播靠它活,没了它大部分妹子不敢发朋友圈blabla。
美颜算法的基础是人脸识别技术,市面上的美颜应用普遍使用了CV科技类公司的人脸识别(特征点提取)接口:MeituKiss超级自拍神器手机,前置摄像头搭载自动美颜技术。Face++为该款手机提供领先的人脸检测和关键点检测技术,实现实时美颜,自动美肌,智能美型,极致美瞳。
美颜相机是一款专为爱自拍的女生量身定制的美图软件。Face++为美颜相机提供领先的人脸检测和关键点检测技术,在图像中精准定位人脸和五官位置,让多款细腻的人像特效瞬间呈现,让用户快速拥有惊艳的面容。
也有小米新机所搭载的AI美颜技术:
需要人脸识别技术的原因是显而易见的,每张图片面部所占区域不同,五官位置各异,我们针对不同区域要做的美化操作也各不相同。我们需要人脸识别技术为我们定位面部并标记面部不同的区域。如果直接对整张图片进行暴力操作,效果会非常难看~
人脸识别(特征提取)技术目前已经较为成熟,旷视、face++提供的接口可以精确提取100个以上的特征点,根据这些特征点,我们可以针对不同部位进行美化:皮肤磨皮、美白,嘴唇增红、眼睛提亮、瘦脸等等。
在本项目中,使用了开源库dlib C++ Library来提取面部特征。这个库我在另外两篇人脸识别文章【换脸系列1】军装照刷爆朋友圈?教你用Python+深度学习自制换脸软件!(改进)和【换脸系列2】浪漫七夕♥和你的TA交♂换身体吧!(单身狗慎入)有过使用和介绍。这是一个优秀的cv&dl&人脸识别库,提供的预训练模型可以提取68个特征点,精确度较高。
实现
本章代码只起演示作用,不完整不保证运行,完整代码请看GitHub
对象分析
美颜算法的处理对象是人像图片(废话),我们分析一下一张人像图片内的对象:
图片里有若干人脸区域和非人脸区域,我们只希望处理那些人脸区域。
每个人脸,由五官、脸颊、下巴、额头等部件组成。(奇怪的名字,逃
每个部件都有自己的特点,美颜算法需要针对不同的部件进行具体的美化操作,即美颜操作的最小对象是部件。
现在,我们知道了一张图片有两种主要对象:脸、部件,若干部件构成一张脸。我们希望,每个部件对象都有若干方法来美化自身。
我们发现,脸和部件对象都需要坐标点来实例化,而且脸部整体也需要若干方法来美化自身。所以我们可以让脸对象继承自部件对象。
class Organ():
pass
class Forehead(Organ):
pass
勾画有效区域
我们首先要知道每个脸和部件的位置(坐标),我们用dlib来检测并提取特征点坐标。
PREDICTOR_PATH = "/home/matt/dlib-18.16/shape_predictor_68_face_landmarks.dat"
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor(PREDICTOR_PATH)
def get_landmarks(im):
rects = detector(im, 1)
return [numpy.matrix([[p.x, p.y] for p in predictor(im, rect).parts()]) for rect in rects]
这样我们就得到了一个列表,每个元素是一个人脸的特征点坐标数
根据标记点序号,我们可以得到人脸和各个部件的特征坐标:
#五官名称
self.organs_name=['jaw','mouth','nose','left eye','right eye','left brow','right brow']
#五官等标记点
self.organs_points=[list(range(0, 17)),list(range(48, 61)),list(range(27, 35)),list(range(42, 48)),list(range(36, 42)),list(range(22, 27)),list(range(17, 22))]
我们怎么利用这些坐标来针对每个部件的有效区域进行美化处理呢?
首先,获得特征点坐标的边界,再根据区域大小,适当扩大一部分,取对应的储存图片数据的numpy数组的切片。这样我们就得到了全局图片的一个局部切片(引用)。
def get_rect(self):
'''获得定位方框'''
ys,xs=self.landmark[:,1],self.landmark[:,0]
self.top,self.bottom,self.left,self.right=np.min(ys),np.max(ys),np.min(xs),np.max(xs)
self.shape=(int(self.bottom-self.top),int(self.right-self.left))
self.size=self.shape[0]*self.shape[1]*3
self.move=int(np.sqrt(self.size/3)/20)
patch=im[np.max([self.top-self.move,0]):np.min([self.bottom+self.move,shape[0]]),np.max([self.left-self.move,0]):np.min([self.right+self.move,shape[1]])]
接下来,我们要根据特征点在一个和局部切片形状相同的mask图层上勾画部件轮廓。这个mask层只在有效区域值为0~1,其他部分为0.我们使用opencv的convexHull和fillConvexPoly函数。同时,我们不希望遮罩很僵硬,使用要用高斯模糊处理。
def _draw_convex_hull(self,im, points, color):
'''勾画多凸边形'''
points = cv2.convexHull(points)
cv2.fillConvexPoly(im, points, color=color)
def get_mask_re(self,ksize=None):
'''获得局部相对坐标遮罩'''
if ksize==None:
ksize=self.ksize
landmark_re=self.landmark.copy()
landmark_re[:,1]-=np.max([self.top-self.move,0])
landmark_re[:,0]-=np.max([self.left-self.move,0])
mask = np.zeros(self.patch_bgr.shape[:2], dtype=np.float64)
self._draw_convex_hull(mask,
landmark_re,
color=1)
mask = np.array([mask, mask, mask]).transpose((1, 2, 0))
mask = (cv2.GaussianBlur(mask, ksize, 0) > 0) * 1.0
return cv2.GaussianBlur(mask, ksize, 0)[:]
如图,这是得到的鼻子和嘴的mask和切片patch
这样我们就可以逐步得到眼睛、鼻子、嘴巴、眉毛的mask和patch,我们把它们放在全局图片并相加,就能得到全部五官的有效区域。
特别的,dlib的特征提取器并不能返回额头的边界坐标,所以我们需要自己计算额头的特征坐标。
画额头
基于小学时培养的绘画素养,我假设额头大体是一个中心在眉心附近的,长轴=短轴=脸宽且与双眼连线平行的一个
的半椭圆。
要画出这样一个半椭圆,我们首先要知道脸宽、中心点、长轴偏移角度,然后使用opencv的ellipse函数。
#画椭圆
radius=(np.linalg.norm(face_landmark[0]-face_landmark[16])/2).astype('int32')
center_abs=tuple(((face_landmark[0]+face_landmark[16])/2).astype('int32'))
angle=np.degrees(np.arctan((lambda l:l[1]/l[0])(face_landmark[16]-face_landmark[0]))).astype('int32')
mask=np.zeros(mask_organs.shape[:2], dtype=np.float64)
cv2.ellipse(mask,center_abs,(radius,radius),angle,180,360,1,-1)
然而这个椭圆(半圆)只是一个对脑门部位的粗略估计,我们还需要将其他部件、头发、背景等部分剔除出去。
我们根据鼻子的肤色来判定一个区域是否为真正的脑门,最后用convexHull勾画一个包括所有脑门区域的点的特征点轮廓
#剔除与五官重合部分
mask[mask_organs[:,:,0]>0]=0
#根据鼻子的肤色判断真正的额头面积
index_bool=[]
for ch in range(3):
mean,std=np.mean(im_bgr[:,:,ch][mask_nose[:,:,ch]>0]),np.std(im_bgr[:,:,ch][mask_nose[:,:,ch]>0])
up,down=mean+0.5*std,mean-0.5*std
index_bool.append((im_bgr[:,:,ch]up))
index_zero=((mask>0)&index_bool[0]&index_bool[1]&index_bool[2])
mask[index_zero]=0
index_abs=np.array(np.where(mask>0)[::-1]).transpose()
landmark=cv2.convexHull(index_abs).squeeze()
return landmark
再根据求得的坐标实例化一个部件类,去除与其他部件重合的部分,获得mask、patch。
“脸”对象
现在,我们已经获得了全部的面部部件的坐标点和各自的遮罩层。于是,可以求得所有部件的集合,以及整个脸部的patch和除了部件之外的部分的mask,来实例化一个“脸对象”。
我们用一个字典organs来储存部件的集合,键值是部件名称如nose,通过get_mask_abs()方法来获得部件相当于全局图片的遮罩。
我们根据所有的坐标点,调用super方法调用父类Organ的实例化函数,再用mask减去其他部件的mask得到纯脸部的mask。
mask_organs=(self.organs['mouth'].get_mask_abs()+mask_nose+self.organs['left eye'].get_mask_abs()+self.organs['right eye'].get_mask_abs()+self.organs['left brow'].get_mask_abs()+self.organs['right brow'].get_mask_abs())
forehead_landmark=self.get_forehead_landmark(im_bgr,landmarks,mask_organs,mask_nose)
self.organs['forehead']=Forehead(im_bgr,img_hsv,temp_bgr,temp_hsv,forehead_landmark,mask_organs,'forehead')
mask_organs+=self.organs['forehead'].get_mask_abs()
# 人脸的完整标记点
self.FACE_POINTS = np.concatenate([landmarks,forehead_landmark])
super(Face,self).__init__(im_bgr,img_hsv,temp_bgr,temp_hsv,self.FACE_POINTS,'face')
mask_face=self.get_mask_abs()-mask_organs
self.patch_mask=self.get_patch(mask_face)
至此,我们勾画出来全部有效区域,接下来我们要为这些有效区域添加美化方法。
美化方法
本项目目前实现了提亮美白、增加鲜艳度、磨皮、锐化四种基本的美化方法,Organ类及其子类Face、Forehead都有这些方法,这样我们就可以根据需要组合使用这些方法,对不同部位进行具体的美化。
提亮美白
我们知道,图片的颜色空间除了三原色的RGB(BGR)还有HSV和HSL。HSV即色相、饱和度、明度(英语:Hue, Saturation, Value),又称HSB,其中B即英语:Brightness。
色相(H)是色彩的基本属性,就是平常所说的颜色名称,如红色、黄色等。
饱和度(S)是指色彩的纯度,越高色彩越纯,低则逐渐变灰,取0-100%的数值。
明度(V),亮度(L),取0-100%。
相对于BGR,HSV更接近人类的视觉直觉,在本项目中,美白和增加鲜艳度就在hsv空间进行。
hsv空间的图片,v通道的值代表各像素的亮度,所以我们只需要增加有效区域的v值就可以了
def whitening(self,rate=0.15,confirm=True):
'''提亮美白arguments:rate:float,-1~1,new_V=min(255,V*(1+rate))confirm:wether confirm this option'''
if confirm:
self.confirm()
self.patch_hsv[:,:,-1]=np.minimum(self.patch_hsv[:,:,-1]+self.patch_hsv[:,:,-1]*self.patch_mask[:,:,-1]*rate,255).astype('uint8')
self.im_bgr[:]=cv2.cvtColor(self.im_hsv, cv2.COLOR_HSV2BGR)[:]
self.update_temp()
else:
self.patch_hsv_temp[:]=cv2.cvtColor(self.patch_bgr_temp, cv2.COLOR_BGR2HSV)[:]
self.patch_hsv_temp[:,:,-1]=np.minimum(self.patch_hsv_temp[:,:,-1]+self.patch_hsv_temp[:,:,-1]*self.patch_mask[:,:,-1]*rate,255).astype('uint8')
self.patch_bgr_temp[:]=cv2.cvtColor(self.patch_hsv_temp, cv2.COLOR_HSV2BGR)[:]
注意到,我们使用
self.patch_hsv_temp[:,:,-1]*self.patch_mask[:,:,-1]*rate
来表示对于有效区域的更改值,在其他美化方法中我们还会使用类似的方法。同时我们预留了confirm参数,如果为FALSE,则更改只在一个全局的临时copy上进行。
增加鲜艳度
同理,增加鲜艳度的操作也在hsv空间进行,增大s通道的值。
def brightening(self,rate=0.3,confirm=True):
'''提升鲜艳度arguments:rate:float,-1~1,new_S=min(255,S*(1+rate))confirm:wether confirm this option'''
patch_mask=self.get_mask_re((1,1))
if confirm:
self.confirm()
patch_new=self.patch_hsv[:,:,1]*patch_mask[:,:,1]*rate
patch_new=cv2.GaussianBlur(patch_new,(3,3),0)
self.patch_hsv[:,:,1]=np.minimum(self.patch_hsv[:,:,1]+patch_new,255).astype('uint8')
self.im_bgr[:]=cv2.cvtColor(self.im_hsv, cv2.COLOR_HSV2BGR)[:]
self.update_temp()
else:
self.patch_hsv_temp[:]=cv2.cvtColor(self.patch_bgr_temp, cv2.COLOR_BGR2HSV)[:]
patch_new=self.patch_hsv_temp[:,:,1]*patch_mask[:,:,1]*rate
patch_new=cv2.GaussianBlur(patch_new,(3,3),0)
self.patch_hsv_temp[:,:,1]=np.minimum(self.patch_hsv[:,:,1]+patch_new,255).astype('uint8')
self.patch_bgr_temp[:]=cv2.cvtColor(self.patch_hsv_temp, cv2.COLOR_HSV2BGR)[:]
磨皮
磨皮即是去除皮肤上痘痘、皱纹等等噪音,让皮肤更加平滑。这里我们使用高斯滤波器和双边滤波器在BGR空间进行操作。
其中kernelsize是根据patch大小计算得到的。
def get_ksize(self,rate=15):
size=max([int(np.sqrt(self.size/3)/rate),1])
size=(size if size%2==1 else size+1)
return (size,size)
def smooth(self,rate=0.6,ksize=None,confirm=True):
'''磨皮arguments:rate:float,0~1,im=rate*new+(1-rate)*srcconfirm:wether confirm this option'''
if ksize==None:
ksize=self.get_ksize(80)
index=self.patch_mask>0
if confirm:
self.confirm()
patch_new=cv2.GaussianBlur(cv2.bilateralFilter(self.patch_bgr,5,*ksize),ksize,0)
self.patch_bgr[index]=np.minimum(rate*patch_new[index]+(1-rate)*self.patch_bgr[index],255).astype('uint8')
self.im_hsv[:]=cv2.cvtColor(self.im_bgr, cv2.COLOR_BGR2HSV)[:]
self.update_temp()
else:
patch_new=cv2.GaussianBlur(cv2.bilateralFilter(self.patch_bgr_temp,3,*ksize),ksize,0)
self.patch_bgr_temp[index]=np.minimum(rate*patch_new[index]+(1-rate)*self.patch_bgr_temp[index],255).astype('uint8')
self.patch_hsv_temp[:]=cv2.cvtColor(self.patch_bgr_temp, cv2.COLOR_BGR2HSV)[:]
锐化
锐化算法我使用的是最简单的卷积锐化算法
用一个形如
的卷积核去对图像进行卷积。
def sharpen(self,rate=0.3,confirm=True):
'''锐化'''
ksize=self.get_ksize(10)
patch_mask=self.get_mask_re((3,3))
kernel = np.zeros( ksize, np.float32)
center=int(ksize[0]/2)
kernel[center,center] = 2.0 #Identity, times two!
#Create a box filter:
boxFilter = np.ones( ksize, np.float32) / (ksize[0]*ksize[1])
#Subtract the two:
kernel = kernel - boxFilter
index=patch_mask>0
if confirm:
self.confirm()
sharp=cv2.filter2D(self.patch_bgr,-1,kernel)
self.patch_bgr[index]=np.minimum(((1-rate)*self.patch_bgr)[index]+sharp[index]*rate,255).astype('uint8')
self.update_temp()
else:
sharp=cv2.filter2D(self.patch_bgr_temp,-1,kernel)
self.patch_bgr_temp[:]=np.minimum(self.patch_bgr_temp+self.patch_mask*sharp*rate,255).astype('uint8')
self.patch_hsv_temp[:]=cv2.cvtColor(self.patch_bgr_temp, cv2.COLOR_BGR2HSV)[:]
至此,所有的基本美化方法就都成型了,我们在需要美化图片的时候只需要调用对应部位的美化方法就可以了。
化妆器对象
dlib的模型加载需要时间,我们希望一次加载可以处理多个图片,读取和保存图片、提取特征也有各自的方法,所以我们需要一个化妆器对象来维持秩序数据和方法的生存周期。
class Makeup():
'''化妆器'''
def __init__(self,predictor_path="./data/shape_predictor_68_face_landmarks.dat"):
self.photo_path=[]
self.PREDICTOR_PATH = predictor_path
self.faces={}
#人脸定位、特征提取器,来自dlib
self.detector = dlib.get_frontal_face_detector()
self.predictor = dlib.shape_predictor(self.PREDICTOR_PATH)
def get_faces(self,im_bgr,im_hsv,temp_bgr,temp_hsv,name,n=1):
'''人脸定位和特征提取,定位到两张及以上脸或者没有人脸将抛出异常im:照片的numpy数组fname:照片名字的字符串返回值:人脸特征(x,y)坐标的矩阵'''
rects = self.detector(im_bgr, 1)
if len(rects) <1:
raise NoFace('Too many faces in '+name)
return {name:[Face(im_bgr,im_hsv,temp_bgr,temp_hsv,np.array([[p.x, p.y] for p in self.predictor(im_bgr, rect).parts()]),i) for i,rect in enumerate(rects)]}
def read_im(self,fname,scale=1):
'''读取图片'''
im = cv2.imdecode(np.fromfile(fname,dtype=np.uint8),-1)
if type(im)==type(None):
print(fname)
raise ValueError('Opencv error reading image "{}" , got None'.format(fname))
return im
def read_and_mark(self,fname):
im_bgr=self.read_im(fname)
im_hsv=cv2.cvtColor(im_bgr, cv2.COLOR_BGR2HSV)
temp_bgr,temp_hsv=im_bgr.copy(),im_hsv.copy()
return im_bgr,temp_bgr,self.get_faces(im_bgr,im_hsv,temp_bgr,temp_hsv,fname)
read_and_mark方法接受文件路径作为参数,读取图片并定位所有人脸,逐个实例化为Face对象。方法返回全局bgr图片和全局临时图片,以及一个键名为fname,键值为一个Face对象的列表,代表图片内的所有Face对象。
美颜操作
作为示例,我们对所有部分进行美白,除眼睛之外的部位进行磨皮,眼睛锐化实现亮眼,嘴唇提升鲜艳度实现红唇。
处理一张图片的主函数如下:
if __name__=='__main__':
path='./heads/x.jpg'
mu=Makeup()
im,temp_bgr,faces=mu.read_and_mark(path)
imc=im.copy()
cv2.imshow('ori',imc)
for face in faces[path]:
face.whitening()
face.smooth(0.7)
face.organs['forehead'].whitening()
face.organs['forehead'].smooth(0.7)
face.organs['mouth'].brightening(0.6)
face.organs['mouth'].smooth(0.7)
face.organs['mouth'].whitening()
face.organs['left eye'].whitening()
face.organs['right eye'].whitening()
face.organs['left eye'].sharpen(0.7)
face.organs['right eye'].sharpen(0.7)
face.organs['left brow'].whitening()
face.organs['right brow'].whitening()
face.organs['left brow'].sharpen()
face.organs['right brow'].sharpen()
face.organs['nose'].whitening()
face.organs['nose'].smooth(0.7)
face.organs['nose'].sharpen()
cv2.imshow('new',im.copy())
cv2.waitKey()
print('Quiting')
效果如图
GUI
为了方便直观的编辑图片,我又用pyqt编写了GUI版本,效果如文章开头。
Future Works
瘦脸、大眼算法。这个要研究一下,局部扭曲和放大。
只提升嘴唇的鲜艳度,把牙齿剔除,否则会造成黄牙。
有时间试一下对一整个视频文件进行美颜处理~
总结
本项目用尽量简洁高效的算法实现了AI美颜算法的demo,对人脸不同部位进行精确的美化操作。
总的来说,效果。。。。算了,直男癌不评价了,去挑口红了(逃
python实现面部特效_【AI美颜算法】300行Python实现基于人脸特征的美颜算法相关推荐
- 2_Python实现基于人脸特征的美颜算法(20181224)
Python实现基于人脸特征的美颜算法(20181224) https://zhuanlan.zhihu.com/p/29718304 https://github.com/BradLarson/GP ...
- python实现面部特效_用Python获取摄像头并实时控制人脸的实现示例
实现流程 从摄像头获取视频流,并转换为一帧一帧的图像,然后将图像信息传递给opencv这个工具库处理,返回灰度图像(就像你使用本地静态图片一样) 程序启动后,根据监听器信息,使用一个while循环,不 ...
- 100行的python作品详解_漫画喵的100行Python代码逆袭
小喵的唠叨话:这次的博客,讲的是使用python编写一个爬虫工具.为什么要写这个爬虫呢?原因是小喵在看完<极黑的布伦希尔特>这个动画之后,又想看看漫画,结果发现各大APP都没有资源,最终好 ...
- java图像检索的算法_图像检索:几种基于纹理特征的图像检索算法
本文节选自<基于纹理的图像检索算法研究>.描述了几种基于纹理特征的图像检索算法. 第 3 章基于纹理特征的图像检索 3.2 基于灰度共生矩阵的纹理分析法 灰度共生矩阵是分析纹理特征的一种有 ...
- PFLD:简单、快速、超高精度人脸特征点检测算法
作者 | 周强(CV君) 来源 | 我爱计算机视觉(公众号id:aicvml) 60s测试:你是否适合转型人工智能? https://edu.csdn.net/topic/ai30?utm_sourc ...
- 图像检索:几种基于纹理特征的图像检索算法
from:图像检索:几种基于纹理特征的图像检索算法 本文节选自<基于纹理的图像检索算法研究>.描述了几种基于纹理特征的图像检索算法. 第 3 章基于纹理特征的图像检索 3.2 基于灰度共生 ...
- python注册人工智能专业_从专业程度上分析Python和人工智能(AI) 它们如何相关?...
Python和人工智能(AI) - 它们如何相关? Python是当今开发人员使用的最流行的编程语言之一.Guido Van Rossum于1991年创建它,自成立以来,它一直是使用最广泛的语言之一, ...
- python实现面部特效_python 实现波浪滤镜特效
本文用 Python 实现 PS 滤镜的波浪特效 import numpy as np from skimage import img_as_float import matplotlib.pyplo ...
- python k线合成_手把手教你写一个Python版的K线合成函数
手把手教你写一个Python版的K线合成函数 在编写.使用策略时,经常会使用一些不常用的K线周期数据.然而交易所.数据源又没有提供这些周期的数据.只能通过使用已有周期的数据进行合成.合成算法已经有一个 ...
最新文章
- 看网友如何定义C++
- Python进阶-----property用法(实现了get,set,delete三种方法)
- Deep Boltzmann Machines
- Luogu P4139 上帝与集合的正确用法【扩展欧拉定理】By cellur925
- AT0 Intrudoction
- springboot之idea不合并空包
- 利用Python进行数据分析--数据加载、存储与文件格式
- 智芯传感压力传感器在咖啡机中的应用
- 深度剖析JAVA软件工程师
- python3.7 win10 64位系统下用pyinstaller打包的程序在32位系统下无法运行
- mysql excel 数据处理_将excel的数据进行sql处理
- C++ override及虚函数的讲解
- 程序员免费自学编程的8大网站!
- Redis学习之zscore命令
- unity人物转方向
- 2020哈工大计算机考研大纲,2020哈尔滨工业大学854计算机基础硕士研究生入学考试大纲...
- 失眠尽快入睡小妙招,这些助眠产品让你一招入睡
- java,js获取本周和下周开始结束日期
- 基于WebGL(ThingJS)的家具城 商场 3D展示 3D可视化 DEMO
- 亲手撸了一个SpringBoot+Vue的企业级项目(附源码)