avatarShuyi Wang

总结

本文介绍了如何使用Python自动判断和压缩大于2M的图片,以适应微信公众平台的图片大小限制,同时保持图片的宽高比。

摘要

文章首先介绍了使用Markdown编写文稿并在不同平台发布时,图片大小可能会成为一个问题,尤其是在微信公众平台上,图片大小超过2M时无法正常上传。作者提到了自己使用的图床和渲染工具,并指出了在使用这些工具时可能遇到的图片上传失败问题。为了解决这个问题,作者决定编写一个Python程序来自动化地处理这个过程。

接着,作者提供了一个GitHub项目链接,其中包含了样例图片和执行代码,并详细说明了如何使用Anaconda环境和相关Python库来设置和运行这个程序。文章详细介绍了如何遍历图片目录,检测图片大小,并对超过2M的图片进行压缩,同时保持宽高比。作者还展示了如何创建输出目录,并将压缩后的图片保存到该目录。

最后,作者强调了代码整合的重要性,将之前的代码片段整合成一个函数,使其更加模块化和可重用。这个函数可以根据不同的阈值和宽高比要求来调整图片大小。文章还讨论了如何根据阈值动态计算新的图片尺寸,以适应不同的需求。

观点

  1. 图片大小限制是发布Markdown文稿到特定平台时的一个痛点,尤其是在微信公众平台上。
  2. 自动化工具能够大大提高图片处理的效率,减少手动操作带来的烦恼和错误。
  3. Python作为一种强大的脚本语言,配合PIL库和glob库,能够轻松实现图片的批量处理。
  4. 代码的模块化和整合对于提高代码的可重用性和可维护性至关重要。
  5. 通过编写可配置的函数,可以根据不同的阈值和宽高比要求灵活调整图片大小,从而适应各种写作平台的要求。
  6. 分享和讨论个人解决方案对于社区成员的学习和成长非常有益。

如何用 Python 智能批量压缩图片?

本文一步步为你介绍,如何用 Python 自动判断多张图片中哪些超出阈值需要压缩,且保持宽高比。如果你想了解 Python 图像处理的基础知识,欢迎动手来尝试。

Photo by JJ Ying on Unsplash

痛点

我喜欢用 Markdown 写文稿,然后发布到不同写作平台。我的好友数字游民 Jarod 称其为 “矩阵式发布”。能这样做的前提,是 Markdown 为我们带来了极低的边际发布成本。试想如果每个写作平台,都需要我手动插入 20–30 张图片,想想都眼晕,我估计立刻会打消发布念头。

我使用七牛作为图床。图片链接成功转换后,选择一款渲染工具,预览文稿格式,看图片、表格、标题等特殊样式是否显示正确。

我曾经用过多种渲染工具。最近我一直在用 Md2All

这款工具最大的特点,是能保证粘贴到各个写作平台时,代码不会乱掉。

点击右上方的 “复制” 按钮,你就可以在任何一个写作平台上,开启富文本编辑器,然后粘贴进去。

工作进行到这一步,已近大功告成。这时,如果你遇到 “图片上传失败” 的报错,想必会很影响心情。

图片上传失败,原因可能有很多。

许多情况下,只是单纯因为网络拥塞。只要你本着愚公移山的精神,往复重新粘贴,总会好的。

但是微信公众平台是个例外。

你时常会遇到这种情况 — — 就是那两张图片,死活也无法正常传上去。

踩坑多次,不得不手动上传图片后。我终于发现了问题所在 — — 微信公众平台对图片大小有限制。

一旦你要上传的图片超过 2M,就无法正常粘贴上传了。

莫非我写作文章时,还要一一检验每张插图的大小?超过阈值的图片压缩,然后再上传?

对我这种插图爱好者来说,这个工作太过琐碎和枯燥了。

你可能会问,不是有许多工具可以批量修改图片大小吗?例如JPEGmini和TinyPNG之类的?

确实有,但是它们不完全符合我的需求。

首先,我并不需要压缩全部图像。压缩后的图片,确实在手机上看起来跟原图毫无区别。但我用的图片,很多是教程里的示例。学生可能需要放大到一定程度,甚至要在大屏幕上打开,来查看代码或者运行结果的细节。只要原图没超过 2M,还是保持原貌比较稳妥。

