隨著AI時(shí)代的進(jìn)步,如今各式各類的美顏相機(jī)出現(xiàn)在大眾面前。今天作者帶領(lǐng)大家深入了解下AI美顏的背后技術(shù)原理。
AI美顏核心技術(shù)之一就是人臉關(guān)鍵點(diǎn)檢測(cè)。PaddleHub已經(jīng)開源了人臉關(guān)鍵點(diǎn)檢測(cè)模型face_landmark_localization。人臉關(guān)鍵點(diǎn)檢測(cè)是人臉識(shí)別和分析領(lǐng)域中的關(guān)鍵一步,它是諸如自動(dòng)人臉識(shí)別、表情分析、三維人臉重建及三維動(dòng)畫等其它人臉相關(guān)問題的前提和突破口。該模型轉(zhuǎn)換自 https://github.com/lsy17096535/face-landmark ,支持同一張圖中的多個(gè)人臉檢測(cè)。它可以識(shí)別人臉中的68個(gè)關(guān)鍵點(diǎn)。
?

?
那么如何利用人臉關(guān)鍵點(diǎn)檢測(cè)模型完成美顏功能呢?
一、加載待美顏圖片,檢測(cè)關(guān)鍵點(diǎn)
以教程中的示例圖片為例展示檢測(cè)到的人臉關(guān)鍵點(diǎn)。
NOTE:在運(yùn)行本教程代碼時(shí),由于本代碼示例是效果疊加的演示,美顏效果疊加代碼請(qǐng)勿重復(fù)運(yùn)行,否則出現(xiàn)怪異的圖片展示屬于正常情況。
importcv2
importpaddlehubashub
importmatplotlib.pyplotasplt
importmatplotlib.imageasmpimg
importnumpyasnp
importmath
?
src_img=cv2.imread('./test_sample.jpg')
?
module=hub.Module(name="face_landmark_localization")
result=module.keypoint_detection(images=[src_img])
?
tmp_img=src_img.copy()
forindex,pointinenumerate(result[0]['data'][0]):
? ? # cv2.putText(img, str(index), (int(point[0]), int(point[1])), cv2.FONT_HERSHEY_COMPLEX, 3, (0,0,255), -1)
? ? cv2.circle(tmp_img, (int(point[0]),int(point[1])),2, (0,0,255),-1)
?
res_img_path='face_landmark.jpg'
cv2.imwrite(res_img_path,tmp_img)
?
img=mpimg.imread(res_img_path)
# 展示預(yù)測(cè)68個(gè)關(guān)鍵點(diǎn)結(jié)果
plt.figure(figsize=(10,10))
plt.imshow(img)
plt.axis('off')
plt.show()
[2020-04-17 00:39:19,108] [? ? INFO] - Installing face_landmark_localization module
[2020-04-17 00:39:19,110] [? ? INFO] - Module face_landmark_localization already installed in /home/aistudio/.paddlehub/modules/face_landmark_localization
[2020-04-17 00:39:19,111] [? ? INFO] - Installing ultra_light_fast_generic_face_detector_1mb_640 module
[2020-04-17 00:39:19,144] [? ? INFO] - Module ultra_light_fast_generic_face_detector_1mb_640 already installed in /home/aistudio/.paddlehub/modules/ultra_light_fast_generic_face_detector_1mb_640

