如何使用python对图片进行批量压缩详解
作者:会编程的猫 发布时间:2022-12-05 04:22:25
前言
最近在研究怎么对图片资源进行无损压缩,网上也找了一些资料。总而言之,收获不少,所以想对最近的学习做个总结。
无损压缩其实是相对而言的,目的是为了减小图片资源的内存大小但又不影响图片的显示质量。下面我将介绍两种批量压缩图片的方法,方法一是使用python和Pillow模块对图片进行压缩,这个方法对jpeg格式的图片有非常高的压缩效率,但该方法不太适合对png图片进行压缩。另一个方式是使用Python和Selenium模块操纵Squoosh批量压缩图片。
使用Python和Pillow模块压缩图片
Pillow是Python上一个功能非常强大的图形处理库,若本地还没安装,可以通过指令:pip install Pillow安装。使用Pillow进行压缩的策略大致总结为三个:1、优化flag,2、渐进式JPEG,3、JPEG动态质量。
我们先用Python写一个简单的保存图片的例子:
from PIL import Image
from io import StringIO
import dynamic_quality
im = Image.open("photo.jpg")
print(im.format,im.size,im.mode)
new_photo = im.copy()
new_photo.thumbnail(im.size,resample=Image.ANTIALIAS)
save_args = {'format':im.format}
if im.format=='JPEG':
save_args['quality'].value=85
new_photo.save("copy_photo.jpg",**save_args)
1、优化flag
开启optimize设置,这是以CPU耗时为代价节省额外的文件大小,由于本质没变,对图片质量没有丝毫影响。
...
if im.format=='JPEG':
save_args['quality'].value=85
save_args['optimize']=True
...
2、渐进式JPEG
当我们将一张图片保存为 JPEG 时,你可以从下面的选项中选择不同的类型:
标准型: JPEG 图片自上而下载入。
渐进式: JPEG 图片从模糊到清晰载入。
渐进式的选项可以在 Pillow 中轻松的启用 (progressive=True
)。渐进式文件的被打包时会有一个小幅的压缩。
...
if im.format=='JPEG':
save_args['quality'].value=85
save_args['optimize']=True
save_args['progressive=True']=True
...
3、JPEG动态质量
最广为人知的减小 JPEG 文件大小的方法就是设置 quality
。很多应用保存 JPEG 时都会设置一个特定的质量数值。
质量其实是个很抽象的概念。实际上,一张 JPEG 图片的每个颜色通道都有不同的质量。质量等级从 0 到 100 在不同的颜色通道上都对应不同的量化表,同时也决定了有多少信息会丢失。
在信号域量化是 JPEG 编码中失去信息的第一个步骤。
我们可以动态地为每一张图片设置最优的质量等级,在质量和文件大小之间找到一个平衡点。我们有以下两种方法可以做到这点:
Bottom-up: 这些算法是在 8x8 像素块级别上处理图片来生成调优量化表的。它们会同时计算理论质量丢失量和和人眼视觉信息丢失量。
Top-down: 这些算法是将一整张图片和它原版进行对比,然后检测出丢失了多少信息。通过不断地用不同的质量参数生成候选图片,然后选择丢失量最小的那一张。
我们选择第二种方法:使用二分法在不同的质量等级下生成候选图片,然后使用 pyssim 计算它的结构相似矩阵 (SSIM) 来评估每张候选图片损失的质量,直到这个值达到非静态可配置的阈值为止。这个方法让我们可以有选择地降低文件大小(和文件质量),但是只适用于那些即使降低质量用户也察觉不到的图片。
下面是计算动态质量的代码dynamic_quality.py:
import PIL.Image
from math import log
from SSIM_PIL import compare_ssim
def get_ssim_at_quality(photo, quality):
"""Return the ssim for this JPEG image saved at the specified quality"""
ssim_photo = "tmp.jpg"
# optimize is omitted here as it doesn't affect
# quality but requires additional memory and cpu
photo.save(ssim_photo, format="JPEG", quality=quality, progressive=True)
ssim_score = compare_ssim(photo, PIL.Image.open(ssim_photo))
return ssim_score
def _ssim_iteration_count(lo, hi):
"""Return the depth of the binary search tree for this range"""
if lo >= hi:
return 0
else:
return int(log(hi - lo, 2)) + 1
def jpeg_dynamic_quality(original_photo):
"""Return an integer representing the quality that this JPEG image should be
saved at to attain the quality threshold specified for this photo class.
Args:
original_photo - a prepared PIL JPEG image (only JPEG is supported)
"""
ssim_goal = 0.95
hi = 85
lo = 80
# working on a smaller size image doesn't give worse results but is faster
# changing this value requires updating the calculated thresholds
photo = original_photo.resize((400, 400))
# if not _should_use_dynamic_quality():
# default_ssim = get_ssim_at_quality(photo, hi)
# return hi, default_ssim
# 95 is the highest useful value for JPEG. Higher values cause different behavior
# Used to establish the image's intrinsic ssim without encoder artifacts
normalized_ssim = get_ssim_at_quality(photo, 95)
selected_quality = selected_ssim = None
# loop bisection. ssim function increases monotonically so this will converge
for i in range(_ssim_iteration_count(lo, hi)):
curr_quality = (lo + hi) // 2
curr_ssim = get_ssim_at_quality(photo, curr_quality)
ssim_ratio = curr_ssim / normalized_ssim
if ssim_ratio >= ssim_goal:
# continue to check whether a lower quality level also exceeds the goal
selected_quality = curr_quality
selected_ssim = curr_ssim
hi = curr_quality
else:
lo = curr_quality
if selected_quality:
return selected_quality, selected_ssim
else:
default_ssim = get_ssim_at_quality(photo, hi)
return hi, default_ssim
然后在下面的代码中引用计算动态质量的方法:
...
if im.format=='JPEG':
save_args['quality'],value=dynamic_quality.jpeg_dynamic_quality(im)
save_args['optimize']=True
save_args['progressive']=True
...
使用Python和Selenium模块操纵Squoosh批量压缩图片
Squoosh 是谷歌发布的一款开源的图片在线压缩服务(伪),虽然需要用浏览器打开,但其实是一个整合了许多命令行工具的前端界面,调用的是本地的计算资源,所以只要打开过Squoosh一次,之后都会秒开,并且离线使用。不过最大的缺点就是不可以批量处理,如果我们要处理大量的图片资源,一张张地进行压缩处理将会消耗大量的人力成本和时间成本,这明显是不能接受的。我们要解决的问题就是写一个脚本来模拟浏览器的操作,使我们的双手得到解放。
Python 调用 Selenium
这是 Squoosh 的主界面,Select an Image 其实是一个输入框,那我们直接用 Selenium 把本地图片的路径输入进去就行了:
输入图片路径之后就会默认压缩成 75% 质量的 MozJPEG,我觉得无论是压缩比和质量都很不错,所以就没有改,等待页面加载完成之后就直接下载:
我们可以认为出现 "..% smaller" 就算是压缩完成,这时候直接点击右边的下载按钮即可。
代码:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import Select
import os
import re
driver = webdriver.Chrome('C:/Users/admin/AppData/Local/Google/Chrome/Application/chromedriver.exe')
# 列出目录下所有的图片,存在 images 这个列表中
images = os.listdir('C:/Users/admin/Pictures/Saved Pictures')
# 处理所有图片
for i in range(len(images)):
# 构建图片路径
path = 'C:/Users/admin/Pictures/Saved Pictures/' + images[i]
# 尝试处理所有图片
try:
# 打开 Squoosh
driver.get('https://squoosh.app')
# 找到输入框
input_box = driver.find_element_by_xpath('.//input[@class="_2zg9i"]')
# 输入图片路径
input_box.send_keys(path)
#设置图片格式
select1 = Select(driver.find_elements_by_css_selector('select')[-1])
if re.match('.*.png',images[i]):
select1.select_by_value("png")
if re.match('.*.jpg',images[i]):
select1.select_by_value("mozjpeg")
# 等待出现 'smaller'字样,10秒不出现则视为处理失败
locator = (By.XPATH, './/span[@class="_1eNmr _1U8bE"][last()]')
WebDriverWait(driver, 25).until(EC.text_to_be_present_in_element(locator, 'smaller'))
# 找到下载按钮
button = driver.find_elements_by_xpath('.//a[@title="Download"]')[-1]
# 点击下载按钮
button.click()
# 输出处理失败的图片路径
except:
print('*'*30)
print('Error: '+ path +' failed!')
print('*'*30)
continue
总结
来源:https://zhuanlan.zhihu.com/p/161274600
猜你喜欢
- 字符串去除数字间的逗号在西文数字的表示中,很多格式是类似这样:123,456,789。如果得到这样的一个字符串,直接用int转换成整型肯定报
- 本文介绍了linux下如何备份与恢复mysql数据库。数据库备份是非常重要的。如果定期做好备份,这样就可以在发生系统崩溃时恢复数据到最后一次
- 要想从命令行启动mysqld服务器,你应当启动控制台窗口(或“DOS window”)并输入命令:C
- 现在市场上的OA基本上可归结为两大阵营,即php阵营和java阵营。但对接触Oa不久的用户来说,看到的往往只是它们的表相,只是明显的价格差异
- 无论是公司的同事还是外界的程序员朋友们,大部分人对JavaScript的高级应用不甚了解,已有的知识架构里会认为JavaScript仅仅是一
- 当然,页面最好不要刷新,但是,拷贝一下浏览器的链接,又希望是下次能定位到你播发的那个视频。方法很简单,改变一下 url 的 fragment
- 上次用Javascript+ASP实现了无刷新的新闻列表,最后还有一个小问题没有解决:下边的分页数列"首页、上10页、下10页、尾
- CURLOPT_RETURNTRANSFER 选项:curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);如
- 一般写ASP PHP代码的朋友都估计是采用直接操作SQL的吧~ 看以下的代码 <% dim conn,rs&nbs
- 更轻量- 出色的颗粒化模块,子模块划分;- 延迟加载;- 强调代码重用(公共基类、插件、扩展);更易用- 统一的API;- 便利(each,
- 最近有网友在留言板里问到jRaiser和jQuery的冲突问题,特此写一篇文章进行解释。冲突的根源众所周知,jQuery是通过一个全局变量$
- 沟通的时候,一般我不主动说自己是做用户体验设计,也不说做以用户为中心的设计,包括UED, UCD。这种专业名词传达的太虚,你也许是名用户体验
- SQL Server2005数据项的分拆与合并:参考示例如下:-- ====================================
- 广州4.18书友会主题的内容提纲自己参与撰写,同时还参与组织和主持。通过这次的深入参与,我发现胡晓同学能坚持下来多不容易,先赞下。由于天公不
- 本文实例讲述了Python单链表原理与实现方法。分享给大家供大家参考,具体如下:Python实现单链表关于链表链表(Linked List)
- Hi, 大家好~ 好久没有发有营养的东西,今天就扔一篇最近热点的Google Chrome 浏览器的试用心得吧。先说个比较搞的事情,Goog
- 方法一.Image { max-width:600px;height:
- 需 求 分 析 1、读取指定目录下的所有文件2、读取指定文件,输出文件内容3、创建一个文件并保存到指定目录实 现 过 程Python写代码简
- 函数局部变量 全局变量 及其作用域#简单类型(int str等)变量的局部变量与全局变量及其作用域的关系name = "xxx&q
- [摘要]了解如何充分利用SQL Server 2000的全文搜索功能。本文包含有关实现最大吞吐量和最佳性能的几点提示和技