MEDIA GUIDES / Image Effects

Learning to Transform Images using Python

Image transformation is a common task that has many applications in areas like social media (think about cropping, resizing, and applying filters to your photos), computer vision (processing tasks like normalization, feature extraction, segmentation, etc), web development, photography, and a wide variety of others. In this comprehensive guide, you’ll learn everything you need to know about transforming images using Python, one of the most popular programming languages for image processing.

Here’s a summary of what you’ll learn:

What is Image Transformation in Python?

Image transformation in Python simply means modifying an image in some way using Python programming. These modifications can range from simple tasks like resizing an image to more complex operations like rotating it at a specific angle or changing its perspective.

We do this by manipulating the pixels, which are the tiny dots that make up an image. Each pixel has a color, and by changing the arrangement or color values of these pixels, we can achieve different effects.

Image transformations can be divided into the following categories:

  • Geometric transformations: These involve changing the shape, size, or position of an image without altering its colors. Examples include: scaling (making images bigger or smaller), rotation, translation, and so on.
  • Color transformations: This involves altering an image’s color characteristics, such as its color model, intensity, or hue. Examples include, brightness and saturation adjustments, posterization, grayscale transformation, color conversion and balancing, etc.
  • Advanced transformations: These involve using sophisticated methods to alter images and are commonly used in areas such as computer vision, medical imaging. Examples include image segmentation, morphing, generative AI transformations, etc.

Python is a versatile programming language with many tools and libraries that enable image processing, such as image transformations. Some of these libraries include OpenCV, Pillow, SciPy, SimpleCV, and many more. However, our focus in this article will be on OpenCV.

Exploring OpenCV: Python’s Main Tool for Editing Photos

OpenCV is an open-source library of programming functions and algorithms mainly for real-time computer vision. It’s originally developed by Intel and written in C++. However, it has bindings and wrappers in other programming languages like Python, Java, Go, etc, which makes it widely popular and a de-facto standard for all things image processing in computer vision.

opencv-python is the official wrapper package for OpenCV Python bindings. It allows developers to access and utilize the extensive functionalities of OpenCV within Python applications. This integration combines the power of OpenCV’s algorithms and functions for image and video processing with the simplicity and efficiency of the Python programming language.

In the next sections, we’ll show you how to use opencv-python to perform various types of image transformations.

Getting Started with opencv-python

To install opencv-python using Pip, run the following command:

pip install opencv-python

You can download the image used in the examples below from here and save it in your project directory (ideally in a virtual environment) – we saved it as sample-image.webp.

Next, create a file main.py with the following code:

import cv2

# Load the image
image = cv2.imread('sample-image.webp')

cv2.imshow('My Image', image)

cv2.waitKey(0)
cv2.destroyAllWindows()

Run the code with python main.py or python3 main.py. A window will pop up showing your image. Press the Esc key to close it. If this works, you’re ready to transform images.

Scaling an Image with Python

Scaling an image means changing its size, such as making it bigger or smaller. This is useful for creating thumbnails, fitting images to a website, or reducing file size. OpenCV makes scaling easy with the cv2.resize() function, which has the following syntax:

resize(src, dsize[, dist[, fx[, fy[, interpolation]]]])

Where:

  • src: The input image file source.
  • dsize: Short for Destination Size. This is the width and height values you want to resize the image to.
  • dist: Short for Destination. This is an optional argument that stores the output image in a predefined variable.
  • fx and fy: Optional values to scale the image in factors in the x and y directions.
  • interpolation: An optional argument that determines how OpenCV fills in missing pixels when enlarging or merges them when shrinking.

For example, our sample image has an original dimension of 3648×5472. Here’s the code to resize it to 400×800 pixels:

import cv2

# Load the image
image = cv2.imread('sample-image.webp')

# Resize to 400x800 pixels
resized_image = cv2.resize(image, (400, 800))

# Save the resized image
cv2.imwrite('resized_photo.jpg', resized_image)

