用 Python 画自画像?看完我笑了

article/2025/11/11 3:10:55


作者 | 李秋键

责编 | 晋兆雨

头图 | CSDN下载自视觉中国

引言:人脸图像的生成在各个行业有着重要应用,例如刑事调查、人物设计、教育培训等。然而一幅逼真的人脸肖像,对于职业画家也要至少数小时才能绘制出来;对于从未接触过绘画的新手,就更是难如登天了。新手绘制出来的人脸草图往往非常简陋抽象,甚至有不匀称、不完整。但如果使用智能人脸画板,无疑是有如神助。

本项目主要来源于中科院和香港城市大学的一项研究DeepFaceDrawing,论文标题是《DeepFaceDrawing: DeepGeneration of Face Images from Sketches》

具体效果如下图可见:

实验前的准备


首先我们使用的python版本是3.6.5所用到的模块如下:

Pyqt5模块:PyQt5是基于Digia公司强大的图形程式框架Qt5的python接口,由一组python模块构成。PyQt5本身拥有超过620个类和6000函数及方法。在可以运行于多个平台,包括:Unix, Windows, and Mac OS。

  • opencv是将用来进行图像处理和生成。

  • numpy模块用来处理矩阵运算。

  • Jittor模块国内清华大学开源的深度学习框架。

  • _thread是多线程库。

网络模型的定义和训练


首先这个图像合成模块采用了一种利用发生器和鉴别器的GAN结构。从融合的特征图生成真实的人脸图像。鉴别器采用多尺度鉴别方式:对输入进行尺度划分,特征图和生成的图像在三个不同的层次上,经过三个不同的过程。:

(1)权重网络层和损失定义:

def weights_init_normal(m):classname = m.__class__.__name__ifclassname.find("Conv") != -1:jt.init.gauss_(m.weight,0.0, 0.02)elifclassname.find("BatchNorm") != -1:jt.init.gauss_(m.weight,1.0, 0.02)jt.init.constant_(m.bias,0.0)def get_norm_layer(norm_type='instance'):if (norm_type == 'batch'):norm_layer = nn.BatchNormelif (norm_type == 'instance'):norm_layer =nn.InstanceNorm2delse:raiseNotImplementedError(('normalization layer [%s] is not found' % norm_type))return norm_layerclass MSELoss:def __init__(self):passdef __call__(self, output,target):from jittor.nn importmse_lossreturn mse_loss(output,target)class BCELoss:def __init__(self):passdef __call__(self, output,target):from jittor.nn importbce_lossreturn bce_loss(output,target)

(2)模型特征编解码:

特征匹配模块包含5个译码网络,以compact作为输入由分量流形得到的特征向量,并将其转换为对应的特征向量为后续生成的特征图的大小。

def define_part_encoder(model='mouth', norm='instance', input_nc=1,latent_dim=512):norm_layer =get_norm_layer(norm_type=norm)image_size = 512if 'eye' in model:image_size = 128elif 'mouth' in model:image_size = 192elif 'nose' in model:image_size = 160elif 'face' in model:image_size = 512else:print("Whole Image!!")net_encoder =EncoderGenerator_Res(norm_layer,image_size,input_nc, latent_dim)  # input longsize 256 to 512*4*4    print("net_encoder of part"+model+" is:",image_size)return net_encoderdef define_part_decoder(model='mouth', norm='instance', output_nc=1,latent_dim=512):norm_layer =get_norm_layer(norm_type=norm)image_size = 512if 'eye' in model:image_size = 128elif 'mouth' in model:image_size = 192elif 'nose' in model:image_size = 160else:print("Whole Image!!")net_decoder =DecoderGenerator_image_Res(norm_layer,image_size,output_nc, latent_dim)  # input longsize 256 to 512*4*4print("net_decoder to imageof part "+model+" is:",image_size)return net_decoderdef define_feature_decoder(model='mouth', norm='instance', output_nc=1,latent_dim=512):norm_layer =get_norm_layer(norm_type=norm)image_size = 512if 'eye' in model:image_size = 128elif 'mouth' in model:image_size = 192elif 'nose' in model:image_size = 160else:print("Whole Image!!")net_decoder =DecoderGenerator_feature_Res(norm_layer,image_size,output_nc, latent_dim)  # input longsize 256 to 512*4*4print("net_decoder to imageof part "+model+" is:",image_size)# print(net_decoder)return net_decoderdef define_G(input_nc, output_nc, ngf, n_downsample_global=3,n_blocks_global=9, norm='instance'):norm_layer =get_norm_layer(norm_type=norm)    netG = GlobalGenerator(input_nc,output_nc, ngf, n_downsample_global, n_blocks_global, norm_layer)return netG