二、實(shí)現(xiàn)美顏方法
1. 瘦臉
首先介紹如何利用識(shí)別到的68個(gè)關(guān)鍵點(diǎn)完成瘦臉功能。 利用其中3號(hào)點(diǎn)到5號(hào)點(diǎn)距離作為瘦左臉距離,13號(hào)點(diǎn)到15號(hào)點(diǎn)距離作為瘦右臉距離。同時(shí)利用局部平移算法完成瘦臉.
defthin_face(image,face_landmark):
"""
?? 實(shí)現(xiàn)自動(dòng)人像瘦臉
?? image: 人像圖片
?? face_landmark: 人臉關(guān)鍵點(diǎn)
?? """
end_point=face_landmark[30]
?
# 瘦左臉,3號(hào)點(diǎn)到5號(hào)點(diǎn)的距離作為瘦臉距離
dist_left=np.linalg.norm(face_landmark[3]-face_landmark[5])
image=local_traslation_warp(image,face_landmark[3],end_point,dist_left)
?
# 瘦右臉,13號(hào)點(diǎn)到15號(hào)點(diǎn)的距離作為瘦臉距離
dist_right=np.linalg.norm(face_landmark[13]-face_landmark[15])
image=local_traslation_warp(image,face_landmark[13],end_point,dist_right)
returnimage
deflocal_traslation_warp(image,start_point,end_point,radius):
"""
?? 局部平移算法
?? """
radius_square=math.pow(radius,2)
image_cp=image.copy()
?
dist_se=math.pow(np.linalg.norm(end_point-start_point),2)
height,width,channel=image.shape
foriinrange(width):
forjinrange(height):
# 計(jì)算該點(diǎn)是否在形變圓的范圍之內(nèi)
# 優(yōu)化,第一步,直接判斷是會(huì)在(start_point[0], start_point[1])的矩陣框中
ifmath.fabs(i-start_point[0])>radiusandmath.fabs(j-start_point[1])>radius:
continue
?
distance= (i-start_point[0])*(i-start_point[0])+(j-start_point[1])*(j-start_point[1])
?
if(distance<radius_square):
# 計(jì)算出(i,j)坐標(biāo)的原坐標(biāo)
# 計(jì)算公式中右邊平方號(hào)里的部分
ratio= (radius_square-distance)/(radius_square-distance+dist_se)
ratio=ratio*ratio
?
# 映射原位置
new_x=i-ratio*(end_point[0]-start_point[0])
new_y=j-ratio*(end_point[1]-start_point[1])
?
new_x=new_xifnew_x>=0else0
new_x=new_xifnew_x<height-1elseheight-2
new_y=new_yifnew_y>=0else0
new_y=new_yifnew_y<width-1elsewidth-2.4
?
# 根據(jù)雙線性插值法得到new_x, new_y的值
image_cp[j,i] =bilinear_insert(image,new_x,new_y)
returnimage_cp
?
defbilinear_insert(image,new_x,new_y):
"""
?? 雙線性插值法
?? """
w,h,c=image.shape
ifc==3:
x1=int(new_x)
x2=x1+1
y1=int(new_y)
y2=y1+1
?
part1=image[y1,x1].astype(np.float)*(float(x2)-new_x)*(float(y2)-new_y)
part2=image[y1,x2].astype(np.float)*(new_x-float(x1))*(float(y2)-new_y)
part3=image[y2,x1].astype(np.float)*(float(x2)-new_x)*(new_y-float(y1))
part4=image[y2,x2].astype(np.float)*(new_x-float(x1))*(new_y-float(y1))
?
insertValue=part1+part2+part3+part4
?
returninsertValue.astype(np.int8)
face_landmark=np.array(result[0]['data'][0],dtype='int')
?
src_img=thin_face(src_img,face_landmark)
?
res_img_path='res.jpg'
cv2.imwrite(res_img_path,src_img)
?
img=mpimg.imread(res_img_path)
# 展示瘦臉圖片
plt.figure(figsize=(10,10))
plt.imshow(img)
plt.axis('off')
plt.show()

2. 大眼
完成瘦臉之后,我們還可以對(duì)人像中的眼睛進(jìn)行放大。在識(shí)別到的左右眼中的一個(gè)位置,對(duì)其進(jìn)行縮放(圖像局部縮放),實(shí)現(xiàn)大眼。
defenlarge_eyes(image,face_landmark,radius=15,strength=10):
"""
?? 放大眼睛
?? image: 人像圖片
?? face_landmark: 人臉關(guān)鍵點(diǎn)
?? radius: 眼睛放大范圍半徑
?? strength:眼睛放大程度
?? """
# 以左眼最低點(diǎn)和最高點(diǎn)之間的中點(diǎn)為圓心
left_eye_top=face_landmark[37]
left_eye_bottom=face_landmark[41]
left_eye_center= (left_eye_top+left_eye_bottom)/2
# 以右眼最低點(diǎn)和最高點(diǎn)之間的中點(diǎn)為圓心
right_eye_top=face_landmark[43]
right_eye_bottom=face_landmark[47]
right_eye_center= (right_eye_top+right_eye_bottom)/2
?
# 放大雙眼
local_zoom_warp(image,left_eye_center,radius=radius,strength=strength)
local_zoom_warp(image,right_eye_center,radius=radius,strength=strength)
deflocal_zoom_warp(image,point,radius,strength):
"""
?? 圖像局部縮放算法
?? """
height=image.shape[0]
width=image.shape[1]
left=int(point[0]-radius)ifpoint[0]-radius>=0else0
top=int(point[1]-radius)ifpoint[1]-radius>=0else0
right=int(point[0]+radius)ifpoint[0]+radius<widthelsewidth-1
bottom=int(point[1]+radius)ifpoint[1]+radius<heightelseheight-1
?
radius_square=math.pow(radius,2)
foryinrange(top,bottom):
offset_y=y-point[1]
forxinrange(left,right):
offset_x=x-point[0]
dist_xy=offset_x*offset_x+offset_y*offset_y
?
ifdist_xy<=radius_square:
scale=1-dist_xy/radius_square
scale=1-strength/100*scale
new_x=offset_x*scale+point[0]
new_y=offset_y*scale+point[1]
new_x=new_xifnew_x>=0else0
new_x=new_xifnew_x<height-1elseheight-2
new_y=new_yifnew_y>=0else0
new_y=new_yifnew_y<width-1elsewidth-2
?
image[y,x] =bilinear_insert(image,new_x,new_y)
# 在瘦臉的基礎(chǔ)上,繼續(xù)放大雙眼
enlarge_eyes(src_img,face_landmark,radius=13,strength=13)
?
cv2.imwrite(res_img_path,src_img)
?
img=mpimg.imread(res_img_path)
plt.figure(figsize=(10,10))
plt.imshow(img)
plt.axis('off')
plt.show()

