Serverless 实战:如何为你的头像增加点装饰?
2010 年 1 月 9 日
每到大型节假日,我们常会发现社交平台都会提供生成头像装饰的小工具,很是新奇好玩。如果从技术的维度看,这类平台 / 工具一般都是通过下面两个方法给我们生成头像装饰的:
- 一是直接加装饰,例如在头像外面加边框,在下面加 logo 等;
- 二是通过机器学习算法增加装饰,例如增加一个圣诞帽等;
使用 Serverless 直接增加头像装饰
增加头像装饰的功能其实很容易实现,首先选择一张图片,上传自己的头像,然后函数部分进行图像的合成,这一部分并没有涉及到机器学习算法,仅仅是图像合成相关算法。
通过用户上传的图片,在指定位置增加预定图片 / 用户选择的图片作为装饰物进行添加:
- 将预定图片 / 用户选择的图片进行美化,此处仅是将其变成圆形:
复制代码
defdo_circle(base_pic): icon_pic = Image.open(base_pic).convert("RGBA") icon_pic = icon_pic.resize((500,500), Image.ANTIALIAS) icon_pic_x, icon_pic_y = icon_pic.size temp_icon_pic = Image.new('RGBA', (icon_pic_x +600, icon_pic_y +600), (255,255,255)) temp_icon_pic.paste(icon_pic, (300,300), icon_pic) ima = temp_icon_pic.resize((200,200), Image.ANTIALIAS) size = ima.size # 因为是要圆形,所以需要正方形的图片 r2 = min(size[0], size[1]) ifsize[0] != size[1]: ima = ima.resize((r2, r2), Image.ANTIALIAS) # 最后生成圆的半径 r3 =60 imb = Image.new('RGBA', (r3 *2, r3 *2), (255,255,255,0)) pima = ima.load()# 像素的访问对象 pimb = imb.load() r = float(r2 /2)# 圆心横坐标 foriinrange(r2): forjinrange(r2): lx = abs(i - r)# 到圆心距离的横坐标 ly = abs(j - r)# 到圆心距离的纵坐标 l = (pow(lx,2) + pow(ly,2)) **0.5# 三角函数 半径 ifl < r3: pimb[i - (r - r3), j - (r - r3)] = pima[i, j] returnimb
- 添加该装饰到用户头像上:
复制代码
defadd_decorate(base_pic): try: base_pic ="./base/%s.png"% (str(base_pic)) user_pic = Image.open("/tmp/picture.png").convert("RGBA") temp_basee_user_pic = Image.new('RGBA', (440,440), (255,255,255)) user_pic = user_pic.resize((400,400), Image.ANTIALIAS) temp_basee_user_pic.paste(user_pic, (20,20)) temp_basee_user_pic.paste(do_circle(base_pic), (295,295), do_circle(base_pic)) temp_basee_user_pic.save("/tmp/output.png") returnTrue exceptExceptionase: print(e) returnFalse
- 除此之外,为了方便本地测试,项目增加了
test()
方法模拟 API 网关传递的数据:
复制代码
deftest(): withopen("test.png",'rb')asf: image = f.read() image_base64 = str(base64.b64encode(image), encoding='utf-8') event = { "requestContext": { "serviceId":"service-f94sy04v", "path":"/test/{path}", "httpMethod":"POST", "requestId":"c6af9ac6-7b61-11e6-9a41-93e8deadbeef", "identity": { "secretId":"abdcdxxxxxxxsdfs" }, "sourceIp":"14.17.22.34", "stage":"release" }, "headers": { "Accept-Language":"en-US,en,cn", "Accept":"text/html,application/xml,application/json", "Host":"service-3ei3tii4-251000691.ap-guangzhou.apigateway.myqloud.com", "User-Agent":"User Agent String" }, "body":"{\"pic\":\"%s\", \"base\":\"1\"}"% image_base64, "pathParameters": { "path":"value" }, "queryStringParameters": { "foo":"bar" }, "headerParameters": { "Refer":"10.0.2.14" }, "stageVariables": { "stage":"release" }, "path":"/test/value", "queryString": { "foo":"bar", "bob":"alice" }, "httpMethod":"POST" } print(main_handler(event,None)) if__name__ =="__main__": test()
- 为了让函数有同一个返回规范,此处增加统一返回的函数:
复制代码
defreturn_msg(error, msg): return_data = { "uuid": str(uuid.uuid1()), "error": error, "message": msg } print(return_data) returnreturn_data
- 最后是涂口函数的写法:
复制代码
importbase64, json fromPILimportImage importuuid defmain_handler(event, context): try: print(" 将接收到的 base64 图像转为 pic") imgData = base64.b64decode(json.loads(event["body"])["pic"].split("base64,")[1]) withopen('/tmp/picture.png','wb')asf: f.write(imgData) basePic = json.loads(event["body"])["base"] addResult = add_decorate(basePic) ifaddResult: withopen("/tmp/output.png","rb")asf: base64Data = str(base64.b64encode(f.read()), encoding='utf-8') returnreturn_msg(False, {"picture": base64Data}) else: returnreturn_msg(True," 饰品添加失败 ") exceptExceptionase: returnreturn_msg(True," 数据处理异常: %s"% str(e))
完成后端图像合成功能,制作前端页面:
复制代码
2020 头像大变样 - 头像 SHOW - 自豪的采用腾讯云 Serverless 架构! thisPic =null functiongetFileUrl(sourceId){ varurl; thisPic =document.getElementById(sourceId).files.item(0) if(navigator.userAgent.indexOf("MSIE") >=1) {// IE url =document.getElementById(sourceId).value; }elseif(navigator.userAgent.indexOf("Firefox") >0) {// Firefox url =window.URL.createObjectURL(document.getElementById(sourceId).files.item(0)); }elseif(navigator.userAgent.indexOf("Chrome") >0) {// Chrome url =window.URL.createObjectURL(document.getElementById(sourceId).files.item(0)); } returnurl; } functionpreImg(sourceId, targetId){ varurl = getFileUrl(sourceId); varimgPre =document.getElementById(targetId); imgPre.aaaaaa = url; imgPre.style ="display: block;"; } functionclickChose(){ document.getElementById("imgOne").click() } functiongetNewPhoto(){ document.getElementById("result").innerText =" 系统处理中,请稍后..." varoFReader =newFileReader(); oFReader.readAsDataURL(thisPic); oFReader.onload =function(oFREvent){ varxmlhttp; if(window.XMLHttpRequest) { // IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码 xmlhttp =newXMLHttpRequest(); }else{ // IE6, IE5 浏览器执行代码 xmlhttp =newActiveXObject("Microsoft.XMLHTTP"); } xmlhttp.onreadystatechange =function(){ if(xmlhttp.readyState ==4&& xmlhttp.status ==200) { if(JSON.parse(xmlhttp.responseText)["error"]) { document.getElementById("result").innerText =JSON.parse(xmlhttp.responseText)["message"]; }else{ document.getElementById("result").innerText =" 长按保存图像 "; document.getElementById("new_photo").aaaaaa ="data:image/png;base64,"+JSON.parse(xmlhttp.responseText)["message"]["picture"]; document.getElementById("new_photo").style ="display: block;"; } } } varurl =" http://service-8d3fi753-1256773370.bj.apigw.tencentcs.com/release/new_year_add_photo_decorate" varobj =document.getElementsByName("base"); varbaseNum ="1" for(vari =0; i < obj.length; i++) { console.log(obj[i].checked) if(obj[i].checked) { baseNum = obj[i].value; } } xmlhttp.open("POST", url,true); xmlhttp.setRequestHeader("Content-type","application/json"); varpostData = { pic: oFREvent.target.result, base: baseNum } xmlhttp.send(JSON.stringify(postData)); } } 2020 头像 SHOW 第一步:选择一个你喜欢的图片