
每到大型节假日,我们常会发现社交平台都会提供生成头像装饰的小工具,很是新奇好玩。如果从技术的维度看,这类平台/工具一般都是通过下面两个方法给我们生成头像装饰的:
一是直接加装饰,例如在头像外面加边框,在下面加logo等;
二是通过机器学习算法增加装饰,例如增加一个圣诞帽等;
使用 Serverless 直接增加头像装饰
增加头像装饰的功能其实很容易实现,首先选择一张图片,上传自己的头像,然后函数部分进行图像的合成,这一部分并没有涉及到机器学习算法,仅仅是图像合成相关算法。
通过用户上传的图片,在指定位置增加预定图片/用户选择的图片作为装饰物进行添加:
将预定图片/用户选择的图片进行美化,此处仅是将其变成圆形:
def do_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]) if size[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) # 圆心横坐标
for i in range(r2): for j in range(r2): lx = abs(i - r) # 到圆心距离的横坐标 ly = abs(j - r) # 到圆心距离的纵坐标 l = (pow(lx, 2) + pow(ly, 2)) ** 0.5 # 三角函数 半径
if l < r3: pimb[i - (r - r3), j - (r - r3)] = pima[i, j] return imb
添加该装饰到用户头像上:
def add_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") return True except Exception as e: print(e) return False
除此之外,为了方便本地测试,项目增加了
test()方法模拟API网关传递的数据:
def test(): with open("test.png", 'rb') as f: 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()
为了让函数有同一个返回规范,此处增加统一返回的函数:
def return_msg(error, msg): return_data = { "uuid": str(uuid.uuid1()), "error": error, "message": msg } print(return_data) return return_data
最后是涂口函数的写法:
import base64, jsonfrom PIL import Imageimport uuid
def main_handler(event, context): try: print("将接收到的base64图像转为pic") imgData = base64.b64decode(json.loads(event["body"])["pic"].split("base64,")[1]) with open('/tmp/picture.png', 'wb') as f: f.write(imgData)
basePic = json.loads(event["body"])["base"] addResult = add_decorate(basePic) if addResult: with open("/tmp/output.png", "rb") as f: base64Data = str(base64.b64encode(f.read()), encoding='utf-8') return return_msg(False, {"picture": base64Data}) else: return return_msg(True, "饰品添加失败") except Exception as e: return return_msg(True, "数据处理异常: %s" % str(e))
完成后端图像合成功能,制作前端页面:
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>2020头像大变样 - 头像SHOW - 自豪的采用腾讯云Serverless架构!</title> <meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1,user-scalable=no"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black"> <script type="text/javascript"> thisPic = null function getFileUrl(sourceId) { var url; thisPic = document.getElementById(sourceId).files.item(0) if (navigator.userAgent.indexOf("MSIE") >= 1) { // IE url = document.getElementById(sourceId).value; } else if (navigator.userAgent.indexOf("Firefox") > 0) { // Firefox url = window.URL.createObjectURL(document.getElementById(sourceId).files.item(0)); } else if (navigator.userAgent.indexOf("Chrome") > 0) { // Chrome url = window.URL.createObjectURL(document.getElementById(sourceId).files.item(0)); } return url; } function preImg(sourceId, targetId) { var url = getFileUrl(sourceId); var imgPre = document.getElementById(targetId); imgPre.aaaaaa = url; imgPre.style = "display: block;"; } function clickChose() { document.getElementById("imgOne").click() } function getNewPhoto() { document.getElementById("result").innerText = "系统处理中,请稍后..." var oFReader = new FileReader(); oFReader.readAsDataURL(thisPic); oFReader.onload = function (oFREvent) { var xmlhttp; if (window.XMLHttpRequest) { // IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码 xmlhttp = new XMLHttpRequest(); } else { // IE6, IE5 浏览器执行代码 xmlhttp = new ActiveXObject("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;"; } } } var url = " http://service-8d3fi753-1256773370.bj.apigw.tencentcs.com/release/new_year_add_photo_decorate" var obj = document.getElementsByName("base"); var baseNum = "1" for (var i = 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"); var postData = { pic: oFREvent.target.result, base: baseNum } xmlhttp.send(JSON.stringify(postData)); } } </script> <!--标准mui.css--> <link rel="stylesheet" href="./css/mui.min.css"></head><body><h3 style="text-align: center; margin-top: 30px">2020头像SHOW</h3><div class="mui-card"> <div class="mui-card-content"> <div class="mui-card-content-inner"> 第一步:选择一个你喜欢的图片 </div> </div> <div class="mui-content"> <ul class="mui-table-view mui-grid-view mui-grid-9"> <li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3"><label> <img aaaaaa="./base/1.png" width="100%"><input type="radio" name="base" value="1" checked></label></li> <li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3"><label> <img aaaaaa="./base/2.png" width="100%"><input type="radio" name="base" value="2"></label></li> <li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3"><label> <img aaaaaa="./base/11.png" width="100%"><input type="radio" name="base" value="11"></label></li> <li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3"><label> <img aaaaaa="./base/4.png" width="100%"><input type="radio" name="base" value="4"></label></li> <li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3"><label> <img aaaaaa="./base/5.png" width="100%"><input type="radio" name="base" value="5"></label></li> <li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3"><label> <img aaaaaa="./base/6.png" width="100%"><input type="radio" name="base" value="6"></label></li> <li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3"><label> <img aaaaaa="./base/12.png" width="100%"><input type="radio" name="base" value="12"></label></li> <li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3"><label> <img aaaaaa="./base/8.png" width="100%"><input type="radio" name="base" value="8"></label></li> <li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3"><label> <img aaaaaa="./base/3.png" width="100%"><input type="radio" name="base" value="3"></label></li> </ul> </div></div><div class="mui-card"> <div class="mui-card-content"> <div class="mui-card-content-inner"> 第二步:上传一张你的头像 </div> <div> <form> <input type="file" name="imgOne" id="imgOne" onchange="preImg(this.id, 'photo')" style="display: none;" accept="image/*"> <center style="margin-bottom: 10px"> <input type="button" value="点击此处上传头像" onclick="clickChose()"/> <img id="photo" aaaaaa="" width="300px" , height="300px" style="display: none;"/> </center> </form> </div> </div></div><div class="mui-card"> <div class="mui-card-content"> <div class="mui-card-content-inner"> 第三步:点击生成按钮获取新年头像 </div> <div> <center style="margin-bottom: 10px"> <input type="button" value="生成新年头像" onclick="getNewPhoto()"/> <p id="result"></p> <img id="new_photo" aaaaaa="" width="300px" , height="300px" style="display: none;"/> </center> </div> </div></div><p style="text-align: center"> 本项目自豪的<br>通过Serverless Framework<br>搭建在腾讯云SCF上</p></body></html>
完成之后:
new_year_add_photo_decorate: component: "@serverless/tencent-scf" inputs: name: myapi_new_year_add_photo_decorate codeUri: ./new_year_add_photo_decorate handler: index.main_handler runtime: Python3.6 region: ap-beijing description: 新年为头像增加饰品 memorySize: 128 timeout: 5 events: - apigw: name: serverless parameters: serviceId: service-8d3fi753 environment: release endpoints: - path: /new_year_add_photo_decorate description: 新年为头像增加饰品 method: POST enableCORS: true param: - name: pic position: BODY required: 'FALSE' type: string desc: 原始图片 - name: base position: BODY required: 'FALSE' type: string desc: 饰品ID
myWebsite: component: '@serverless/tencent-website' inputs: code: src: ./new_year_add_photo_decorate/web index: index.html error: index.html region: ap-beijing bucketName: new-year-add-photo-decorate
完成之后就可以实现头像加装饰的功能,效果如下:
Serverless 与人工智能联手增加头像装饰
直接加装饰的方式其实是可以在前端实现的,但是既然用到了后端服务和云函数,那么我们不妨就将人工智能与 Serverless 架构结果来实现一个增加装饰的小工具。
实现这一功能的主要做法就是通过人工智能算法(此处是通过 Dlib 实现)进行人脸检测:
print("dlib人脸关键点检测器,正脸检测")predictorPath = "shape_predictor_5_face_landmarks.dat"predictor = dlib.shape_predictor(predictorPath)detector = dlib.get_frontal_face_detector()dets = detector(img, 1)
此处的做法是只检测一张脸,检测到即进行返回:
for d in dets: x, y, w, h = d.left(), d.top(), d.right() - d.left(), d.bottom() - d.top()
print("关键点检测,5个关键点") shape = predictor(img, d)
print("选取左右眼眼角的点") point1 = shape.part(0) point2 = shape.part(2)
print("求两点中心") eyes_center = ((point1.x + point2.x) // 2, (point1.y + point2.y) // 2)
print("根据人脸大小调整帽子大小") factor = 1.5 resizedHatH = int(round(rgbHat.shape[0] * w / rgbHat.shape[1] * factor)) resizedHatW = int(round(rgbHat.shape[1] * w / rgbHat.shape[1] * factor))
if resizedHatH > y: resizedHatH = y - 1
print("根据人脸大小调整帽子大小") resizedHat = cv2.resize(rgbHat, (resizedHatW, resizedHatH))
print("用alpha通道作为mask") mask = cv2.resize(a, (resizedHatW, resizedHatH)) maskInv = cv2.bitwise_not(mask)
print("帽子相对与人脸框上线的偏移量") dh = 0 bgRoi = img[y + dh - resizedHatH:y + dh, (eyes_center[0] - resizedHatW // 3):(eyes_center[0] + resizedHatW // 3 * 2)]
print("原图ROI中提取放帽子的区域") bgRoi = bgRoi.astype(float) maskInv = cv2.merge((maskInv, maskInv, maskInv)) alpha = maskInv.astype(float) / 255
print("相乘之前保证两者大小一致(可能会由于四舍五入原因不一致)") alpha = cv2.resize(alpha, (bgRoi.shape[1], bgRoi.shape[0])) bg = cv2.multiply(alpha, bgRoi) bg = bg.astype('uint8')
print("提取帽子区域") hat = cv2.bitwise_and(resizedHat, cv2.bitwise_not(maskInv))
print("相加之前保证两者大小一致(可能会由于四舍五入原因不一致)") hat = cv2.resize(hat, (bgRoi.shape[1], bgRoi.shape[0])) print("两个ROI区域相加") addHat = cv2.add(bg, hat)
print("把添加好帽子的区域放回原图") img[y + dh - resizedHatH:y + dh, (eyes_center[0] - resizedHatW // 3):(eyes_center[0] + resizedHatW // 3 * 2)] = addHat
return img
在 Serverless 架构下的完整代码:
import cv2import dlibimport base64import json
def addHat(img, hat_img): print("分离rgba通道,合成rgb三通道帽子图,a通道后面做mask用") r, g, b, a = cv2.split(hat_img) rgbHat = cv2.merge((r, g, b))
print("dlib人脸关键点检测器,正脸检测") predictorPath = "shape_predictor_5_face_landmarks.dat" predictor = dlib.shape_predictor(predictorPath) detector = dlib.get_frontal_face_detector() dets = detector(img, 1)
print("如果检测到人脸") if len(dets) > 0: for d in dets: x, y, w, h = d.left(), d.top(), d.right() - d.left(), d.bottom() - d.top()
print("关键点检测,5个关键点") shape = predictor(img, d)
print("选取左右眼眼角的点") point1 = shape.part(0) point2 = shape.part(2)
print("求两点中心") eyes_center = ((point1.x + point2.x) // 2, (point1.y + point2.y) // 2)
print("根据人脸大小调整帽子大小") factor = 1.5 resizedHatH = int(round(rgbHat.shape[0] * w / rgbHat.shape[1] * factor)) resizedHatW = int(round(rgbHat.shape[1] * w / rgbHat.shape[1] * factor))
if resizedHatH > y: resizedHatH = y - 1
print("根据人脸大小调整帽子大小") resizedHat = cv2.resize(rgbHat, (resizedHatW, resizedHatH))
print("用alpha通道作为mask") mask = cv2.resize(a, (resizedHatW, resizedHatH)) maskInv = cv2.bitwise_not(mask)
print("帽子相对与人脸框上线的偏移量") dh = 0 bgRoi = img[y + dh - resizedHatH:y + dh, (eyes_center[0] - resizedHatW // 3):(eyes_center[0] + resizedHatW // 3 * 2)]
print("原图ROI中提取放帽子的区域") bgRoi = bgRoi.astype(float) maskInv = cv2.merge((maskInv, maskInv, maskInv)) alpha = maskInv.astype(float) / 255
print("相乘之前保证两者大小一致(可能会由于四舍五入原因不一致)") alpha = cv2.resize(alpha, (bgRoi.shape[1], bgRoi.shape[0])) bg = cv2.multiply(alpha, bgRoi) bg = bg.astype('uint8')
print("提取帽子区域") hat = cv2.bitwise_and(resizedHat, cv2.bitwise_not(maskInv))
print("相加之前保证两者大小一致(可能会由于四舍五入原因不一致)") hat = cv2.resize(hat, (bgRoi.shape[1], bgRoi.shape[0])) print("两个ROI区域相加") addHat = cv2.add(bg, hat)
print("把添加好帽子的区域放回原图") img[y + dh - resizedHatH:y + dh, (eyes_center[0] - resizedHatW // 3):(eyes_center[0] + resizedHatW // 3 * 2)] = addHat
return img
def main_handler(event, context): try: print("将接收到的base64图像转为pic") imgData = base64.b64decode(json.loads(event["body"])["pic"]) with open('/tmp/picture.png', 'wb') as f: f.write(imgData)
print("读取帽子素材以及用户头像") hatImg = cv2.imread("hat.png", -1) userImg = cv2.imread("/tmp/picture.png")
output = addHat(userImg, hatImg) cv2.imwrite("/tmp/output.jpg", output)
print("读取头像进行返回给用户,以Base64返回") with open("/tmp/output.jpg", "rb") as f: base64Data = str(base64.b64encode(f.read()), encoding='utf-8')
return { "picture": base64Data } except Exception as e: return { "error": str(e) }
这样,我们就完成了通过用户上传人物头像进行增加圣诞帽的功能。
总结
传统情况下,如果我们要做一个增加头像装饰的小工具,可能需要一个服务器,哪怕没有人使用,也必须有一台服务器苦苦支撑,这样导致有时仅仅是一个 Demo,也需要无时无刻的支出成本。但在 Serverless 架构下,其弹性伸缩特点让我们不惧怕高并发,其按量付费模式让我们不惧怕成本支出。
更多内容推荐
37 丨数据采集实战:如何自动化运营微博?
今天我带你做一个微博自动化运营的实战,在这个过程中,你需要掌握哪些工具呢?
2019 年 3 月 8 日
Serverless 实战:如何快速实现图片压缩与水印添加?
当图片数量很多、尺寸很大的时候,压缩、标准化和水印添加就会占用很多的资源。那么,我们是否能够利用 Serverless 架构实现图片压缩与水印的一条龙服务,同时用户量的激增也不会影响整体体验呢?
Google Closure Stylesheets 让我们更易于使用 CSS
Google已经基于Apache License 2.0把Closure Stylesheets开源,这种工具属于Closure Tools包之内,在处理CSS的时候很有用。Closure Stylesheets是一个Java程序,它向CSS中添加了变量、函数、条件语句以及混合类型,使得我们更易于处理大型的CSS文件。
实现表单(2):错误处理,动态表单元素,内容动态加载
无
2018 年 7 月 17 日
基于 Serverless Framework 的人工智能小程序开发
本文将会介绍通过微信小程序,在 Serverless 架构上实现一款基于人工智能的相册小工具。
10 丨 Python 爬虫:如何自动化下载王祖贤海报?
相比于使用第三方工具,Python爬虫都有哪些长处?
2019 年 1 月 4 日
新零售下的精准营销利器:人脸属性识别
随着近几年深度学习的快速发展,人脸属性分析也获得了越来越多的关注。
如何在 Serverless 架构下优雅上传文件?
Serverless可以看作是一个新的技术、新的架构。我们在接触新鲜事物的时候,或多或少都要有一个适应期,如何在Serverless架构下上传文件,就是需要适应的部分。
疫情之下,“口罩人脸识别”是怎样炼成的?
疫情为当下的人脸识别系统提出了全新的要求和挑战:在佩戴口罩场景下如何高效的完成人脸识别。
虹膜识别技术
本文来自微信京东数科技术说公众号。
第 16 讲 | 如何在游戏中载入 UI 和菜单?
在游戏中,UI的呈现有哪些方式?每种实现方式又有什么优劣?具体我们又该如何摆放UI界面?
2018 年 7 月 10 日
真 Anchor Free 目标检测:CenterNet 详解
本文详细介绍anchor-free系列的目标检测算法CenterNet。
贾佳亚:腾讯核心计算机视觉研究部门解密丨二叉树视频
贾佳亚博士,腾讯优图实验室杰出科学家(Distinguished Scientist),在腾讯负责计算机视觉、图像处理、模式识别、机器学习等人工智能领域的研究,及人工智能与各种应用场景结合的深度探索。贾佳亚是香港中文大学终身教授。加入腾讯前,他曾与微软研究院、谷歌、高通、英特尔、Adobe 等图像和人工智能研究机构开展过深度联合研究工作。贾佳亚博士在大学任职期间发表过逾百篇顶级会议和刊物论文。
爱奇艺短视频质量评估模型
短视频信息流产品是目前最炙手可热的互联网产品,完全占领了用户的碎片时间,据艾瑞统计2018年短视频产品月独立设备数有6亿+台。
浏览器 API(小实验):动手整理全部 API
浏览器的API数目繁多,但我们没法把课程变成API参考手册,所以这一节课,我设计了一个实验,一起来给API分分类。
2019 年 4 月 18 日
一张自拍测肤质靠不靠谱?聊聊美图的 AI 测肤技术
12月21日,美图公司影像实验室MTlab在北京召开发布会,正式发布其人工智能最新技术成果:AI测肤技术MTskin——即可以通过一张面部照片诊断皮肤问题。
AIRedux(2) : 深入理解 Store、Action、Reducer
无
2018 年 6 月 19 日
React Native 探索(二):布局篇
本文主要分享了react-native的布局内容。
推荐阅读
浏览器 CSSOM:如何获取一个元素的准确位置
2019 年 3 月 16 日
rich-text 组件:如何单击预览 rich-text 中的图片并保存?
2020 年 5 月 28 日
大规模机器学习在爱奇艺视频分析理解中的实践
iOS & Android 去马赛克处理
2020 年 6 月 27 日
Chrome 38 通过<picture>元素支持 Art Direction
图片即时优化的三种简单解决方案
第 12 讲 | 如何设置精灵的变形、放大和缩小?
2018 年 6 月 26 日
电子书

大厂实战PPT下载
换一换 
刘宏伟 | 美团 资深技术专家
郑雨迪 | 郑雨迪,Oracle 高级研究员,计算机博士 《深入拆解 Java 虚拟机》专栏作者
林选磊 | TigerGraph 解决方案工程师










评论