Unit 12.1A · Term 1

PIL Image Filters

Image filters transform a picture by changing its pixels. Pillow provides ready-made ImageFilter effects, whole-image ImageEnhance controls, and everything you need to design your own pixel-by-pixel filters.

Learning Objectives

  • 12.6.2.4 Create filters for image processing

Lesson Presentation

12.1A-pil-filters.pdf · Slides for classroom use

Conceptual Anchor

A Filter Is a Transformation

Think about a photo-editing app. A filter takes an input image, applies a rule, and produces a new output image. Some rules look at neighbouring pixels (blur, sharpen, edges); others modify each pixel directly (negative, grayscale, sepia, warm tone).

Image.open() Load the source image
filter / enhance Apply a transformation
compare Inspect the result
save() Write a new image file

Built-in Filters — Visual Reference

The easiest way to understand an image filter is to compare the same source image after different transformations. All examples below use the same generated scene.

Filter Typical purpose Python
BLUR General blur / smoothing img.filter(ImageFilter.BLUR)
GaussianBlur(radius) Adjustable blur strength img.filter(ImageFilter.GaussianBlur(5))
SHARPEN Make edges more pronounced img.filter(ImageFilter.SHARPEN)
CONTOUR Extract contour-like lines img.filter(ImageFilter.CONTOUR)
DETAIL Enhance fine detail img.filter(ImageFilter.DETAIL)
EDGE_ENHANCE Strengthen edges img.filter(ImageFilter.EDGE_ENHANCE)
FIND_EDGES Detect edge-like intensity changes img.filter(ImageFilter.FIND_EDGES)
EMBOSS Create a relief / emboss effect img.filter(ImageFilter.EMBOSS)
SMOOTH Light smoothing img.filter(ImageFilter.SMOOTH)

Interactive Filter Lab

Choose a filter to see the same image transformed immediately. The browser preview approximates the visual idea; the code shown below is the Pillow command you would use in Python.

Effect Original image Python img = Image.open("photo.jpg")

Using ImageFilter

1 Apply and Save a Built-in Filter

from PIL import Image, ImageFilter img = Image.open("photo.jpg") blurred = img.filter( ImageFilter.GaussianBlur(4) ) blurred.save("photo_blurred.png")

2 Chain Filters

from PIL import Image, ImageFilter img = Image.open("photo.jpg") result = img.filter(ImageFilter.SHARPEN) result = result.filter(ImageFilter.FIND_EDGES) result.save("sketch_effect.png")

Order Matters

A filter changes the input for the next filter. SHARPEN → FIND_EDGES does not necessarily produce the same result as FIND_EDGES → SHARPEN.

Image Enhancement

ImageEnhance adjusts the appearance of the whole image. A factor of 1.0 keeps the original. Lower and higher factors decrease or increase the selected property.

Try the factors

Brightness 1.00
Contrast 1.00
Color 1.00
Sharpness 1.00
brightness = ImageEnhance.Brightness(img).enhance(1.00) contrast = ImageEnhance.Contrast(brightness).enhance(1.00) color = ImageEnhance.Color(contrast).enhance(1.00) result = ImageEnhance.Sharpness(color).enhance(1.00)
Enhancer 0.0 1.0 > 1.0
Brightness Black Original Brighter
Contrast Flat gray-like result Original More contrast
Color Grayscale Original More colour saturation
Sharpness Blurred Original Sharper

3 Increase Brightness

from PIL import Image, ImageEnhance img = Image.open("photo.jpg") enhancer = ImageEnhance.Brightness(img) bright = enhancer.enhance(1.5) bright.save("bright.png")

Why Do Blur and Edge Filters Look at Neighbours?

Many built-in filters do not change a pixel independently. They examine a small neighbourhood around the pixel and combine those values. This small matrix is commonly called a kernel.

3 × 3 Neighbourhood

P
P
P
P
X
P
P
P
P

The new value of centre pixel X can depend on the surrounding pixels P.

Conceptual difference

Blur makes nearby values more similar
Sharpen emphasises differences
Edge detection looks for strong local changes

Custom Filters — Pixel Mathematics

A custom filter can read a pixel (r, g, b), calculate new channel values and write the result back. Use the controls below to see exactly what happens to one pixel.

Input Pixel

R 142
G 87
B 201
Input (142, 87, 201)
Output (113, 168, 54)

Formula

Worked Custom Filters

4 Manual Grayscale

from PIL import Image img = Image.open("photo.jpg").convert("RGB") w, h = img.size for x in range(w): for y in range(h): r, g, b = img.getpixel((x, y)) gray = int( 0.299 * r + 0.587 * g + 0.114 * b ) img.putpixel( (x, y), (gray, gray, gray) ) img.save("manual_gray.png")

5 Negative

