How to do image processing in Python

Learn how to do image processing in Python. Discover different methods, tips, real-world applications, and how to debug common errors.

How to do image processing in Python
Published on: 
Tue
Mar 10, 2026
Updated on: 
Fri
Mar 13, 2026
The Replit Team

Python allows you to manipulate digital images for tasks from simple enhancements to complex data extraction. Its powerful libraries provide a straightforward way to automate these visual tasks with minimal code.

In this article, you'll explore essential techniques and practical tips. You'll also discover real-world applications and get advice on how to debug your code to solve common image manipulation challenges effectively.

Basic image processing with PIL/Pillow

from PIL import Image
img = Image.open('sample.jpg')
print(f"Format: {img.format}, Size: {img.size}, Mode: {img.mode}")
img.show() # Display the image--OUTPUT--Format: JPEG, Size: (800, 600), Mode: RGB

The Pillow library is the standard for image manipulation in Python. Your script starts by using Image.open() to load an image file into an object. This object holds not just the visual data but also crucial metadata you can use to guide your script's logic.

Before you manipulate an image, you can inspect its core properties:

  • format: Identifies the file type, like JPEG or PNG.
  • size: Gives you the dimensions in pixels (width, height).
  • mode: Defines the number and type of color channels. RGB, for instance, confirms a standard red, green, and blue color image.

Finally, the show() method provides a simple way to display the current state of the image, which is great for quick visual checks during development.

Common image manipulation techniques

Beyond just inspecting properties, Pillow lets you perform essential manipulations like resizing, rotating, applying filters, and converting between different color modes.

Resizing and rotating images with PIL

from PIL import Image
img = Image.open('sample.jpg')
resized_img = img.resize((400, 300))
rotated_img = img.rotate(45)
resized_img.save('resized.jpg')
rotated_img.save('rotated.jpg')--OUTPUT--# No output, but two new files are created

Altering an image's geometry is straightforward. The resize() and rotate() methods don't change your original image; instead, they return new image objects with the transformations applied.

  • resize() takes a tuple defining the new (width, height) in pixels.
  • rotate() accepts an angle in degrees to turn the image counter-clockwise.

Once you have your new image object, you just call save() with a filename to write the result to a new file on your disk.

Applying filters and enhancing images

from PIL import Image, ImageFilter, ImageEnhance
img = Image.open('sample.jpg')
blurred = img.filter(ImageFilter.BLUR)
enhancer = ImageEnhance.Contrast(img)
enhanced = enhancer.enhance(1.5) # Increase contrast by 50%
enhanced.save('enhanced.jpg')--OUTPUT--# No output, but a new enhanced image file is created

Pillow's ImageFilter and ImageEnhance modules give you powerful tools for artistic effects and corrections. You can apply common filters or fine-tune properties like brightness and contrast with just a few lines.

  • The filter() method applies a predefined effect, such as ImageFilter.BLUR, directly to the image.
  • For enhancements, you first create an enhancer object, like ImageEnhance.Contrast(img). Then, you call its enhance() method with a factor. A factor of 1.5 increases the contrast by 50%, while 1.0 would return the original image.

Converting between color modes

from PIL import Image
img = Image.open('sample.jpg')
grayscale = img.convert('L')
binary = grayscale.point(lambda x: 0 if x < 128 else 255, '1')
grayscale.save('grayscale.jpg')
binary.save('binary.png')--OUTPUT--# No output, but two new files are created

Changing an image's color space is a common task, often done to simplify it for further processing. The convert('L') method transforms a color image into grayscale, where each pixel is represented by a single brightness value instead of three color channels.

  • From there, you can create a binary image using the point() method. It applies a function—in this case, lambda x: 0 if x < 128 else 255—to every pixel.
  • This function sets pixels darker than a threshold to black (0) and all others to white (255). The second argument, '1', ensures the output is a true 1-bit binary image.

Advanced image processing techniques

While Pillow is excellent for foundational tasks, you'll need libraries like NumPy and OpenCV to manipulate pixels directly or perform more complex computer vision operations.

Using NumPy for direct pixel manipulation

import numpy as np
from PIL import Image
img = Image.open('sample.jpg')
img_array = np.array(img)
# Invert image colors
inverted_array = 255 - img_array
inverted_img = Image.fromarray(inverted_array)
inverted_img.save('inverted.jpg')--OUTPUT--# No output, but a new inverted image file is created

