Hassan Agmir Hassan Agmir

Python Compress Image: Pillow, OpenCV

Hassan Agmir
Python Compress Image: Pillow, OpenCV

Image compression is an essential skill for developers, web designers, data scientists, and anyone working with digital images. As the demand for high-quality visuals grows, so does the need for efficient storage and rapid loading times. Uncompressed or poorly compressed images can bloat your applications, slow down websites, and consume excessive bandwidth and disk space. Fortunately, Python offers a rich ecosystem of libraries and tools that make image compression straightforward and flexible. In this article, we’ll explore everything you need to know to compress images in Python effectively—whether you’re optimizing photos for the web, reducing the size of images in a dataset, or archiving large collections.

Table of Contents

  1. Why Compress Images?
  2. Understanding Image Formats and Compression Types
  3. Key Python Libraries for Image Compression
  4. Lossy vs. Lossless Compression
  5. Basic Compression with Pillow (PIL)
  6. Advanced Compression with OpenCV
  7. Batch Processing and Automation
  8. Using WebP and Other Modern Formats
  9. Integrating Compression into Web Workflows
  10. Evaluating Compression Quality
  11. Performance Tips and Best Practices
  12. Real-World Use Cases
  13. Handling Edge Cases
  14. Conclusion

Why Compress Images?

  1. Faster Load Times: Large images significantly increase page load times. Compressing images ensures that web pages render quickly, improving user experience and SEO rankings.
  2. Reduced Bandwidth Usage: For web and mobile applications, compressed images reduce network transfer sizes, saving bandwidth and lowering hosting costs.
  3. Storage Efficiency: In applications dealing with large image datasets—such as machine learning, archiving, or photo management—compression minimizes disk usage and backup times.
  4. Cost Savings: Cloud storage and CDN services often charge based on data volume. Compressing images reduces storage bills and CDN costs.
  5. Scalability: Efficient image handling allows applications to scale gracefully, even under high traffic.

Understanding Image Formats and Compression Types

Before compressing images, you need to understand the major image formats and their compression characteristics:


Format Extension Compression Type Use Cases
| JPEG  | .jpg/.jpeg  | Lossy  | Photographs, web graphics
| PNG  | .png  | Lossless  | Logos, icons, images with transparency
| GIF  | .gif  | Lossless (limited colors)  | Simple animations, low-color images
| WebP  | .webp  | Both (lossy & lossless)  | Modern web, mix of photos & transparency
| TIFF  | .tif/.tiff  | Both (lossy & lossless)  | Printing, archiving

  • Lossy compression reduces file size by discarding some image data, potentially impacting quality.
  • Lossless compression retains all original data, offering smaller savings but preserving perfect fidelity.

Key Python Libraries for Image Compression

The Python ecosystem offers several powerful libraries:

  1. Pillow (PIL Fork): The de facto image-processing library in Python; easy to use and widely supported.
  2. OpenCV: A computer vision library that includes advanced image-processing capabilities and format conversions.
  3. imgaug / torchvision.transforms: Libraries mainly for data augmentation, but they also support resizing and format conversion that can aid compression.
  4. pyvips: A high-performance library for large image processing, supporting streaming and minimal memory overhead.
  5. PIL-SIMD: A fork of Pillow optimized with SIMD instructions for faster processing on supported hardware.

Lossy vs. Lossless Compression

Lossy Compression

  • Pros: Dramatic reduction in file size; suitable for photos.
  • Cons: Potential artifacts and quality degradation if over-compressed.

Lossless Compression

  • Pros: No quality loss; ideal for graphics, text, and images requiring transparency.
  • Cons: Less dramatic size reduction; PNGs can be larger than comparable JPEGs.

Basic Compression with Pillow (PIL)

Pillow makes basic image compression extremely easy. Below is a typical workflow:

from PIL import Image

def compress_image(input_path, output_path, quality=75, optimize=True):
    """
    Compress an image using Pillow.

    :param input_path: Path to the source image.
    :param output_path: Path where the compressed image will be saved.
    :param quality: JPEG quality (1–95). Lower quality → smaller size.
    :param optimize: If True, Pillow will perform an extra pass to optimize the image.
    """
    with Image.open(input_path) as img:
        img.save(output_path, format='JPEG', quality=quality, optimize=optimize)

  • quality: Controls the trade-off between size and image fidelity.
  • optimize: Runs a pass to optimize the Huffman coding tables in JPEG, further reducing size.

Tips:

  • Convert images to RGB mode before saving as JPEG if they’re in a different mode (e.g., RGBA or P).
  • For PNG, use img.save(output_path, format='PNG', optimize=True); Pillow automatically applies lossless compression.

Advanced Compression with OpenCV

OpenCV offers more fine-grained control via its imwrite parameters:

import cv2

def compress_with_opencv(input_path, output_path, quality=75):
    """
    Compress an image using OpenCV.

    :param input_path: Path to the source image.
    :param output_path: Path where the compressed image will be saved.
    :param quality: JPEG quality (0–100).
    """
    img = cv2.imread(input_path)
    encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), quality]
    cv2.imwrite(output_path, img, encode_param)

Advantages of OpenCV:

  • Faster I/O for large-scale applications.
  • More image-processing functions (denoising, color conversion, scaling) that can improve perceived quality at lower bitrates.

Batch Processing and Automation

For projects involving hundreds or thousands of images, manual processing is infeasible. Here’s how to automate:

