Turn Your Photo into a Comic Avatar with Serverless AI – Step‑by‑Step Guide
This tutorial shows how to use the AnimeGAN v2 model to convert personal photos into comic‑style avatars, expose the AI service via a Python Bottle API on a Serverless platform, configure deployment with Serverless Devs, and integrate the backend into a WeChat mini‑program for end‑users.
Background
The author wanted a cartoon‑style avatar but lacked the tools to create one manually, so they decided to leverage an AI model (AnimeGAN v2) and deploy it using a Serverless architecture for broader accessibility.
Backend Service
The backend uses the popular AnimeGAN v2 filter library to transform images. A Python Bottle application loads several pre‑trained models from a local directory and provides an HTTP endpoint /images/comic_style that accepts a POST request with a base64‑encoded image and a style identifier.
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 + 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 = 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 result
app = bottle.default_app()
if __name__ == "__main__":
bottle.run(host='localhost', port=8099)Key Serverless adaptations:
Models are loaded during instance initialization to reduce cold‑start latency.
Only the /tmp directory is writable, so uploaded images are cached there.
Even though Function Compute is stateless, temporary files are given random names to avoid clashes.
Binary uploads are not universally supported, so the API uses base64‑encoded payloads.
Style List Endpoint
An additional endpoint /system/styles returns a JSON map of available styles, each with a preview image URL, allowing the front‑end to display options.
@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'
}
}
}
}Serverless Deployment with Serverless Devs
The service is packaged using Serverless Devs. The s.yaml file defines global variables, NAS configuration, VPC settings, and a function component named image_server with 3 GB memory and a 300 s timeout. It also sets up an HTTP trigger and a custom domain ( avatar.aialbum.net).
edition: 1.0.0
name: start-ai
access: "default"
vars:
region: cn-hangzhou
service:
name: ai
nasConfig:
userId: 10003
groupId: 10003
mountPoints:
- serverAddr: 0fe764bf9d-kci94.cn-hangzhou.nas.aliyuncs.com
nasDir: /python3
fcDir: /mnt/python3
vpcConfig:
vpcId: vpc-bp1rmyncqxoagiyqnbcxk
securityGroupId: sg-bp1dpxwusntfryekord6
vswitchIds:
- vsw-bp1wqgi5lptlmk8nk5yi0
services:
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: /*Deployment steps:
Install dependencies inside a Docker container: s build --use-docker Deploy the service: s deploy Create the NAS directory and upload the Python dependencies: s nas command mkdir /mnt/python3/python then
s nas upload -r 本地依赖路径 /mnt/python3/pythonWeChat Mini‑Program Frontend
The mini‑program uses the ColorUI framework and consists of a single page. The layout (WXML) provides image upload, style selection, and a button to trigger the AI conversion. The JavaScript (index.js) handles page state, fetches the style list from /system/styles, uploads the selected image as base64, calls /images/comic_style, and displays the result.
// index.js
const app = getApp()
Page({
data: {
styleList: {},
currentStyle: "动漫风",
currentSubStyle: "v1模型",
userChosePhoho: undefined,
resultPhoto: undefined,
previewStyle: undefined,
getPhotoStatus: false
},
onLoad() {
wx.showLoading({title: '加载中'})
app.doRequest(`system/styles`, {}, {method: "GET"}).then(result => {
wx.hideLoading()
this.setData({
styleList: result,
currentStyle: Object.keys(result)[0],
currentSubStyle: Object.keys(result[Object.keys(result)[0]].detailList)[0]
})
})
},
// ... (event handlers for style change, photo selection, request, etc.)
})A unified request helper abstracts wx.request to simplify GET/POST calls.
// unified request
doRequest: async function (uri, data, option) {
return new Promise((resolve, reject) => {
wx.request({
url: this.url + uri,
data: data,
header: {"Content-Type": 'application/json'},
method: option && option.method ? option.method : "POST",
success: res => resolve(res.data),
fail: () => reject(null)
})
})
}Additional Notes
The mini‑program requires HTTPS endpoints; certificate configuration is omitted for brevity.
Separate backend functions for style listing and image processing keep memory usage low and reduce cost.
Large model files (>800 MB) cannot be uploaded directly; they are mounted via NAS.
After deployment, users can open the mini‑program, choose a photo, pick a cartoon style, and receive a generated comic‑style avatar.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Alibaba Cloud Native
We publish cloud-native tech news, curate in-depth content, host regular events and live streams, and share Alibaba product and user case studies. Join us to explore and share the cloud-native insights you need.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
