小程序|基于 Serverless 架构的头像漫画风处理小程序

小程序|基于 Serverless 架构的头像漫画风处理小程序

文章图片


前言 我一直都想要有一个漫画版的头像 , 奈何手太笨 , 用了很多软件 “捏不出来” , 所以就在想着 , 是否可以基于 AI 实现这样一个功能 , 并部署到 Serverless 架构上让更多人来尝试使用呢?
后端项目 后端项目采用业界鼎鼎有名的动漫风格转化滤镜库 AnimeGAN 的 v2 版本 , 效果大概如下:

关于这个模型的具体的信息 , 在这里不做详细的介绍和说明 。 通过与 Python Web 框架结合 , 将 AI 模型通过接口对外暴露:
from PIL import Imageimport ioimport torchimport base64import bottleimport randomimport jsoncacheDir = '/tmp/'modelDir = './model/bryandlee_animegan2-pytorch_main'getModel = lambda modelName: torch.hub.load(modelDir \"generator\" pretrained=modelName source='local')models = { 'celeba_distill': getModel('celeba_distill') 'face_paint_512_v1': getModel('face_paint_512_v1') 'face_paint_512_v2': getModel('face_paint_512_v2') 'paprika': getModel('paprika')randomStr = lambda num=5: \"\".join(random.sample('abcdefghijklmnopqrstuvwxyz' num))face2paint = torch.hub.load(modelDir \"face2paint\" size=512 source='local')@bottle.route('/images/comic_style' method='POST')def getComicStyle(): result = { try: postData = https://mparticle.uc.cn/api/json.loads(bottle.request.body.read().decode(/"utf-8\")) style = postData.get(\"style\" 'celeba_distill') image = postData.get(\"image\") localName = randomStr(10) # 图片获取 imagePath = cacheDir + localName with open(imagePath 'wb') as f: f.write(base64.b64decode(image)) # 内容预测 model = models[style
imgAttr = Image.open(imagePath).convert(\"RGB\") outAttr = face2paint(model imgAttr) img_buffer = io.BytesIO() outAttr.save(img_buffer format='JPEG') byte_data = https://mparticle.uc.cn/api/img_buffer.getvalue() img_buffer.close() result[/"photo\"
= 'data:image/jpg;base64 %s' % base64.b64encode(byte_data).decode() except Exception as e: print(\"ERROR: \" e) result[\"error\"
= True return resultapp = bottle.default_app()if __name__ == \"__main__\": bottle.run(host='localhost' port=8099) 整个代码是基于 Serverless 架构进行了部分改良的:



















【小程序|基于 Serverless 架构的头像漫画风处理小程序】
实例初始化的时候 , 进行模型的加载 , 已经可能的减少频繁的冷启动带来的影响情况; 在函数模式下 , 往往只有/tmp目录是可写的 , 所以图片会被缓存到/tmp目录下; 虽然说函数计算是“无状态”的 , 但是实际上也有复用的情况 , 所有数据在存储到tmp的时候进行了随机命名; 虽然部分云厂商支持二进制的文件上传 , 但是大部分的 Serverless 架构对二进制上传支持的并不友好 , 所以这里依旧采用 Base64 上传的方案; 上面的代码 , 更多是和 AI 相关的 , 除此之外 , 还需要有一个获取模型列表 , 以及模型路径等相关信息的接口: import bottle@bottle.route('/system/styles' method='GET')def styles(): return { \"AI动漫风\": { 'color': 'red' 'detailList': { \"风格1\": { 'uri': \"images/comic_style\" 'name': 'celeba_distill' 'color': 'orange' 'preview': 'https://serverless-article-picture.oss-cn-hangzhou.aliyuncs.com/1647773808708_20220320105649389392.png'\"风格2\": { 'uri': \"images/comic_style\" 'name': 'face_paint_512_v1' 'color': 'blue' 'preview': 'https://serverless-article-picture.oss-cn-hangzhou.aliyuncs.com/1647773875279_20220320105756071508.png'\"风格3\": { 'uri': \"images/comic_style\" 'name': 'face_paint_512_v2' 'color': 'pink' 'preview': 'https://serverless-article-picture.oss-cn-hangzhou.aliyuncs.com/1647773926924_20220320105847286510.png'\"风格4\": { 'uri': \"images/comic_style\" 'name': 'paprika' 'color': 'cyan' 'preview': 'https://serverless-article-picture.oss-cn-hangzhou.aliyuncs.com/1647773976277_20220320105936594662.png'app = bottle.default_app()if __name__ == \"__main__\": bottle.run(host='localhost' port=8099) 可以看到 , 此时我的做法是 , 新增了一个函数作为新接口对外暴露 , 那么为什么不在刚刚的项目中 , 增加这样的一个接口呢?而是要多维护一个函数呢? AI 模型加载速度慢 , 如果把获取AI处理列表的接口集成进去 , 势必会影响该接口的性能; AI 模型所需配置的内存会比较多 , 而获取 AI 处理列表的接口所需要的内存非常少 , 而内存会和计费有一定的关系 , 所以分开有助于成本的降低; 关于第二个接口(获取 AI 处理列表的接口) , 相对来说是比较简单的 , 没什么问题 , 但是针对第一个 AI 模型的接口 , 就有比较头疼的点: 模型所需要的依赖 , 可能涉及到一些二进制编译的过程 , 所以导致无法直接跨平台使用; 模型文件比较大 (单纯的 Pytorch 就超过 800M) , 函数计算的上传代码最多才 100M , 所以这个项目无法直接上传; 所以这里需要借助 Serverless Devs 项目来进行处理: 参考 https://www.serverless-devs.com/fc/yaml/readme 完成 s.yaml 的编写: edition: 1.0.0name: start-aiaccess: \"default\"vars: # 全局变量 region: cn-hangzhou service: name: ai nasConfig: # NAS配置 配置后function可以访问指定NAS userId: 10003 # userID 默认为10003 groupId: 10003 # groupID 默认为10003 mountPoints: # 目录配置 - serverAddr: 0fe764bf9d-kci94.cn-hangzhou.nas.aliyuncs.com # NAS 服务器地址 nasDir: /python3 fcDir: /mnt/python3 vpcConfig: vpcId: vpc-bp1rmyncqxoagiyqnbcxk securityGroupId: sg-bp1dpxwusntfryekord6 vswitchIds: - vsw-bp1wqgi5lptlmk8nk5yi0services: image: component: fc props: # 组件的属性值 region: ${vars.region service: ${vars.service function: name: image_server description: 图片处理服务 runtime: python3 codeUri: ./ ossBucket: temp-code-cn-hangzhou handler: index.app memorySize: 3072 timeout: 300 environmentVariables: PYTHONUSERBASE: /mnt/python3/python triggers: - name: httpTrigger type: http config: authType: anonymous methods: - GET - POST - PUT customDomains: - domainName: avatar.aialbum.net protocol: HTTP routeConfigs: - path: /* 然后进行: 1、依赖的安装:s build --use-docker 2、项目的部署:s deploy 3、在 NAS 中创建目录 , 上传依赖: s nas command mkdir /mnt/python3/pythons nas upload -r 本地依赖路径 /mnt/python3/python 完成之后可以通过接口对项目进行测试 。另外 , 微信小程序需要 https 的后台接口 , 所以这里还需要配置 https 相关的证书信息 , 此处不做展开 。小程序项目 小程序项目依旧采用 colorUi , 整个项目就只有一个页面: 页面相关布局:scroll-view scroll-y class=\"scrollPage\"image src='https://mparticle.uc.cn/images/topbg.jpg' mode='widthFix' class='response'/imageview class=\"cu-bar bg-white solid-bottom margin-top\"view class=\"action\"text class=\"cuIcon-title text-blue\"/text第一步:选择图片/view/viewview class=\"padding bg-white solid-bottom\"view class=\"flex\"view class=\"flex-sub bg-grey padding-sm margin-xs radius text-center\" bindtap=\"chosePhoto\"本地上传图片/viewview class=\"flex-sub bg-grey padding-sm margin-xs radius text-center\" bindtap=\"getUserAvatar\"获取当前头像/view/view/viewview class=\"padding bg-white\" hidden=\"{{!userChosePhoho\"view class=\"images\"image src=https://mparticle.uc.cn/"{{userChosePhoho\" mode=\"widthFix\" bindtap=\"previewImage\" bindlongpress=\"editImage\" data-image=\"{{userChosePhoho\"/image/viewview class=\"text-right padding-top text-gray\"* 点击图片可预览 , 长按图片可编辑/view/viewview class=\"cu-bar bg-white solid-bottom margin-top\"view class=\"action\"text class=\"cuIcon-title text-blue\"/text第二步:选择图片处理方案/view/viewview class=\"bg-white\"scroll-view scroll-x class=\"bg-white nav\"view class=\"flex text-center\"view class=\"cu-item flex-sub {{style==currentStyle?'text-orange cur':''\" wx:for=\"{{styleList\" wx:for-index=\"style\" bindtap=\"changeStyle\" data-style=\"{{style\"{{style/view/view/scroll-view/viewview class=\"padding-sm bg-white solid-bottom\"view class=\"cu-avatar round xl bg-{{item.color margin-xs\" wx:for=\"{{styleList[currentStyle

相关经验推荐