天天看點

PaddleHub扣臉和換臉主要思路環境介紹預訓練模型介紹合成代碼

PaddleHub扣臉和換臉

  • 主要思路
  • 環境介紹
  • 預訓練模型介紹
  • 合成代碼

主要思路

  1. 利用人臉關鍵點檢測檢測出臉部位置,然後根據檢測結果将目标背景與目标臉臉部軸心放齊
  2. 再根據檢測結果裁剪出臉部區域,對臉部區域進行分割,分割出臉和背景,根據分割結果将臉摳出來備用
  3. 将分割出來的臉經過亮度變化(很重要),大小變化,貼合再背景圖的臉部中心位置

環境介紹

PaddleHub, 依托于paddlepaddle的深度學習架構,裡面包含有CV,NLP等的預訓練模型,其安裝代碼為

pip install paddlehub
           

此外還有opencv,matplotlib,math等常用包。安裝方法不再贅述。

預訓練模型介紹

本次主要用到了兩個與訓練包,face_landmark_localization和ace2p。

face_landmark_localization:用于面部檢測,其訓練集為AFW/AFLW,網絡結構為Face_Landmark。送入一張面部照片可以将一下面部點的坐标傳回

PaddleHub扣臉和換臉主要思路環境介紹預訓練模型介紹合成代碼

ace2p:人體解析(Human Parsing)是細粒度的語義分割任務,其旨在識别像素級别的人類圖像的組成部分(例如,身體部位和服裝)訓練集為LIP,網絡結構為ACE2P。ACE2P通過融合底層特征,全局上下文資訊和邊緣細節,端到端地訓練學習人體解析任務。該結構針對Intersection over Union名額進行針對性的優化學習,提升準确率。以ACE2P單人人體解析網絡為基礎的解決方案在CVPR2019第三屆LIP挑戰賽中赢得了全部三個人體解析任務的第一名。該PaddleHub Module采用ResNet101作為骨幹網絡,接受輸入圖檔大小為473x473x3。

PaddleHub扣臉和換臉主要思路環境介紹預訓練模型介紹合成代碼

合成代碼

這裡将面部識别和分割進行封裝在了一起

import cv2
import paddlehub as hub
import numpy as np
class face_Seg(object):
    '''
    imgFile:原始圖檔資料
    origLandMark:第一次detection的坐标
    faceImg:裁剪後的圖檔
    resLandMark:修改後的坐标
    mapImg:分割後的标注圖檔
    resFaceImg:最終分割後圖檔
    '''
    def __init__(self, imgFile):
        self.imgFile = imgFile
        self.origLandMark = None
        self.faceImg = None
        self.resLandMark = None
        self.mapImg = None
        self.resFaceImg = None
        self.oriMapImg = None
        self.orgBox = None
    
    @staticmethod
    def keypoint_detection(images):
        return hub.Module(name="face_landmark_localization").keypoint_detection(images=images)
    
    @staticmethod
    def human_parser(images):
        return hub.Module(name="ace2p").segmentation(images=images)
    '''
    擷取原始圖像
    '''
    def getOrgImgFile(self):
        return self.imgFile
    '''
    擷取面部監測點資訊
    '''
    def getOrLandMark(self):
        if np.all(self.origLandMark == None):
            try:
                res = self.keypoint_detection(images=[self.imgFile])
            except:
                print("無法打開圖檔或檢測有問題!")
                return None
            self.origLandMark = np.array(res[0]['data'][0])
        return self.origLandMark
    '''
    擷取面部圖檔的裁剪後圖像
    '''
    def getFaceImg(self):
        if np.all(self.faceImg == None):
            LandMark = self.getOrLandMark()
            x = LandMark[:,0]
            y = LandMark[:,1]
            self.orgBox = (int(y.min()),int(y.max()),int(x.min()),int(x.max()))
            self.faceImg = self.imgFile[int(y.min()):int(y.max()), int(x.min()):int(x.max())]
            resLM = list(map(lambda a:[a[0]-int(x.min()),a[1]-int(y.min())], LandMark))
            self.resLandMark = np.array(resLM)
        return self.faceImg
    '''
    擷取原圖中截取的部分
    '''
    def getOrgBox(self):
        if self.orgBox == None:
            self.getFaceImg()
        return self.orgBox

    '''
    擷取分割圖檔标記
    '''
    def getMapImg(self):
        if np.all(self.mapImg == None):
            res = self.human_parser(images=[self.getFaceImg()])
            self.mapImg = np.array(res[0]['data'])
        return self.mapImg
    '''
    僅傳回臉部區域分割結果,和對應臉部區域的特征點坐标
    '''
    def getResult(self, savepath = None):
        if np.all(self.resFaceImg == None):
            mapimg = self.getMapImg()
            self.resFaceImg = self.getFaceImg().copy()
            X,Y = mapimg.shape
            for i in range(X):
                for j in range(Y):
                    if mapimg[i,j] != 13:
                        self.resFaceImg[i,j] = [255,255,255]
        if savepath != None:
            cv2.imwrite(savepath, self.resFaceImg)
            print("成功儲存圖檔!")
        return self.resFaceImg, self.resLandMark
    '''
    将分割圖檔擴充到原始圖檔大小,并傳回
    '''
    def getOriMapImg(self):
        if np.all(self.oriMapImg == None):
            X,Y,Z = self.imgFile.shape
            mapimg = self.getMapImg()
            self.oriMapImg = np.zeros((X,Y))
            LandMark = self.getOrLandMark()
            x = LandMark[:,0]
            y = LandMark[:,1]
            for i in range(X):
                for j in range(Y):
                    if x.min()<i<x.max() and y.min()<j<y.max():
                        self.oriMapImg[j,i] = mapimg[j-int(y.min())-1, i-int(x.min())-1]
        return self.oriMapImg
           