其次,我懒。每次写完文章,还得手动运行一个应用,找出这篇文章对应的图片,拖动进去…… 不好意思,这活儿我懒得干。

幸好,凡是简单重复的枯燥活儿,都是电脑的拿手好戏。否则我们学编程干什么?

我用 Python 做个程序,替我找出全部大于 2M 的图片,进行压缩。压缩的时候,须要保持图片的宽高比例。

如果你对 Python 图像预处理功能比较感兴趣,不妨跟着我的介绍,一起试试看。

数据

我已经为你准备好了样例图片和执行代码,并且存储在了一个 Github 项目中。请访问 这个链接,下载压缩包后,解压查看。

可以看到,在 image 目录下,有 2 个 png 格式的图像文件。

我们打开来看看,一张 cat.png 是可爱的猫咪。

另一张,是小松鼠。

猜猜哪张图片更大?

小松鼠这张图片,尺寸低于 2M。猫咪那张,却有 2.9M,不符合微信公众平台的要求。

我们下面要用 Python 自行判断这些图片中,哪些超过了 2M,需要进行压缩。

然后,对超过 2M 的图片,按照原先的宽高比压缩后,存储到一个指定的文件夹里面去。

环境

我们使用 Python 集成运行环境 Anaconda。

请到 这个网址 下载最新版的 Anaconda。下拉页面,找到下载位置。根据你目前使用的系统,网站会自动推荐给你适合的版本下载。我使用的是 macOS,下载文件格式为 pkg。

下载页面区左侧是 Python 3.6 版,右侧是 2.7 版。请选择 2.7 版本。

双击下载后的 pkg 文件,根据中文提示一步步安装即可。

安装好 Anaconda 后,我们还需要确保安装几个必要的软件包。

请到你的 “终端”(Linux, macOS)或者 “命令提示符”(Windows)下面,进入咱们刚刚下载解压后的样例目录。

执行以下命令:

pip install -U PIL
pip install -U glob

安装完毕后,执行:

jupyter notebook

这样就进入到了 Jupyter 笔记本环境。我们新建一个 Python 2 笔记本。

这样就出现了一个空白笔记本。

点击左上角笔记本名称,修改为有意义的笔记本名 “demo-python-resize-image”。

准备工作完毕,下面我们就可以用 Python 读入并处理图像文件了。

代码

我们首先读入几个后面将用到的软件包。

from glob import glob
from PIL import Image
import os

然后,我们指定图片来源目录。因为图片存储在了样例目录的子目录 image 下面,所以只需要指定为 “image” 就好了。

source_dir = 'image'

下面我们设置压缩后图片的输出目录。这里为了对比清晰,我们将其设定为 output,也是样例目录的子目录。注意此时这个目录还不存在。我们后面会做处理。

target_dir = 'output'

下面,是关键环节之一。我们须要遍历 image 目录,找出全部的图片名称。

这里我们用到的,是 glob 软件包。其中的 glob 函数可以在我们指定的目录里,寻找所有符合要求的文件。

filenames = glob('{}/*'.format(source_dir))

我们使用了星号(*)作为通配符,意味着我们要查找image目录下所有文件的名称。

输出 filenames 试试看。

print(filenames)
['image/squirrel.png', 'image/cat.png']

可见 filenames 是个列表,里面包含了咱们需要处理的全部图片文件。

下面,我们就来尝试检测每张图片的大小。

for filename in filenames:
    with Image.open(filename) as im:
        width, height = im.size
        print(filename, width, height, os.path.getsize(filename))

我们遍历 filenames 中的所有图片路径,用 PIL 对象的 size 属性获得图片的宽度(width) 和高度 (height) 数值。用 os.path.getsize () 函数来获取文件大小。

然后,我们把这些内容按文件分别打印出来。

('image/squirrel.png', 1024, 768, 1466487)
('image/cat.png', 2067, 1163, 2851538)

因为我们需要判断某张图片的大小是否超出微信公众平台设置的 2M 阈值,因此我们需要计算一下,2M 阈值换算成比特,到底是个多大的的数字,以便后面的比对。