# Display the resized image
cv2.imshow('Resized Image', resized_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

When you run the above code, a new file named resized_photo.jpg will be created in your project directory.

Scaling with a Factor

Alternatively, you can also scale an image by a percentage (such as 50% smaller), using the fx and fy parameters. For example, the following code will scale down the sample image to 1824×2736, which is half the original dimension:

# Scale to 50% of original size
resized_image = cv2.resize(image, None, fx=0.5, fy=0.5)

Image Translation

Image translation is the process of shifting an image’s pixels in a specific direction. This could be horizontally (along the x-axis) or vertically (along the y-axis). OpenCV uses a 2×3 transformation matrix to define how an image should be shifted. The matrix looks like this:

M = [[1, 0, tx]],
        [0, 1, ty]]

The cv2.wrapAffine() function in OpenCV is specifically useful for performing operations like translation, rotation, scaling, and shearing.

Here’s an example that moves the sample image 300 pixels up and 200 pixels down:

import cv2
import numpy as np

# Load the image
image = cv2.imread('sample-image.webp')

# Get the image dimensions
height, width = image.shape[:2]

# Define the translation matrix
tx, ty = 300, 200  # Move 300 pixels right, 200 pixels down
translation_matrix = np.float32([[1, 0, tx], [0, 1, ty]])

# Apply translation
translated_image = cv2.warpAffine(image, translation_matrix, (width, height))

# Save the image
cv2.imwrite('translated_image.jpg', translated_image)

In the above code, we’re using numpy’s (Numpy is installed automatically with opencv-python as a dependency) float32 function to create the matrix.

This is what the translated image looks like:

Notice the parts of the image that move outside the frame disappear, leaving black borders (default) around the image. You can change how empty areas are filled with the borderMode parameter.

For example, the following snippet turns the border to purple:

translated_image = cv2.warpAffine(image, translation_matrix, (width, height), borderMode=cv2.BORDER_CONSTANT, borderValue=(255, 34, 204))

How to Create Mirrors and Rotate Images

Creating a mirror image involves flipping an image either horizontally or vertically or around both axes. OpenCV provides the cv2.flip() function for this. Rotating an image, on the other hand, involves turning it around a specific point, such as the center, by a certain angle.

Create Mirror Image

The cv2.flip() function has the following syntax:

cv2.flip(src, flipCode[, dst] )

Where:

  • src: The source image that you want to flip.
  • flipCode: An integer specifying the type of flip to perform; 1 to flip the image horizontally; 0 to flip the image vertically; -1 to flip both axes (horizontally and vertically).
  • dst (optional): The destination array where the result will be stored. If not provided, OpenCV will create and return a new image.

For example, here’s the code to flip our sample image horizontally:

import cv2
# Load the image
image = cv2.imread('sample-image.webp')
# Flip horizontally
flipped_image_y = cv2.flip(image, 1)
# Save the image
cv2.imwrite('flipped_image_y.jpg', flipped_image_y)

To flip the image vertically, change the integer value to 0:

# Flip vertically
flipped_image_y = cv2.flip(image, 0)

And to flip the image around both axes, use -1:

# Flip both axes
flipped_image_y = cv2.flip(image, -1)

Rotate an Image

To rotate an image with OpenCV, we’ll use the cv2.getRotationMatrix2D() function to create a rotation matrix and then, use cv2.warpAffine() to apply the rotation.

Here’s an example to rotate our sample image 45 degrees clockwise:

