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

简介: 当一个程序员想要个漫画风的头像时...

前言

我一直都想要有一个漫画版的头像,奈何手太笨,用了很多软件 “捏不出来”,所以就在想着,是否可以基于 AI 实现这样一个功能,并部署到 Serverless 架构上让更多人来尝试使用呢?

后端项目

后端项目采用业界鼎鼎有名的动漫风格转化滤镜库 AnimeGAN 的 v2 版本,效果大概如下:

关于这个模型的具体的信息,在这里不做详细的介绍和说明。通过与 Python Web 框架结合,将 AI 模型通过接口对外暴露:

from PIL import Image
import io
import torch
import base64
import bottle
import random
import json
cacheDir = '/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 = json.loads(bottle.request.body.read().decode("utf-8"))style = postData.get("style", 'celeba_distill')image = postData.get("image")localName = randomStr(10)# 图片获取imagePath = cacheDir + localNamewith 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 = 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"] = Truereturn result
app = bottle.default_app()
if __name__ == "__main__":bottle.run(host='localhost', port=8099)

整个代码是基于 Serverless 架构进行了部分改良的:

  1. 实例初始化的时候,进行模型的加载,已经可能的减少频繁的冷启动带来的影响情况;
  2. 在函数模式下,往往只有/tmp目录是可写的,所以图片会被缓存到/tmp目录下;
  3. 虽然说函数计算是“无状态”的,但是实际上也有复用的情况,所有数据在存储到tmp的时候进行了随机命名;
  4. 虽然部分云厂商支持二进制的文件上传,但是大部分的 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)

可以看到,此时我的做法是,新增了一个函数作为新接口对外暴露,那么为什么不在刚刚的项目中,增加这样的一个接口呢?而是要多维护一个函数呢?

  1. AI 模型加载速度慢,如果把获取AI处理列表的接口集成进去,势必会影响该接口的性能;
  2. AI 模型所需配置的内存会比较多,而获取 AI 处理列表的接口所需要的内存非常少,而内存会和计费有一定的关系,所以分开有助于成本的降低;

关于第二个接口(获取 AI 处理列表的接口),相对来说是比较简单的,没什么问题,但是针对第一个 AI 模型的接口,就有比较头疼的点:

  1. 模型所需要的依赖,可能涉及到一些二进制编译的过程,所以导致无法直接跨平台使用;
  2. 模型文件比较大 (单纯的 Pytorch 就超过 800M),函数计算的上传代码最多才 100M,所以这个项目无法直接上传;

所以这里需要借助 Serverless Devs 项目来进行处理:

参考 Yaml规范 - Serverless Devs

完成 s.yaml 的编写:

edition: 1.0.0
name: start-ai
access: "default"
vars: # 全局变量region: cn-hangzhouservice:name: ainasConfig:                  # NAS配置, 配置后function可以访问指定NASuserId: 10003             # userID, 默认为10003groupId: 10003            # groupID, 默认为10003mountPoints:              # 目录配置- serverAddr: 0fe764bf9d-kci94.cn-hangzhou.nas.aliyuncs.com # NAS 服务器地址nasDir: /python3fcDir: /mnt/python3vpcConfig:vpcId: vpc-bp1rmyncqxoagiyqnbcxksecurityGroupId: sg-bp1dpxwusntfryekord6vswitchIds:- vsw-bp1wqgi5lptlmk8nk5yi0
services:image:component:  fcprops: #  组件的属性值region: ${vars.region}service: ${vars.service}function:name: image_serverdescription: 图片处理服务runtime: python3codeUri: ./ossBucket: temp-code-cn-hangzhouhandler: index.appmemorySize: 3072timeout: 300environmentVariables:PYTHONUSERBASE: /mnt/python3/pythontriggers:- name: httpTriggertype: httpconfig:authType: anonymousmethods:- GET- POST- PUTcustomDomains:- domainName: avatar.aialbum.netprotocol: HTTProuteConfigs:- path: /*

然后进行:

1、依赖的安装:s build --use-docker

2、项目的部署:s deploy

3、在 NAS 中创建目录,上传依赖:

s nas command mkdir /mnt/python3/python
s nas upload -r 本地依赖路径 /mnt/python3/python

完成之后可以通过接口对项目进行测试。

另外,微信小程序需要 https 的后台接口,所以这里还需要配置 https 相关的证书信息,此处不做展开。

小程序项目

小程序项目依旧采用 colorUi,整个项目就只有一个页面:

