圖片壓縮 Resize & Compression Image With Python (pngquant, ImageMagick)


  1. 說明
  2. ImageMagick (jpg files)

筆記使用 Python 以及 Pillow Packages 批次將圖片重新調整比例以及壓縮圖大小,以節省儲存空間。

logo

說明

不多說,直接上程式碼,注意需要先安裝 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。

ImageMagick Download Page

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