NumPy gives you direct access to an image's pixel data. By converting the Pillow object into a NumPy array with np.array(), you transform the image into a grid of numbers. This structure allows you to perform fast, mathematical operations across all pixels at once.

  • The expression 255 - img_array is a vectorized operation. It subtracts each pixel's color value from 255, which effectively inverts the image's colors.
  • After manipulating the array, you use Image.fromarray() to convert the numerical data back into a standard image object that can be saved.

Image processing with OpenCV

import cv2
img = cv2.imread('sample.jpg')
# Apply a Gaussian blur
blurred = cv2.GaussianBlur(img, (15, 15), 0)
# Add a weighted combination for sharpening
sharpened = cv2.addWeighted(img, 1.5, blurred, -0.5, 0)
cv2.imwrite('sharpened.jpg', sharpened)--OUTPUT--# No output, but a new sharpened image file is created

OpenCV is a go-to library for advanced computer vision tasks. It reads images directly into a format that's ready for complex mathematical operations, much like NumPy. This example demonstrates a common sharpening technique known as unsharp masking.

  • First, cv2.GaussianBlur() creates a smoothed-out version of the image.
  • Then, cv2.addWeighted() combines the original and blurred images. It subtracts a portion of the blur from an amplified original, which enhances edges and details.

Finally, cv2.imwrite() saves the sharpened result to a new file.

Advanced feature detection with OpenCV

import cv2
img = cv2.imread('sample.jpg', 0) # Load as grayscale
edges = cv2.Canny(img, 100, 200) # Edge detection
contours, _ = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contour_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
cv2.drawContours(contour_img, contours, -1, (0, 255, 0), 2)
cv2.imwrite('contours.jpg', contour_img)--OUTPUT--# No output, but a new image file with contours is created

OpenCV excels at identifying objects within an image. The process starts by loading the image in grayscale, which simplifies the data for analysis. The cv2.Canny() function then scans this simplified image to detect edges—the boundaries between different parts of the picture.

  • With the edges identified, cv2.findContours() traces them to find continuous outlines, or contours, of shapes.
  • Finally, cv2.drawContours() visualizes these findings by drawing the outlines directly onto the image, making it easy to see the detected shapes.

Move faster with Replit

Replit is an AI-powered development platform that transforms natural language into working applications. Describe what you want to build, and Replit Agent creates it—complete with databases, APIs, and deployment.

You can use Replit Agent to turn the image processing concepts from this article into production-ready applications.

  • Build a bulk image utility that automatically resizes and rotates a folder of images using Pillow's resize() and rotate() methods.
  • Create an automatic photo enhancer that applies filters and adjusts contrast with ImageFilter and ImageEnhance.
  • Deploy a contour detection tool that uses OpenCV's cv2.Canny() and cv2.findContours() to identify and outline objects in uploaded images.

Describe your app idea, and Replit Agent will write the code, test it, and fix issues automatically, all in your browser.

Common errors and challenges

When you're working with images in Python, you'll inevitably run into a few common roadblocks that can trip you up.

A FileNotFoundError is one of the most frequent issues, and it simply means your script can't locate the image file you're trying to open. This usually happens because the path to the file is incorrect or the image isn't in the same directory as your script. To fix this, double-check for typos in the filename and consider using an absolute path for greater reliability.

Processing large images or many files at once can quickly consume your system's memory, causing your script to slow down or crash. The best way to handle this is by using a with statement when opening files. This practice ensures that Python automatically closes the file and releases its memory as soon as you're done with it, even if an error occurs.

You might also see errors when using the save() method, which often stem from format incompatibility. For example, you can't save an image with transparency as a JPEG. The solution is to either convert the image to a compatible mode, like RGB, before saving or to choose a different file format like PNG that supports the features you need.

Troubleshooting FileNotFoundError when opening images

This error is a classic 'file not found' problem, triggered when your script can't locate the specified image. The traceback is your friend here—it will point directly to the line with Image.open(), pinpointing the exact source of the failure. The following code demonstrates this common issue by attempting to open a file that doesn't exist.

from PIL import Image
img = Image.open('nonexistent_image.jpg')
img.show()

The code attempts to load 'nonexistent_image.jpg', but since the file isn't there, Python raises a FileNotFoundError. The next example demonstrates how to handle this situation so your script doesn't crash.

from PIL import Image
import os

image_path = 'nonexistent_image.jpg'
if os.path.exists(image_path):
img = Image.open(image_path)
img.show()
else:
print(f"Error: File {image_path} not found")

You can avoid this error by checking if a file exists before opening it. The os.path.exists() function confirms the file's presence, preventing a crash. By placing your Image.open() call inside an if block, the code only executes if the path is valid. This proactive check is crucial when dealing with files that might be moved or when paths are provided by users, ensuring your script handles missing files gracefully.