页面相关布局:

<scroll-view scroll-y class="scrollPage"><image src='/images/topbg.jpg' mode='widthFix' class='response'></image><view class="cu-bar bg-white solid-bottom margin-top"><view class="action"><text class="cuIcon-title text-blue"></text>第一步:选择图片</view></view><view class="padding bg-white solid-bottom"><view class="flex"><view class="flex-sub bg-grey padding-sm margin-xs radius text-center" bindtap="chosePhoto">本地上传图片</view><view class="flex-sub bg-grey padding-sm margin-xs radius text-center" bindtap="getUserAvatar">获取当前头像</view></view></view><view class="padding bg-white" hidden="{{!userChosePhoho}}"><view class="images"><image src="{{userChosePhoho}}" mode="widthFix" bindtap="previewImage" bindlongpress="editImage" data-image="{{userChosePhoho}}"></image></view><view class="text-right padding-top text-gray">* 点击图片可预览,长按图片可编辑</view></view><view class="cu-bar bg-white solid-bottom margin-top"><view class="action"><text class="cuIcon-title text-blue"></text>第二步:选择图片处理方案</view></view><view 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></view><view class="padding-sm bg-white solid-bottom"><view class="cu-avatar round xl bg-{{item.color}} margin-xs" wx:for="{{styleList[currentStyle].detailList}}"wx:for-index="substyle" bindtap="changeStyle" data-substyle="{{substyle}}" bindlongpress="showModal" data-target="Image"> <view class="cu-tag badge cuIcon-check bg-grey" hidden="{{currentSubStyle == substyle ? false : true}}"></view><text class="avatar-text">{{substyle}}</text></view><view class="text-right padding-top text-gray">* 长按风格圆圈可以预览模板效果</view></view><view class="padding-sm bg-white solid-bottom"><button class="cu-btn block bg-blue margin-tb-sm lg" bindtap="getNewPhoto" disabled="{{!userChosePhoho}}"type="">{{ userChosePhoho ? (getPhotoStatus ? 'AI将花费较长时间' : '生成图片') : '请先选择图片' }}</button></view><view class="cu-bar bg-white solid-bottom margin-top" hidden="{{!resultPhoto}}"><view class="action"><text class="cuIcon-title text-blue"></text>生成结果</view></view><view class="padding-sm bg-white solid-bottom" hidden="{{!resultPhoto}}"><view wx:if="{{resultPhoto == 'error'}}"><view class="text-center padding-top">服务暂时不可用,请稍后重试</view><view class="text-center padding-top">或联系开发者微信:<text class="text-blue" data-data="zhihuiyushaiqi" bindtap="copyData">zhihuiyushaiqi</text></view></view><view wx:else><view class="images"><image src="{{resultPhoto}}" mode="aspectFit" bindtap="previewImage" bindlongpress="saveImage" data-image="{{resultPhoto}}"></image></view><view class="text-right padding-top text-gray">* 点击图片可预览,长按图片可保存</view></view></view><view class="padding bg-white margin-top margin-bottom"><view class="text-center">自豪的采用 Serverless Devs 搭建</view><view class="text-center">Powered By Anycodes <text bindtap="showModal" class="text-cyan" data-target="Modal">{{"<"}}作者的话{{">"}}</text></view></view><view class="cu-modal {{modalName=='Modal'?'show':''}}"><view class="cu-dialog"><view class="cu-bar bg-white justify-end"><view class="content">作者的话</view><view class="action" bindtap="hideModal"><text class="cuIcon-close text-red"></text></view></view><view class="padding-xl text-left">大家好,我是刘宇,很感谢您可以关注和使用这个小程序,这个小程序是我用业余时间做的一个头像生成小工具,基于“人工智障”技术,反正现在怎么看怎么别扭,但是我会努力让这小程序变得“智能”起来的。如果你有什么好的意见也欢迎联系我<text class="text-blue" data-data="service@52exe.cn" bindtap="copyData">邮箱</text>或者<text class="text-blue" data-data="zhihuiyushaiqi" bindtap="copyData">微信</text>,另外值得一提的是,本项目基于阿里云Serverless架构,通过Serverless Devs开发者工具建设。</view></view>
</view>
<view class="cu-modal {{modalName=='Image'?'show':''}}"><view class="cu-dialog"><view class="bg-img" style="background-image: url("{{previewStyle}}");height:200px;"><view class="cu-bar justify-end text-white"><view class="action" bindtap="hideModal"><text class="cuIcon-close "></text></view></view></view><view class="cu-bar bg-white"><view class="action margin-0 flex-sub  solid-left" bindtap="hideModal">关闭预览</view></view></view>
</view>
</scroll-view>
页面逻辑也是比较简单的:
// index.js
// 获取应用实例
const app = getApp()
Page({data: {styleList: {},currentStyle: "动漫风",currentSubStyle: "v1模型",userChosePhoho: undefined,resultPhoto: undefined,previewStyle: undefined,getPhotoStatus: false},// 事件处理函数bindViewTap() {wx.navigateTo({url: '../logs/logs'})},onLoad() {const that = thiswx.showLoading({title: '加载中',})app.doRequest(`system/styles`, {}, option = {method: "GET"}).then(function (result) {wx.hideLoading()that.setData({styleList: result,currentStyle: Object.keys(result)[0],currentSubStyle: Object.keys(result[Object.keys(result)[0]].detailList)[0],})})},changeStyle(attr) {this.setData({"currentStyle": attr.currentTarget.dataset.style || this.data.currentStyle,"currentSubStyle": attr.currentTarget.dataset.substyle || Object.keys(this.data.styleList[attr.currentTarget.dataset.style].detailList)[0]})},chosePhoto() {const that = thiswx.chooseImage({count: 1,sizeType: ['compressed'],sourceType: ['album', 'camera'],complete(res) {that.setData({userChosePhoho: res.tempFilePaths[0],resultPhoto: undefined})}})},headimgHD(imageUrl) {imageUrl = imageUrl.split('/'); //把头像的路径切成数组//把大小数值为 46 || 64 || 96 || 132 的转换为0if (imageUrl[imageUrl.length - 1] && (imageUrl[imageUrl.length - 1] == 46 || imageUrl[imageUrl.length - 1] == 64 || imageUrl[imageUrl.length - 1] == 96 || imageUrl[imageUrl.length - 1] == 132)) {imageUrl[imageUrl.length - 1] = 0;}imageUrl = imageUrl.join('/'); //重新拼接为字符串return imageUrl;},getUserAvatar() {const that = thiswx.getUserProfile({desc: "获取您的头像",success(res) {const newAvatar = that.headimgHD(res.userInfo.avatarUrl)wx.getImageInfo({src: newAvatar,success(res) {that.setData({userChosePhoho: res.path,resultPhoto: undefined})}})}})},previewImage(e) {wx.previewImage({urls: [e.currentTarget.dataset.image]})},editImage() {const that = thiswx.editImage({src: this.data.userChosePhoho,success(res) {that.setData({userChosePhoho: res.tempFilePath})}})},getNewPhoto() {const that = thiswx.showLoading({title: '图片生成中',})this.setData({getPhotoStatus: true})app.doRequest(this.data.styleList[this.data.currentStyle].detailList[this.data.currentSubStyle].uri, {style: this.data.styleList[this.data.currentStyle].detailList[this.data.currentSubStyle].name,image: wx.getFileSystemManager().readFileSync(this.data.userChosePhoho, "base64")}, option = {method: "POST"}).then(function (result) {wx.hideLoading()that.setData({resultPhoto: result.error ? "error" : result.photo,getPhotoStatus: false})})},saveImage() {wx.saveImageToPhotosAlbum({filePath: this.data.resultPhoto,success(res) {wx.showToast({title: "保存成功"})},fail(res) {wx.showToast({title: "异常,稍后重试"})}})},onShareAppMessage: function () {return {title: "头头是道个性头像",}},onShareTimeline() {return {title: "头头是道个性头像",}},showModal(e) {if(e.currentTarget.dataset.target=="Image"){const previewSubStyle = e.currentTarget.dataset.substyleconst previewSubStyleUrl = this.data.styleList[this.data.currentStyle].detailList[previewSubStyle].previewif(previewSubStyleUrl){this.setData({previewStyle: previewSubStyleUrl})}else{wx.showToast({title: "暂无模板预览",icon: "error"})return }}this.setData({modalName: e.currentTarget.dataset.target})},hideModal(e) {this.setData({modalName: null})},copyData(e) {wx.setClipboardData({data: e.currentTarget.dataset.data,success(res) {wx.showModal({title: '复制完成',content: `已将${e.currentTarget.dataset.data}复制到了剪切板`,})}})},
})

