How to Rotate Images in Python Using a Horizon Detection Algorithm

February 22, 2017
by
· 4 min read

This article was originally published at Algorithimia’s website. The company was acquired by DataRobot in 2021. This article may not be entirely up-to-date or refer to products and offerings no longer in existence. Find out more about DataRobot MLOps here

When 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: 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.

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)

Demo
See DataRobot MLOps in Action
Request a demo
About the author
DataRobot

The Next Generation of AI

DataRobot AI Cloud is the next generation of AI. The unified platform is built for all data types, all users, and all environments to deliver critical business insights for every organization. DataRobot is trusted by global customers across industries and verticals, including a third of the Fortune 50. For more information, visit https://www.datarobot.com/.

Meet DataRobot
  • Listen to the blog
     
  • Share this post
    Subscribe to DataRobot Blog
    Thank you

    We will contact you shortly

    Thank You!

    We’re almost there! These are the next steps:

    • Look out for an email from DataRobot with a subject line: Your Subscription Confirmation.
    • Click the confirmation link to approve your consent.
    • Done! You have now opted to receive communications about DataRobot’s products and services.

    Didn’t receive the email? Please make sure to check your spam or junk folders.

    Close

    Newsletter Subscription
    Subscribe to our Blog