2*1024*1024

计算结果如下:

2097152

显然,刚才的打印结果里面,cat.png 图像超出了这个阈值。

我们心里有数了。

下面就把阈值 (threshold) 设置为这个数值。

threshold = 2*1024*1024

我们来看看自己的直觉和程序判断的实际情况是否一致:

for filename in filenames:
    filesize = os.path.getsize(filename)
    if filesize >= threshold:
        print(filename)

此处我们要求 Python 打印全部超出阈值的文件路径。结果如下:

image/cat.png

测试结果正确。程序只需要调整猫咪照片的尺寸。

正式进行压缩和输出之前,我们需要建立输出目录。虽然前面我们设定了,这个子目录叫做 output,但是实际的演示目录里,它还尚未创建。

我们先用 os.path.exists () 函数判定这个目录是否存在。当判定为不存在时,我们采用 os.makedirs () 函数来创建它。

if not os.path.exists(target_dir):
    os.makedirs(target_dir)

下面我们计算一下,对需要压缩的图片,新的宽度和高度应该是多少。

for filename in filenames:
    filesize = os.path.getsize(filename)
    if filesize >= threshold:
        print(filename)
        with Image.open(filename) as im:
            width, height = im.size
            new_width = 1024
            new_height = int(new_width * height * 1.0 / width)
            print('adjusted size:', new_width, new_height)

我们把新的宽度设置为了 1024,然后按照同等宽高比例算出新的高度取值。

注意这里宽度和高度必须设置为整数类型,否则会报错。

输出结果如下:

image/cat.png
('adjusted size:', 1024, 576)

为了把猫咪照片压缩为宽度 1024 的图片,我们需要设定高度为 576,以保证压缩后的图片与原始图片的宽高比一致。

下面我们续写函数,正式调用 PIL 的 resize 函数将新的图片设定为新的宽度和高度数值。然后,我们使用 PIL 的 save 函数,把生成的图片存储到指定的路径。

for filename in filenames:
    filesize = os.path.getsize(filename)
    if filesize >= threshold:
        print(filename)
        with Image.open(filename) as im:
            width, height = im.size
            new_width = 1024
            new_height = int(new_width * height * 1.0 / width)
            resized_im = im.resize((new_width, new_height))
            output_filename = filename.replace(source_dir, target_dir)
            resized_im.save(output_filename)

输出结果还是需要压缩的图片路径。

image/cat.png

压缩成功了吗?

我们打开样例目录看看。

可以看到,output 子目录已经自动生成。里面有一张图片。名称依然是 cat.png。它的大小已经变成了 836KB。我们打开它,看看显示是否正确。

依然是这张可爱的猫咪。看不出与原图有什么显著的区别,而且宽高比也正常。测试成功。

整合

但是这里,我们还需要完成一个重要步骤 — — 把之前的代码进行整合。

许多初学者写代码,总会忽略这一步。

虽然你的代码已经成功完成了预期的任务,但如不及时进行整理,过一段时间再来看,你会抓不住头绪。

想想看,等你回来的时候,你的 Jupyter Notebook 是这个样子的:

你不仅会忘了不同函数之间的调用关系,而且对于哪些参数需要设定,都一头雾水。

没错,这就是人脑的工作特点 — — 我们会遗忘。

所以,趁热打铁,把你做过的功能进行模块化整合很有必要。

整合后,你实现的功能就成了一个有机的整体,只通过参数和外部交互。你只需要用注释告诉自己参数设置的含义。后面再需要调用相关功能的时候,就可以直接通过参数变化,拿来就用了。

趁着记忆犹新,咱们把刚刚全部的功能整合到一个函数里面。

def resize_images(source_dir, target_dir, threshold):
    filenames = glob('{}/*'.format(source_dir))
    if not os.path.exists(target_dir):
        os.makedirs(target_dir)
    for filename in filenames:
        filesize = os.path.getsize(filename)
        if filesize >= threshold:
            print(filename)
            with Image.open(filename) as im:
                width, height = im.size
                new_width = 1024
                new_height = int(new_width * height * 1.0 / width)
                resized_im = im.resize((new_width, new_height))
                output_filename = filename.replace(source_dir, target_dir)
                resized_im.save(output_filename)

