惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

jdhao's digital space

Manage uv.lock file with Renovate Set up Python Provider for Neovim Ripgrep Config to Search Hidden Files Pre-commit Setup for Your Project I read the nvim v0.12 release note so you don't have to Return Different Values for Each Call of A Mock Migrate Python Project from Pip to Uv 德语常用不规则动词 葱油鸡腿制作 Check Trailing White Spaces in Your Project 菜谱:茄子肉丁 object vs nested type in data mapping in Elasticsearch Node, Index, Shard in Elasticsearch Logging setup for Pytest Select fields in Elasticsearch: _source, fields and stored_fields 中式葱花饼制作 菜谱: 凉拌苤蓝(卜留克/kohlrabi) 我也有高考 PTSD Garmin Course Syncing Not Working? Prevent Accidental Index Delete in Elasticsearch How to Import GPX File into Garmin Watch Python system PATH issues When We Use Pytest 菜谱:泰式打抛牛肉 菜谱:烤箱羊肉串 How to Filter Warnings in Python/pytest 家常烤箱烤鸡腿 Comparison between Several Desktop Speakers How to Use LuaRocks Package in Neovim Macbook 外接显示器 家常萝卜炖羊排 Run the Job Immediately after Starting Scheduler in Python APScheduler Retry for Google Cloud Client 菜谱:土豆金枪鱼沙拉 菜谱:椰香咖喱鸡 凉拌绿豆宽粉制作 Make Python logging Work in GCP Liveness and Readiness Check in Kubernetes Notes on Using GCP Logging 西班牙土豆饼制作 Elasticsearch Version Conflict Error How to Use the Elasticsearch task API Speed up document indexing in Elasticsearch via bulk indexing Index refresh issue in Elasticsearch Google Cloud Storage Usage 家常煎羊排制作 凉拌茄子制作 Configure Python logging with dictConfig Debugging Wezterm Issues Black Formatter Setup for Python Project Git line ending config Garmin Forerunner 965 Essential Tips and Setups How to Download Files from Google Cloud Storage in the Databricks Workspace Notebook Databricks Cli Usage Working with Databricks Workspace Files 手抓羊肉饭制作 Databricks Init Scripts Using Virutal Environment in Python with venv File Systems in Databricks LATERAL VIEW EXPLODE in Spark 菜谱:麻婆豆腐 在德国做台湾卤肉饭 FastAPI testing and OpenAPI doc generation Change Timezone in Databricks Spark How to Profile Your Python Script/Module 菜谱:茄子肉沫 Migrating from Packer.nvim to Lazy.nvim How to Extract PDF file on macOS How to Deploy Fastapi Application with Docker Nerdfont Icon Missing after Wezterm Upgrade Pylsp setup for Neovim in 2023 How to Parse Query Param With Multiple Values in FastAPI 菜谱:土豆胡萝卜烧牛肉 Zsh Startup Files in macOS PATH Variable Changed inside Tmux on macOS? Work with JSON File in Neovim Running/importing Python code/module in Databricks Agile and Scrum 菜谱:凉拌牛肉 Awesome Command Line Tools Written in Rust How to get or set Databricks spark configuration Set Up German Version macOS Add A Custom Search Engine for Vimium 中国大陆小米手机如何使用 Google Pay 春节回乡记 滇西之行 2023 贵阳行 2023 程序员海外工作---语言篇 2023 长沙行 2023 西安行 德国工签申请指南 2022 年博客回顾 感染 omicron 记录 How to Override Default Options in Neovim Variadic Arguments in Lua How to Enable Method Autocompletion for OpenCV How to Read Local CSV File to Table in MySQL I read the nvim v0.8 release note so you do not have to Creating A Trigger in PostgreSQL Cost of Living in Shenzhen You Do Not Need a Plugin for This Feature
Pillow/PIL 缩放索引图像时的一个问题
2020-11-18 · via jdhao's digital space

今天遇到了一个有趣的问题,有人在 v2ex 上说自己的图片,经过 PIL 缩放到 800x600 以后,非常模糊,有什么办法可以解决。

现象与问题#

我想这不可能啊,我把他的原图下载到本地,然后使用 PIL 进行缩放保存,原始的代码如下:

from PIL import Image


def main():
    img = Image.open('big_image.png')
    im_resize = img.resize((800, 600), resample=Image.LANCZOS)
    im_resize.save('big-resized-pil.png', quality=90)


if __name__ == "__main__":
    main()

发现保存的图片中,文字部分的确很模糊,Image.LANCZOS 已经是最好的滤波器了,按理说不应该出现这种问题。我又把 PIL 提供的其他几种滤波器都试了一下,发现出来的结果没区别,都很差。