Fixing memory issues with the with statement

When you process images in a loop without explicitly closing them, your script's memory usage can skyrocket. Each open image consumes resources that aren't released, eventually causing your program to slow down or crash. The code below shows this problem in action.

from PIL import Image
img = Image.open('large_image.jpg')
# Processing without closing can lead to memory issues in loops
processed = img.resize((800, 600))
processed.save('resized_large.jpg')

The img object isn't closed after use, so it continues to occupy memory. While this is minor for one file, it becomes a major issue in larger scripts. The following example shows a more robust approach.

from PIL import Image
with Image.open('large_image.jpg') as img:
processed = img.resize((800, 600))
processed.save('resized_large.jpg')
# Image is automatically closed after the block

Using a with statement is the Pythonic way to handle files. When you open an image using with Image.open(...) as img, Python guarantees the file will be closed automatically once the code inside the block finishes. This is crucial for preventing memory leaks, especially when you're processing many images in a loop or working with very large files. It's a simple change that makes your code more robust and efficient by managing memory for you.

Resolving format compatibility issues with save()

You'll sometimes run into an error with the save() method if an image's mode is incompatible with the chosen file format. For instance, JPEGs don't support transparency, so saving an RGBA image as a .jpg will fail. The following code demonstrates this exact issue.

from PIL import Image
img = Image.open('sample.jpg')
# Converting to RGBA mode but saving as JPEG (which doesn't support alpha)
img_rgba = img.convert('RGBA')
img_rgba.save('transparent_image.jpg')

The script adds a transparency layer by converting the image to RGBA mode. Because the JPEG format can't handle transparency, the save() method fails. The following code demonstrates how to resolve this mismatch before saving.

from PIL import Image
img = Image.open('sample.jpg')
# Save RGBA images as PNG which supports transparency
img_rgba = img.convert('RGBA')
img_rgba.save('transparent_image.png')

The solution is to match the file format to the image's mode. Since JPEGs don't support transparency, saving the RGBA image as a .png resolves the error. The PNG format is built to handle alpha channels, so save('transparent_image.png') works perfectly. This issue often arises when you work with images that have special features like transparency or indexed color palettes, as not all formats are created equal.

Real-world applications

With a handle on the core techniques, you can now tackle practical workflows like automatically watermarking images or batch processing for social media.

Watermarking images with PIL

You can easily add a text watermark to your images using Pillow's ImageDraw module, which lets you draw text directly onto an image.

from PIL import Image, ImageDraw

img = Image.open('sample.jpg')
draw = ImageDraw.Draw(img)
watermark_text = '© 2023'
position = (img.width - 150, img.height - 50)
draw.text(position, watermark_text, fill=(255, 255, 255))
img.save('watermarked.jpg')
print("Watermark added to image")

This script overlays text onto an image by creating a drawing context with ImageDraw.Draw(). This object allows you to add elements like text directly to the pixel data.

  • The position is calculated dynamically from the image's dimensions, ensuring the watermark appears in the bottom-right corner.
  • The draw.text() method then renders the text, using the fill parameter to set its color to white.

The original image object is modified in place before being saved to a new file.

Batch processing images for social media

You can write a simple script to batch-process a single image, automatically creating optimized versions for every social media platform you use.

from PIL import Image
import os

img = Image.open('sample.jpg')
os.makedirs('social_media', exist_ok=True)

platforms = {
'instagram': (1080, 1080),
'twitter': (1200, 675),
'facebook': (1200, 630)
}

for platform, size in platforms.items():
resized = img.copy()
resized.thumbnail(size)
output_path = f"social_media/{platform}_image.jpg"
resized.save(output_path)
print(f"Created {platform} version: {size}")

This script automates creating multiple image versions from a single source. It loops through a dictionary that maps social media platforms to specific dimensions. For each platform, it creates a fresh copy of the source image using img.copy(), ensuring the original remains untouched.

  • The thumbnail() method resizes the copy. Unlike resize(), it maintains the aspect ratio while ensuring the image fits within the target (width, height).
  • Each optimized image is then saved to a social_media directory with a unique filename, like instagram_image.jpg.

Get started with Replit

Turn these concepts into a real tool. Describe what you want to build to Replit Agent, like “a web app that batch-resizes images” or “a utility that applies a grayscale filter to a folder of photos.”

It will write the code, test for errors, and deploy your app from a single prompt. Start building with Replit and see your ideas come to life.

Get started free

Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.

Get started for free

Create & deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.