How to use these problems
These exercises move from simple pixel arithmetic to filtering and histograms. Try each one before reading the hint. They are written for Python with NumPy and Pillow, but the ideas carry over to MATLAB, OpenCV, or any other tool.
Work with a few different pictures: a normal photo, a low-contrast one, and a noisy or dark one. Results that look right on one image may fail on another.
Setup
Install the libraries, using a virtual environment if you like:
pip install numpy pillow matplotlib
Load an image as an array:
from PIL import Image
import numpy as np
img = Image.open("photo.jpg").convert("RGB")
arr = np.array(img) # shape: (height, width, 3)
print(arr.shape, arr.dtype)
Level 1: Pixels, size, and resolution
Problem 1. An image is 3000 pixels wide and 2000 pixels tall. How many pixels does it contain? Give the answer in megapixels.
Hint: multiply width by height, then divide by one million.
Problem 2. Estimate the uncompressed size in bytes of a 1920 by 1080 image with three 8-bit channels.
Hint: width × height × channels × bytes per channel. Then compare it with the size of a JPEG of the same image. Why do they differ? See how file size is determined.
Problem 3. At what physical width does a 2400-pixel-wide image print if the print resolution is 300 pixels per inch? And at 150?
Hint: divide pixels by pixels per inch. The DPI guide walks through this calculation.
Problem 4. Write code that prints the minimum, maximum, and mean value of each color channel.
Level 2: Point operations
Point operations change each pixel based only on its own value.
Problem 5. Grayscale. Convert a color image to grayscale by taking a weighted sum of the channels. Compare a simple average with a weighted version that gives green more weight.
Hint: the eye is most sensitive to green. Common weights are roughly 0.30 red, 0.59 green, and 0.11 blue.
Problem 6. Brightness and contrast. Implement out = a * in + b. Make sure values are clipped to 0 to 255.
def adjust(arr, a=1.0, b=0):
out = arr.astype(np.float32) * a + b
return np.clip(out, 0, 255).astype(np.uint8)
Question: what happens if you skip the clipping and cast directly to uint8?
Problem 7. Negative. Produce the negative of an image.
Problem 8. Threshold. Convert a grayscale image to black and white using a fixed threshold. Then try picking the threshold from the image itself, for example from its mean value.
Problem 9. Gamma correction. Apply out = 255 * (in / 255) ** gamma for a few gamma values. Describe how the picture changes for values below and above 1.
Level 3: Filtering
Problem 10. Box blur. Write a function that replaces each pixel by the average of its 3 by 3 neighborhood. Decide how to handle borders, and compare padding with zeros against repeating the edge pixels.
Problem 11. Gaussian blur. Build a small Gaussian kernel and apply it. How does increasing the kernel size or standard deviation affect the result?
Problem 12. Sharpening. Subtract a blurred copy from the original, then add a scaled version of the difference back. Try a few scale factors and look for halos.
Problem 13. Edge detection. Apply horizontal and vertical Sobel kernels to a grayscale image and combine the results into an edge magnitude map.
sobel_x = np.array([[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]], dtype=np.float32)
Problem 14. Noise. Add random noise to an image, then compare how well a mean filter and a median filter remove it.
Hint: the median filter often works better against isolated bright or dark specks.
Level 4: Geometry and resampling
Problem 15. Resize. Scale an image to half its width and height using nearest-neighbor sampling, then using bilinear interpolation. Compare the results at zoomed-in view.
Problem 16. Rotate. Rotate an image by 90 degrees using array operations only. Then rotate by an arbitrary angle and explain why output pixel coordinates are mapped back to the input rather than forward.
Problem 17. Crop to aspect ratio. Write a function that center-crops an image to a 4:5 ratio without distortion.
Problem 18. Downscale and upscale. Shrink an image to a quarter of its size, enlarge it back, and measure the difference from the original. What does that tell you about lost detail?
Level 5: Histograms and color
Problem 19. Histogram. Plot the histogram of a grayscale image. What does a histogram bunched on the left suggest about the photo?
Problem 20. Histogram equalization. Implement it from the cumulative distribution and apply it to a low-contrast image. Where does it help, and where does it exaggerate noise?
Problem 21. Channel manipulation. Swap red and blue channels, or boost only the red channel. Describe the visual effect.
Problem 22. Color space conversion. Convert RGB to HSV (use a library function), then raise saturation and convert back.
Problem 23. Simple color detection. Create a mask selecting pixels near a target color, for example the red of a sign, and highlight them.
Conceptual questions
- Why is a lossy format like JPEG a poor choice for repeated editing?
- What is aliasing, and when does it appear when shrinking images?
- Why does convolving an image with a symmetric kernel give the same result as correlating with it?
- Why do pixel dimensions, rather than the DPI tag, decide how detailed an image looks on screen?
- What is the difference between spatial-domain and frequency-domain filtering?
Checking your work
- Compare your result with a library function, such as Pillow’s filters or OpenCV functions. Small differences at borders are normal.
- Display before and after images side by side.
- Test with an image that has a known answer, such as a gradient or a checkerboard.
- Check the
dtypeof your arrays. Many bugs come from unsigned integers wrapping around, for instance 250 + 10 becoming 4.
Where to go next
Once these feel comfortable, apply them to something larger. Our list of image processing project ideas suggests topics grouped by difficulty, from simple tools to more advanced systems.