【换脸系列2】浪漫七夕♥和你的TA交♂换身体吧!(单身狗慎入)

【换脸系列2】浪漫七夕♥和你的TA交♂换身体吧!(单身狗慎入)

****

项目地址:QuantumLiu/FaceSwapper

百度网盘

****

还有三天就是七夕情人节了,你是不是正在规划怎么和TA度过一个浪漫的七夕节呢?

情书?

玫瑰、红酒、烛光晚餐?

买买买?

相信你的七夕已经安排得很浪漫了~

这里还有一款因缺思厅的Python程序,可以为你的浪漫七夕增添一点幽默!

这个程序实现了一张照片内两个人脸的互换,照片可以来自你和TA的合影,也可以是和你的好朋友、老板、仇人,甚至任何你想恶搞的人~

(我说我一个单身狗,怎么想的做这个程序,强行喂自己狗粮,摔!)




(本文使用图片,除特朗普和希拉里以外都来自pixabay别样网

使用

程序提供了GUI版,也提供了命令行版和接口。

最简单的,下载程序以后(coupleswapper.exe以及ico文件,txt文件和data文件夹),

双击程序,打开主界面。

或者通过源码:

python coupleswapper.py


点击加载图片,选择要打开的照片。

程序会弹出Origin窗口预览。

接下来点击转换,即可完成转换,会弹出result窗口预览。

如果你想看一看对比反差并保存对比图,请点击保存对比,会弹出预览图片并让你选择保存位置。


如果只想保存转换后的结果,请点击保存结果。

祝各位玩的开心~

汪!

预告:编写本程序让单身狗作者受到了来自自己的1T点暴击,本系列下一作品,是单身狗七夕YY神器【换脸系列3】和女神/男神在一起的应该是我!


编程实现

在本系列上一篇【换脸系列1】军装照刷爆朋友圈?教你用Python+深度学习自制换脸软件!(改进)中,我们实现了将一张照片上的脸换到另一张照片的头上,并且用面向对象编程的方法定义了一个Faceswapper类。

这一次,我们是要在一张照片上,将两个脸互换。

显而易见,读取和写入图片、人脸定位和特征提取,这些功能我们都在Faceswapper里实现了,新的任务需要的操作是一样的,我们希望直接复用已有的操作。

使用类的继承是最常用的实现复用的方法。

我们新定义一个类Coupleswapper,他继承自Faceswapper类。

class Coupleswapper(Faceswapper):
    '''
    双人照人脸交换器类,继承自Faceswapper类
    实例化时载入多个照片资源
    '''

这样,新的Coupleswapper类继承了父类的所有属性和方法。

接下来,我们根据任务的不同,改写一些类。

在父类中的get_landmarks方法中,我们要求脸数只能为1,否则抛出异常,最后返回一个landmarks的martrix。

现在,我们做的是给一张图片的两个脸换脸,脸数应当>=2,而且最后一次返回一个有两组landmark的list。所以我们需要改写get_landmarks方法。

def get_landmarks(self,im,fname,n=2):
    '''
    人脸定位和特征提取,定位到两张及以上脸或者没有人脸将抛出异常
    im:
        照片的numpy数组
    fname:
        照片名字的字符串
    返回值:
        人脸特征(x,y)坐标的矩阵
    '''
    rects = self.detector(im, 1)

    if len(rects) >=5:
        raise TooManyFaces('Too many faces in '+fname)
    if len(rects) <2:
        raise NoFace('No enough face in' +fname)
    return [np.matrix([[p.x, p.y] for p in self.predictor(im, rect).parts()]) for rect in rects]

当然,执行换脸完整过程的swap函数也要改写。

换每个脸的过程和原程序差不多,但是我们需要一个copy作为输出画布,还要在两个图片间进行互为脸/头来源进行循环。

def swap(self,im_name):
    '''
    主函数 人脸交换
    im_name
        合影图片的键名字符串
    '''
    im,landmarks=self.heads[im_name]
    out_im=im.copy()#画布
    for i in [1,-1]:
        landmarks_head,landmarks_face=landmarks[:2][::i]#实现倒序
        M = self.transformation_from_points(landmarks_head[self.ALIGN_POINTS],
                                       landmarks_face[self.ALIGN_POINTS])

        face_mask = self.get_face_mask(im, landmarks_face)
        warped_mask = self.warp_im(face_mask, M, im.shape)
        combined_mask = np.max([self.get_face_mask(im, landmarks_head), warped_mask],
                                  axis=0)
        warped_face = self.warp_im(im, M, im.shape)
        warped_corrected_im = self.correct_colours(im, warped_face, landmarks_head)
        out_im=out_im * (1.0 - combined_mask) + warped_corrected_im * combined_mask
    return out_im

GUI界面

面向大众的程序还要有直观、易操作的GUI界面。

我们使用pyqt+pyinstaller来实习GUI和exe发布。

import os,traceback
from PyQt5.QtWidgets import QFileDialog
from PyQt5 import QtCore,QtGui, QtWidgets
import cv2
import numpy as np
from coupleswapper import Coupleswapper,TooManyFaces,NoFace

class Ui_Form(QtWidgets.QMainWindow):
    def __init__(self):
        super(Ui_Form,self).__init__()
        self.swapper=[]
        self.im_path=''
        self.cur_im_path=''
        self.img_swapped=None
        self.img_ori=None
        self.compare=None

    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(270, 360)
        Form.setAccessibleName("")
        self.verticalLayout = QtWidgets.QVBoxLayout(Form)
        self.verticalLayout.setObjectName("verticalLayout")
        Form.setWindowIcon(QtGui.QIcon('./male_female.ico'))
        self.help_label=QtWidgets.QLabel(Form)
        self.verticalLayout.addWidget(self.help_label)
        with open('./readme.txt','r',encoding='utf8') as f:
            self.readme=f.read()
        #显示运行log
        self.statu_text=QtWidgets.QTextBrowser(Form)
        self.vb=self.statu_text.verticalScrollBar()
        self.verticalLayout.addWidget(self.statu_text)#支持滚轮
        #加载按钮
        self.bt_load = QtWidgets.QPushButton(Form)
        self.bt_load.setDefault(True)
        self.bt_load.setObjectName("bt_load")
        self.bt_load.clicked.connect(self.load_image)
        self.verticalLayout.addWidget(self.bt_load)
        #转换按钮
        self.bt_swap = QtWidgets.QPushButton(Form)
        self.bt_swap.setDefault(True)
        self.bt_swap.setObjectName("bt_swap")
        self.bt_swap.clicked.connect(self.swap)
        self.verticalLayout.addWidget(self.bt_swap)
        #保存结果按钮
        self.bt_save = QtWidgets.QPushButton(Form)
        self.bt_save.setDefault(True)
        self.bt_save.setObjectName("bt_save")
        self.bt_save.clicked.connect(self.save_result)
        self.verticalLayout.addWidget(self.bt_save)
        #保存对比图按钮
        self.bt_save_comp = QtWidgets.QPushButton(Form)
        self.bt_save_comp.setDefault(True)
        self.bt_save_comp.clicked.connect(self.save_compare)
        self.bt_save_comp.setObjectName("bt_save_comp")
        self.verticalLayout.addWidget(self.bt_save_comp)


        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "交换♂身体"))
        self.bt_load.setText(_translate("Form", "加载图片"))
        self.bt_swap.setText(_translate("Form", "转换"))
        self.bt_save.setText(_translate("Form", "保存结果"))
        self.bt_save_comp.setText(_translate("Form", "保存对比"))
        self.help_label.setText(_translate("Form",self.readme))
        self.statu_text.setText(_translate('Form','欢迎使用,请选择文件'))


    def load_image(self):
        '''
        加载原图
        '''
        try:
            im_path,_=QFileDialog.getOpenFileName(self,'打开图片文件','./','Image Files(*.png *.jpg *.bmp)')
            if not os.path.exists(im_path):
                return
            self.im_path=im_path
            self.statu_text.append('打开图片文件:'+self.im_path)
            if not self.swapper:
                self.swapper=Coupleswapper([self.im_path])
            elif not self.im_path== self.cur_im_path:
                self.swapper.load_heads([self.im_path])
            self.img_ori=self.swapper.heads[os.path.split(self.im_path)[-1]][0]
            cv2.imshow('Origin',self.img_ori)
        except (TooManyFaces,NoFace):
            self.statu_text.append(traceback.format_exc()+'\n人脸定位失败,请重新选择!保证照片中有两张可识别的人脸。')
            return

    def swap(self):
        '''
        执行换脸
        '''
        if not (self.swapper and os.path.exists(self.im_path)):
            return
        self.statu_text.append('转换成功!')
        self.img_swapped=self.swapper.swap(os.path.split(self.im_path)[-1])
        self.img_swapped[self.img_swapped>254.9]=254.9
        self.img_swapped=self.img_swapped.astype('uint8')
        cv2.imshow('Result',self.img_swapped)

    def save_result(self):
        '''
        保存结果
        '''
        output_path,_=QFileDialog.getSaveFileName(self,'选择保存位置','./','Image Files(*.png *.jpg *.bmp)')
        if not output_path:
            self.statu_text.append('无效路径,请重新选择')
            return
        self.swapper.save(output_path,self.img_swapped)
        self.statu_text.append('成功保存到:'+output_path)

    def save_compare(self):
        '''
        保存对比图
        '''
        self.compare=np.concatenate([self.img_ori,self.img_swapped],1)
        cv2.imshow('Compare',self.compare)
        output_path,_=QFileDialog.getSaveFileName(self,'选择保存位置','./','Image Files(*.png *.jpg *.bmp)')
        if not output_path:
            self.statu_text.append('无效路径,请重新选择')
            return
        self.swapper.save(output_path,self.compare)
        self.statu_text.append('成功保存对比图到:'+output_path)
if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    Form = QtWidgets.QWidget()
    ui = Ui_Form()
    ui.setupUi(Form)
    try:
        Form.show()
    except:
        traceback.print_exc()
    finally:
        cv2.destroyAllWindows()
    sys.exit(app.exec_())
编辑于 2017-08-25

文章被以下专栏收录

    一个沉迷于编程的阿语本科在读生的梦呓。以深度学习和python为主,可能涉及量化交易、CUDA编程、MATLAB、多语言混编和哲♂学。