from PIL import Image img = Image.open("photo.jpg").convert("RGB") w, h = img.size for x in range(w): for y in range(h): r, g, b = img.getpixel((x, y)) img.putpixel( (x, y), (255 - r, 255 - g, 255 - b) ) img.save("negative.png")

6 Sepia

from PIL import Image img = Image.open("photo.jpg").convert("RGB") w, h = img.size for x in range(w): for y in range(h): r, g, b = img.getpixel((x, y)) tr = min(255, int(0.393*r + 0.769*g + 0.189*b)) tg = min(255, int(0.349*r + 0.686*g + 0.168*b)) tb = min(255, int(0.272*r + 0.534*g + 0.131*b)) img.putpixel((x, y), (tr, tg, tb)) img.save("sepia.png")

7 Threshold

Convert the pixel to an intensity. If that intensity is above the threshold, make it white; otherwise make it black.

65 < 128 → BLACK
threshold = 128
205 > 128 → WHITE
from PIL import Image img = Image.open("photo.jpg").convert("RGB") w, h = img.size threshold = 128 for x in range(w): for y in range(h): r, g, b = img.getpixel((x, y)) average = (r + g + b) // 3 if average > threshold: new_color = (255, 255, 255) else: new_color = (0, 0, 0) img.putpixel((x, y), new_color) img.save("threshold.png")

8 Warm Filter

from PIL import Image img = Image.open("photo.jpg").convert("RGB") w, h = img.size for x in range(w): for y in range(h): r, g, b = img.getpixel((x, y)) new_r = min(255, r + 20) new_g = g new_b = max(0, b - 20) img.putpixel( (x, y), (new_r, new_g, new_b) ) img.save("warm.png")

9 Keep Only the Red Channel

from PIL import Image img = Image.open("photo.jpg").convert("RGB") w, h = img.size for x in range(w): for y in range(h): r, g, b = img.getpixel((x, y)) img.putpixel( (x, y), (r, 0, 0) ) img.save("red_only.png")

Keep RGB Values in Range

Standard 8-bit RGB channels use values from 0 to 255. Custom formulas may produce values outside this range, so clamp values before writing them back.

10 Clamp a Calculated Value

# Keep a number between 0 and 255 new_r = min(255, max(0, calculated_r)) new_g = min(255, max(0, calculated_g)) new_b = min(255, max(0, calculated_b))

A Small Helper Function

For larger projects you can write def clamp(v): return min(255, max(0, v)) and reuse it for every channel.

Further Viewing

Blurring, Sharpening and Smoothing with Pillow

A short visual walkthrough from Real Python showing built-in ImageFilter operations and comparisons.

Watch

Common Pitfalls

1. filter() Returns a New Image

img.filter(...) does not replace img. Store the result: blurred = img.filter(ImageFilter.BLUR).

2. Unexpected Image Mode

If your custom filter expects r, g, b, first use img = img.convert("RGB").

3. Values Outside 0–255

A custom formula can produce negative values or values above 255. Clamp channel values before calling putpixel().

4. Slow Pixel-by-Pixel Code

Nested for loops are excellent for learning how filters work, but large images may process slowly. Resize a test image while developing.

5. Overwriting Your Original File

During practice, save results under a new filename such as photo_blur.png instead of replacing the source file.

Practice Tasks

Remember

Which Pillow module contains BLUR, SHARPEN and FIND_EDGES?

Understand

Explain the difference between a neighbour-based filter such as blur and a custom negative filter that changes each pixel independently.

Apply

Load an image, apply GaussianBlur(5), and save the result as blurred.png.

Apply

Use ImageEnhance.Brightness to create one version at factor 0.6 and another at 1.6.

Analyze

A pixel is (220, 80, 30). Calculate the result of the negative filter (255-r, 255-g, 255-b).

Analyze

A warm filter adds 50 to red. Explain why min(255, r + 50) is safer than simply using r + 50.

Create

Design your own custom filter using a clear mathematical rule. Explain what happens to R, G and B, then apply it to every pixel.

Create

Build a mini filter pipeline that produces three files from one source: a blurred image, an edge image and your own custom colour effect.

Self-Check Quiz

Q1: Which module provides BLUR, SHARPEN and FIND_EDGES?

ImageFilter from Pillow.

Q2: What does enhancement factor 1.0 mean?

It returns the original appearance for that enhancement property.

Q3: What does ImageEnhance.Color(...).enhance(0) produce?

A grayscale / black-and-white appearance because colour saturation is removed.

Q4: Why might a custom filter begin with convert("RGB")?

So every pixel can be handled consistently as three values: r, g, b.

Q5: What does a threshold filter do?

It compares pixel intensity with a threshold and assigns one result below the threshold and another result above it — commonly black or white.

Q6: What is the negative of (100, 150, 200)?

(155, 105, 55).

Q7: Why is clamping necessary in custom colour filters?

Because standard RGB channel values must remain between 0 and 255.