图形界面的定义


在这篇论文中,作者一方面将人脸关键区域(双眼、鼻、嘴和其他区域)作为面元,学习其特征嵌入,将输入草图的对应部分送到由数据库样本中面元的特征向量构成的流形空间进行校准。另一方面,参考 pix2pixHD [5]的网络模型设计,使用 conditional GAN 来学习从编码的面元特征到真实图像的映射生成结果。

(1)鼠标绘制函数的定义:

class OutputGraphicsScene(QGraphicsScene):def __init__(self, parent=None):QGraphicsScene.__init__(self, parent)# self.modes = mode_listself.mouse_clicked = Falseself.prev_pt = Noneself.setSceneRect(0,0,self.width(),self.height())# self.masked_image = Noneself.selectMode = 0# save the history of editself.history = []self.ori_img = np.ones((512,512, 3),dtype=np.uint8)*255self.mask_put = 1 # 1 marksuse brush while 0 user eraseself.convert = False# self.setPos(0 ,0)self.firstDisplay = Trueself.convert_on = Falsedef reset(self):self.convert = Falseself.ori_img = np.ones((512,512, 3),dtype=np.uint8)*255self.updatePixmap(True)self.prev_pt = Nonedef setSketchImag(self,sketch_mat, mouse_up=False):self.ori_img =sketch_mat.copy()self.image_list = []self.image_list.append(self.ori_img.copy() )def mousePressEvent(self,event):if not self.mask_put orself.selectMode == 1:self.mouse_clicked =Trueself.prev_pt = Noneelse:self.make_sketch(event.scenePos())def make_sketch_Eraser(self,pts):if len(pts)>0:for pt in pts:cv2.line(self.color_img,pt['prev'],pt['curr'],self.paint_color,self.paint_size)cv2.line(self.mask_img,pt['prev'],pt['curr'],(0,0,0),self.paint_size )self.updatePixmap()def modify_sketch(self, pts):if len(pts)>0:for pt in pts:cv2.line(self.ori_img,pt['prev'],pt['curr'],self.paint_color,self.paint_size)self.updatePixmap()def get_stk_color(self, color):self.stk_color = colordef erase_prev_pt(self):self.prev_pt = Nonedef reset_items(self):for i inrange(len(self.items())):item = self.items()[0]self.removeItem(item)def undo(self):iflen(self.image_list)>1:num =len(self.image_list)-2self.ori_img =self.image_list[num].copy()self.image_list.pop(num+1)self.updatePixmap(True)def getImage(self):returnself.ori_img*(1-self.mask_img)  +self.color_img*self.mask_imgdefupdatePixmap(self,mouse_up=False):sketch = self.ori_imgqim = QImage(sketch.data,sketch.shape[1], sketch.shape[0], QImage.Format_RGB888)if self.firstDisplay :self.reset_items()self.imItem =self.addPixmap(QPixmap.fromImage(qim))self.firstDispla = Falseelse:self.imItem.setPixmap(QPixmap.fromImage(qim))def fresh_board(self):print('======================================================')while(True):if(self.convert_on):print('======================================================')time.sleep(100)iter_start_time =time.time()self.updatePixmap()print('TimeSketch:',time.time() - iter_start_time)


(2)GUI界面:其核心思路并非直接用输入草图作为网络生成条件,而是将人脸进行分块操作后利用数据驱动的思想对抽象的草图特征空间进行隐式建模,并在这个流形空间中找到输入草图特征的近邻组合来重构特征,进而合成人脸图像。

