NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

Serverless 实战:如何快速实现图片压缩与水印添加?

  • 2020-05-27
  • 本文字数:4200 字

    阅读完需:约 14 分钟

Serverless实战:如何快速实现图片压缩与水印添加?

实际生活中,我们常常会有上传图片的需求,例如在相册系统中添加相片、发布文章时添加配图等等。图片与 Web 服务是紧密关联在一起的,但每张图片的大小、占用的空间等都是参差不齐的,而且有些图片上传到网站之后,容易被其它平台或开发者采集盗用,所以很多人都习惯于在图片上传之后进行图片压缩、标准化以及添加水印。


当图片数量很多、尺寸很大的时候,压缩、标准化和水印添加就会占用很多的资源。那么,我们是否能够利用 Serverless 架构实现图片压缩与水印的一条龙服务,同时用户量的激增也不会影响整体体验?

Serverless 与图像处理

一般来说,传统的图像处理方法会比较占用资源,导致服务器压力较大,甚至影响用户体验:



那么我们是否可以通过 Serverless 架构实现一个异步处理流程?



什么是异步处理流程?简单来说,就是用户直接上传图片到对象存储,将图片等资源进行持久化,然后通过对象存储相关的触发器,触发指定函数,函数进行图像压缩以及图像水印等相关操作,再次进行持久化。


以相册系统为例,用户上传图片之后,系统进行压缩以及水印并生成缩略图,存储到对象存储中。当用户浏览图片列表时,就展示带有水印的缩略图,这样可以大大提升加载速度,而水印可以当作图像的一种版权保护,当用户点击图片查看原图时,可以为用户展示原始图片。

图像压缩

图像压缩,在这里我们只把图像大小作为压缩依据,除此之外,还可以对图像的质量进行处理。


如果单以尺寸进行压缩处理,可以看作是将一个image对象以宽度传入,通过resize方法进行大小的调整,实现压缩功能。


def compressImage(image, width):    height = image.size[1] / (image.size[0] / width)    return image.resize((int(width), int(height)))
复制代码

图像水印

图像水印大多采用的是文字水印,当然我们还可以使用图片水印等。


此处为了将水印放在图像的右下角,并且恰好不超出图像范围,我们对每个字符大小进行了获取:


height = []width = []for eveStr in watermarkStr:    thisWidth, thisHeight = drawImage.textsize(eveStr, font)    height.append(thisHeight)    width.append(thisWidth)
复制代码


这样处理之后,我们得到的height列表就是所有即将水印文字的高度,width列表是所有即将水印文字的宽度。如果要将水印放在右下角,我们只需要在图片整体高度上减去height列表最大值,在图片整体宽度基础上减去width列表的总和即可:


def watermarImage(image, watermarkStr):    txtImage = Image.new('RGBA', image.size, (0, 0, 0, 0))    font = ImageFont.truetype("Brimborion.TTF", 40)    drawImage = ImageDraw.Draw(txtImage)    height = []    width = []    for eveStr in watermarkStr:        thisWidth, thisHeight = drawImage.textsize(eveStr, font)        height.append(thisHeight)        width.append(thisWidth)    drawImage.text((txtImage.size[0] - sum(width) - 10, txtImage.size[1] - max(height) - 10),                   watermarkStr, font=font,                   fill=(255, 255, 255, 255))    return Image.alpha_composite(image, txtImage)
复制代码

部署到云函数

通过函数的事件描述,可以确定腾讯云函数的对象存储触发器事件结果为:


{  "Records": [  {      "cos": {          "cosSchemaVersion": "1.0",          "cosObject": {              "url": "http://testpic-1253970026.cos.ap-chengdu.myqcloud.com/testfile",              "meta": {                  "x-cos-request-id": "NWMxOWY4MGFfMjViMjU4NjRfMTUyMV8yNzhhZjM=",                  "Content-Type": ""              },              "vid": "",              "key": "/1253970026/testpic/testfile",              "size": 1029          },          "cosBucket": {              "region": "cd",              "name": "testpic",              "appid": "1253970026"          },          "cosNotificationId": "unkown"      },      "event": {          "eventName": "cos: ObjectCreated:Post",          "eventVersion": "1.0",          "eventTime": 1545205770,          "eventSource": "qcs::cos",          "requestParameters": {              "requestSourceIP": "192.168.15.101",              "requestHeaders": {                  "Authorization": "q-sign-algorithm=sha1&q-ak=AKIDQm6iUh2NJ6jL41tVUis9KpY5Rgv49zyC&q-sign-time=1545205709;1545215769&q-key-time=1545205709;1545215769&q-header-list=host;x-cos-storage-class&q-url-param-list=&q-signature=098ac7dfe9cf21116f946c4b4c29001c2b449b14"              }          },          "eventQueue": "qcs:0:lambda:cd:appid/1253970026:default.printevent.$LATEST",          "reservedInfo": "",          "reqid": 179398952      }  }]}
复制代码