因为项目会请求比较多次的后台接口,所以,我将请求方法进行额外的抽象:

// 统一请求接口doRequest: async function (uri, data, option) {const that = thisreturn new Promise((resolve, reject) => {wx.request({url: that.url + uri,data: data,header: {"Content-Type": 'application/json',},method: option && option.method ? option.method : "POST",success: function (res) {resolve(res.data)},fail: function (res) {reject(null)}})})}

完成之后配置一下后台接口,发布审核即可。

本文作者刘宇(花名:江昱)

原文链接

本文为阿里云原创内容,未经允许不得转载。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/511083.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

异动分析技术解决方案—异动归因之指标拆解

简介&#xff1a;归因的方法有多种&#xff0c;这篇文章的重点是指标拆解&#xff0c;也是我们做业务分析时最常用到的方法。我们的目的是解放人力&#xff0c;将指标拆解实现自动化&#xff0c;一方面可以加快业务迭代速度&#xff0c;快速定位问题&#xff1b;另一方面可以对…

阿里巴巴云原生混部系统 Koordinator 正式开源

简介&#xff1a; 脱胎于阿里巴巴内部&#xff0c;经过多年双 11 打磨&#xff0c;每年为公司节省数十亿的混部系统 Koordinator 今天宣布正式开源。通过开源&#xff0c;我们希望将更好的混部能力、调度能力开放到整个行业&#xff0c;帮助企业客户改进云原生工作负载运行的效…

