Serverless:让图像合成更简单

本文涉及的产品
简介: 在生产生活中,图像的合成实际上是一个比较常见的需求。而图像的合成往往也会在一些活动中,有着比较重要的作用和价值,例如在一个固定的图片模板上增加一些文字和一些验证码,形成一个新的图片,然后分享出去做一些营销活动;或者是在某些节假日,通过图像的合成,在头像上增加一些装饰物等。本文将会继续探索Serverless在图像处理中的应用,尤其是图像合成相关的应用。本文将会以阿里云函数计算以及华为云函数计算为例,为读者进行图像合成相关操作的探索。

前言

在生产生活中,图像的合成实际上是一个比较常见的需求。而图像的合成往往也会在一些活动中,有着比较重要的作用和价值,例如在一个固定的图片模板上增加一些文字和一些验证码,形成一个新的图片,然后分享出去做一些营销活动;或者是在某些节假日,通过图像的合成,在头像上增加一些装饰物等。本文将会继续探索Serverless在图像处理中的应用,尤其是图像合成相关的应用。本文将会以阿里云函数计算以及华为云函数计算为例,为读者进行图像合成相关操作的探索。

为头像增加圣诞帽

为头像增加装饰是一个比较常见的小功能,本例子将会通过Serverless实现头像增加圣诞帽的功能。主要功能就是选择一个图片,然后函数部分进行图像的合成,这一部分主要是识别图片中的头像,然后在头像上增加圣诞帽的行为。

