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架构。

相关实践学习
【AI破次元壁合照】少年白马醉春风,函数计算一键部署AI绘画平台
本次实验基于阿里云函数计算产品能力开发AI绘画平台,可让您实现“破次元壁”与角色合照,为角色换背景效果,用AI绘图技术绘出属于自己的少年江湖。
从 0 入门函数计算
在函数计算的架构中,开发者只需要编写业务代码,并监控业务运行情况就可以了。这将开发者从繁重的运维工作中解放出来,将精力投入到更有意义的开发任务上。
目录
相关文章
|
传感器 供应链 监控
数字化应用场景
数字化应用场景
808 0
|
虚拟化
vmware克隆虚拟机后没有ip地址的问题
解决vmware克隆虚拟机后没有内网ip的问题
|
3月前
|
应用服务中间件 Shell nginx
七、Docker核心技术:深入理解网络模式 (Bridge, Host, None, Container)
容器不仅仅是孤立的运行环境,它们需要相互通信,也需要与外部世界进行交互。理解 Docker 的不同网络模式,是构建和部署复杂多容器应用的关键。本节将深入探讨 Docker 原生提供的四种网络模式以及强烈推荐使用的自定义网络。要让它们通信,需要将其中一个容器也连接到另一个网络上。默认 bridge 网络不支持容器名DNS解析,只能通过IP地址通信。容器没有自己的独立IP地址,它共享宿主机的IP。网络模式启动一个容器后,如何查看该容器的IP地址?时,该容器默认会连接到哪个网络?模式运行,并且其内部的应用监听。
643 4
|
4月前
|
人工智能 自然语言处理 定位技术
从功能到场景:2025年数字人平台排名与实用推荐全攻略
面对繁多数字人平台,如何选型?本文从生成效率、质量、集成度等五大维度,深度评测2025年主流平台。必火AI凭借全链路智能创作、1分钟克隆+3分钟成片的高效流程,适配个人IP、企业营销、培训等场景,荣登榜首。附实用选型指南,助你精准决策,开启高效内容创作新时代。(238字)
636 41
|
7月前
|
存储 监控 算法
园区导航系统技术架构实现与原理解构
本文聚焦园区导航场景中室内外定位精度不足、车辆调度路径规划低效、数据孤岛难以支撑决策等技术痛点,从架构设计到技术原理,对该系统从定位到数据中台进行技术拆解。
351 0
园区导航系统技术架构实现与原理解构
|
11月前
|
安全 小程序 Java
weixin027校园二手平台的设计与实现+ssm(文档+源码)_kaic
本项目基于微信小程序开发校园二手交易平台,旨在解决大学生闲置物品交易问题。系统采用Java语言和MySQL数据库设计,支持用户浏览、收藏、评价商品及发布闲置物品。管理员可审核商品和用户信息,确保交易安全。系统具备在线搜索功能,方便用户查找商品,并提供实时沟通渠道,增强平台透明度和用户体验。该平台简化了二手交易流程,满足了大学生对便捷、高效交易的需求,具有重要的实际应用价值。
|
消息中间件 Java 大数据
Kafka ISR机制详解!
本文详细解析了Kafka的ISR(In-Sync Replicas)机制,阐述其工作原理及如何确保消息的高可靠性和高可用性。ISR动态维护与Leader同步的副本集,通过不同ACK确认机制(如acks=0、acks=1、acks=all),平衡可靠性和性能。此外,ISR机制支持故障转移,当Leader失效时,可从ISR中选取新的Leader。文章还包括实例分析,展示了ISR在不同场景下的变化,并讨论了其优缺点,帮助读者更好地理解和应用ISR机制。
1331 0
Kafka ISR机制详解!
|
存储 消息中间件 算法
深入解析OpenStack Cinder:块存储服务详解
本文介绍了OpenStack及其块存储服务Cinder。OpenStack是一个开源云计算管理平台,提供基础设施即服务(IaaS),核心服务包括计算、网络、存储等。Cinder主要用于为虚拟机提供持久性块存储,具备多种功能,如卷操作、备份、快照及与实例的交互等。此外,还详细介绍了Cinder的工作流程、命令行操作及不同存储插件的使用。
1963 8
|
JavaScript Java 测试技术
基于SpringBoot+Vue的大学生二手闲置物品置换交易管理系统的详细设计和实现(源码+lw+部署文档+讲解等)
基于SpringBoot+Vue的大学生二手闲置物品置换交易管理系统的详细设计和实现(源码+lw+部署文档+讲解等)
470 0

热门文章

最新文章