import os
from concurrent.futures import ThreadPoolExecutor
from PIL import Image

def batch_compress(directory, output_dir, quality=75):
    os.makedirs(output_dir, exist_ok=True)
    files = [f for f in os.listdir(directory) if f.lower().endswith(('jpg','jpeg','png'))]

    def process(file_name):
        input_path = os.path.join(directory, file_name)
        output_path = os.path.join(output_dir, file_name)
        with Image.open(input_path) as img:
            img.save(output_path, format='JPEG', quality=quality, optimize=True)

    with ThreadPoolExecutor() as executor:
        executor.map(process, files)

  • ThreadPoolExecutor: Utilizes multiple threads to process images in parallel—ideal for I/O-bound compression tasks.
  • Scalability: For CPU-bound tasks (e.g., resizing, filtering), consider using ProcessPoolExecutor instead.

Using WebP and Other Modern Formats

Google’s WebP offers superior compression compared to JPEG and PNG:

from PIL import Image

def compress_to_webp(input_path, output_path, quality=80, lossless=False):
    with Image.open(input_path) as img:
        img.save(output_path, format='WEBP', quality=quality, lossless=lossless)

  • lossless=True: Enables lossless WebP compression.
  • Animated WebP: Pillow also supports writing animated WebP files by passing a sequence of images.

Advantages of WebP:

  • Higher compression ratios for both lossy and lossless.
  • Supports transparency and animation.
  • Broad support in modern browsers and tools.

Integrating Compression into Web Workflows

Flask Example: On-the-Fly Compression

from flask import Flask, send_file, request
from io import BytesIO
from PIL import Image

app = Flask(__name__)

@app.route('/compress')
def compress_route():
    quality = int(request.args.get('quality', 75))
    img = Image.open('static/original.jpg')
    buf = BytesIO()
    img.save(buf, format='JPEG', quality=quality, optimize=True)
    buf.seek(0)
    return send_file(buf, mimetype='image/jpeg')

  • Parameterization: Allow clients to request different quality levels.
  • Buffering: Avoids writing temporary files to disk.

Django Integration

In Django, integrate compression into a custom storage backend or a signal handler that compresses images upon upload.

Evaluating Compression Quality

Compression quality is subjective. Use objective metrics:

  1. Peak Signal-to-Noise Ratio (PSNR): Measures similarity; higher PSNR is better.
  2. Structural Similarity Index (SSIM): Models perceived changes in structural information.
import cv2
import numpy as np
from skimage.metrics import structural_similarity as ssim

def compare_images(img1_path, img2_path):
    img1 = cv2.imread(img1_path, cv2.IMREAD_COLOR)
    img2 = cv2.imread(img2_path, cv2.IMREAD_COLOR)
    psnr_value = cv2.PSNR(img1, img2)
    ssim_value = ssim(
        cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY),
        cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY),
        data_range=img2.max() - img2.min()
    )
    return psnr_value, ssim_value

Performance Tips and Best Practices

  1. Resize Before Compressing: Downscale images to needed dimensions to save more than JPEG quality adjustments alone.
  2. Cache Compressed Versions: Avoid recompressing the same image multiple times.
  3. Use Dedicated Tools for Bulk Jobs: Consider jpegoptim, optipng, or cwebp command-line tools invoked via Python’s subprocess for extreme optimization.
  4. Leverage Cloud/Third-Party Services: Services like Cloudinary or Imgix provide on-the-fly compression, resizing, and CDN delivery.
  5. Profile Your Pipeline: Use Python’s cProfile and line profilers to identify bottlenecks.

Real-World Use Cases

  1. E-Commerce: Compress product photos to accelerate page loads and reduce cart abandonment.
  2. Content Delivery Networks (CDNs): Integrate compression into the origin server to minimize bandwidth.
  3. Machine Learning: Preprocess large image datasets (e.g., ImageNet) to accelerate training and reduce storage.
  4. Mobile Apps: Serve appropriately compressed images to different device resolutions.
  5. Archival: Batch-compress and archive historic photograph collections in lossless formats.

Handling Edge Cases

  • Transparency in JPEG: JPEG does not support transparency; convert RGBA images to RGB with a background fill:
  • bg = Image.new("RGB", img.size, (255,255,255))  # white background
    bg.paste(img, mask=img.split()[3])  # paste using alpha channel as mask
    
  • Animated GIFs: To compress GIFs, consider converting to video formats or using specialized libraries like gifsicle.
  • Color Profiles: Strip EXIF data and color profiles if not needed:
  • img.info.pop('exif', None)
    

Conclusion

Image compression in Python is a multifaceted topic that touches on file formats, quality trade-offs, performance considerations, and integration into larger workflows. With libraries like Pillow and OpenCV, you have powerful tools at your fingertips to balance image fidelity and file size. By understanding lossy versus lossless methods, leveraging modern formats like WebP, and automating tasks with batch scripts or web frameworks, you can ensure your images are optimized for any application—be it the web, mobile, or archival storage.

Embrace these techniques to deliver faster, leaner, and more efficient image-driven experiences. Whether you’re an experienced developer or just starting, mastering Python-based image compression will elevate your projects and delight your users.

Subscribe to my Newsletters

Stay updated with the latest programming tips, tricks, and IT insights! Join my community to receive exclusive content on coding best practices.

© Copyright 2025 by Hassan Agmir . Built with ❤ by Me