《数字图像处理》dlib人脸检测获取关键点,delaunay三角划分,实现人脸的几何变换warpping,接着实现两幅人脸图像之间的渐变合成morphing

article/2025/9/4 21:42:28

       这学期在上《数字图像处理》这门课程,老师布置了几个大作业,自己和同学一起讨论完成后,感觉还挺有意思的,就想着把这个作业整理一下 :

 

目录

1.实验任务和要求

2.实验原理

3.实验代码

3.1利用人脸特征点检测工具dlib获取人脸关键点

目录

1.实验任务和要求

2.实验原理

3.实验代码

3.1利用人脸特征点检测工具dlib获取人脸关键点以及delaunay三角划分

3.2  实现人脸的warpping(几何变换)

3.3 实现两张人脸的morphing(渐变合成)

1.实验任务和要求

(1) 利用人脸特征点检测工具如dlib(http://dlib.net/)获得人脸关键点;

(2) 获得Delaunay Triangulation,可利用Opencv

(https://www.learnopencv.com/delaunay-triangulation-and-voronoi-diagram-using-opencv-c-python/)或matlab提供的delaunay函数;

(3) 自己编写triangle-to-triangle的几何变换(形变warp)函数,实现人脸Warping, 即表观属性(色彩,亮度,身份等)保持,仅变换几何属性,如人脸的胖瘦变化和角度变化,注意身份不变还是这个人;

(4)实现两幅人脸图像之间的几何属性交换和表观渐变合成视频Morphing,即从第一张人脸逐渐过渡到第二张人脸,几何属性(shape,view)和表观属性(illumination,color,identification等)都变了。

2.实验原理

       实验原理方面,delaunay三角划分和其余原理均可以较轻松地在网上查到,故不再赘述,这里只列出进行仿射变换的原理:

       根据人脸检测得到的特征点进行delaunay三角划分后,就需要对三角形逐个进行仿射变换了,具体原理如下:  

      给定两个三角形:ABC和A’B’C’,现在需要找到一个变换矩阵T来把ABC中的像素变换到A’B’C’中去。

   图2.2.1 三角形仿射变换

       假设ABC内一点坐标为(x,y),那么就需要找到其在A’B’C’中的对应点(x’,y’),然后把(x,y)点处的像素值赋值给(x’,y’)点即可。现在给定变换矩阵T,则变换公式如下:

公式2.2.2 仿射变换公式

其中,  即为变换矩阵T。

        当然,仿射变换有两种,分为前向仿射和后向仿射。前向仿射是根据(x,y)点的坐标寻找(x’,y’)点的坐标,根据公式2.2.3

                                                                  (x’,y’)=T(x,y)

公式2.2.3 前向仿射变换公式

进行相应像素值的赋值;

图2.2.4 前向仿射变换图示

       后向仿射是根据(x’,y’)点的坐标寻找(x,y)点的坐标,根据公式2.2.5

                                                            (x,y)=T-1(x’,y’)

公式2.2.5 后向仿射变换公式

进行相应像素值的赋值。


                                            图2.2.6 后向仿射变换图示

       遍历三角形内的各个点,根据得到的仿射变换矩阵T或者是T-1寻找其在另一个三角形中对应的点,然后进行像素值的赋值即可。

3.实验代码

3.1利用人脸特征点检测工具dlib获取人脸关键点以及delaunay三角划分

       注意:因为dlib官方给的是人脸68个关键点的检测,而在人脸图像的warpping的处理中,会涉及到整张图像,所以新增了8个关键点,分别对应着图像的四个角以及四个边的中间点,本代码将结果保存到了txt文件中:

import dlib
import cv2 as cv
import numpy as np#dlib人脸特征点检测调用,返回值是68个检测点构成的一个列表
def character_point(image, save_path):detector = dlib.get_frontal_face_detector()    #使用dlib库提供的人脸提取器predictor = dlib.shape_predictor('F:/BaiduNetdiskDownload/pictures/renlian/shape_predictor_68_face_landmarks.dat')   #构建特征提取器
#注意,这个dat文件要放在和你代码相同的路径下rects = detector(image,1)  #rects表示人脸数 人脸检测矩形框4点坐标:左上角(x1,y1)、右下角(x2,y2)f = open(save_path, 'w+')points_lst = []for i in range(len(rects)):landmarks = np.matrix([[p.x, p.y] for p in predictor(image,rects[i]).parts()])  #人脸关键点识别 landmarks:获取68个关键点的坐标#shape=predictor(img,box) 功能:定位人脸关键点 img:一个numpy ndarray,包含8位灰度或RGB图像  box:开始内部形状预测的边界框#返回值:68个关键点的位置for idx, point in enumerate(landmarks):        #enumerate函数遍历序列中的元素及它们的下标# 68点的坐标pos = (int(point[0, 0]), int(point[0, 1]))#print(idx,pos)points_lst.append(pos)#将特征点写入txt文件中,方便后面使用f.write(str(point[0, 0]))f.write('\t')f.write(str(point[0, 1]))f.write('\n')f.write(str(0))f.write('\t')f.write(str(0))f.write('\n')        #图像左上角的点  f.write(str(0))f.write('\t')f.write(str(image.shape[0]//2))f.write('\n')        #左边界中间的点f.write(str(0))f.write('\t')f.write(str(image.shape[0]-1))f.write('\n')        #左下角的点f.write(str(image.shape[1]//2))f.write('\t')f.write(str(0))f.write('\n')        #上边界中间的点f.write(str(image.shape[1]//2))f.write('\t')f.write(str(image.shape[0]-1))f.write('\n')        #下边界中间的点f.write(str(image.shape[1]-1))f.write('\t')f.write(str(0))f.write('\n')        #右上角的点f.write(str(image.shape[1]-1))f.write('\t')f.write(str(image.shape[0]//2))f.write('\n')        #右边界中间的点f.write(str(image.shape[1]-1))f.write('\t')f.write(str(image.shape[0]-1))f.write('\n')        #右下角的点points_lst.append((0,0))                                    #图像左上角的点points_lst.append((0,image.shape[0]//2))                    #左边界中间的点points_lst.append((0,image.shape[0]-1))                     #左下角的点points_lst.append((image.shape[1]//2,0))                    #上边界中间的点points_lst.append((image.shape[1]//2,image.shape[0]-1))     #下边界中间的点points_lst.append((image.shape[1]-1,0))                     #右上角的点points_lst.append((image.shape[1]-1,image.shape[0]//2))     #右边界中间的点points_lst.append((image.shape[1]-1,image.shape[0]-1))      #右下角的点for i,item in enumerate(points_lst):pos=item#'''# 利用cv2.circle给每个特征点画一个圈,共68个cv.circle(image, pos, 2, color=(0, 255, 0))#利用cv.putText输出1-68#各参数依次是:图片,添加的文字,坐标,字体,字体大小,颜色,字体粗细cv.putText(image, str(i), pos, cv.FONT_HERSHEY_PLAIN, 0.8, (0, 255, 0), 1,cv.LINE_AA)#cv.namedWindow("img", point_detect)     cv.imshow("img", image)       #显示图像cv.waitKey(100)        #等待按键,随后退出#'''#cv.imwrite('telangpu_point_detect',image)f.close()return points_lst,imagedef rect_contains(rect, point):if point[0] < rect[0]:return Falseelif point[1] < rect[1]:return Falseelif point[0] > rect[2]:return Falseelif point[1] > rect[3]:return Falsereturn True#Draw delaunay triangles
def draw_delaunay(image, subdiv, delaunary_color,save_path,save_path_index):f1=open(save_path_index,'w')   #创建text文本,存入三角形三个顶点的索引save_path_lst=[]with open(save_path,'r') as f:  #打开存有76个关键点的文件,放入列表中for line in f:x,y=line.split()save_path_lst.append((int(x),int(y)))triangleList = subdiv.getTriangleList()for t in triangleList:pt1 = (int(t[0]), int(t[1]))pt2 = (int(t[2]), int(t[3]))pt3 = (int(t[4]), int(t[5]))if rect_contains(rect, pt1) and rect_contains(rect, pt2) and rect_contains(rect, pt3):cv.line(image, pt1, pt2, delaunary_color, 1, cv.LINE_AA, 0)cv.line(image, pt2, pt3, delaunary_color, 1, cv.LINE_AA, 0)cv.line(image, pt3, pt1, delaunary_color, 1, cv.LINE_AA, 0)for index,item in enumerate(save_path_lst):if pt1==item:f1.write(str(index))f1.write('\t')elif pt2==item:f1.write(str(index))f1.write('\t')elif pt3==item:f1.write(str(index))f1.write('\t')f1.write('\n')f1.close()#####################################
#           以下是主程序             #
#####################################
image = cv.imread("clinton.jpg")  #读取图像
save_path1 = 'clinton1.txt'  #将特征点保存入text文件中
point_list1,image1 = character_point(image, save_path1) #dlib人脸特征点检测调用,返回值是68个检测点构成的一个列表save_path2='tri3_clinton.txt'  #创建text文本,存入三角形三个顶点的索引      size = image.shape                      #使用矩形定义要区分的空间
rect = (0, 0, size[1], size[0])subdiv = cv.Subdiv2D(rect)              #创建 Subdiv2D 的实例
delaunary_color = (0, 0, 255)       #使用红色画三角形
image_origin = image.copy()         #拷贝一份图像
animate=True          #当画三角形的时候打开动画
for p in point_list1:subdiv.insert(p)#显示动画if animate :img_copy = image_origin.copy()# Draw delaunay trianglesdraw_delaunay( img_copy, subdiv, (0, 0, 255) ,save_path1,save_path2); cv.imshow('win_delaunay', img_copy)k=cv.waitKey(100)#if k==ord('s'):#cv.destroyAllWindows()  draw_delaunay(image_origin, subdiv, delaunary_color,save_path1,save_path2); #dim=(550,700)
#image_origin=cv.resize(image_origin,dim)  #更改图像尺寸cv.imshow('win_delaunay', image_origin)
#cv.imshow('win_0',image)
k=cv.waitKey(0)
if k==ord('s'):cv.imwrite("after delaunary/'s image.jpg",image_origin)cv.imwrite("clinton point detect.jpg",image1)cv.destroyAllWindows()

结果显示:

人脸关键点检测:

delaunay三角划分:

3.2  实现人脸的warpping(几何变换)

      下面这段代码,我把前面的人脸检测获取关键点和delaunay三角划分一起整合进来了,为的是构成单独的一个完整的代码,这样的话,关键点的坐标和三角形的索引就直接转化成了列表,不需要再次读入txt文件了:

import cv2
import numpy as np
#import matplotlib.pyplot as plt
import dlib#################################################################
#定义读取坐标点函数
def readpoints(path):points=[]with open(path,'r') as f:for line in f:x,y=line.split()x=int(x)y=int(y)points.append((x,y))return points
#######################################################################
#dlib人脸特征点检测调用,返回值是68个检测点构成的一个列表
def character_point(image, save_path):detector = dlib.get_frontal_face_detector()    #使用dlib库提供的人脸提取器predictor = dlib.shape_predictor(save_path)   #构建特征提取器rects = detector(image,1)  #rects表示人脸数 人脸检测矩形框4点坐标:左上角(x1,y1)、右下角(x2,y2)points_lst = []for i in range(len(rects)):landmarks = np.matrix([[p.x, p.y] for p in predictor(image,rects[i]).parts()])  #人脸关键点识别 landmarks:获取68个关键点的坐标#shape=predictor(img,box) 功能:定位人脸关键点 img:一个numpy ndarray,包含8位灰度或RGB图像  box:开始内部形状预测的边界框#返回值:68个关键点的位置for idx, point in enumerate(landmarks):        #enumerate函数遍历序列中的元素及它们的下标# 68点的坐标pos = (int(point[0, 0]), int(point[0, 1]))#print(idx,pos)points_lst.append(pos)points_lst.append((0,0))                                    #图像左上角的点points_lst.append((0,image.shape[0]//2))                    #左边界中间的点points_lst.append((0,image.shape[0]-1))                     #左下角的点points_lst.append((image.shape[1]//2,0))                    #上边界中间的点points_lst.append((image.shape[1]//2,image.shape[0]-1))     #下边界中间的点points_lst.append((image.shape[1]-1,0))                     #右上角的点points_lst.append((image.shape[1]-1,image.shape[0]//2))     #右边界中间的点points_lst.append((image.shape[1]-1,image.shape[0]-1))      #右下角的点return points_lst######################################################################
#delaunay三角划分
def rect_contains(rect, point):if point[0] < rect[0]:return Falseelif point[1] < rect[1]:return Falseelif point[0] > rect[2]:return Falseelif point[1] > rect[3]:return Falsereturn Truedef get_delaunary(img,point_list):                                size=img.shaperect = (0, 0, size[1], size[0])      #使用矩形定义要区分的空间subdiv = cv2.Subdiv2D(rect)              #创建 Subdiv2D 的实例for p in point_list:subdiv.insert(p)lst=[]   #创建列表,存入三角形三个顶点的索引triangleList = subdiv.getTriangleList()for t in triangleList:pt1 = (int(t[0]), int(t[1]))pt2 = (int(t[2]), int(t[3]))pt3 = (int(t[4]), int(t[5]))if rect_contains(rect, pt1) and rect_contains(rect, pt2) and rect_contains(rect, pt3):#cv2.line(image, pt1, pt2, delaunary_color, 1, cv2.LINE_AA, 0)#cv2.line(image, pt2, pt3, delaunary_color, 1, cv2.LINE_AA, 0)#cv2.line(image, pt3, pt1, delaunary_color, 1, cv2.LINE_AA, 0)lst_1=[]for index,item in enumerate(point_list):if pt1==item:lst_1.append(index)elif pt2==item:lst_1.append(index)elif pt3==item:lst_1.append(index)lst.append(lst_1)return lst
#######################################################################################
#仿射变换函数求取变换矩阵以及变换矩阵的应用
# 获得仿射变换矩阵
def getAffineTransform(srctri, dsttri):srctri = np.float32(srctri)dsttri = np.float32(dsttri)srctri1 = []for i in range(len(srctri)):src = srctri[i]src = list(src)srctri1.append(src)A = np.reshape(np.array(srctri1), (3, 2))  # 求出基于原始多边形顶点的坐标所组成的矩阵dsttri1 = []for i in range(len(dsttri)):dst = dsttri[i]dst = list(dst)dst.append(1)  #得出[x,y,1]的形式dsttri1.append(dst)B = np.reshape(np.array(dsttri1), (3, 3))  # 求出基于目标多边形顶点的坐标所组成的矩阵MT= np.linalg.pinv(B).dot(A)M=MT.Treturn M#计算三角形面积
def is_trangle_area(x1,y1,x2,y2,x3,y3):return abs((x1*(y2-y3))+(x2*(y3-y1))+(x3*(y1-y2)))#将仿射变换矩阵应用到图像中
def warpAffine(src,warpMat,size,dstTri):dst=np.zeros((size[1],size[0],3),dtype=src.dtype)x1=dstTri[0][0]y1=dstTri[0][1]x2=dstTri[1][0]y2=dstTri[1][1]x3=dstTri[2][0]y3=dstTri[2][1]abc=is_trangle_area(x1,y1,x2,y2,x3,y3)#k=0for j in range(dst.shape[0]):for i in range(dst.shape[1]):abp=is_trangle_area(x1,y1,x2,y2,i,j)acp=is_trangle_area(x1,y1,x3,y3,i,j)bcp=is_trangle_area(x2,y2,x3,y3,i,j)if abc==abp+acp+bcp :lst=[]lst.append(i)lst.append(j)lst.append(1)A=np.reshape(np.array(lst),(3,1))  #将列表转换为矩阵(3*1)B=np.dot(warpMat,A)                #应用仿射变换矩阵,得到仿射后的坐标lst_dst=[]lst1=list(B)for index in range(len(lst1)):lst_dst.append(lst1[index][0])x=lst_dst[0]-1y=lst_dst[1]-1m=int(y)n=int(x)u=y-mv=x-ndst[j][i]=(1-u)*(1-v)*src[m][n]+(1-u)*v*src[m][n+1]+u*(1-v)*src[m+1][n]+u*v*src[m+1][n+1]#'''return dstdef applyAffineTransform(src,srcTri,dstTri,size):#获取仿射变换矩阵MM=cv2.getAffineTransform(np.float32(srcTri),np.float32(dstTri))#M=getAffineTransform(srcTri,dstTri)#将仿射变换应用到图像块中dst=cv2.warpAffine(src,M,(size[0],size[1]),None,flags=cv2.INTER_LINEAR,borderMode=cv2.BORDER_REFLECT_101)#dst=warpAffine(src,M,size,dstTri)return dst#####################################################################
#定义warp函数来对图像块进行形变处理
def warpImage(srcTri,dstTri,img1,img):#为每个三角形寻找边界矩形r1=cv2.boundingRect(np.float32(srcTri))r=cv2.boundingRect(np.float32(dstTri))#每个三角形三个顶点与矩形左上角顶点的偏移t1Rect=[]tRect=[]for i in range(0,3):t1Rect.append(((srcTri[i][0]-r1[0]),(srcTri[i][1]-r1[1])))tRect.append(((dstTri[i][0]-r[0]),(dstTri[i][1]-r[1])))#为r设置掩膜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)#裁剪矩形块img1Rect=img1[r1[1]:r1[1]+r1[3],r1[0]:r1[0]+r1[2]]#imgRect=img[r[1]:r[1]+r[3],r[0]:r[0]+r[2]]#对img1Rect进行warpping操作size=(r[2],r[3])warpImage1=applyAffineTransform(img1Rect,t1Rect,tRect,size)#dim=(size)#img2Rect=cv2.resize(img1Rect,dim)#imgmix=(1.0 - alpha) * img2Rect + alpha * warpImage1#把warpping后的warpImage1复制到输出图像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) + warpImage1 * mask#imgRect=imgRect * (1-mask) + warpImage1#return imgRect
######################################################################################
def interpolation(image):'''定义插值函数:return:'''image_h=image.shape[0]image_w=image.shape[1]for j in range(image_h):for i in range(image_w):if image[j][i].all()==0 and (j+2)<image_h and (i+2)<image_w:image[j][i]=image[j+2][i+2]return image
##########################################################################################if __name__=='__main__':#设置alpha值alpha=1#读取图像img_ori=cv2.imread('clinton.jpg')img_refer=cv2.imread('telangpu1.jpg')#将img图像转为浮点数据类型img=np.float32(img_ori)#读取坐标点save_path_68='F:/BaiduNetdiskDownload/pictures/renlian/shape_predictor_68_face_landmarks.dat'point_76_lst_ori=character_point(img_ori,save_path_68)      point_76_lst_refer=character_point(img_refer,save_path_68)     #获取76个坐标点#points1=readpoints('telangpu1.txt')  #readpoints函数在前面有定义#points2=readpoints('clinton1.txt')points=[]#'''for i in range(0,len(point_76_lst_ori)):x=alpha*point_76_lst_ori[i][0]+(1-alpha)*point_76_lst_refer[i][0]y=alpha*point_76_lst_ori[i][1]+(1-alpha)*point_76_lst_refer[i][1]points.append((int(x),int(y)))    #获取目标图像的76个坐标点#'''lst=get_delaunary(img_ori,point_76_lst_ori) #获取每个三角形顶点在point_76_lst_ori中的索引#为最终输出分配空间warppingImage=np.zeros(img.shape,dtype=img.dtype)#'''for item in lst:x=int(item[0])y=int(item[1])z=int(item[2])t1=[point_76_lst_ori[x],point_76_lst_ori[y],point_76_lst_ori[z]]t=[points[x],points[y],points[z]]warpImage(t1,t,img,warppingImage)     #对每个小块进行warp操作                           #'''#warppingImage=interpolation(np.uint8(warppingImage))   #对图像进行插值,去除黑线,函数在上面有定义cv2.imshow('after warpping \'s image',np.uint8(warppingImage))cv2.imshow('img_ori',img_ori)k=cv2.waitKey(0)if k==ord('s'):#cv2.imwrite('final.jpg', np.uint8(warppingImage))cv2.destroyAllWindows()

       注意:在这里有一段代码,是自己编写的,但是大家也可以调用cv2本身的库函数,效果要比自己写的好很多,而且速度也很快,而且调用库函数的话,是不需要进行插值的:

def applyAffineTransform(src,srcTri,dstTri,size):#获取仿射变换矩阵MM=cv2.getAffineTransform(np.float32(srcTri),np.float32(dstTri))#M=getAffineTransform(srcTri,dstTri)#将仿射变换应用到图像块中dst=cv2.warpAffine(src,M,(size[0],size[1]),None,flags=cv2.INTER_LINEAR,borderMode=cv2.BORDER_REFLECT_101)#dst=warpAffine(src,M,size,dstTri)return dst

结果:


                alpha=0                                                                                      alpha=0.2


                           alpha=0.4                                                            alpha=0.6


                              alpha=0.8                                                        alpha=1.0

3.3 实现两张人脸的morphing(渐变合成)

       这里依然和上节的人脸warpping一样,把获取人脸的关键点和delaunay三角划分整合到了里面;并且依然是自己编写的仿射变换函数,但是这里结果却并不令人满意,会有大量的黑色条纹出现,需要进一步的改进,建议这里直接调用cv2的库函数即可:

import numpy as np
import cv2
import dlib# Read points from text file 从text文件中读取点
def readPoints(path) :# Create an array of points.points = []; # Read pointswith open(path) as file :for line in file :x, y = line.split()points.append((int(x), int(y)))return points
########################################################################
def interpolation(image):'''定义插值函数:return:'''image_h=image.shape[0]image_w=image.shape[1]for j in range(image_h):for i in range(image_w):if image[j][i].all()==0 and (j+2)<image_h and (i+2)<image_w:image[j][i]=image[j+2][i+2]return image
############################################################################################
#dlib人脸特征点检测调用,返回值是68个检测点构成的一个列表
def character_point(image, save_path):detector = dlib.get_frontal_face_detector()    #使用dlib库提供的人脸提取器predictor = dlib.shape_predictor(save_path)   #构建特征提取器rects = detector(image,1)  #rects表示人脸数 人脸检测矩形框4点坐标:左上角(x1,y1)、右下角(x2,y2)points_lst = []for i in range(len(rects)):landmarks = np.matrix([[p.x, p.y] for p in predictor(image,rects[i]).parts()])  #人脸关键点识别 landmarks:获取68个关键点的坐标#shape=predictor(img,box) 功能:定位人脸关键点 img:一个numpy ndarray,包含8位灰度或RGB图像  box:开始内部形状预测的边界框#返回值:68个关键点的位置for idx, point in enumerate(landmarks):        #enumerate函数遍历序列中的元素及它们的下标# 68点的坐标pos = (int(point[0, 0]), int(point[0, 1]))#print(idx,pos)points_lst.append(pos)points_lst.append((0,0))                                    #图像左上角的点points_lst.append((0,image.shape[0]//2))                    #左边界中间的点points_lst.append((0,image.shape[0]-1))                     #左下角的点points_lst.append((image.shape[1]//2,0))                    #上边界中间的点points_lst.append((image.shape[1]//2,image.shape[0]-1))     #下边界中间的点points_lst.append((image.shape[1]-1,0))                     #右上角的点points_lst.append((image.shape[1]-1,image.shape[0]//2))     #右边界中间的点points_lst.append((image.shape[1]-1,image.shape[0]-1))      #右下角的点return points_lst
########################################################################
#delaunay三角划分
def rect_contains(rect, point):if point[0] < rect[0]:return Falseelif point[1] < rect[1]:return Falseelif point[0] > rect[2]:return Falseelif point[1] > rect[3]:return Falsereturn Truedef get_delaunary(img,point_list):                                size=img.shaperect = (0, 0, size[1], size[0])      #使用矩形定义要区分的空间subdiv = cv2.Subdiv2D(rect)              #创建 Subdiv2D 的实例for p in point_list:subdiv.insert(p)lst=[]   #创建列表,存入三角形三个顶点的索引triangleList = subdiv.getTriangleList()for t in triangleList:pt1 = (int(t[0]), int(t[1]))pt2 = (int(t[2]), int(t[3]))pt3 = (int(t[4]), int(t[5]))#print(pt1)if rect_contains(rect, pt1) and rect_contains(rect, pt2) and rect_contains(rect, pt3):#cv2.line(image, pt1, pt2, delaunary_color, 1, cv2.LINE_AA, 0)#cv2.line(image, pt2, pt3, delaunary_color, 1, cv2.LINE_AA, 0)#cv2.line(image, pt3, pt1, delaunary_color, 1, cv2.LINE_AA, 0)lst_1=[]for index,item in enumerate(point_list):if pt1==item:lst_1.append(index)elif pt2==item:lst_1.append(index)elif pt3==item:lst_1.append(index)#print(item)#print(lst_1)lst.append(lst_1)return lst
########################################################################
# 获得仿射变换矩阵
def getAffineTransform(srctri, dsttri):srctri = np.float32(srctri)dsttri = np.float32(dsttri)srctri1 = []for i in range(len(srctri)):src = srctri[i]src = list(src)srctri1.append(src)A = np.reshape(np.array(srctri1), (3, 2))  # 求出基于原始多边形顶点的坐标所组成的矩阵dsttri1 = []for i in range(len(dsttri)):dst = dsttri[i]dst = list(dst)dst.append(1)  #得出[x,y,1]的形式dsttri1.append(dst)B = np.reshape(np.array(dsttri1), (3, 3))  # 求出基于目标多边形顶点的坐标所组成的矩阵MT= np.linalg.pinv(B).dot(A)M=MT.Treturn M#计算三角形面积
def is_trangle_area(x1,y1,x2,y2,x3,y3):return abs((x1*(y2-y3))+(x2*(y3-y1))+(x3*(y1-y2)))#将仿射变换矩阵应用到图像中
def warpAffine(src,warpMat,size,dstTri):dst=np.zeros((size[1],size[0],3),dtype=src.dtype)x1=dstTri[0][0]y1=dstTri[0][1]x2=dstTri[1][0]y2=dstTri[1][1]x3=dstTri[2][0]y3=dstTri[2][1]abc=is_trangle_area(x1,y1,x2,y2,x3,y3)#k=0for j in range(dst.shape[0]):for i in range(dst.shape[1]):abp=is_trangle_area(x1,y1,x2,y2,i,j)acp=is_trangle_area(x1,y1,x3,y3,i,j)bcp=is_trangle_area(x2,y2,x3,y3,i,j)if abc==abp+acp+bcp :lst=[]lst.append(i)lst.append(j)lst.append(1)A=np.reshape(np.array(lst),(3,1))  #将列表转换为矩阵(3*1)B=np.dot(warpMat,A)                #应用仿射变换矩阵,得到仿射后的坐标lst_dst=[]lst1=list(B)for index in range(len(lst1)):lst_dst.append(lst1[index][0])#print(dst.shape)#print(lst_dst)#dst[j][i]=src[(int(lst_dst[1])-1)][int(lst_dst[0])-1]#'''x=lst_dst[0]-1y=lst_dst[1]-1m=int(y)n=int(x)u=y-mv=x-ndst[j][i]=(1-u)*(1-v)*src[m][n]+(1-u)*v*src[m][n+1]+u*(1-v)*src[m+1][n]+u*v*src[m+1][n+1]#'''#dst=cv2.resize(dst,(size[0],size[1]))return dstdef applyAffineTransform(src, srcTri, dstTri, size) :warpMat=getAffineTransform(srcTri,dstTri)#warpMat = cv2.getAffineTransform( np.float32(srcTri), np.float32(dstTri) )#M = cv2.GetAffineTransform(src, dst)  src:原始图像中的三个点的坐标 dst:变换后的这三个点对应的坐标;M:根据三个对应点求出的仿射变换矩阵(2*3)dst=warpAffine(src,warpMat,size,dstTri)#dst = cv2.warpAffine( src, warpMat, (size[0], size[1]), None, flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT_101 )#cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]]) → dst#src:输入图像 M:变换矩阵 dsize:输出图像的大小 flags:插值方法的组合(int类型) borderMode:边界像素模式(int类型) borderValue:边界填充值;默认情况下,它为0return dst########################################################################################
def morphTriangle(img1, img2, img_1,img_2,t1, t2, t, alpha) :#为每个三角形寻找边界矩形r1 = cv2.boundingRect(np.float32([t1]))r2 = cv2.boundingRect(np.float32([t2]))r = cv2.boundingRect(np.float32([t]))#r=cv2.boundingRect(cnt) cnt:一个轮廓点集合;r:返回值,x,y(矩阵左上点的坐标);w(矩阵的宽),h(矩阵的高)#各个矩形左上角点的偏移点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])))#通过填充三角形来获取掩膜mask = np.zeros((r[3], r[2], 3), dtype = np.float32)  #3是3个通道的意思cv2.fillConvexPoly(mask, np.int32(tRect), (1.0, 1.0, 1.0), 16, 0); #cv2.fillConvexPoly( image , 多边形顶点array , RGB color)#把扭曲图像应用到小的矩形块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 混合矩形块#imgRect = (1.0 - alpha) * warpImage1 + alpha * warpImage2#复制矩形块的三角形区域到到输出图像img_1[r[1]:r[1]+r[3], r[0]:r[0]+r[2]] = img_1[r[1]:r[1]+r[3], r[0]:r[0]+r[2]] * ( 1 - mask ) + warpImage1 * maskimg_2[r[1]:r[1]+r[3], r[0]:r[0]+r[2]] = img_2[r[1]:r[1]+r[3], r[0]:r[0]+r[2]] * ( 1 - mask ) + warpImage2 * mask############################################################################################
def morphing(alpha,img1,img2,points1,points2,lst,imgMorph1,imgMorph2):# 将图像转换为浮点数据类型img1 = np.float32(img1)img2 = np.float32(img2)#  读取对应点的数组 points = []; #计算加权平均点坐标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))# 从列表中读取三角形,列表中存放的是delaunay三角划分后的每个三角形的顶点的索引for i in range(len(lst)):a=lst[i][0]b=lst[i][1]c=lst[i][2]           #获得的t为三角形的三个顶点坐标t1 = [points1[a], points1[b], points1[c]]   #x,y,z分别为delaunay三角划分后对应的每个三角形的点的索引(顺序)t2 = [points2[a], points2[b], points2[c]]t = [ points[a], points[b], points[c] ]#一次变形一个三角形morphTriangle(img1, img2, imgMorph1,imgMorph2, t1, t2, t, alpha)return imgMorph1,imgMorph2
#######################################################################################
def  morphing_save(save_path_index):lst=np.linspace(0,1,num=11)for i,item in enumerate(lst):alpha =item#beta=lst[i+1]imgM1,imgM2=morphing(alpha,img1,img2,point_76_lst_ori,point_76_lst_refer,save_path_index,imgMorph1,imgMorph2)imgM1=interpolation(np.uint8(imgM1))imgM2=interpolation(np.uint8(imgM2))   #对图像进行插值处理,去除黑线imgMorph=(1.0 - alpha)*imgM1 + alpha * imgM2cv2.imwrite("F:\\BaiduNetdiskDownload\\pictures\\renlian\\Morphing zibian\\{}.jpg".format(str(i+1)),imgMorph)#cv2.waitKey(50)
###########################################################################################################
if __name__=='__main__':# Read images 读取图像img1 = cv2.imread('telangpu1.jpg'); img2 = cv2.imread('clinton.jpg'); #读取坐标点#points1 = readPoints('F:\\BaiduNetdiskDownload\\pictures\\renlian\\telangpu1.txt')    #points1为列表,里面存放的是获取的关键点的坐标[(),()...]#points2 = readPoints('F:\\BaiduNetdiskDownload\\pictures\\renlian\\clinton1.txt')save_path_68='F:/BaiduNetdiskDownload/pictures/renlian/shape_predictor_68_face_landmarks.dat'point_76_lst_ori=character_point(img1,save_path_68)      point_76_lst_refer=character_point(img2,save_path_68)     #获取76个坐标点#save_path_index='tri2.txt'   #从tri.txt文件中读取三角形,tri.txt中存放的是delaunay三角划分后的每个三角形的顶点的索引#为最终输出分配空间imgMorph1 = np.zeros(img1.shape, dtype = np.float32)imgMorph2 = np.zeros(img1.shape, dtype = np.float32)lst_index=get_delaunary(img1,point_76_lst_ori)       #获取每个三角形顶点在point_76_lst_ori中的索引'''animate=True                 #打开动画 lst=np.linspace(0,1,num=11)for i in lst:alpha=i      if animate:imgMorph1,imgMorph2=morphing(alpha,img1,img2,point_76_lst_ori,point_76_lst_refer,lst_index,imgMorph1,imgMorph2)imgMorph1=interpolation(np.uint8(imgMorph1))imgMorph2=interpolation(np.uint8(imgMorph2))   #对图像进行插值处理,去除黑线imgMorph=(1.0 - alpha)*imgMorph1 + alpha * imgMorph2cv2.imshow('Morphed Face',imgMorph)cv2.waitKey(100)#展示结果cv2.imshow("Morphed Face", np.uint8(imgMorph))cv2.waitKey(0)'''morphing_save(lst_index)   #保存每个alptha值对应的morphing后的图片,前面有定义'''save_morphing_path='F:\BaiduNetdiskDownload\pictures\renlian\Morphing' buff=[]suffix='.jpg'image_lst=seek_imagename(suffix)  #寻找指定的morphing后的图像名称for image_name in image_lst:img_path=os.path.join(save_morphing_path,image_name)buff.append(imageio.imread(img_path))gif=imageio.mimsave('Morphed Face',buff,'GIF',duration=1)      #保存生成的gif图像# Display Result#cv2.imshow("Morphed Face", np.uint8(imgMorph))#cv2.waitKey(0)'''

       这里面的morphing_save函数,是把每个alpha值对应合成的图片保存到一个文件夹之中,以便进行后续的gif动图制作或者是mp4视频的制作

       注意:这里面,我把制作动图和mp4的代码写到了另一个py文件中,因为我试了试在morphing后面放入代码,结果总是跑不通,大家也可以自己试试能不能跑通:

制作gif动图的代码:

# !usr/bin/env python
# -*- coding:utf-8 _*-
"""
@Author:M兴M
@Blog(个人博客地址): https://blog.csdn.net/MbingxingM?spm=1000.2115.3001.5343@File:creategif.py
@Time:2022/4/29 22:21@Motto:不积跬步无以至千里,不积小流无以成江海,程序人生的精彩需要坚持不懈地积累!
"""
import imageio
from pathlib import Pathdef imgs2gif(imgPaths, saveName, duration=None, loop=0, fps=None):"""生成动态图片 格式为 gif:param imgPaths: 一系列图片路径:param saveName: 保存gif的名字:param duration: gif每帧间隔, 单位 秒:param fps: 帧率:param loop: 播放次数(在不同的播放器上有所区别), 0代表循环播放:return:"""if fps:duration = 1 / fpsimages = [imageio.v2.imread(str(img_path)) for img_path in imgPaths]imageio.mimsave(saveName, images, "gif", duration=duration, loop=loop)pathlist = Path(r"F:\\BaiduNetdiskDownload\\pictures\\renlian\\Morphing\\").glob("*.jpg")p_lis = []
for n, p in enumerate(pathlist):if n % 1 == 0:         #间隔几张图片显示p_lis.append(p)imgs2gif(p_lis, "morphing.gif", 0.033 * 11, 0)

制作成mp4视频的代码:

"""
import cv2
#获取一张图片的宽高作为视频的宽高
image=cv2.imread('E:/face_morphing/mo/0.jpg')
image_info=image.shape
height=image_info[0]
width=image_info[1]
size=(height,width)
fps=10
fourcc=cv2.VideoWriter_fourcc(*"mp4v")
video = cv2.VideoWriter('E:\face_morphing\mo\001.mp4', cv2.VideoWriter_fourcc(*"mp4v"), fps, (width,height)) #创建视频流对象-格式一
for i in range(0,101,5):file_name = "E:/face_morphing/mo/" + str(i) +".jpg "image=cv2.imread(file_name)video.write(image)  # 向视频文件写入一帧--只有图像,没有声音
cv2.waitKey()#video = cv2.VideoWriter('E:\face_morphing\morphing_video\001.mp4', cv2.VideoWriter_fourcc('m', 'p', '4', 'v'), fps, (width,height)) #创建视频流对象-格式二参数1 即将保存的文件路径
参数2 VideoWriter_fourcc为视频编解码器fourcc意为四字符代码(Four-Character Codes),顾名思义,该编码由四个字符组成,下面是VideoWriter_fourcc对象一些常用的参数,注意:字符顺序不能弄混cv2.VideoWriter_fourcc('I', '4', '2', '0'),该参数是YUV编码类型,文件名后缀为.avi cv2.VideoWriter_fourcc('P', 'I', 'M', 'I'),该参数是MPEG-1编码类型,文件名后缀为.avi cv2.VideoWriter_fourcc('X', 'V', 'I', 'D'),该参数是MPEG-4编码类型,文件名后缀为.avi cv2.VideoWriter_fourcc('T', 'H', 'E', 'O'),该参数是Ogg Vorbis,文件名后缀为.ogv cv2.VideoWriter_fourcc('F', 'L', 'V', '1'),该参数是Flash视频,文件名后缀为.flvcv2.VideoWriter_fourcc('m', 'p', '4', 'v')    文件名后缀为.mp4
参数3 为帧播放速率
参数4 (width,height)为视频帧大小"""
import cv2if __name__ == '__main__':# 保存视频的FPS,可以适当调整, 帧率过低,视频会有卡顿fps = 5  photo_size = (600, 800)# 可以用(*'DVIX')或(*'X264'),如果都不行先装ffmepg: sudo apt-get install ffmepgfourcc = cv2.VideoWriter_fourcc(*'mp4v')# video: 要保存的视频地址video = 'F:/BaiduNetdiskDownload/pictures/renlian/Morphing/video_fps1.mp4'videoWriter = cv2.VideoWriter(video, fourcc, fps, photo_size)  for i in range(1, 12):# image: 图片地址image = "F:/BaiduNetdiskDownload/pictures/renlian/Morphing/" + str(i) + ".jpg"frame = cv2.imread(image)videoWriter.write(frame)#videoWriter.release()

每个alpha值对应的合成图片:

alpha=0            alpha=0.1        alpha=0.2          alpha=0.3          alpha=0.4         alpha=0.5

               

alpha=0.6           alpha=0.7        alpha=0.8        alpha=0.9           alpha=1.0

             

最后合成的gif动图:


http://chatgpt.dhexx.cn/article/rYAV5vhN.shtml

相关文章

OpenCV:图像变形(Image Morphing)

融合/形变技术 Image Morphing的原理是十分简单的。有两幅图像 I 和 J ,我们希望通过融合图像 I 和 J 来创建一幅新的图像 M. 图像 I 和 J 的融合过程是由参数 alpha 来控制&#xff0c;参数 alpha 介于0和1之间。当alpha 0&#xff0c;新的图像 M看起来更接近 I;当alpha 1&…

计算机动画作业:图像morphing

本学期选了计算机动画课程&#xff0c;第一次作业是图像morphing&#xff0c; 本来打算选择基于四边网格的morphing&#xff0c; 但因为要用到曲面插值&#xff0c;感觉比较麻烦&#xff0c;因此使用基于三角网格的face morphing。 一、总体方案 1、检测人脸特征点&#xff0…

Morphing

<script src"http://widgets.amung.us/classic.js" type"text/javascript"></script> <script type"text/javascript"> </script> Morphing 这篇文章给大家介绍一下morphing&#xff0c;它是一种变型动画&#xff0c;…

深度学习中的GPU与CUDA

对应视频教程&#xff1a;https://www.bilibili.com/video/BV1S5411X7FY/ 文章目录 1. 显卡&#xff08;GPU&#xff09;与驱动2. 显卡与CUDA3. 如何查看自己的显卡 1. 显卡&#xff08;GPU&#xff09;与驱动 显卡&#xff0c;也称之为 GPU。GPU 的全称是 Graphics Processin…

CUDA详解

CUDA&#xff08;Compute Unified Device Architecture&#xff0c;统一计算设备架构&#xff09;&#xff0c;是显卡厂商NVIDIA推出的运算平台。 CUDA™是一种由NVIDIA推出的通用并行计算架构&#xff0c;该架构使GPU能够解决复杂的计算问题。 它包含了CUDA指令集架构&#xf…

CUDA入门

1. 引言 CUDA为a platform and programming model for CUDA-enabled GPUs。该平台通过GPU来进行计算。CUDA为GPU编程和管理 提供C/C语言扩展和API。 CUDA编程中&#xff0c;会同时使用CPU和GPU进行计算&#xff1a; CPU system&#xff1a;称为host。GPU system&#xff1a;…

cuda和cudatoolkit

Pytorch 使用不同版本的 cuda 由于课题的原因&#xff0c;笔者主要通过 Pytorch 框架进行深度学习相关的学习和实验。在运行和学习网络上的 Pytorch 应用代码的过程中&#xff0c;不少项目会标注作者在运行和实验时所使用的 Pytorch 和 cuda 版本信息。由于 Pytorch 和 cuda 版…

最新CUDA环境配置(Win10 + CUDA 11.6 + VS2019)

最新CUDA环境配置(Win10 CUDA 11.6 VS2019) 本篇博客根据NVIDIA 官方文档所述, 并根据自己实践得出. 供各位需要的朋友参考. 1.前言 本篇文章的软件环境为: Windows 10CUDA 11.6VS2019 CUDA是目前做人工智能, 深度学习等方向的必备工具库. 由CUDA衍生出的加速工具很多, …

一文搞懂CUDA

什么是cuda 统一计算设备架构&#xff08;Compute Unified Device Architecture, CUDA&#xff09;&#xff0c;是由NVIDIA推出的通用并行计算架构。解决的是用更加廉价的设备资源&#xff0c;实现更高效的并行计算。 CUDA是NVIDIA公司所开发的GPU编程模型&#xff0c;它提供…

GPU,CUDA,cuDNN的理解

我们知道做深度学习离不开GPU,不过一直以来对GPU和CPU的差别,CUDA以及cuDNN都不是很了解,所以找了些资料整理下,希望不仅可以帮助自己理解,也能够帮助到其他人理解。 先来讲讲CPU和GPU的关系和差别吧。截图来自资料1(CUDA的官方文档): 从上图可以看出GPU(图像处理器,…

CUDA编程之快速入门

CUDA(Compute Unified Device Architecture)的中文全称为计算统一设备架构。做图像视觉领域的同学多多少少都会接触到CUDA,毕竟要做性能速度优化,CUDA是个很重要的工具,CUDA是做视觉的同学难以绕过的一个坑,必须踩一踩才踏实。CUDA编程真的是入门容易精通难,具有计算机体…

CUDA学习

想想学习CUDA的时间也应该有十来天了&#xff0c;也该是做一个小总结了&#xff0c;说说我理解的CUDA&#xff0c;它到底是什么东西&#xff1f; 其实说到CUDA&#xff0c;还真的没几个人知道&#xff0c;说实话&#xff0c;我也听说不久&#xff0c;主要因为它2007年才刚发布&…

CUDA简介

CUDA简介 CUDA是什么 CUDA&#xff0c;Compute Unified Device Architecture的简称&#xff0c;是由NVIDIA公司创立的基于他们公司生产的图形处理器GPUs&#xff08;Graphics Processing Units,可以通俗的理解为显卡&#xff09;的一个并行计算平台和编程模型。 通过CUDA&#…

CUDA是什么-CUDA简介

在大家开始深度学习时&#xff0c;几乎所有的入门教程都会提到CUDA这个词。那么什么是CUDA&#xff1f;她和我们进行深度学习的环境部署等有什么关系&#xff1f;通过查阅资料&#xff0c;我整理了这份简洁版CUDA入门文档&#xff0c;希望能帮助大家用最快的时间尽可能清晰的了…

java队列和栈 共同_java 栈和队列的模拟--java

栈的定义&#xff1a;栈是一种特殊的表这种表只在表头进行插入和删除操作。因此&#xff0c;表头对于栈来说具有特殊的意义&#xff0c;称为栈顶。相应地&#xff0c;表尾称为栈底。不含任何元素的栈称为空栈。 栈的逻辑结构&#xff1a;假设一个栈S中的元素为an,an-1,..,a1&am…

栈和队列学习总结

一、栈 1、特点及应用 先进后出。(如果会和队列先进先出记混的话,就记场景吧:弹栈弹栈,就是把最上面的最新进来的弹出去;而队列就像我们火车站排队检票出站一样,谁排在前面谁就先出去。) 应用的话,其实我们经常接触呀。比如Undo操作(就是撤销操作)就是使用的栈的思想…

栈和队列的共同点和不同点

堆栈都是一种数据项按序排列的数据结构&#xff0c;只能在一端(称为栈顶(top))对数据项进行插入和删除。 要点&#xff1a;堆&#xff1a;顺序随意 栈&#xff1a;后进先出(Last-In/First-Out) 堆 堆&#xff1a;什么是堆&#xff1f;又该怎么理解呢&#xff1f; ①堆通常是一…

栈和队列实现和实例分析

目录 前言栈队列实例分析结语 前言 本篇文章主要讲述数据结构中栈和队列的实现&#xff0c;以及相关实例分析。 栈 注意本文所讲述的栈是数据结构的一种&#xff0c;并不是内存划区中的栈区&#xff0c;但是这两者有相似之处&#xff0c;即&#xff1a;存储数据时满足数据先…

栈和队列的共同处和不同处

共同处 栈和队列的共同处是&#xff1a;它们都是由几个数据特性相同的元素组成的有限序列&#xff0c;也就是所谓的线性表。 不同处 队列 队列&#xff08;queue&#xff09;是限定仅在表的一端插入元素、在另一端删除元素的线性表。 在队列中&#xff0c;允许插入的一端被…

索引的优缺点以及索引的设计原则

索引概述 索引&#xff08;index&#xff09; 是帮助 MySQL 高效获取数据的数据结构&#xff08;有序&#xff09;。 在数据之外&#xff0c;数据库系统还维护者满足特定查找算法的数据结构&#xff0c;这些数据结构以某种方式引用&#xff08;指向&#xff09;数据&#xff0…