在對圖像進行處理前需要定義兩個函數,首先是計算兩個向量之間的夾角,主要是判斷人像需要旋轉的角度,第二個函數就是将面部圖檔進行旋轉,根據第一個計算出來的角度進行旋轉。代碼如下

import math
import numpy as np
import matplotlib.pyplot as plt 
'''
計算兩個向量的夾角,用于校準兩張圖檔的臉部軸心位置
'''
def angle_between(v1,v2):
    angle1 = math.atan2(v1[0], v1[1])
    angle2 = math.atan2(v2[0], v2[1])
    return (angle2-angle1)/math.pi*180 
'''
旋轉圖檔并對圖檔矯正
'''
def rotate_bound(image, angle, center=None):

    (h, w) = image.shape[:2]
    # 定義旋轉中心,可以選擇自定義的中心進行旋轉
    if np.all(center == None):
        (cX, cY) = (w // 2, h // 2)
    else:
        (cX, cY) = (int(center[0]), int(center[1]))
 
    M = cv2.getRotationMatrix2D((cX, cY), -angle, 1.0)
    cos = np.abs(M[0, 0])
    sin = np.abs(M[0, 1])
 
    nW = int((h * sin) + (w * cos))
    nH = int((h * cos) + (w * sin))
 
    M[0, 2] += (nW / 2) - cX
    M[1, 2] += (nH / 2) - cY
 
    return cv2.warpAffine(image, M, (nW, nH))
           

首先我們計算背景圖檔和扣臉圖檔的夾角,這裡将我男神彭于晏的帥氣面龐摳出來

# 面部基本點檢測,男神的臉
tar = face_Seg.keypoint_detection(images = [cv2.imread('./tar.jpg')])  
LandMark = np.array(tar[0]['data'][0])
# 面部基本點檢測,背景圖檔
pic = face_Seg(cv2.imread('./pic.png'))  
baseLandMark = pic.getOrLandMark()
# 擷取兩個面部軸心向量
lineA = LandMark[27] - LandMark[8]
lineB = baseLandMark[27] -baseLandMark[8]
angle = angle_between(lineB,lineA)
# 旋轉男神圖像使得圖像與背景圖像的面部軸心夾角一緻
tar = rotate_bound(cv2.imread('./tar.jpg'), angle,LandMark[8])
# 将旋轉後圖像放入face_Seg類中友善進一步處理
tar = face_Seg(tar)
           

此時我們有兩個照片1,背景圖檔pic,和面部圖檔tar這裡還需要将tar放入face_Seg類中,便于進行下一步的摳圖計算。

# 擷取面部切割結果并将面部切割的結果儲存為face.jpg
FaceImg, LandMark = tar.getResult('face.jpg')
# 原圖檔處理,與上面一樣
picFaceImg, picLandMark = pic.getResult('picface.jpg')
# 将兩個臉的大小調整到一緻
X,Y,Z = picFaceImg.shape
FaceImg = cv2.resize(FaceImg, (X,Y))
# 圖檔合并過程
result = pic.getOrgImgFile()
mask = 255 * np.ones(FaceImg.shape, FaceImg.dtype)
center = pic.getOrgBox()
center = (int((center[2]+center[3])/2) ,int((center[0]+center[1])/2))
flags = cv2.NORMAL_CLONE
FaceImg1 = FaceImg+5 # 調整亮度,是圖像與背景融合更好
output = cv2.seamlessClone(FaceImg1, result, mask, center, flags)
plt.imshow(output)
cv2.imwrite('OK.jpg',output)
           

最後看一下效果吧,jupyter中看原圖代碼為

# 圖檔顯示
from PIL import Image
import matplotlib.pyplot as plt
# 原圖顯示
plt.figure(figsize=[20,6])
plt.subplot(1,3,1)
plt.imshow(Image.open('pic.png'))
# 融合的圖檔
plt.subplot(1,3,2)
plt.imshow(Image.open('tar.jpg'))
# 融合後的圖檔
plt.subplot(1,3,3)
plt.imshow(Image.open('OK.jpg'))
plt.show()
           
PaddleHub扣臉和換臉主要思路環境介紹預訓練模型介紹合成代碼

整體來看圖像契合還是比較好的,但是對于亮度的處理比較粗糙,需要改進。

另外還有一種思路就是,我們通過采集視訊将每個歐拉角度下的人臉資料都采集下來,那麼就可以按照歐拉角度将人臉進行貼合更換,應該會更好。

繼續閱讀