class WindowUI(QtWidgets.QMainWindow,Ui_SketchGUI):def __init__(self):super(WindowUI,self).__init__()self.setupUi(self)self.setEvents()self._translate =QtCore.QCoreApplication.translateself.output_img = Noneself.brush_size =self.BrushSize.value()self.eraser_size =self.EraseSize.value()self.modes = [0,1,0] #0marks the eraser, 1 marks the brushself.Modify_modes = [0,1,0]#0 marks the eraser, 1 marks the brushself.output_scene =OutputGraphicsScene()self.output.setScene(self.output_scene)self.output.setAlignment(Qt.AlignTop | Qt.AlignLeft)self.output.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)self.output.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)self.output_view =QGraphicsView(self.output_scene)#self.output_view.fitInView(self.output_scene.updatePixmap())self.input_scene =InputGraphicsScene(self.modes, self.brush_size,self.output_scene)self.input.setScene(self.input_scene)self.input.setAlignment(Qt.AlignTop | Qt.AlignLeft)self.input.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)self.input.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)self.input_scene.convert_on= self.RealTime_checkBox.isChecked()self.output_scene.convert_on= self.RealTime_checkBox.isChecked()self.BrushNum_label.setText(self._translate("SketchGUI",str(self.brush_size)))self.EraserNum_label.setText(self._translate("SketchGUI",str(self.eraser_size)))self.start_time =time.time()# self.# try:#     # thread.start_new_thread(self.output_scene.fresh_board,())#    thread.start_new_thread(self.input_scene.thread_shadow,())# except:#     print("Error: unable to startthread")# print("Finish")def setEvents(self):self.Undo_Button.clicked.connect(self.undo)self.Brush_Button.clicked.connect(self.brush_mode)self.BrushSize.valueChanged.connect(self.brush_change)self.Clear_Button.clicked.connect(self.clear)self.Eraser_Button.clicked.connect(self.eraser_mode)self.EraseSize.valueChanged.connect(self.eraser_change)self.Save_Button.clicked.connect(self.saveFile)#weight barself.part0_Slider.valueChanged.connect(self.changePart)self.part1_Slider.valueChanged.connect(self.changePart)self.part2_Slider.valueChanged.connect(self.changePart)self.part3_Slider.valueChanged.connect(self.changePart)self.part4_Slider.valueChanged.connect(self.changePart)self.part5_Slider.valueChanged.connect(self.changAllPart)self.Load_Button.clicked.connect(self.open)self.Convert_Sketch.clicked.connect(self.convert)self.RealTime_checkBox.clicked.connect(self.convert_on)self.Shadow_checkBox.clicked.connect(self.shadow_on)self.Female_Button.clicked.connect(self.choose_Gender)self.Man_Button.clicked.connect(self.choose_Gender)self.actionSave.triggered.connect(self.saveFile)def mode_select(self, mode):for i inrange(len(self.modes)):self.modes[i] = 0self.modes[mode] = 1def brush_mode(self):self.mode_select(1)self.brush_change()self.statusBar().showMessage("Brush")def eraser_mode(self):self.mode_select(0)self.eraser_change()self.statusBar().showMessage("Eraser")def undo(self):self.input_scene.undo()self.output_scene.undo()def brush_change(self):self.brush_size =self.BrushSize.value()self.BrushNum_label.setText(self._translate("SketchGUI",str(self.brush_size)))if self.modes[1]:self.input_scene.paint_size = self.brush_sizeself.input_scene.paint_color = (0,0,0)self.statusBar().showMessage("Change Brush Size in ",self.brush_size)def eraser_change(self):self.eraser_size =self.EraseSize.value()self.EraserNum_label.setText(self._translate("SketchGUI",str(self.eraser_size)))if self.modes[0]:print( self.eraser_size)self.input_scene.paint_size = self.eraser_sizeself.input_scene.paint_color = (1,1,1)self.statusBar().showMessage("Change Eraser Size in ",self.eraser_size)def changePart(self):self.input_scene.part_weight['eye1'] = self.part0_Slider.value()/100self.input_scene.part_weight['eye2']= self.part1_Slider.value()/100self.input_scene.part_weight['nose'] = self.part2_Slider.value()/100self.input_scene.part_weight['mouth'] = self.part3_Slider.value()/100self.input_scene.part_weight[''] = self.part4_Slider.value()/100self.input_scene.start_Shadow()#self.input_scene.updatePixmap()def changAllPart(self):value =self.part5_Slider.value()self.part0_Slider.setProperty("value", value)self.part1_Slider.setProperty("value", value)self.part2_Slider.setProperty("value", value)self.part3_Slider.setProperty("value", value)self.part4_Slider.setProperty("value", value)self.changePart()def clear(self):self.input_scene.reset()self.output_scene.reset()self.start_time =time.time()self.input_scene.start_Shadow()self.statusBar().showMessage("Clear Drawing Board")def convert(self):self.statusBar().showMessage("Press Convert")self.input_scene.convert_RGB()self.output_scene.updatePixmap()def open(self):fileName, _ =QFileDialog.getOpenFileName(self, "Open File",QDir.currentPath(),"Images Files (*.*)") #jpg;*.jpeg;*.pngif fileName:image =QPixmap(fileName)mat_img =cv2.imread(fileName)mat_img = cv2.resize(mat_img,(512, 512), interpolation=cv2.INTER_CUBIC)mat_img =cv2.cvtColor(mat_img, cv2.COLOR_RGB2BGR)if image.isNull():QMessageBox.information(self, "Image Viewer","Cannotload %s." % fileName)return#cv2.imshow('open',mat_img)self.input_scene.start_Shadow()self.input_scene.setSketchImag(mat_img)def saveFile(self):cur_time =strftime("%Y-%m-%d-%H-%M-%S", gmtime())file_dir ='./saveImage/'+cur_timeif notos.path.isdir(file_dir) :os.makedirs(file_dir)cv2.imwrite(file_dir+'/hand-draw.jpg',self.input_scene.sketch_img*255)cv2.imwrite(file_dir+'/colorized.jpg',cv2.cvtColor(self.output_scene.ori_img,cv2.COLOR_BGR2RGB))print(file_dir)def convert_on(self):# ifself.RealTime_checkBox.isCheched():print('self.RealTime_checkBox',self.input_scene.convert_on)self.input_scene.convert_on= self.RealTime_checkBox.isChecked()self.output_scene.convert_on= self.RealTime_checkBox.isChecked()def shadow_on(self):_translate =QtCore.QCoreApplication.translateself.input_scene.shadow_on =not self.input_scene.shadow_onself.input_scene.updatePixmap()ifself.input_scene.shadow_on:self.statusBar().showMessage("Shadow ON")else:self.statusBar().showMessage("Shadow OFF")def choose_Gender(self):ifself.Female_Button.isChecked():self.input_scene.sex = 1else:self.input_scene.sex = 0self.input_scene.start_Shadow()