这个函数暴露给外部的接口,是 3 个参数:

  • source_dir:图片源目录
  • target_dir:压缩图片输出目录
  • threshold:阈值

检查一下,我们会发现不对劲的地方 — — 虽然阈值是我们将来可以调整的选项,但是压缩的时候,图片的宽度却是手动设定的数值(1024)。这样将来面对一个阈值高出 3 倍的写作平台,我们依然把图片压缩到这么小,似乎有些矫枉过正。

另外,如果这张图片是那种极为长的图,那即便宽度不是很长,也可能会因为高度超出阈值。单纯调整宽度到 1024,也许会失效。

解决办法也很简单,我们设置高度,然后对应调整宽度。

你可以看到,因为我们把代码集成整理在一处,许多原先我们可能考虑不周的问题,此时就纷纷显现了出来。

了解了问题所在,我们来调整一下代码。

我们因为要通过阈值计算宽度或者高度,所以需要引入数学计算模块。

import math

调整后的函数如下:

def resize_images(source_dir, target_dir, threshold):
    filenames = glob('{}/*'.format(source_dir))
    if not os.path.exists(target_dir):
        os.makedirs(target_dir)
    for filename in filenames:
        filesize = os.path.getsize(filename)
        if filesize >= threshold:
            print(filename)
            with Image.open(filename) as im:
                width, height = im.size
                if width >= height:
                    new_width = int(math.sqrt(threshold/2))
                    new_height = int(new_width * height * 1.0 / width)
                else:
                    new_height = int(math.sqrt(threshold/2))
                    new_width = int(new_height * width * 1.0 / height)
                resized_im = im.resize((new_width, new_height))
                output_filename = filename.replace(source_dir, target_dir)
                resized_im.save(output_filename)

这样,将来无论你的图片目录在哪里,你要满足哪个写作平台的图片大小要求,都可以通过简单设置这样几个数值,调用函数来完成新需求。

我们尝试用原先的参数取值,执行一次。

执行之前,我们删除掉 output 目录,以测试功能。

然后执行模块化之后的函数。

resize_images(source_dir, target_dir, threshold)

执行时,依然只是输出需要压缩的文件路径。

image/cat.png

检查刚刚又重新生成的 output 目录,猫咪照片呢?

没问题。不仅显示正常,而且大小也已经正常压缩。

小结

总结一下,通过本文我们接触到了以下知识点:

  • 如何利用glob软件包遍历指定目录,获得符合条件的全部文件路径列表;
  • 如何用PIL图像处理工具读取图像文件,检查宽度、高度,重新设定图像大小,并且存储新生成的图像;
  • 如何用os函数库检查文件或目录是否存在,创建目录,以及获取文件尺寸。

更重要的,是我们尝试了如何用 Python 这一脚本语言,帮我们智能化做出判断,并且在后台完成琐碎的重复操作。

另外,你应该已经了解了,完成功能并不意味着完事大吉。为了让自己的代码可以充分重用、易于共享并提高效能,你需要梳理与整合代码,将其充分模块化,只曝露输入输出接口给用户(包括将来的自己),避免固定取值设置。

讨论

你之前遇到过需要智能批量调整图片大小的问题吗?你是如何解决的?用过哪些工具?它们能自动帮你判断图片是否需要压缩吗?欢迎留言,把你的经验和思考分享给大家,我们一起交流讨论。

如果你觉得本文有用,请点击 clap

如果本文可能对你的朋友有帮助,请转发给他们。

欢迎关注我的专栏,以便及时收到后续的更新内容。

如果需要阅读我的文章全集,请关注我的微信公众号「玉树芝兰」,注意如果不加星标,会错过新推送提示

知识星球目前已发布了数十篇精华文章。我把标题和链接做了个表格,放在了飞书文档。欢迎你 点击这个链接查看

欢迎关注我的视频号,时常更新。

Python
Recommended from ReadMedium
avatarTechnocrat
Using Streamlit for UI

32 min read