足不出户,确保交付——独家交付秘籍(第二回)

简介&#xff1a;在后疫情时代&#xff0c;遇到无法出差、无法访客时&#xff0c;如何保障交付按时保质的进行&#xff0c;做好运维质量的保障&#xff0c;从而不影响企业整体营收&#xff0c;已成为我们生死攸关的难题。小锤在遇到无法只通过远程就完成项目交付验收&#xff0…

如何使用 PTS 快速发起微服务压测

简介&#xff1a;本文讲阐述什么是微服务架构、微服务架构对系统稳定性带来的影响&#xff0c;以及用性能测试验证稳定性的必要性、用户进行微服务压测的痛点和 PTS 的独特优势、云上使用 PTS 快速发起微服务压测的步骤&#xff0c;以及压测完成后排查分析相关问题的 Tips。 作…

一首让计算机崩溃的歌曲!

大家好&#xff0c;我是轩辕。想象一个场景&#xff1a;手机里播放一首音乐&#xff0c;然后你带着手机从一台电脑旁经过&#xff0c;电脑就被你弄崩溃死机了。这是不是有点像电影里的桥段&#xff1f;一年前&#xff0c;我写过一篇文章&#xff1a;电脑关机了&#xff0c;黑客…

OpenYurt 之 Yurthub 数据过滤框架解析

简介&#xff1a;OpenYurt 是业界首个非侵入的边缘计算云原生开源项目&#xff0c;通过边缘自治&#xff0c;云边协同&#xff0c;边缘单元化&#xff0c;边缘流量闭环等能力为用户提供云边一体化的使用体验。在 Openyurt 里边缘网络可以使用数据过滤框架在不同节点池里实现边缘…

金融核心系统云原生转型的三个挑战、六个误区和四个步骤

嘉宾 | 马振雄出品 | CSDN云原生近年来&#xff0c;云原生技术发展如火如荼&#xff0c;IT系统云原生转型已成共识&#xff0c;留给各行各业的只是何时云原生、怎样云原生的时间和方式问题。金融行业作为各类转型历来的排头兵&#xff0c;在推进云原生转型上同样不逞多让。金融…

Java 定时任务技术趋势

简介&#xff1a;定时任务是每个业务常见的需求&#xff0c;比如每分钟扫描超时支付的订单&#xff0c;每小时清理一次数据库历史数据&#xff0c;每天统计前一天的数据并生成报表等等。 作者&#xff1a;黄晓萌&#xff08;学仁&#xff09; Java 中自带的解决方案 使用 Ti…

从再造到赋能——360数科举办第二届技术开放日

8月31日&#xff0c;360数科在北京举办“从再造到赋能——2022年技术开放日”。这是360数科的第二届开放日&#xff0c;全面展示了公司在金融科技业务全环节的精细化运营成果&#xff0c;呈现了360数科从市场连接者到流程再造者的技术演化历程&#xff0c;展望了其成为行业赋能…

EventBridge 特性介绍|以 IaC 的方式使用 EventBridge