总结


这里给出模型的体验网址:

http://www.geometrylearning.com:3000/index_621.html

该方法核心亮点之一,便是以多通道特征图作为中间结果来改善信息流。从本质上看,这是将输入草图作为软约束来替代传统方法中的硬约束,因此能够用粗糙甚至不完整的草图来生成高质量的完整人脸图像。

反思DeepFaceDrawing

1)画不出丑脸:

从图中可以看出,即使给出丑陋的草图,输出的也会是平均来说漂亮的人脸,这大概是因为所用的训练数据集都是名人,平均“颜值”较高,因此神经网络学到了一种漂亮的平均;这能算是一种在“颜值上的”数据不平衡问题吗。

2)安全问题

比如人脸支付场景中,可能存在利用该项技术盗刷的问题。随着人脸活体检测技术的发展,这种隐患应该能得以有效避免。

3)技术攻击性

相比于Deepfake,本文的DeepFaceDrawing应该算是相对无害的。

4)商业价值

如论文作者所说,这项技术在犯罪侦查、人物设计、教育培训等方面都可以有所作为。期待有一天这项技术更加通用,这样一来其商业价值会更大。

完整代码:

链接:https://pan.baidu.com/s/1ARIzPEbUSNzAIdPsRl6h-A

提取码:4llk