根据这个结构,我们可以确定相关详细信息,例如存储桶 /APPID 以及图片的 Key 等。然后我们将上面的代码按照函数计算的格式进行改写:


# -*- coding: utf8 -*-import osfrom PIL import Image, ImageFont, ImageDrawfrom qcloud_cos_v5 import CosConfigfrom qcloud_cos_v5 import CosS3Client
secret_id = os.environ.get('secret_id')secret_key = os.environ.get('secret_key')region = os.environ.get('region')cosClient = CosS3Client(CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key))

def compressImage(image, width): height = image.size[1] / (image.size[0] / width) return image.resize((int(width), int(height)))

def watermarImage(image, watermarkStr): txtImage = Image.new('RGBA', image.size, (0, 0, 0, 0)) font = ImageFont.truetype("Brimborion.TTF", 40) drawImage = ImageDraw.Draw(txtImage) height = [] width = [] for eveStr in watermarkStr: thisWidth, thisHeight = drawImage.textsize(eveStr, font) height.append(thisHeight) width.append(thisWidth) drawImage.text((txtImage.size[0] - sum(width) - 10, txtImage.size[1] - max(height) - 10), watermarkStr, font=font, fill=(255, 255, 255, 255)) return Image.alpha_composite(image, txtImage)

def main_handler(event, context): for record in event['Records']: bucket = record['cos']['cosBucket']['name'] + '-' + record['cos']['cosBucket']['appid'] key = record['cos']['cosObject']['key'] download_path = '/tmp/{}'.format(key) upload_path = '/tmp/new_pic-{}'.format(key)
# 下载图片 response = cosClient.get_object(Bucket=bucket, Key=key) response['Body'].get_stream_to_file(download_path)
# 图片处理 image = Image.open(download_path) image = compressImage(image, width=500) image = watermarImage(image, "Hello Serverless") image.save(upload_path)
# 上传图片 cosClient.put_object_from_local_file( Bucket=bucket, LocalFilePath=upload_path, Key="/compress-watermark/" + key )
复制代码


此时,新建serverless.yaml文件:


MyPicture:  component: "@serverless/tencent-scf"  inputs:    name: MyPicture    codeUri: ./    handler: index.main_handler    runtime: Python3.6    region: ap-guangzhou    description: My Picture Compress And Watermark    memorySize: 128    timeout: 20    environment:      variables:        secret_id: 用户密钥 id        secret_key: 用户密钥 key        region: ap-guangzhou    events:      - cos:          name: picture-1256773370.cos.ap-guangzhou.myqcloud.com          parameters:            bucket: picture-1256773370.cos.ap-guangzhou.myqcloud.com            filter:              prefix: source/            events: cos:ObjectCreated:*            enable: true
复制代码


从中我们可以看到,函数有一个cos触发器,这个触发器是针对存储桶picture-1256773370下面source/目录下的资源创建进行触发。

简单测试

现在,我们通过serverless部署项目:



部署完成之后,我们在存储桶picture-1256773370中,新建source/目录与compress-watermark/目录。


其中,前者用来上传文件,后者用来生成新的文件。随机搜索一张图片:



可以看到这张图片 4.5M,还是蛮大的,将这个图片上传到source/目录下:



稍等片刻,我们就可以在compress-watermark/目录下发现一个新的文件生成:



将文件下载下来,查看详情:



可以看到,图片尺寸明显变小,从 4.5M 压缩到了 340K。与此同时,图像右下角出现了预设的水印标志。


至此,我们完成了通过 COS 触发器实现图片压缩与水印的功能。

总结

通过本文展示的操作,我们成功实现了用户上传图像,并通过 Serverless 架构对其进行压缩与增加水印的功能。事实上, Serverless 架构可以解决很多传统生产中遇到的问题,并且可以更节约资源和成本,以本文为例,当我们的服务面临高并发的时候,传统情况下,很可能会由于图像压缩,水印的操作导致服务挂掉,但是通过这样一个策略,就算是出现了高并发,也仅仅是将图片传入对象存储,至于转换的逻辑、压缩的逻辑以及水印的逻辑等都由 Serverless 架构帮我们实现,既安全稳定,又节约成本和资源。


当然,除了压缩和水印,我们还可以利用 Serverless 架构来实现图像标准化、不同尺寸图像制作、视频压缩、不同分辨率的视频制作,甚至可以通过深度学习对图像进行打标签等。


作者介绍:


