Integration Guide
Django
Django's ImageField depends on Pillow for image validation. For more advanced tasks like generating thumbnails or watermarking on upload, use Django signals or a dedicated library like django-imagekit.
from django.db import models
class Photo(models.Model):
image = models.ImageField(upload_to="photos/")
from django.db.models.signals import post_save
from PIL import Image
import os
def create_thumbnail(sender, instance, **kwargs):
if instance.image:
with Image.open(instance.image.path) as img:
img.thumbnail((300, 300), Image.Resampling.LANCZOS)
thumb_path = instance.image.path.replace("/photos/", "/thumbs/")
img.save(thumb_path)
post_save.connect(create_thumbnail, sender=Photo)
FastAPI
FastAPI's file upload handling works seamlessly with Pillow via UploadFile → BytesIO → Image.open().
from fastapi import FastAPI, UploadFile, File
from fastapi.responses import StreamingResponse
from PIL import Image
import io
app = FastAPI()
@app.post("/resize")
async def resize_image(file: UploadFile = File(...)):
contents = await file.read()
img = Image.open(io.BytesIO(contents))
img.thumbnail((800, 800), Image.Resampling.LANCZOS)
buf = io.BytesIO()
img.save(buf, format="WEBP", quality=85)
buf.seek(0)
return StreamingResponse(buf, media_type="image/webp")
Flask
from flask import Flask, request, send_file
from PIL import Image
import io
app = Flask(__name__)
@app.post("/convert")
def convert():
file = request.files["image"]
img = Image.open(file.stream).convert("RGB")
buf = io.BytesIO()
img.save(buf, "JPEG", quality=90)
buf.seek(0)
return send_file(buf, mimetype="image/jpeg")
NumPy
Pillow and NumPy arrays can be converted freely. This allows you to apply arbitrary mathematical operations on pixel data and then render back to an image.
import numpy as np
from PIL import Image
img = Image.open("photo.jpg")
arr = np.array(img)
print(arr.shape)
inv = 255 - arr
result = Image.fromarray(inv.astype(np.uint8))
result.save("inverted.jpg")
PyTorch / torchvision (AI & Deep Learning)
PyTorch uses Pillow as its default image loader. torchvision.transforms natively operates directly on PIL Images for data augmentation and neural network preprocessing pipelines.
from torchvision import transforms
from PIL import Image
train_transform = transforms.Compose([
transforms.RandomResizedCrop(224, scale=(0.8, 1.0)),
transforms.RandomHorizontalFlip(p=0.5),
transforms.ColorJitter(brightness=0.2, contrast=0.2),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
)
])
pil_img = Image.open("training_sample.jpg")
tensor = train_transform(pil_img)
print(tensor.shape)
to_pil = transforms.ToPILImage()
reconstructed_img = to_pil(tensor)
reconstructed_img.save("inspected.png")
OpenCV
OpenCV and Pillow use different colour channel orders. OpenCV is BGR, Pillow is RGB. You must convert when passing data between libraries.
import cv2
import numpy as np
from PIL import Image
pil_img = Image.open("photo.jpg")
cv_img = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
cv_img = cv2.imread("photo.jpg")
pil_img = Image.fromarray(cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB))
Tesseract OCR + OpenCV + Pillow Pipeline
The standard Python Computer Vision stack for document intelligence and OCR projects combines all three libraries:
- OpenCV: High-speed image pre-processing (grayscale conversion, Otsu thresholding, noise removal).
- Tesseract OCR (PyTesseract): Optical Character Recognition engine to extract text strings and word coordinate bounding boxes (test images online with the Tesseract Online OCR Tool).
- Pillow: High-fidelity TrueType text overlays, bounding box annotations (
ImageDraw), and final multi-format export.
import cv2
import numpy as np
import pytesseract
from PIL import Image, ImageDraw, ImageFont
cv_img = cv2.imread("receipt.png")
gray = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
ocr_data = pytesseract.image_to_data(thresh, output_type=pytesseract.Output.DICT)
pil_img = Image.fromarray(cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(pil_img)
for i in range(len(ocr_data["text"])):
confidence = int(ocr_data["conf"][i])
text = ocr_data["text"][i].strip()
if confidence > 50 and text:
x, y, w, h = ocr_data["left"][i], ocr_data["top"][i], ocr_data["width"][i], ocr_data["height"][i]
draw.rectangle([x, y, x + w, y + h], outline="lime", width=2)
draw.text((x, max(0, y - 14)), text, fill="lime")
pil_img.save("ocr_annotated.png")
Tkinter
Tkinter's native PhotoImage only supports GIF and PGM. Use ImageTk.PhotoImage from Pillow to display any image format in a Tkinter window.
import tkinter as tk
from PIL import Image, ImageTk
root = tk.Tk()
img = Image.open("photo.jpg").resize((400, 300))
photo = ImageTk.PhotoImage(img)
label = tk.Label(root, image=photo)
label.pack()
root.mainloop()
Qt (PyQt5 / PySide6)
Convert Pillow images to Qt's QImage via NumPy as an intermediate layer.
from PyQt5.QtGui import QImage, QPixmap
from PIL import Image
import numpy as np
img = Image.open("photo.jpg").convert("RGBA")
data = np.array(img)
h, w, ch = data.shape
qimage = QImage(data.tobytes(), w, h, ch * w, QImage.Format_RGBA8888)
pixmap = QPixmap.fromImage(qimage)