想看兩張臉合成一張臉的樣子么,OpenCV 來(lái)幫你實(shí)現(xiàn)!

提了好幾天的人臉融合技術(shù),今天終于被提上日程,該技術(shù)是基于之前介紹的技術(shù)基礎(chǔ)上延伸得到的,如果之前沒(méi)有了解過(guò)這兩篇文章,建議提前看下,
OpenCV-Python 繪制人臉 Delaunay 三角剖分(人臉識(shí)別核心技術(shù)之一)

1,Image Morphing 介紹

圖像融合簡(jiǎn)單來(lái)說(shuō),通過(guò)把圖像設(shè)置為不同的透明度,把兩張圖像融合為一張圖像(一般要求圖像需要是等尺寸大小的),公式如下:
M(x,y) = (1-\alpha)I(x,y)+\alpha J(x,y)\\ \alpha 為設(shè)置的透明度參數(shù)\\ I(x,y) 為圖像 I 坐標(biāo)為 (x,y) 的像素值;\\J(x,y)為圖像 J 坐標(biāo)為 (x,y) 的像素值;\\ M(x,y) 為融合之后圖像的像素值;
2,Image Morphing 簡(jiǎn)單嘗試

可以根據(jù)這個(gè)公式嘗試實(shí)現(xiàn)一下融合技術(shù),利用 OpenCV 的 cv2.addWeighted() 函數(shù),代碼如下:

import cv2
import  numpy as np

file_path1 = "E:/data_ceshi/1.jpg"
file_path2 = "E:/data_ceshi/2.jpg"
img1 = cv2.imread(file_path1)
img2 = cv2.imread(file_path2)
morph_img = cv2.addWeighted(img1,0.5,img2,0.5,0)
save_img = np.hstack((img1,morph_img,img2))
cv2.imwrite("E:/data_ceshi/save.jpg",save_img)
cv2.imshow("morph_img",save_img)
cv2.waitKey(0)

這里 alpha 設(shè)置為 0.5, 最終結(jié)果如下圖:

save.jpg

左右兩邊分別為欲融合的兩張圖片,中間的為最終的融合結(jié)果,看起來(lái)非常不好,圖片中臉的部分的確融合了一步分但是給我們的感覺(jué)就是明顯的失真效果,太假了

以上對(duì)人臉進(jìn)行融合之前,若想要達(dá)到不錯(cuò)的效果需要對(duì)人臉區(qū)域進(jìn)行對(duì)齊操作,而這一步就需要用到之前介紹的技術(shù):人臉68個(gè)特征點(diǎn)提取Delaunay 三角剖分

3,特征點(diǎn)提取

在做人臉對(duì)齊時(shí),不僅需要考慮人臉部分需要對(duì)齊,這里也需要考慮圖片的整體性(例如頭發(fā)、脖子、肩膀等部位),因此這里除去 dlib 提取68個(gè)特征點(diǎn)之外,又加入了12個(gè)特征點(diǎn)(人工標(biāo)記)分別圖像四角、四邊中點(diǎn)、是肩膀處,右耳邊緣、脖子等

circle.jpg

4,Delaunay 三角剖分

這里三角剖分目的網(wǎng)格化圖像臉部區(qū)域,方便尋找特征點(diǎn),為后面使用仿射變換進(jìn)行對(duì)齊操作:

delaunay.jpg

從三角剖分圖上來(lái)看,人臉區(qū)域輪廓是非常相似的,人臉融合時(shí)需要把臉部每一個(gè)對(duì)應(yīng)的小三角區(qū)域事先一一對(duì)齊,然后利用設(shè)置的透明度參數(shù)來(lái)做最終的效果融合。這樣結(jié)果就顯得不那么失真。

5,臉部融合

下面將臉部融合技術(shù)拆解為幾部分:

1,臉部特長(zhǎng)點(diǎn)提取、三角剖分(前面已經(jīng)詳細(xì)介紹了,這里就不再一一展開了);

2,對(duì) 1 中的三角剖分每個(gè)頂點(diǎn)做對(duì)應(yīng)點(diǎn)銜接并記錄下來(lái),對(duì)應(yīng)點(diǎn)記錄的是三角形三頂點(diǎn)的索引數(shù),如下圖所示:

image

3,圖片中對(duì)每一個(gè)三角剖分區(qū)域做放射變換,用到的函數(shù):getAffineTransform() 得到仿射變換矩陣,warpAffine() 進(jìn)行放射變換,最終得到兩個(gè)變換圖像,

4,對(duì) 3 中得到的兩圖像中像素值調(diào)整透明度參數(shù),來(lái)進(jìn)行圖像融合

最終結(jié)果如下:

image
image
image

結(jié)果來(lái)看,臉部區(qū)域能夠取得不錯(cuò)的結(jié)果,但整體來(lái)看仍然有很大的瑕疵,但是我們可以通過(guò)手動(dòng)選擇更多特征對(duì)應(yīng)點(diǎn)來(lái)改善這種效果,最后附上完整代碼

import cv2
import numpy as np
import sys


