Python开发图片压缩小工具

这个小工具非常简单,就是把图像以最小的损失量压缩。网络上的工具要么是要钱,要么一张张图片导入导出实在是太麻烦啦🙃笔者博客的图片全部都托管在gitee上,有1MB的大小限制,所以就搞个小工具来批量压缩图片啦大家有需要自取

图像存储原理

首先区别两个知识点:图片格式&颜色模式

  • 图片格式:文件存储的时候文件编码的数据结构,比如常见的.jpg和.png是两种图片存储格式
  • 颜色模式:指的是颜色表示的方式,常见的有RGB和YUV

一般来说,咱们常见的.jpg图片,使用手机拍的、电脑截图的如果没有经过特殊转码处理,一般采用RGB颜色模式。

一张数字图片,可以理解为一个二维数组,每一个元素就是常说的像素点,每个像素点就是一个颜色方块,当这些方块非常非常小时,就组成了一张图片(微分思想)。我们知道使用三基色可以合成所有颜色,这也就是RGB颜色模式的由来。由红R+绿G+蓝B三个颜色分量合成一个像素点,目前一般采样RGB24也就是一个分量一个字节(1Byte=8bits),那么一个像素点就是3Bytes(24bits)。计算一张采用RGB24颜色模式的分辨率A×B的图片大小:

$$
\text{Size} = A\cdot B \cdot 3Bytes
$$

当然还有更节省存储的RGB8和RGB16,也有色板精度更高的RGB32

图像压缩实例代码

所以根据图像存储原理及得到的图像存储大小计算方法就可以编写我们的最优抽样压缩代码了。在开始操作前,要先意识到有两个坑:

  • 由分辨率计算得到的图像大小会和实际图片大小偏差很大,这个常常是因为图片带有其他辅助信息。比如笔者用手机拍照得到的图片,放在电脑上用属性查看器是可以看到拍摄时间、拍摄地点经纬度以及相机光圈等属性
  • 存储时,电脑磁盘不是以字节为最小单位!操作过格式化磁盘的小伙伴应该都知道有一个可调选项是单元分配大小,笔者电脑为了提高速度选择了较大的单元大小(4096B),那么存储2000B大小文件时还是会使用4096B的空间噢!就像一个很大的柜子里放了一颗小戒指!

所以这个图像压缩算法也可以用来剥离平时照片里的敏感信息噢!

那么接下来让我们开始设计代码吧😜

  1. 找出目标图片:笔者这边做了后缀筛选
  2. 文件大小超标检查
  3. 若超标,首先剥离图像的拍摄辅助信息
  4. 若还超标,则做抽样压缩

根据图像存储大小公式,我们可计算压缩比为:

$$
C = \sqrt{\frac{Size}{MaxSize}}
$$

然后计算抽样后的像素数组长宽数量:

$$
W’ = \frac{W}{C},H’=\frac{H}{C}
$$

最后,进行图像抽样并保存就可以啦!图像抽样时,可以选择不同的滤波方式,滤波的用途比如在长度为5的数组抽样4个则理论上最佳抽样点位置不在数据点上,那么就可以滤波得到逻辑抽样点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# -*- coding: utf-8 -*-
"""
Created on Wed Feb 10 18:46:53 2021

@author: 江榕煜

@brief: 图片压缩工具
"""

'''辅助模块导入'''
import os
from PIL import Image
import glob
import math


'''
在你使用之前
请务必适配该部分
'''
DIR = r'./'; # 要进行压缩处理的图像路径,现在默认文件运行路径
saveDIR = r'./' # 压缩处理后图像存储路径
MaxSizeOfImg = 1e6; #限制压缩后最大图像大小,单位:Byte

condense_JPG = True; # 若压缩 .jpg 后缀文件请使能该项。若不用,请设置为False
condense_PNG = True; # 若压缩 .png 后缀文件请使能该项


'''图片文件匹配'''
fileList_JPG = glob.glob('%s%s%s' % (DIR, '*', '.jpg'));
fileList_PNG = glob.glob('%s%s%s' % (DIR, '*', '.png'));

fileList = [];

if condense_JPG:
fileList += fileList_JPG;
if condense_PNG:
fileList += fileList_PNG;

'''存储路径存在性检查'''
if not os.path.exists(saveDIR):
os.mkdir(saveDIR);
print("创建文件夹:"+saveDIR)

'''开始压缩处理'''
for imgFile in fileList:
'''文件大小检查'''
thisFileSize = os.path.getsize(imgFile)
if thisFileSize < MaxSizeOfImg: # 符合要求跳过不处理
continue;
else:

'''用户提示'''
print("----------")
print("压缩图像"+str(imgFile)+"(大小:"+str(thisFileSize)+"Bytes)")


'''计算存储路径'''
nameRout = imgFile.split('/')
nameRout = nameRout[-1].split('\\')
nameRout = nameRout[-1]
saveDir = '%s%s' % (saveDIR,nameRout)


'''先剥离图像原本的辅助数据'''
thisImg = Image.open(imgFile) # 打开图像源文件
cleanImg = thisImg.resize(thisImg.size)
thisImg.close()
cleanImg.save(saveDir)
cleanImg.close()


'''图像抽样压缩'''
thisFileSize = os.path.getsize(saveDir)
if thisFileSize < MaxSizeOfImg:
continue;
else:
condenseCoe = math.sqrt( float(thisFileSize)/MaxSizeOfImg); # 计算压缩比

thisImg = Image.open(saveDir) # 打开源图像文件

thisImgHeight = thisImg.height; # 获取图像像素个数
thisImgWidth = thisImg.width;

print("压缩比为:"+str(condenseCoe))
print("源图像分辨率:%d,%d" % (thisImgWidth,thisImgHeight))

thisImgHeight = int(float(thisImgHeight)/condenseCoe); # 计算压缩后像素个数
thisImgWidth = int(float(thisImgWidth)/condenseCoe);

print("压缩图像分辨率:%d,%d" % (thisImgWidth,thisImgHeight))

'''像素抽样'''
condenseImg = thisImg.resize((thisImgWidth,thisImgHeight)
#,Image.ANTIALIAS # 滤波方法
)

'''存储压缩后的图像'''
thisImg.close()
condenseImg.save(saveDir)
condenseImg.close()

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2022-2023 RY.J
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信