刘宇,腾讯 Serverless 团队后台研发工程师。毕业于浙江大学,硕士研究生学历,曾在滴滴出行、腾讯科技做产品经理,本科开始有自主创业经历,是 Anycodes 在线编程的负责人(该软件累计下载量超 100 万次)。目前投身于 Serverless 架构研发,著书《Serverless 架构:从原理、设计到项目实战》,参与开发和维护多个 Serverless 组件,是活跃的 Serverless Framework 的贡献者,也曾多次公开演讲和分享 Serverless 相关技术与经验,致力于 Serverless 的落地与项目上云。


2020-05-27 15:585881

评论 4 条评论

发布
用户头像
serverlesss是否可以支持并发处理?高并发的情况下,如果要达到近实时处理,对硬件配置和分布式的支持?
2020-06-08 16:05
回复
用户头像
想要上传成功后就能在前台展示压缩过的图怎么办?等异步压缩完要很久
2020-05-30 20:42
回复
异步压缩确实可能会有一定的延时,但是这个延时理论不会太大,虽然他是一个队列处理,但是实际上确是来了一个请求,就会启动一个实例,也就是说你可以认为“异步压缩过程”可以同时处理几百张,几千张图片,所以这个速度理论不会慢太多的。如果单纯的就是想要压缩,可以直接前台压缩。
2020-06-01 14:39
回复
用户头像
Serverless,未来可期!
2020-05-29 18:39
回复
没有更多了
发现更多内容

蚂蚁大规模 Sigma 集群 Etcd 拆分实践

SOFAStack

etcd #k8s SIGMA

架构实战营 4 期第五模块作业

jialuooooo

架构实战营

网关流控利器:结合 AHAS 实现 Ingress/Nginx 流量控制

阿里巴巴云原生

nginx 阿里云 高可用 云原生 ingress

基于 Prometheus 的边缘计算监控实践

火山引擎边缘云

云原生 监控 边缘计算

逐鹿万亿赛道:智能重卡规模量产的困境与进化

脑极体

在Rainbond上使用Locust进行压力测试

北京好雨科技有限公司

阿里云刘伟光:3.5万字拆解核心系统转型,核心从业者如何寻得“出路”

OceanBase 数据库

阿里 数字化转型 OceanBase 社区版 核心系统

瀑布式开发与敏捷开发的区别是什么

PingCode

混合云应用双活容灾最佳实践

阿里巴巴云原生

阿里云 运维 云原生 混合云 多活容灾

淘宝小部件在 2021 双十一中的规模化应用

阿里巴巴终端技术

小程序 淘宝 双十一

架构实战营:模块五作业

Geek_93ffb0

「架构实战营」

【组件攻击链】一文看懂Spring全家桶各类RCE漏洞

H

网络安全 漏洞

☕【Java深层系列】「技术盲区」让我们一起探索一下Netty(Java)底层的“零拷贝Zero-Copy”技术(上)

洛神灬殇

Netty 零拷贝 zero copy 1月日更

2021年小总结暨2022年打脸计划

秦怀杂货店

总结 程序人生、

创新推出 | Serverless 场景排查问题利器:函数实例命令行操作

阿里巴巴云原生

阿里云 Serverless 云原生 函数计算

一个cpp协程库的前世今生(二十)外部调度

SkyFire

c++ cocpp

实时云渲染,汽车产业数字化转型新动能

3DCAT实时渲染

云计算 数字化 汽车 云渲染

Ubuntu16.04/Scala2.11.8安装教程

CRMEB

VuePress 博客优化之拓展 Markdown 语法

冴羽

JavaScript Vue markdown vuepress 博客搭建

金融云原生漫谈(六)|安全平稳高于一切的金融行业,如何构建云原生安全防线

York

容器 云原生 安全 金融科技

当基础设施故障后,声网 SD-RTN 如何保障 RTE 服务的高可用性

声网

人工智能 云计算

开启报名!2021年度龙蜥社区优秀开发者评选,非你莫属!

OpenAnolis小助手

Linux 开源 社群 比赛

最佳实践 | 如何避免一行错误代码造成的血案?

Atlassian

Atlassian Jira 代码评审

软件架构治理 之 架构优化方向

码猿外

架构设计 技术债 软件架构治理

项目管理如何有效进行

PingCode

如何快速调度 PTS 的百万并发能力

阿里巴巴云原生

阿里云 云原生 Jmeter 压测 PTS

旺链科技加入工信部区块链重点实验室,深化数据安全产业融合

旺链科技

区块链 工信部 数据安全 Vone新闻

使用MSF生成shellcode

喀拉峻

黑客 网络安全 安全 WEB安全

Discord模式等十大场景,环信带你玩转泛娱乐行业

环信

即时通讯 IM 泛娱乐 Discord

使用 google_breakpad 分析 Electron 崩溃日志文件

编程三昧

Electron 1月月更 google_breakpad

Arm vs x86: 解释指令集、架构和所有关键差异

DisonTangor

android X86 arm

Serverless实战:如何快速实现图片压缩与水印添加?_云原生_刘宇_InfoQ精选文章