#Read points from  text file
def readPoints(path):
    # Create an array of points
    points = []
    # Read points
    with open(path) as file:
        for line in file:
            x,y = line.split()
            points.append((int(x),int(y)))

    return points


# Apply affine tranform calculated using srcTri and sdtTri to src and output an image of size
def applyAffineTransform(src,srcTri,dstTri,size):

    #Given a pair of triangles,find the affine transform.

    warpMat = cv2.getAffineTransform(np.float32(srcTri),np.float32(dstTri))

    #Apply the Affine Transform just foundto the src image
    dst = cv2.warpAffine(src,warpMat,(size[0],size[1]),None,flags=cv2.INTER_LINEAR,borderMode=cv2.BORDER_REFLECT_101)

    return dst


# Warps and alpha blends triangular regions from img1 and img2 to img
def morphTriangle(img1,img2,img,t1,t2,t,alpha):

    #Find bounding rectangle for each triangle
    r1 = cv2.boundingRect(np.float32([t1]))
    r2 = cv2.boundingRect(np.float32([t2]))
    r = cv2.boundingRect(np.float32([t]))

    # Offset points by left top corner of the respective rectangles
    t1Rect = []
    t2Rect = []
    tRect = []

    for i in range(0,3):
        tRect.append(((t[i][0] - r[0]),(t[i][1]-r[1])))
        t1Rect.append(((t1[i][0]-r1[0]),(t1[i][1]-r1[1])))
        t2Rect.append(((t2[i][0] -r2[0]),(t2[i][1]-r2[1])))

    # Get mask by filling triangles
    mask = np.zeros((r[3],r[2],3),dtype = np.float32)
    cv2.fillConvexPoly(mask,np.int32(tRect),(1.0,1.0,1.0),16,0)

    # Apply warpImage to small rectangular patched
    img1Rect = img1[r1[1]:r1[1]+r1[3],r1[0]:r1[0]+r1[2]]
    img2Rect = img2[r2[1]:r2[1]+r2[3],r2[0]:r2[0]+r2[2]]

    size = (r[2],r[3])
    warpImage1 = applyAffineTransform(img1Rect,t1Rect,tRect,size)
    warpImage2 = applyAffineTransform(img2Rect,t2Rect,tRect,size)

    # Alpha blend rectangular patches
    imgRect = (1.0-alpha) *warpImage1 +alpha*warpImage2

    # Copy triangular region of rectangular patch to tje output image
    print(r[1],r[3],r[0],r[2])
    print(imgRect.shape)
    img[r[1]:r[1]+r[3],r[0]:r[0]+r[2]] = img[r[1]:r[1]+r[3],r[0]:r[0]+r[2]]*(1-mask) +imgRect*mask



if __name__ =='__main__':
    filename1 = "E:/data_ceshi/2.jpg"
    filename2 = "E:/data_ceshi/3.jpg"

    points_txt1 = "E:/data_ceshi/2.txt"
    points_txt2  ="E:/data_ceshi/3.txt"

    alpha = 0.5

    # Read images
    img1 = cv2.imread(filename1)
    img2 = cv2.imread(filename2)

    # Convertat to float data type
    img1 = np.float32(img1)
    img2 = np.float32(img2)

    # Read array of corresponding points
    points1 = readPoints(points_txt1)
    points2 = readPoints(points_txt2)
    points = []


    # Compute weighted average point coordinate

    for i in range(0,len(points1)):
        x = (1-alpha) *points1[i][0] +alpha *points2[i][0]
        y = (1-alpha)*points1[i][1] + alpha*points2[i][1]
        points.append((x,y))


    imgMorph = np.zeros(img1.shape,dtype = img1.dtype)

    # Read triangles for tri.txt
    with open("E:/data_ceshi/tri.txt") as file:
        for line in file:
            x,y,z = line.split()

            x = int(x)
            y = int(y)
            z = int(z)

            t1 = [points1[x],points1[y],points1[z]]
            t2 = [points2[x],points2[y],points2[z]]
            t = [points[x],points[y],points[z]]
            # Morph one triangle at a time
            morphTriangle(img1,img2,imgMorph,t1,t2,t,alpha)



    # Display Results

    out_img = np.hstack((img1,imgMorph,img2))
    cv2.imwrite("E:/data_ceshi/out_img.jpg",out_img)

    cv2.imshow("Morphed Face",np.uint8(imgMorph))
    cv2.waitKey(0)

小總結(jié)

雖然本次面向?qū)ο笫侨四?,但相同技術(shù)原理也可以運(yùn)用到其他物體上面,比如把蘋果和橘子相融合、人臉區(qū)域更換等功能,如果有更好的 idea 的話,可能會(huì)得到意想不到的結(jié)果!

最后文章中完整源碼和文件都已經(jīng)打包到 Github 上去了,關(guān)注微信公號(hào) : Z先生點(diǎn)記 ,后臺(tái)回復(fù)關(guān)鍵詞 FaceMorph 即可獲取;OpenCV Python 部分暫時(shí)更新到這里(后續(xù)還會(huì)有的),接下來(lái)將更新 Numpy 程序包的相關(guān)使用教程!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容