限时领|《AI 百问百答》专栏课+实体书(包邮)! 了解详情
写点什么

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:586215

评论 4 条评论

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

主流国产数据库的HTAP实现,TiDB实现的最早并应用的最深

TiDB 社区干货传送门

数据库架构设计

一键自动化博客发布工具,用过的人都说好(公众号篇)

程序那些事

工具 程序那些事 自动发布

牛马真的沉默了,入职第一天就干活

秃头小帅oi

抖音商品API接口:开启电商自动化和数据洞察之门

Noah

云+AI,火山引擎助力泛互联网行业创新和增长

新消费日报

面向AI的开发:从大模型(LLM)、检索增强生成(RAG)到智能体(Agent)的应用

京东科技开发者

深入解析:Netty 与 Dubbo 的关系与应用

Liam

程序员 dubbo 后端 Netty 分布式系统

大模型在蓝鲸运维体系应用——蓝鲸运维开发智能助手

嘉为蓝鲸

运维 大模型 运维管理

TiDB 的平面文件与数据体积

TiDB 社区干货传送门

管理与运维

TiDB Server 的优雅下线

TiDB 社区干货传送门

数据库连接

数字孪生智慧工厂解决方案——打造绿色、透明重卡超级工厂

图扑物联

工业物联网 组态软件 数字孪生 web组态 智慧工厂

AI功能探测,TiDB Vector对比PG Vector

TiDB 社区干货传送门

版本测评 性能测评 7.x 实践

2024年安全生产月资料合集,抓紧保存!

草料二维码

资料分享 安全生产月

TiDB 黑科技工具集之 dbgen(造数工具)

TiDB 社区干货传送门

实践案例

看不懂正则表达式?试试可视化工具吧!

京东科技开发者

TiDB 如何利用 Copilot 优化数据库操作,提升用户体验与内部效率?

TiDB 社区干货传送门

大厂B端/G端数据可视化项目如何做设计评审

京东科技开发者

9 个适用于小型企业的顶级API管理解决方案

幂简集成

API API接口管理 API接口工具 API管理

25更敢为 | TATA木门25周年庆典盛大启幕 探索家居品质生活新静界

极客天地

TiKV的Raft逻辑梳理总结

TiDB 社区干货传送门

TiKV 底层架构

听说京东618裁员没?上午还在赶需求,下午就开会通知被裁了~

王中阳Go

Go 面试 微服务 后端 Go进阶

长江云 IPTV 融合业务 0 改造平滑迁移上云,《面向 AIGC 的数智广电新质生产力构建白皮书》开放下载

Baidu AICLOUD

vmware 专有云 IPTV

小冰携手火山引擎,让 AI 从崭露头角到落地生根

新消费日报

京东面试:SpringBoot同时可以处理多少请求?

王磊

rbenv:Ruby 多版本管理利器

不在线第一只蜗牛

ruby 后端 项目开发

2024深圳国际数字能源展览会

AIOTE智博会

能源展 数字能源展 深圳能源展

你还在用工单系统的思维建设ITSM吗?

嘉为蓝鲸

ITSM 运维管理 平台化

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