作者简介
李秋键,CSDN 博客专家,CSDN达人课作者。硕士在读于中国矿业大学,开发有taptap安卓武侠游戏一部,vip视频解析,文意转换工具,写作机器人等项目,发表论文若干,多次高数竞赛获奖等等。


更多精彩推荐
☞中招!330 万台老年机被植木马,背后黑幕细思极恐
☞什么?性能强大的 M1 芯片不支持 Docker ?
☞恭喜您被选为CSDN插件内测用户:点此领取福利
☞关于动态规划,你想知道的都在这里了!
☞一文告诉你雾计算与云计算的区别及对物联网的价值!
☞28岁年轻操盘手,加密市场“空手套白狼”成就亿万身家
点分享点点赞点在看

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

相关文章

python turtle画熊-Python turtle画图库画姓名实例

*****看一下我定义的change()和run()函数****** 绘图坐标体系: 作用:设置主窗体的大小和位置 ? 1 turtle.setup(width, height, startx, starty) #width : 窗口宽度,若值是整数&#xff…

用Python画佩奇完整版

用Python画佩奇完整版 文章完全是依照这篇博客写的 啥是佩奇,Python 告诉你! 想把这篇博客完善一下,大致没有多大变化,从本质来说就加个身体的函数,由于本人水平有限,编译的代码有点繁琐,希望大…

用Java画城堡_一分钟简笔画—跟我一起画城堡 大师来详解

我们可以用简笔画,一分钟画出一个城堡。因为是一分钟就需要画出来,所以我们需要用一些简单的图形,来组成城堡的样子,如果过于复杂是无法画出城堡的。下面小编就教大家用简笔画的方法,跟我一起一分钟画出城堡。 工具/材…

教你学画画03---如何用 Python Turtle 绘制画带有云彩的彩虹

在之前的博客中我们做了 满星螺旋今天我们将画一条带有一些云彩的彩虹。这个对孩子们来说应该特别有趣,因为我们将绘制明亮的颜色和快乐的物体 设置画面 要设置舞台,让我们首先配置画布的高度和宽度。我们需要screensize命令来设置窗口的大小,以及setworldcoordinates命令…

使用Python Turtle画一个小人发射爱心

最近刚开始学Python Turtle,尝试着画了一个发射爱心的小人,这是效果图: 以下是代码段: import turtle as t #画人的脑袋和躯干 t.speed(0) t.penup() t.goto(-100,100) t.pendown() t.pensize(3) t.circle(50) t.right(90) t.forward(100) …

心形函数用计算机怎么画,怎么用几何画板画爱心,真神奇!

原标题:怎么用几何画板画爱心,真神奇! 爱心的形状除了能用一些普通的画图工具绘制,还可以借助强大的几何绘图软件——几何画板,千万不要以为几何画板只能画一些基本几何图形,其实它的功能强大着呢。 其实在…

你画过哪些好玩儿的作品?

CJQtime ,搬砖师在读 韦易笑、 朱炫、 ze ran 等 18453 人赞同 进知乎两年了,一直是个透明人,从来不问不答,只是看到自己喜欢的答案会点个赞而已。基友在 身边有个会画画的朋友是怎样的体验? - 艺术发了些我平时瞎画的…

教你用Python画一个漫画版谷爱凌~

最近各大社交媒体可是被谷爱凌给刷屏了,就在2月8日上午,她在北京冬奥会自由式滑雪女子大跳台比赛中获得冠军,以从未在赛场尝试过的高难度动作获得了全场最高分。 不仅如此,谷爱凌在17岁时就已经拥有了六十多枚奖牌,仅在…

教你怎么画出一幅印象派的画

如何把一张图片变成一幅印象派画作呢? 日本东京的Reiichiro Nakano小哥哥开源了一个项目,画富士山、大本钟,金门大桥……风格温暖美丽,令人着迷。 不论是山川建筑,还是萌物爱宠,都可以用蜡笔一样的质感&am…

【报名】AIGC带来无限可能!数据猿携手业界大咖聚焦大模型热点问题|数据猿直播...