这一功能的主要做法就是,通过人工智能算法(此处是通过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()


   # 关键点检测,5个关键点")

   shape = predictor(img, d)


   # 选取左右眼眼角的点")

   point1 = shape.part(0)

   point2 = shape.part(2)


   # 求两点中心

   eyes_center = ((point1.x + point2.x) // 2, (point1.y + point2.y) // 2)


   # 根据人脸大小调整帽子大小

   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


   # 根据人脸大小调整帽子大小

   resizedHat = cv2.resize(rgbHat, (resizedHatW, resizedHatH))


   # 用alpha通道作为mask

   mask = cv2.resize(a, (resizedHatW, resizedHatH))

   maskInv = cv2.bitwise_not(mask)


   # 帽子相对与人脸框上线的偏移量

   dh = 0

   bgRoi = img[y + dh - resizedHatH:y + dh,

           (eyes_center[0] - resizedHatW // 3):(eyes_center[0] + resizedHatW // 3 * 2)]


   # 原图ROI中提取放帽子的区域

   bgRoi = bgRoi.astype(float)

   maskInv = cv2.merge((maskInv, maskInv, maskInv))

   alpha = maskInv.astype(float) / 255


   # 相乘之前保证两者大小一致(可能会由于四舍五入原因不一致)

   alpha = cv2.resize(alpha, (bgRoi.shape[1], bgRoi.shape[0]))

   bg = cv2.multiply(alpha, bgRoi)

   bg = bg.astype('uint8')


   # 提取帽子区域

   hat = cv2.bitwise_and(resizedHat, cv2.bitwise_not(maskInv))


   # 相加之前保证两者大小一致(可能会由于四舍五入原因不一致)")

   hat = cv2.resize(hat, (bgRoi.shape[1], bgRoi.shape[0]))

   # 两个ROI区域相加")

   addHat = cv2.add(bg, hat)


   # 把添加好帽子的区域放回原图

   img[y + dh - resizedHatH:y + dh,

   (eyes_center[0] - resizedHatW // 3):(eyes_center[0] + resizedHatW // 3 * 2)] = addHat


   return img

整体代码通过Python Web框架进行封装:

# -*- coding: utf-8 -*-


import cv2

import dlib

import base64

import json

import uuid

import bottle


app = bottle.default_app()


predictorPath = "shape_predictor_5_face_landmarks.dat"

predictor = dlib.shape_predictor(predictorPath)

detector = dlib.get_frontal_face_detector()


return_msg = lambda error, msg: {

   "uuid": str(uuid.uuid1()),

   "error": error,

   "message": msg

}



def addHat(img, hat_img):

   # 分离rgba通道,合成rgb三通道帽子图,a通道后面做mask用

   r, g, b, a = cv2.split(hat_img)

   rgbHat = cv2.merge((r, g, b))


   # dlib人脸关键点检测器,正脸检测

   dets = detector(img, 1)


   # 如果检测到人脸

   if len(dets) > 0:

       for d in dets:

           x, y, w, h = d.left(), d.top(), d.right() - d.left(), d.bottom() - d.top()


           # 关键点检测,5个关键点")

           shape = predictor(img, d)


           # 选取左右眼眼角的点")

           point1 = shape.part(0)

           point2 = shape.part(2)


           # 求两点中心

           eyes_center = ((point1.x + point2.x) // 2, (point1.y + point2.y) // 2)


           # 根据人脸大小调整帽子大小

           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


           # 根据人脸大小调整帽子大小

           resizedHat = cv2.resize(rgbHat, (resizedHatW, resizedHatH))


           # 用alpha通道作为mask

           mask = cv2.resize(a, (resizedHatW, resizedHatH))

           maskInv = cv2.bitwise_not(mask)


           # 帽子相对与人脸框上线的偏移量

           dh = 0

           bgRoi = img[y + dh - resizedHatH:y + dh,

                   (eyes_center[0] - resizedHatW // 3):(eyes_center[0] + resizedHatW // 3 * 2)]


           # 原图ROI中提取放帽子的区域

           bgRoi = bgRoi.astype(float)

           maskInv = cv2.merge((maskInv, maskInv, maskInv))

           alpha = maskInv.astype(float) / 255


           # 相乘之前保证两者大小一致(可能会由于四舍五入原因不一致)

           alpha = cv2.resize(alpha, (bgRoi.shape[1], bgRoi.shape[0]))

           bg = cv2.multiply(alpha, bgRoi)

           bg = bg.astype('uint8')


           # 提取帽子区域

           hat = cv2.bitwise_and(resizedHat, cv2.bitwise_not(maskInv))


           # 相加之前保证两者大小一致(可能会由于四舍五入原因不一致)")

           hat = cv2.resize(hat, (bgRoi.shape[1], bgRoi.shape[0]))

           # 两个ROI区域相加")

           addHat = cv2.add(bg, hat)


           # 把添加好帽子的区域放回原图

           img[y + dh - resizedHatH:y + dh,

           (eyes_center[0] - resizedHatW // 3):(eyes_center[0] + resizedHatW // 3 * 2)] = addHat


           return img



@bottle.route('/add/hat', method='POST')

def addHatIndex():

   try:

       try:

           # 将接收到的base64图像转为pic

           postData = json.loads(bottle.request.body.read().decode("utf-8"))

           imgData = base64.b64decode(postData.get("image", None))

           with open('/tmp/picture.png', 'wb') as f:

               f.write(imgData)

       except Exception as e:

           print(e)

           return return_msg(True, "未能成功获取到头像,请检查pic参数是否为base64编码。")


       try:

           # 读取帽子素材以及用户头像

           hatImg = cv2.imread("hat.png", -1)

           userImg = cv2.imread("/tmp/picture.png")


           output = addHat(userImg, hatImg)

           cv2.imwrite("/tmp/output.jpg", output)

       except Exception as e:

           return return_msg(True, "图像添加圣诞帽失败,请检查图片中是否有圣诞帽或者图片是否可读。")


       # 读取头像进行返回给用户,以Base64返回

       with open("/tmp/output.jpg", "rb") as f:

           base64Data = str(base64.b64encode(f.read()), encoding='utf-8')


       return return_msg(False, {"picture": base64Data})

   except Exception as e:

       return return_msg(True, str(e))


同时声称依赖文件requirements.txt:

dlib==19.21.1

bottle==0.12.19

opencv-python==4.5.1.48

完成之后,我们需要进行依赖的安装,这里为了方便安装依赖,方便部署代码以及更新代码,可以使用Serverless devs开发者工具。首先进行Yaml的配置:

ServerlessBookChristmasHatDemo:

 Component: fc

 Provider: alibaba

 Access: anycodes_release

 Properties:

   Region: cn-hongkong

   Service:

     Name: serverless-book-case

     Description: Serverless 实践图书案例

     Vpc:

       SecurityGroupId: sg-j6c45wkv4vf4ghg104mc

       VSwitchIds:

         - vsw-j6c797ywau90y6y03ohbq

       VpcId: vpc-j6c9lk4av0859r4e0tff7

     Log: Auto

     Nas: Auto

   Function:

     Name: ai-cv-image-christmas-hat

     Description: 圣诞帽

     CodeUri:

       Src: ./src

       Excludes:

         - src/.fun

     Handler: index.app

     Environment:

       - Key: PYTHONUSERBASE

         Value: /mnt/auto/.fun/python

     MemorySize: 3072

     Runtime: python3

     Timeout: 60

     Triggers:

       - Name: ImageAI

         Type: HTTP

         Parameters:

           AuthType: ANONYMOUS

           Methods:

             - GET

             - POST

             - PUT

           Domains:

             - Domain: add-christmas-hat.cv.case.serverless.fun

               Protocol:

                 - HTTP

               Routes:

                 - Path: '/*'

配置完成,可以通过Serverless devs进行依赖安装:

s ServerlessBookChristmasHatDemo install docker

安装依赖之后,将安装好的依赖部署到NAS:

s ServerlessBookChristmasHatDemo nas sync ./src/.fun

最后将代码部署到线上:

s deploy

部署完成之后,可以通过Python语言进行代码的测试,测试脚本如下:

import base64

import urllib.request

import json


with open("test.png", 'rb') as f:

   image = f.read()

   image_base64 = str(base64.b64encode(image), encoding='utf-8')


url = "http://add-christmas-hat.cv.case.serverless.fun/add/hat"

response = urllib.request.urlopen(urllib.request.Request(url=url, data=json.dumps({

   "image": image_base64

}).encode("utf-8"))).read().decode("utf-8")

responseAttr = json.loads(response)

if not responseAttr["error"]:

   imgData = base64.b64decode(responseAttr['message']['picture'])

   with open('./picture.png', 'wb') as f:

       f.write(imgData)

else:

   print(responseAttr['message'])

测试图片为:

执行之后的结果是:

至此,我们完成了一个基于阿里云函数计算以及AI的图像合成的服务。

为头像增加固定装饰

其实这个功能很简单,主要功能就是选择一个图片,上传自己的头像,然后函数部分进行图像的合成,这一部分并没有涉及到机器学习算法,仅仅是图像合成相关算法。

主要通过用户上传的图片,在指定位置增加预定图片作为装饰物进行添加,以华为云函数工作流为例,整个业务流程是:

  • 将预定图片图片进行美化,此处仅是将其变成圆形:

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():

   try:

       base_pic = "./code/decorate.png"

       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

入口函数的业务逻辑为:

def handler(event, context):

   jsonResponse = {

       'statusCode': 200,

       'isBase64Encoded': False,

       'headers': {

           "Content-type": "application/json"

       },

   }


   # 将接收到的base64图像转为pic

   imgData = base64.b64decode(json.loads(base64.b64decode(event["body"]))["image"])

   with open('/tmp/picture.png', 'wb') as f:

       f.write(imgData)

   addResult = add_decorate()

   if addResult:

       with open("/tmp/output.png", "rb") as f:

           base64Data = str(base64.b64encode(f.read()), encoding='utf-8')

       jsonResponse['body'] = json.dumps({"picture": base64Data})

   else:

       jsonResponse['body'] = json.dumps({"error": True})


   return jsonResponse


增加的装饰为(即在用户上传的头像右下角,增加下面的图片作为装饰):

完成之后,安装相关依赖,然后打包上传到华为云的函数工作流:

新建函数,上传Zip代码包之后,还需要设置函数的超时时间和内存:

  • 超时时间:15秒
  • 内存:512MB

完成之后,可以创建触发器起:

创建触发器之后,在本地进行代码测试:

import base64

import urllib.request

import json


with open("test.png", 'rb') as f:

   image = f.read()

   image_base64 = str(base64.b64encode(image), encoding='utf-8')


url = "http://13535b378a964b96b8296808abda0bae.apig.cn-north-1.huaweicloudapis.com/photo_decorate"

response = urllib.request.urlopen(urllib.request.Request(url=url, data=json.dumps({

   "image": image_base64

}).encode("utf-8"))).read().decode("utf-8")

responseAttr = json.loads(response)

imgData = base64.b64decode(responseAttr['picture'])

with open('./picture.png', 'wb') as f:

   f.write(imgData)

所选择的测试图片为:

输出结果为:

至此,我们完成了在华为云创建一个图像合成的函数,可以为我们上传的头像在右下角增加一个小老鼠的装饰。

总结

Serverless架构毕竟是一个新的技术,或者说是一个比较新的Framework,如果刚开始就通过它来做一些很重的产品,可能会让学习者失去兴趣,但是前期可以通过Serverless架构不断的实现一些有趣的功能,小的应用,例如监控告警、图像识别、图像压缩、图像合成、文本摘要、关键词提取、简单的MapReduce等,通过这些小的应用,一方面可以让我们更加深入了解Serverless架构,另一方面也可以让我们对Serverless的实际应用和价值产生更大的信心。

传统情况下,我们如果要做这样的一个工具,可能需要一个服务器,哪怕没有人使用,也要有一台服务器苦苦支撑,那么仅仅就是一个Demo,也要无时无刻的支出成本,但是在Serverless架构下,通过Serverless弹性伸缩特点让我们不惧怕高并发,通过Serverless的按量付费模式,让我们不惧怕成本支出。

希望各位读者可以通过我的抛砖引玉,更加深入的了解Serverless架构。

相关实践学习
基于函数计算一键部署掌上游戏机
本场景介绍如何使用阿里云计算服务命令快速搭建一个掌上游戏机。
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
目录
相关文章
|
18天前
|
存储 运维 Serverless
Serverless架构在图像处理领域展现出了强大的优势
【4月更文挑战第22天】Serverless架构在图像处理中表现出显著优势:弹性伸缩自动适应负载变化,节省成本;按需付费减少费用,适合需求波动场景;简化运维让开发者专注应用创新;快速迭代部署提升市场响应速度;高可用性和容错性保证服务稳定性;跨平台支持增强兼容性;丰富生态加速开发进程。因此,Serverless是图像处理的理想选择。
25 1
|
18天前
|
存储 弹性计算 Serverless
Serverless架构在图像处理的优势
Serverless架构在图像处理的优势
27 2
|
18天前
|
运维 安全 Serverless
Serverless架构在图像处理中的优势探讨
Serverless架构在图像处理中的优势探讨
24 1
|
18天前
|
监控 算法 Serverless
Serverless架构在图像处理中的优势
随着信息时代的到来,图像处理在各个领域发挥着越来越重要的作用,无论是在数字媒体、医学影像、安防监控还是人工智能等领域,图像处理都扮演着关键的角色,尤其是在应对图像处理的复杂性和高并发需求时,Serverless架构作为一种新兴的解决方案,正在迅速崭露头角。Serverless架构的出现彻底改变了传统的软件架构模式,将开发者从繁琐的服务器管理中解放出来,使其能够更专注于业务逻辑和算法的优化。还有就是在图像处理领域,Serverless架构的优势体现得尤为明显,它能够根据实际需求动态分配计算资源,实现弹性扩展,满足高并发和波动性需求,以及Serverless架构的按需付费模式也能够帮助开发者降低成
48 2
Serverless架构在图像处理中的优势
|
18天前
|
弹性计算 安全 Serverless
图像处理场景下的Serverless架构
【4月更文挑战第15天】图像处理场景下的Serverless架构
|
7月前
|
人工智能 Serverless 异构计算
AI 作画火了,如何用 Serverless 函数计算部署 Stable Diffusion?
AI 作画火了,如何用 Serverless 函数计算部署 Stable Diffusion?
525 0
AI 作画火了,如何用 Serverless 函数计算部署 Stable Diffusion?
|
人工智能 Serverless 异构计算
玩转AIGC,5分钟 Serverless 部署 Stable Diffustion 服务
双重奖品设置,完成体验场景可得社区1000 积分兑换奖品,还可参加 AI 生成图像比赛赢取 Airpods、500 元猫超卡及社区定制抱枕!
32798 7
玩转AIGC,5分钟 Serverless 部署 Stable Diffustion 服务
|
机器学习/深度学习 Kubernetes Cloud Native
serverless论文总结
serverless论文总结
254 0
serverless论文总结
|
消息中间件 存储 弹性计算
实战 | 基于 Serverless 技术的视频截帧架构如何实现?
视频直播是一种创新的在线娱乐形式,具有多人实时交互特性,在电商、游戏、在线教育、娱乐等多个行业都有着非常广泛的应用
198 0
实战 | 基于 Serverless 技术的视频截帧架构如何实现?
|
18天前
|
机器学习/深度学习 数据采集 人工智能
基于Serverless架构的验证码识别功能
人工智能领域中的验证码识别与 Serverless 架构碰撞会有哪些火花呢?本文将会通过 Serverless 架构,通过卷积神经网络(CNN)算法,实现一个验证码识别功能。
548 1
基于Serverless架构的验证码识别功能

热门文章

最新文章