Algorithmia

How to Rotate Images in Python Using a Horizon Detection Algorithm

Rotate an image and level it using an algorithmWhen we look at an image, it’s fairly easy to detect the horizon line.

For computers, this task is somewhat more difficult: they need to understand the basic structure of the image, locate edges which might indicate a horizon, and pare out the edges which do not matter. Fortunately, Algorithmia boils this all down to a single API call: just send your image to deep horizon, an algorithm for horizon detection, and it tells you where the horizon line is.

Let’s see how we can use this tool, in combination with Pillow (a fork of the Python Image Library), to automatically recompose any image so it’s level.

Step 1: Install the Algorithmia Client

While this demo is written using our Python client, our services are equally easy to use in a variety of other programming languages, or even via cURL. Installing the Algorithmia client is simple. Just use pip to install the package:

pip install algorithmia

You’ll also need a free Algorithmia account, which includes 5,000 free credits a month – enough to level thousands of images.

To get started, simply sign up, then grab your API key.

Step 2: Call the Deep Horizon Microservice

It only takes a couple lines of code to call any of our algorithms. Let’s wrap it up in a function:

import Algorithmia

def find_horizon(infile):
    """Find the horizon line on an image"""
    algo = client.algo('ukyvision/deephorizon/0.1.0')
    image = base64.b64encode(open(infile, "rb").read())
    return algo.pipe({'image':'data:image/jpg;base64,'+image}).result

This simply calls the version 0.1.0 of the ukyvision/deephorizon algorithm, passes it an image (after reading the file and base64 encoding the contents), and gets back the results, which will have the values “left” and “right”. Each of these is a pair of [x,y]  image space coordinates, which are the endpoints of the horizon line.

If we had omitted the version number and simply called client.algo(“ukyvision/deephorizon”), it would run the most recent version of the algorithm. If the author ever changes the API, this could break, so it is better to always include a specific version number in your Algorithmia calls.

Also note that we’re prefixing the image content with data:image/jpg;base64, to indicate that it is a base64-encoded jpg. This prefix would be slightly different for other filetypes / encodings. Deep Horizon also accepts image URLs or data URIs, which are especially useful for large files.

Step 2: Determine Image Rotation

Now that we know where the horizon line’s endpoints are, we need to calculate how many degrees of rotation will be needed to make the image level.

import math

def calculate_rotation(coords):
    """Transform coordinates {left: [x1,y1], right: [x2,y2]} to rotation, in degrees """
    (x1, y1) = coords['left']
    (x2, y2) = coords['right']
    slope = (y2-y1)/(x2-x1)
    return math.degrees(math.atan(slope))

From our high school math classes, we may remember that the slope of a line, in degrees, is the inverse-tangent of the rise divided by the run.

In other words, if we have coordinates [x1, y1] and [x2, y2] of a line, then the slope is tan-1 of (y2-y1) / (x2-x1).

Step 3: Rotate the Image

Pillow provides a one line command to rotate an image. We’ll add the library using pip:

pip install Pillow

from PIL import Image

def rotate_image(infile, outfile, degrees, crop):
    """Rotate an image by a number of degrees, crop if desired, and save to outfile"""
    Image.open(infile).rotate(degrees, expand=not crop, resample=Image.BILINEAR).save(outfile)

Then, we can create a utility function to rotate an image and save it as a new file. Pillow will also automatically crop the image, unless we disable this with the parameter ‘expand’. Lastly, we’ll get better image quality if we add bilinear resampling.

Step 5: Putting It All Together

Once we have these three functions, straightening an image is as simple as finding the horizon, calculating the slope, and then rotating by the negative of that slope:

# get your API key at algorithmia.com/user#credentials
client = Algorithmia.client('your_api_key')
infile = "/some/filename.jpg"
outfile = "/some/outputfile.jpg"
line = find_horizon(infile)
rotation = calculate_rotation(line)
rotate_image(infile, outfile, -rotation, True)

…and that’s all we need to do. We now have a script, we can read any JPG image, detect the horizon line, and straighten it out for you while automatically cropping and resampling the output image.

You could easily modify it to handle other filetypes, turn off cropping or resampling, or pull files from the web (or Dropbox or Amazon S3 via our Data API).

Tools used:

Here’s the whole script, ready for you to cut-and-paste, or grab it (and other fun examples) from Algorithmia’s sample-apps repository on Github

import Algorithmia
import base64
import math
from PIL import Image


def find_horizon(infile):
    """Find the horizon line on an image"""
    algo = client.algo('ukyvision/deephorizon/0.1.0')
    image = base64.b64encode(open(infile, "rb").read())
    return algo.pipe({'image':'data:image/jpg;base64,'+image}).result


def calculate_rotation(coords):
    """Transform coordinates {left: [x1,y1], right: [x2,y2]} to rotation, in degrees """
    (x1, y1) = coords['left']
    (x2, y2) = coords['right']
    slope = (y2-y1)/(x2-x1)
    return math.degrees(math.atan(slope))


def rotate_image(infile, outfile, degrees, crop):
    """Rotate an image by a number of degrees, crop if desired, and save to outfile"""
    Image.open(infile).rotate(degrees, expand=not crop, resample=Image.BILINEAR).save(outfile)

# get your API key at algorithmia.com/user#credentials
client = Algorithmia.client('your_api_key')
infile = "/some/filename.jpg"
outfile = "/some/outputfile.jpg"
line = find_horizon(infile)
rotation = calculate_rotation(line)
rotate_image(infile, outfile, -rotation, True)

Developer Evangelist for Algorithmia, helping programmers make the most efficient and productive use of their abilities.

More Posts