圖片壓縮 Resize & Compression Image With Python (pngquant, ImageMagick)
2023-08-02
筆記使用 Python 以及 Pillow Packages 批次將圖片重新調整比例以及壓縮圖大小,以節省儲存空間。
說明
不多說,直接上程式碼,注意需要先安裝 pillow
pip install pillow
from PIL import Image
import os
import glob
import random
def convert(file):
input_image_path = file
file_name, extension = os.path.splitext(file)
output_image_path = f'{file_name}_minify.{os.path_sp}.{extension}'
original_image = Image.open(input_image_path)
# Resize
new_width = original_image.size[0] // 2
new_height = original_image.size[1] // 2
resized_image = original_image.resize((new_width, new_height))
# Compression: 100 is best quality
compression_quality = 35
resized_image.save(output_image_path, quality=compression_quality)
os.chdir(r"D:\ImagesFolder")
jpg_files = glob.glob("*.jpg")
for jpg in jpg_files:
convert(jpg)
針對 PNG 的綜合處理方式,使用 256 色或 Qunatize Method 2 能夠有效壓縮為 25% 的大小,但顏色上會略為失真。
Resize 的方式對於減少 Size 有效,但會減少解析度,權衡下使用 256 色或 Qunatize Method 2 讓畫質失真可能較為接受。
from PIL import Image
import os
def compress_image(input_path):
try:
for i in range(25, 95, 5):
with Image.open(input_path) as img:
width, height = img.size
new_width = int(width * (i / 100))
new_height = int(height * (i / 100))
img = img.resize((new_width, new_height), Image.ANTIALIAS)
img.save(f'resize_{i}.png', 'png', optimize=True)
with Image.open(input_path) as img:
img = img.convert('P', palette=Image.ADAPTIVE)
img.save(f'adaptive.png', 'png', optimize=True)
with Image.open(input_path) as img:
img = img.quantize(method = 2)
img.save(f'quantize_method_2.png', 'png', optimize=True)
for color in (128, 256):
with Image.open(input_path) as img:
img = img.quantize(colors = color)
img.save(f'color_{color}.png', 'png', optimize=True)
with Image.open(input_path) as img:
img.save(f'default.png', 'png', optimize=True)
except Exception as e:
print(f"Exception:{e}")
if __name__ == "__main__":
input_image_path = r"D:\ImagesFolder\img.png"
compress_image(input_image_path)
雖然 PIL 提供了一定程度的圖片壓縮能力,但如果要有效減少檔案大小,就會犧牲圖片的解析度或者是畫質,而且影響程度幾乎是肉眼明顯可見。
替代的選擇是使用更好的圖片壓縮演算法,例如 pngquant 這個 cli 工具。
python 搭配 subprocess 的方式去進行 pngquant 的調度使用,結合達成壓縮圖片且不明顯損失畫質的成果。
import os
import subprocess
source_folder = r'C:\images'
output_folder = r'C:\images\minify'
pngquant_path = r'C:\apps\pngquant.exe'
start = time.time()
if not os.path.exists(output_folder):
os.makedirs(output_folder)
for filename in os.listdir(source_folder):
if filename.endswith('.png') or filename.endswith('.jpg'):
input_image = os.path.join(source_folder, filename)
output_image = os.path.join(output_folder, filename)
subprocess.run([pngquant_path, input_image, '--output', output_image, '--skip-if-larger', '--force', '--speed', '11'])
end = time.time()
print(f'壓縮完成! {round(end - start, 3)}')
關於 pngquant 的 parameter
options:
--force overwrite existing output files (synonym: -f)
--skip-if-larger only save converted files if they're smaller than original
--output file destination file path to use instead of --ext (synonym: -o)
--ext new.png set custom suffix/extension for output filenames
--quality min-max don't save below min, use fewer colors below max (0-100)
--speed N speed/quality trade-off. 1=slow, 4=default, 11=fast & rough
--nofs disable Floyd-Steinberg dithering
--posterize N output lower-precision color (e.g. for ARGB4444 output)
--strip remove optional metadata (default on Mac)
--verbose print status messages (synonym: -v)
ImageMagick (jpg files)
ImageMagick 可以用來處理 jpg files,同樣是 exe 的方式執行,可以結合 python 先進行圖片 resize 接著再 compression。
from PIL import Image
import os
import glob
import config as c
import subprocess
from concurrent.futures import ThreadPoolExecutor
def convert(file):
input_image_path = os.path.join(c.source, file)
file_name, extension = os.path.splitext(file)
original_image = Image.open(input_image_path)
minimun_width = 470
scale = original_image.size[0] // minimun_width
print(f"Scaling factor for {file}: {scale}")
# Resize
new_width = original_image.size[0] // scale
new_height = original_image.size[1] // scale
resized_image = original_image.resize((new_width, new_height), Image.ANTIALIAS)
output_path = os.path.join(c.output, file)
resized_image.save(output_path)
# Post-processing with ImageMagick
command = [c.imconvert, output_path, output_path]
subprocess.run(command, text=True, capture_output=True)
print(f"Processed {file}")
return output_path
def main():
os.chdir(c.source)
jpg_files = glob.glob("*.jpg")
with ThreadPoolExecutor(max_workers=os.cpu_count()) as executor:
results = list(executor.map(convert, jpg_files))
if __name__ == "__main__":
main()
imconvert.bat
C:\Apps\ImageMagick-7.1.1-30-portable-Q16-HDRI-x64\convert.exe %1 -quality 20 %2