【手摸手学ModelArts】传说中的“云毕业照“,先睹为快!
ModelArts实现换脸合成毕业照
本文首次发表于华为云社区ModelArts版块,主要是作者在学习使用AI开发平台ModelArts过程中的一些经验产出。此次要和大家分享的是如何基于ModerArts实现简单的“换脸”合成毕业照,想法和源码均来自华为云社区。
↑开局一张图,故事全靠编。
故事的最开始要从前几天晚上旁听的一场ModelArts闭门会说起,在闭门会上通过会议主持者、著名开源布道者林旅强大佬了解到社区有开发者在做“云毕业照”的项目,当时听到的时候我还没什么项目,毕竟我不会后端代码。然后无意中搜到Richard的论坛ID并且找到了“云毕业照”初级版的详细介绍和源代码--[技术分享]为了弥补没有毕业照的遗憾,我用AI为学长学姐们“定制”了一套毕业照,因为一句“建议使用华为云ModelArts平台进行开发”并且附上源代码,让我热血澎湃想动手实践一把,感受一下ModelArts丝滑般的体验,挖掘一下“云毕业照”背后的奥秘。懒人福利:简版操作指南见附件
准备
本文默认您已经注册了华为云账户,并且已领取相关优惠券。当然没有领到优惠券也没关系,目前有ModelArts免费算力活动,而且
【2020华为云AI实战营】等活动已经强势来袭,这也是学习AI的大好时机。此次实践只需用到ModelArts,但会涉及到notobook中jupyter的一些简单操作,比如通过。
ModelArts: https://www.huaweicloud.cn/product/modelarts.html
AI开发平台ModelArts是面向开发者的一站式AI开发平台,为机器学习与深度学习提供海量数据预处理及半自动化标注、大规模分布式Training、自动化模型生成,及端-边-云模型按需部署能力,帮助用户快速创建和部署模型,管理全周期AI工作流。
访问密钥配置: https://support.huaweicloud.cn/prepare-modelarts/modelarts_08_0002.html
获取访问密钥并完成ModelArts全局配置只是使用AI开发平台ModerArts的其中一个步骤,当然尤为重要的一步。
源码地址:https://github.com/younero/face_swap_for_graduate
gitee备份:https://gitee.com/hu-qi/face_swap_for_graduate
创建免费算力实例
2020年了,想学AI的朋友赶紧来 huaweicloud.ai ,不仅资源丰富,还能动手实践,“理论学再多,不如动手做!华为专家带你免费学AI”,就等您来!一段硬广之后,开启今天的实践。点击图片或者打开华为云普惠AI链接-https://activity.huaweicloud.cn/2020ModelArts_Promotion.html, 点击立即免费体验应该是能进入到notebook创建界面,如果提示需要授权的话可能还需执行准备阶段提到的访问密钥配置操作。
在创建Notebook界面,我们选择Python3作为工作环境,选择类型为GPU的[限时免费]体验规格GPU版,点击下一步确认配置费用是0元再提交就可以创建一个免费的Notebook。待Notebook启动成功之后点击右侧的【打开】进入jupyter工作台,我们有30东的时间来完成“换脸”合成实践(PS:1东===2分钟),当然还可以无限续“东”。
源码拉取及概览
首先我们要获取到源代码,这里我们通过jupyter的Terminal来进行源代码的拉取和解压。所以,先打开一个Terminal吧!
接着我们执行命令行拉取源代码,
ls
是查看当前目录下文件,cd work
切换到jupyter的工作目录,wget
命令可以下载一个文件$ ls$ cd work $ wget https://gitee.com/hu-qi/face_swap_for_graduate/repository/archive/master.zip?ref=master&sha=0a411b246baff90cfb5ec6547ecf93a438c547e8&format=zip&captcha_type=captcha&captcha=ycuaij
接着我们可以再用
ls
查看一下,应该是拿到了一个zip文件,通过unzip
来解压文件,解压之后执行ls
查看结果$ ls $ unzip master.zip\?ref\=master
正常的话,回到jupyter目录,我们能看到生成了一个源码目录和源码压缩包
打开一个.py文件能查看到本次实践的源码,不过我们只Copy代码稍作修改,用Pytorch-1.0.0来新建代码完成我们的实践。如果您有兴趣的话,可以看看各个文件夹下的文件。两个face**.py结尾的是核心代码,moulds下放的是学士服源图,作者按F\M区分男、女款,按G\J\L\N\W\Y区分为工学、军事学、理学、农学、文学、医学,还是蛮讲究的。因为源码是可以部署的web服务,会有template等相关文件。本
安装依赖
因为很少接触python,在使用Pytorch-1.0.0安装dlib依赖的时候我踩了不少雷,幸好每次报错都能通过google自行解决,所以本次实践才能成功完成。在安装dlib的时候我发现默认环境的pip版本有点低,于是先升级pip;然后查阅文档说通过安装一些相关的依赖如cmake、boost能保障dlib的正常安装。在实践中过程中我做了多次尝试,本来想通过摄像头捕获图像减少人工上传,结果没做到,所以这次实践的图片需要自行上传到jupyter。在dlib的安装过程中,可能需要5分钟的等待时间,如果超过5分钟未响应,可以点击顶部方块图标再点击刷新图标进行刷新再重新执行。只要出现“Done”就意味着这30东之类我们都不需要再执行安装依赖操作.
# 初始化依赖 !pip install --upgrade pip %time !pip install opencv-python %time !pip install cmake %time !pip install boost %time !pip install dlib print('Done!')
编(kao)写(bei)代码
代码部分请恕在下愚笨,没法跟大伙儿讲解,相关的资料还只看了一点点,我浅显的理解是已经有了成熟的算法模型,将图片A和图标B的人脸部分描点检测并进行替换。下面我把源码再copy一下,期待评论区的各位大佬给我们小白讲解讲解。我一直想摆脱【Copy攻城狮】的雅号,没想到在ModelArt居然又sh是从Copy开始入门,惭愧!
import os import cv2 import dlib import random import numpy as np from PIL import Image predictor_path = 'shape_predictor_68_face_landmarks.dat' # 模型路径 detector = dlib.get_frontal_face_detector() # dlib的正向人脸检测器 predictor = dlib.shape_predictor(predictor_path) # dlib的人脸形状检测器 def get_image_size(image): """ 获取图片大小(高度,宽度) :param image: image :return: (高度,宽度) """ image_size = (image.shape[0], image.shape[1]) return image_size def get_face_landmarks(image, face_detector, shape_predictor): """ 获取人脸标志,68个特征点 :param image: image :param face_detector: dlib.get_frontal_face_detector :param shape_predictor: dlib.shape_predictor :return: np.array([[],[]]), 68个特征点 """ dets = face_detector(image, 1) num_faces = len(dets) if num_faces == 0: print("Sorry, there were no faces found.") exit() shape = shape_predictor(image, dets[0]) face_landmarks = np.array([[p.x, p.y] for p in shape.parts()]) return face_landmarks def get_face_mask(image_size, face_landmarks): """ 获取人脸掩模 :param image_size: 图片大小 :param face_landmarks: 68个特征点 :return: image_mask, 掩模图片 """ mask = np.zeros(image_size, dtype=np.uint8) points = np.concatenate([face_landmarks[0:16], face_landmarks[26:17:-1]]) cv2.fillPoly(img=mask, pts=[points], color=255) return mask def get_affine_image(image1, image2, face_landmarks1, face_landmarks2): """ 获取图片1仿射变换后的图片 :param image1: 图片1, 要进行仿射变换的图片 :param image2: 图片2, 只要用来获取图片大小,生成与之大小相同的仿射变换图片 :param face_landmarks1: 图片1的人脸特征点 :param face_landmarks2: 图片2的人脸特征点 :return: 仿射变换后的图片 """ three_points_index = [18, 8, 25] M = cv2.getAffineTransform(face_landmarks1[three_points_index].astype(np.float32), face_landmarks2[three_points_index].astype(np.float32)) dsize = (image2.shape[1], image2.shape[0]) affine_image = cv2.warpAffine(image1, M, dsize) return affine_image.astype(np.uint8) def get_mask_center_point(image_mask): """ 获取掩模的中心点坐标 :param image_mask: 掩模图片 :return: 掩模中心 """ image_mask_index = np.argwhere(image_mask > 0) miny, minx = np.min(image_mask_index, axis=0) maxy, maxx = np.max(image_mask_index, axis=0) center_point = ((maxx + minx) // 2, (maxy + miny) // 2) return center_point def get_mask_union(mask1, mask2): """ 获取两个掩模掩盖部分的并集 :param mask1: mask_image, 掩模1 :param mask2: mask_image, 掩模2 :return: 两个掩模掩盖部分的并集 """ mask = np.min([mask1, mask2], axis=0) # 掩盖部分并集 mask = ((cv2.blur(mask, (3, 3)) == 255) * 255).astype(np.uint8) # 缩小掩模大小 mask = cv2.blur(mask, (5, 5)).astype(np.uint8) # 模糊掩模 return mask def swap(face, mould): im1 = cv2.imread(face) # face_image im1 = cv2.resize(im1, (600, im1.shape[0] * 600 // im1.shape[1])) landmarks1 = get_face_landmarks(im1, detector, predictor) # 68_face_landmarks im1_size = get_image_size(im1) # 脸图大小 im1_mask = get_face_mask(im1_size, landmarks1) # 脸图人脸掩模 im2 = cv2.imread(mould) # face_image landmarks2 = get_face_landmarks(im2, detector, predictor) # 68_face_landmarks im2_size = get_image_size(im2) # 摄像头图片大小 im2_mask = get_face_mask(im2_size, landmarks2) # 摄像头图片人脸掩模 affine_im1 = get_affine_image(im1, im2, landmarks1, landmarks2) # im1(脸图)仿射变换后的图片 affine_im1_mask = get_affine_image(im1_mask, im2, landmarks1, landmarks2) # im1(脸图)仿射变换后的图片的人脸掩模 union_mask = get_mask_union(im2_mask, affine_im1_mask) # 掩模合并 point = get_mask_center_point(affine_im1_mask) # im1(脸图)仿射变换后的图片的人脸掩模的中心点 seamless_im = cv2.seamlessClone(affine_im1, im2, mask=union_mask, p=point, flags=cv2.NORMAL_CLONE) # 进行泊松融合 cv2.imwrite('hello.png', seamless_im, [cv2.IMWRITE_PNG_COMPRESSION, 0]) # 结果图片存到jupyter display(Image.open('hello.png')) # 展示结果 def process(face, sex, major): n = 1 mould_path = 'moulds/%s/%d/%s' % (sex, n, major) x = len(os.listdir(mould_path)) mould = mould_path + '/%d.jpg' % int(x * random.random()) seamless_im = swap(face, mould) return seamless_im # 第一个参数为上传的图片名称 # 第二个参数为性别 F-男 M-女 # 第三个参数为学科: 按G-工学、J-军事学、L-理学、N-农学、W-文学、Y-医学 process('JayChou.jpg', 'F',"G")
上传图片并执行脚本
真正到了检验的时候,首先我们要上传一张正面免冠去罩的靓照,本来想借此机会自爆,结果翻遍了整个手机相册都没能找到一张自拍,摆了摆了。于是baidu了一张周天王的照片,希望没有侵犯视觉中国的版权,先不管了,侵删侵删啊!
然后执行脚本,不出意外的话,当当当当,“工科男“周天王的“云毕业照”成功生成!
结语
有时候,身上这股倔劲不知道是不是没使对地方,一不留神又熬了一宿。只因为之前创建的notebook被我手贱删了,而又没有存本地,关键的修改代码又忘记了,导致一直在调错,还是希望官方能出个VSCode版本的插件,隔壁IoT部门都整了个IoT Link,用起来爽得不要不要的。看来为了后面的学习,还得先装个PyCharm。接下来祝愿大家一切安好!公众号的读者点击【阅读原文】可以去报名【2020华为云AI实战营】,期待我们一起学AI,共同进步!
- 点赞
- 收藏
- 关注作者
评论(0)