百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

Serverless 实战:如何为你的头像增加点装饰?

toyiye 2024-07-06 00:34 22 浏览 0 评论

每到大型节假日,我们常会发现社交平台都会提供生成头像装饰的小工具,很是新奇好玩。如果从技术的维度看,这类平台 / 工具一般都是通过下面两个方法给我们生成头像装饰的:

  • 一是直接加装饰,例如在头像外面加边框,在下面加 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 架构下,其弹性伸缩特点让我们不惧怕高并发,其按量付费模式让我们不惧怕成本支出。

关注我并转发此篇文章,私信我“领取资料”,即可免费获得InfoQ价值4999元迷你书!

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码