我又用 OpenCV 试了一下,发现使用 OpenCV 缩放得到的图片效果还可以,下面是我的代码:

 import cv2

 def main():
     im = cv2.imread('big_image.png')
     # https://docs.opencv.org/master/da/d54/group__imgproc__transform.html#ga47a974309e9102f5f08231edc7e7529d
     im_resize = cv2.resize(im, (800, 600), interpolation=cv2.INTER_LANCZOS4)
     write_param = [cv2.IMWRITE_PNG_COMPRESSION, 5]
     cv2.imwrite('big-resized.png', im_resize, write_param)


 if __name__ == "__main__":
     main()

缩放以后的图片明显比 PIL 缩放出来的效果好了很多,上面的 Interpolation 参数类似于 PIL 中的 resample 参数,用来控制图像缩放时候使用的滤波器,不光 cv2.INTER_LANCZOS4 出来的效果可以,甚至 cv2.INTER_AREA 出来的效果也不错。

即便 OpenCV 和 PIL 实现某个滤波器的细节有差异,缩放出来的结果差异也不应该如此明显,这样巨大的差异很难用实现细节不同来解释清楚。

原因#

后面那个问题又有人回复,说可能是因为提问者给的图是索引图 (index image,也叫 palette image) (索引图读取以后,使用 print(im.mode) 会输出 'P')。如果图片本身是索引图,PIL 在读取图片并不会自动转换为 RGB 图片,因此返回的 Image 对象仍然是索引图,这就带来了问题。

如果我们仔细阅读 Image.resize() 方法的文档,其中对 resample 参数的解释提到:

If the image has mode “1” or “P”, it is always set to PIL.Image.NEAREST

这样就解释了为什么我们最初使用 Image.LANCZOS 滤波器不管用的问题:因为这个图像是索引图像,PIL 只会使用 Image.NEAREST 滤波器,我们自己设定的滤波器压根没生效,所以才会出现糟糕的压缩效果。

知道了这个原因,解决方法也很简单,那就是读取图像的时候,使用 Image.convert() 把图像转为 RGB 模式:

    img = Image.open('big_image.png').convert('RGB')

然后对图像进行缩放,得到的结果就与 OpenCV 缩放以后的结果基本一致了。

什么是索引图#

我们常见的 RGB 图片,图片的每个像素分别用 R, G, B 三个颜色分量表示,一般取值是在 0-255 之间,每个颜色分量占 1 byte,所以一个像素占 3 byte 空间,如果图像大小为 MxN,那么图像占的内存空间大小约为 MxNx3 byte。

索引图是一种特殊的图 ,它的存在主要是为了节省空间,索引图附带了一个 color palette/table 或者叫 color map,对应了 256 种颜色(所以 color table 大小为 256x3)),然后图像像素每个位置值在 0-255 之间,数值代表该处像素在 color table 对应的颜色的索引值,实际展示该图片的时候,我们利用这个索引就能在 color table 中找到真正要展示的颜色。从这个描述可以看出,索引图一个像素只需要 1 比特,所以索引图占的空间大小约为 RGB 图的 1/3 ,大大减少了存储占用1

我们在 PIL 中可以轻松创建索引图,一个改编例子如下:

from PIL import Image
from PIL import ImageDraw


im = Image.new("P", (400, 400), 0)
palette_color = im.getpalette()
print(palette_color)

im.putpalette([
    0, 0, 0,  # black background
    255, 0, 0,  # index 1 is red
    255, 255, 0,  # index 2 is yellow
    255, 153, 0,  # index 3 is orange
])

# It seems that the image palette is automatically filled with numbers to make
# it have size 256*3 (768), if you do not provide that much number to
# putpaletter() method.
palette_color = im.getpalette()
print(palette_color)

d = ImageDraw.ImageDraw(im)
d.polygon((0, 0, 0, 400, 400, 400), fill=1)
d.rectangle((100, 100, 300, 300), outline=2)
d.ellipse((120, 120, 280, 280), outline=3)

im.save("out.gif")

在 PIL 中,如果图像是一个索引图,我们可以使用 Image.getpalette() 方法得到它的 color palette,该 palette 是一个 768 元素的 list,按照 [r, g, b, r, g, b, ...] 这种顺序排布。为了方便找到某个 index 对应的颜色,我们可以把它变形:

palette_color = np.asarray(palette).reshape(256, 3)

参考#


  1. 这只是一个粗糙计算,实际中还要考虑图像编码等其他因素。 ↩︎