3. 紅唇
目前已經(jīng)疊加了瘦臉、大眼的美顏功能,我們還可以給人像增添氣色,給人像畫上紅唇。我們只需將識(shí)別到的唇部位置給涂上紅色即可達(dá)到相應(yīng)的目的。
defrouge(image,face_landmark,ruby=True):
"""
?? 自動(dòng)涂口紅
?? image: 人像圖片
?? face_landmark: 人臉關(guān)鍵點(diǎn)
?? ruby:是否需要深色口紅
?? """
image_cp=image.copy()
?
ifruby:
rouge_color= (0,0,255)
else:
? ? rouge_color= (0,0,200)
?
points=face_landmark[48:68]
hull=cv2.convexHull(points)
cv2.drawContours(image, [hull],-1,rouge_color,-1)
cv2.addWeighted(image,0.2,image_cp,0.9,0,image_cp)
returnimage_cp
# 繼續(xù)疊加紅唇
src_img=rouge(src_img,face_landmark)
?
cv2.imwrite(res_img_path,src_img)
?
img=mpimg.imread(res_img_path)
plt.figure(figsize=(10,10))
plt.imshow(img)
plt.axis('off')
plt.show()

4. 美白
人像涂上了口紅嗎,顯得氣色更佳了些。同時(shí),很多人還會(huì)追求白皙的皮膚。最后我們還可以加上美膚功能。由于標(biāo)記出來的68個(gè)關(guān)鍵點(diǎn)沒有涵蓋額頭的位置,我們需要預(yù)估額頭位置。為了簡(jiǎn)單估計(jì)二頭所在區(qū)域,本教程以0號(hào)、16號(hào)點(diǎn)所在線段為直徑的半圓為額頭位置。
defwhitening(img,face_landmark):
? ? """
? ? 美白
? ? """
? ? # 簡(jiǎn)單估計(jì)額頭所在區(qū)域
? ? # 根據(jù)0號(hào)、16號(hào)點(diǎn)畫出額頭(以0號(hào)、16號(hào)點(diǎn)所在線段為直徑的半圓)
? ? 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((lambdal:l[1]/l[0])(face_landmark[16]-face_landmark[0]))).astype('int32')
? ? face=np.zeros_like(img)
? ? cv2.ellipse(face,center_abs,(radius,radius),angle,180,360,(255,255,255),2)
?
? ? points=face_landmark[0:17]
? ? hull=cv2.convexHull(points)
? ? cv2.polylines(face, [hull],True, (255,255,255),2)
?
? ? index=face>0
? ? face[index] =img[index]
? ? dst=np.zeros_like(face)
? ? # v1:磨皮程度
? ? v1=3
? ? # v2: 細(xì)節(jié)程度
? ? v2=2
?
? ? tmp1=cv2.bilateralFilter(face,v1*5,v1*12.5,v1*12.5)
? ? tmp1=cv2.subtract(tmp1,face)
? ? tmp1=cv2.add(tmp1,(10,10,10,128))
? ? tmp1=cv2.GaussianBlur(tmp1,(2*v2-1,2*v2-1),0)
? ? tmp1=cv2.add(img,tmp1)
? ? dst=cv2.addWeighted(img,0.1,tmp1,0.9,0.0)
? ? dst=cv2.add(dst,(10,10,10,255))
?
? ? index=dst>0
? ? img[index] =dst[index]
?
? ? returnimg
# 美白
src_img=whitening(src_img,face_landmark)
?
cv2.imwrite(res_img_path,src_img)
img=mpimg.imread(res_img_path)
plt.figure(figsize=(10,10))
plt.imshow(img)
plt.axis('off')
plt.show()