import cv2
# Load the image
image = cv2.imread('sample-image.webp')
# Get image dimensions
height, width = image.shape[:2]
# Rotate 45 degrees
angle = 45
center = (width // 2, height // 2)
# Create rotation matrix
rotation_matrix = cv2.getRotationMatrix2D(center, angle, scale = 1.0) # The scale is set to 1.0, meaning the image size remains unchanged during rotation.
# Apply rotation
rotated_image = cv2.warpAffine(image, rotation_matrix, (width, height))
# Save rotated image
cv2.imwrite('rotated_image.jpg', rotated_image)

Producing the following output:

Notice that parts of the image were cut off – this is due to the warpAffine function fitting the rotated image into the original (width, height) canvas, cutting off the image corners.

We can prevent this by calculating a new bounding box dimension after rotation and adjusting the rotation matrix to translate the rotated image so it’s centered on the expanded canvas. Here’s the code for that:

import cv2
import numpy as np
# Load the image
image = cv2.imread('sample-image.webp')
height, width = image.shape[:2]
# Rotation settings
angle = 45
scale = 1.0
center = (width / 2, height / 2)
# Step 1: Get rotation matrix
rotation_matrix = cv2.getRotationMatrix2D(center, angle, scale)
# Step 2: Compute new bounding dimensions
radians = np.deg2rad(angle)
cosA = abs(np.cos(radians))
sinA = abs(np.sin(radians))
new_width = int((height * sinA) + (width * cosA))
new_height = int((height * cosA) + (width * sinA))
# Step 3: Adjust the rotation matrix to account for translation
rotation_matrix[0, 2] += (new_width / 2) - center[0]
rotation_matrix[1, 2] += (new_height / 2) - center[1]
# Step 4: Perform the rotation with expanded canvas
rotated_image = cv2.warpAffine(image, rotation_matrix, (new_width, new_height))
# Save the rotated image
cv2.imwrite('rotated_image.jpg', rotated_image)

Now you can rotate the image without it getting cropped:

Simplifying Image Transformation in Python with Cloudinary

While OpenCV is powerful, sometimes you might be looking for a simpler way to perform image transformations, especially if you’re working on web applications or need to handle image uploading, storage, and delivery along with transformations. Cloudinary is a cloud-based platform that simplifies image transformations, especially for web applications. It handles tasks like resizing, cropping, and applying effects without needing to write complex code.

To start using Cloudinary, you need to sign up for an account to get your credentials (cloud Name, API key, and API secret), which will be used to authenticate your requests.

The Cloudinary Python SDK provides useful methods and functions for integrating Cloudinary seamlessly within your existing Python application through an intuitive and flexible API.

You can install the SDK with pip using the following command:

pip install cloudinary

To avoid exposing your credentials in your application, create a .env file and add them there:

CLOUDINARY_CLOUD_NAME=YOUR_CLOUDINARY_CLOUD_NAME
CLOUDINARY_API_KEY=YOUR_CLOUDINARY_API_KEY
CLOUDINARY_API_SECRET=YOUR_CLOUDINARY_API_SECRET

Then install python-dotenv to load the environment variables into your application:

pip install python-dotenv

The following example applies image rotation and cropping to the sample image, returning a URL to view the transformation result:

import cloudinary
import cloudinary.uploader
import cloudinary.api
from cloudinary.utils import cloudinary_url
import os
from dotenv import load_dotenv

# Load the environment variables from the .env file
load_dotenv()
 
# Access environment variables
cloudinary_cloud_name = os.getenv("CLOUDINARY_CLOUD_NAME")
cloudinary_api_key = os.getenv("CLOUDINARY_API_KEY")
cloudinary_api_secret = os.getenv("CLOUDINARY_API_SECRET")

cloudinary.config(
  cloud_name = cloudinary_cloud_name,
  api_key = cloudinary_api_key,
  api_secret = cloudinary_api_secret
)

# Upload the image to Cloudinary
def upload_image(file_path):
    try:
        result = cloudinary.uploader.upload(file_path, resource_type="image")
        print("Image uploaded successfully")
        return result.get("public_id")
    except Exception as e:
        print(f"Error uploading image: {e}")
        return None
    
# Apply rotation and return the transformed URL
def rotate_image(file_path):
    public_id = upload_image(file_path)
    if not public_id:
        return None

    print("Applying transformations...")
    try:
        transformed_url, _ = cloudinary_url(
            public_id,
            fetch_format="png",
            quality="auto",
            transformation=[
                {'width': 600, 'height': 600, 'crop': 'fill'},
                {'angle': 45},
            ]
        )
        return transformed_url
    except Exception as e:
        print(f"Error applying transformation: {e}")
        return None

# Example usage
image_path = "sample-image.webp"
transformed_image = rotate_image(image_path)
    
print(transformed_image)

Here’s the generated URL:

<ahref="https://res.cloudinary.com/cloudinarymich/image/upload/c_fill,h_600,w_600/a_45/f_png,q_auto/ap6cm2gqb4a8hsdqwnsl">https://res.cloudinary.com/cloudinarymich/image/upload/c_fill,h_600,w_600/a_45/f_png,q_auto/ap6cm2gqb4a8hsdqwnsl

Notice how Cloudinary automatically adjusts the image to fit in the bounding box without cutting parts of the image off–this is one of the several advantages of using Cloudinary to handle your image processing tasks.

Additionally, Cloudinary uses its own resources to transform your images, allowing you to process thousands of images at scale without worrying about overloading your own servers or slowing down your applications. They also deliver your images via a global CDN, allowing users anywhere in the world to receive images quickly, with reduced latency and optimized formats for their device and connection.

Apart from rotating and cropping images, there are several other types of transformations you can achieve with Cloudinary, including:

  • Adding effects and enhancements
  • Background removal
  • Text and object overlays
  • Generative AI transformations
  • Face and object detection
  • Animated images
  • and more!

Wrapping Up

The ability to transform images using Python opens up a world of creative possibilities and practical applications. Whether you’re enhancing your personal photos, working on computer vision projects, or building web applications, the examples you’ve learned in this article will serve as a solid foundation in your journey through the amazing world of image editing with Python!

As we’ve shown, OpenCV allows you to perform detailed transformations like scaling, translation, and rotation directly on your computer, while Cloudinary simplifies the process for web applications, making it easy to deliver optimized images. Your choice between the two will often depend on your project’s specific requirements, as both tools offer unique advantages.

Sign up for free to start enjoying Cloudinary’s capabilities in your image editing workflow.

Frequently Asked Questions

Do I need to know advanced math to use OpenCV?

You don’t need to be an expert in advanced math to use OpenCV for basic to intermediate image transformation – OpenCV handles the complex calculations automatically behind the scenes. However, a basic understanding of a few mathematical concepts, such as linear algebra and trigonometry can be very helpful in understanding how some of OpenCV’s functions work.

Why do my rotated images have black borders?

When you rotate an image using OpenCV, the original rectangular shape of the image no longer perfectly aligns with the canvas. The corners of the rotated image will extend beyond the original dimensions.

QUICK TIPS
Colby Fayock
Cloudinary Logo Colby Fayock

In my experience, here are tips that can help you better master image transformation using Python beyond what the article covered:

  1. Use lookup tables (LUTs) for fast color adjustments
    For repetitive color transformations like gamma correction or histogram equalization, precompute values using LUTs. This drastically speeds up processing time compared to pixel-wise operations in loops.
  2. Leverage NumPy slicing for instant mirroring and cropping
    For mirroring, flipping, and cropping, NumPy array slicing is often faster and more memory-efficient than OpenCV functions, especially when dealing with real-time image streams.
  3. Preprocess with integral images for fast regional analysis
    Use cv2.integral() for computing summed-area tables, which allows quick regional mean or sum calculations — excellent for dynamic contrast adjustments and local transformations.
  4. Batch-process with multiprocessing or Dask
    When handling large batches of image transformations, Python’s multiprocessing or libraries like Dask can significantly improve throughput by parallelizing tasks.
  5. Use cv2.remap for complex transformations
    Instead of chaining multiple affine transformations, predefine your transformation mapping using cv2.remap() for precise control over non-linear warps, fisheye corrections, or lookup-based warping.
  6. Integrate with GPU acceleration using CuPy or OpenCV CUDA modules
    For large image sets or high-resolution video, offload intensive operations like filtering, resizing, or edge detection to GPU using OpenCV’s CUDA support or CuPy, a GPU-compatible NumPy alternative.
  7. Build pipelines with scikit-image for composability
    Combine OpenCV with scikit-image when you need more composable, functional-style processing pipelines — especially useful for integrating segmentation, morphology, or texture analysis.
  8. Use memory-mapped I/O for huge image datasets
    When working with massive image archives (e.g., satellite imagery), use memory-mapped files (np.memmap) to load and transform slices of data without exhausting system RAM.
  9. Encode transformations as JSON or YAML configs
    For maintainable, reusable workflows in production systems, serialize your image transformation parameters into structured config files — then load them dynamically into your processing scripts.
  10. Validate transformations visually with difference maps
    Use cv2.absdiff() or SSIM (from skimage.metrics) to compare before-and-after images. This helps catch subtle transformation errors — like pixel drift or unintentional cropping — especially in batch jobs.
Last updated: Oct 6, 2025