‍数据智能产业创新服务媒体 ——聚焦数智 改变商业 进入2023年,ChatGPT就像一阵春雷,惊醒了人们对于人工智能快要沉睡的热情。不少科技企业都对外宣称自己有ChatGPT产品,或者正在开发ChatGPT产品,更大量企业则在想方设法接入Cha…

国外大学生用AI写论文,还次次拿到A

大学生用 AI 写论文,次次拿 A 近日,国外大学生 Urdadgirl69 在 Reddit 上发帖称,自己利用 AI 写论文、完成电影和书的观后感作业,门门功课拿到了 A。Urdadgirl69 表示,一开始,自己还有点羞愧,但…

中国算力总规模全球第二,如何实现算力的技术升维?

光子盒研究院出品 近日,人民日报记者从工信部了解到,近年来,我国算力产业年增长率近30%,算力总规模位居全球第二。 工信部数据显示,截至去年底,我国算力总规模达到180EFLOPS(每秒18000京次浮点运…

吴军长文讲解算力:全球算力竞争中我们要做的三件事

这两天看到吴军的一段访谈,绝对是在算力这个话题领域中最深入浅出的一篇讲解了,把算力的概念、演进趋势,再到与经济发展如何挂钩、中国算力产业的发展方向,做了一个非常完整的梳理。 其中,吴军提到了算力竞争中&#…

我国的超算中心、智算中心、数据中心有多少?在哪里?啥规模?

目前,全国国家超级计算中心有十座,分别位于天津、广州、长沙、深圳、济南、无锡、郑州、昆山、成都和西安,其中深圳和西安中心二期正在建设,文昌航天超算中心已进入建设尾声。 超算中心项目 2023年4月17日,科技部启动了…

成为机器人工程师需要学习那些技术

机器人工程师是未来比较吃香的工作岗位,要成为机器人工程师,ChatGPT的回答是,建议你需要学习以下技术: 1、机械工程:了解机械结构、运动学和动力学,以及机械设计和制造方面的知识。 2、电子工程&#xff1…

统计字符串中每个字符出现的频率

基本实现&#xff1a; //统计countStr() {var str abcabca;var result {};for (var i 0; i < str.length; i) {//判断result是否有该字符if (result[str[i]]) {result[str[i]];} else {result[str[i]] 1;}}console.log(result, result);}, 优化&#xff1a; //统计 co…

2500个常用中文字符 + 130常用中英文字符

参考自 《现代汉语常用字表》常用字(2500字) 常用字符 一乙二十丁厂七卜人入八九几儿了力乃刀又三于干亏士工土才寸下大丈与万上小口巾山千乞川亿个勺久凡及夕丸么广亡门义之尸弓己已子卫也女飞刃习叉马乡丰王井开夫天无元专云扎艺木五支厅不太犬区历尤友匹车巨牙屯比互切瓦止…

7-6 统计字符出现次数 (20 分)

7-6 统计字符出现次数 (20 分) 本题要求编写程序&#xff0c;统计并输出某给定字符在给定字符串中出现的次数。 输入格式&#xff1a; 输入第一行给出一个以回车结束的字符串&#xff08;少于80个字符&#xff09;&#xff1b;第二行输入一个字符。 输出格式&#xff1a; …

最会惹怒老板的星座

第一名&#xff1a;白羊座 当白羊不爽的时候&#xff0c;他会直接找老板&#xff0c;摊牌谈条件&#xff0c;也不管周围环境如何&#xff0c;丝毫不考虑老板的面子问题。 第二名&#xff1a;金牛座 一条路走到黑的坚持是金牛的蜜糖&#xff0c;也是金牛的砒霜。 第三名&#xf…

占星周运(白羊座)

冥王拱太阳 海王六合太阳 北交拱土星,北交合水星 火星六合天王 金星型木星 羊羊&#xff0c;继续追求金钱和财产吧。记忆也是一种财产。你的家庭生活既是充满活力/暴躁的&#xff0c;也是有吸引力/引人注目的。拥抱孩子。在五月剩下的时间里&#xff0c;偶然认识的朋友、交谈&…