简介&#xff1a;本文将重点介绍 EventBridge 和 IaC 的重点概念和特性&#xff0c;然后演示如何应用 IaC 理念自动化部署 EventBridge 来使用这些概念和特性。 作者&#xff1a;王川&#xff08;弗丁&#xff09; 引言 EventBridge 作为构建 EDA 架构的基础设施&#xff0c…

如何使用 Serverless Devs 部署静态网站到函数计算(上)

简介&#xff1a;部署个静态网站到函数计算~ 前言 公司经常有一些网站需要发布上线&#xff0c;对比了几款不同的产品后&#xff0c;决定使用阿里云的函数计算&#xff08;FC)来托管构建出来的静态网站。 FC 弹性实例自带的500 Mb 存储空间对静态网站来说简直是太充足了 。 函…

盛邦安全创始人权晓文入选IDC中国CSO名人堂十大人物

在近日举办的IDC 2022 CSO全球网络安全峰会&#xff08;中国站&#xff09;上&#xff0c;盛邦安全入选API领域推荐厂商&#xff0c;盛邦安全创始人权晓文入选“中国CSO名人堂&#xff08;十大人物&#xff09;”&#xff0c;展现了盛邦安全在网络安全创新方面的不断进取和突出…

硬核调试实操 | 手把手带你实现 Serverless 断点调试

简介&#xff1a;本文将借助 Serverless Devs 工具&#xff0c;对函数计算 (FC&#xff09;应用的断点调试步骤进行详细指导&#xff0c;手把手带你实现 Serverless 的断点调试&#xff0c;并从以下四个方面为你厘清“硬核调试”的脉络步骤&#xff0c;干货满满。 导读&#x…

兑现 Service Mesh 的新价值:精确控制“爆炸半径”

简介&#xff1a;本文分享了阿里云内部所沉淀的全链路流量打标与路由的能力&#xff0c;做出服务网格技术新体验的同时&#xff0c;很好地兑现了服务网格的新价值。 作者&#xff1a;至简 软件是以持续迭代的方式去不断演进的。某种程度上&#xff0c;我们并不担心软件不完善…

15M安装包就能玩《原神》,带你了解云游戏背后的技术秘密

简介&#xff1a;对于大多数玩家来说&#xff0c;云游戏已经不是一个陌生的概念&#xff0c;它经常和秒玩、不吃设备、大屏临场感、上手门槛低、真香等字眼一起出现在评论留言区。的确&#xff0c;对于既想尝试高品质游戏大作又不想一直卷装备的玩家来说&#xff0c;云游戏做到…

ref绑定dom的三种写法

1、字符串形式 这种字符出串写法因为效率不好&#xff0c;所以不推荐使用 语法 标签上使用ref"name" 进行绑定 方法中this.refs.name拿到dom <input ref"input1" type"text" placeholder"点击按钮弹出内容" /> <button onC…

一文看懂边缘云在广电行业的应用

简介&#xff1a;随着中国广电的5G布局在不断加速&#xff0c;各地广电运营商均已开展面向边缘云建设和业务探索。边缘云作为5G网络架构中关键一环&#xff0c;具有广覆盖、低时延、大带宽的技术特点&#xff0c;是打通智慧广电建设的“经脉”&#xff0c;对未来开展4K/8K超高清…

2022 互联网中秋月饼大赏,腾讯送火腿,字节寓意圆满,你最钟爱哪款呢?(文末有抽奖)...

整理 | 梦依丹出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;配图来自视觉中国又是一年花好处&#xff0c;人月中秋两团圆&#xff01;今年的中秋&#xff0c;你是在家乡还是在他乡度过呢&#xff1f;无论在何处&#xff0c;只要心在一起&#xff0c;多远都不是距…

宜搭小技巧|自动计算日期时长,3个公式帮你敲定

简介&#xff1a;使用「时间函数」实现日期时长自动计算功能&#xff0c;让表单填写更轻松。 上一期&#xff0c;我们学会了如何巧用日期组件保证时间填写不出错。 今天&#xff0c;宜小搭要出差&#xff0c;由于公司要根据出差时长发放补贴&#xff0c;但手动计算出差天数太…

构造函数、实例、原型对象、继承

一、构造函数与原型对象之间的关系&#xff1a; 有一个Star构造函数&#xff0c;每一个构造函数里面都有一个原型对象&#xff0c;是通过构造函数的prototype指向这个原型对象的 同样在这个原型对象里面也有一个属性叫constructor&#xff0c;它又指回了构造函数 可以把构造函…