
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 todeep horizon, analgorithm 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 ClientWhile this demo is written using ourPython 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 organize thousands of documents by language.
To get started, simplysign up, thengrab your API key.
Step 2: Call the Deep Horizon MicroserviceIt only takes a couple lines of code to call any of our algorithms. Let’s wrap it up in a function:
import Algorithmiaimport base64
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 ordata URIs, which are especially useful for large files.
Step 2: Determine Image RotationNow 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 mathdef 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 ImagePillow provides a one line command to rotate an image. We’ll add the library using pip:
pip install Pillow
from PIL import Imagedef 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 TogetherOnce 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:
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 (orDropbox or Amazon S3 via ourData API).
Tools used: Algorithmia Client for Python Horizon Detection Algorithm Pillow Image LibraryHere’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 Algorithmiaimport 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) Jon Peck
Developer Evangelist for Algorithmia, helping programmers make the most efficient and productive use of their abilities.
More Posts