Create a Highly Scalable Image Processing Service on AWS Lambda and API Gateway

  • 2019-03-14 12:14 AM
  • 9

In this guide we'll be building an image processing service on AWS Lambda and API Gateway.

Now with that out of the way, let’s see how you can get a simple Python 3.6 application running that

  • converts an input photo into a black and white photo
  • has OpenCV Python dependency

accessible through an API that

  • accepts binary data payload (jpeg)
  • returns binary data payload (jpeg)

all without

  • provisioning a server
  • paying for it*

*Unless your traffic exceeds the generous Lambda free pricing tier.

First make sure you have Docker installed.

For this example we’ll use a service from AWS called Lambda that allows us to deploy our function and its dependencies and easily connect it to an API. In order to create the API we’ll use API Gateway — service also provided by AWS.

For the simplicity of this tutorial we’ll deploy our code by uploading it to Lambda via the AWS Web Console. We’ll also write our function code inside the AWS console to keep things simple. In any serious case you would do deployments via AWS CLI.

  1. Start by logging in to the AWS Console and search for Lambda.

Create a Highly Scalable Image Processing Service on AWS Lambda and API Gateway

2. Click on Create function.

Create a Highly Scalable Image Processing Service on AWS Lambda and API Gateway

3. Set up the function parametres.
We’re naming our function lambda-demo. Make sure to pick Python 3.6 as the Runtime and create a new role from AWS policy templates.

Create a Highly Scalable Image Processing Service on AWS Lambda and API Gateway

4. After creating the function, you’ll be given some template code in the Lambda console.

import json

def lambda_handler(event, context):
    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

You can invoke this function right away by configuring a test event. Click on Test and configure the first test event. For the purposes of this article, the default tempate works fine.

Create a Highly Scalable Image Processing Service on AWS Lambda and API Gateway

Create a Highly Scalable Image Processing Service on AWS Lambda and API Gateway

After creating the test event, click on Test. You should receive the following in the function logs:

{
  "statusCode": 200,
  "body": "\"Hello from Lambda!\""
}

Brilliant.

Now let’s create something more useful than that.

Let’s build a function that takes an image as input and turns it to grayscale. We will be using OpenCV for that, specifically its Python bindings. Although using OpenCV might be overkill for such a task, it demonstrates how such a useful library can be included in your Lambda environment with relative ease.

We will now

  1. Generate a Lambda-ready Python package for OpenCV.
  2. Upload that package into Lambda Layers so it can be used in any function you build.
  3. Import OpenCV to our Lambda function.

1. Generate Lambda-ready Python package for OpenCV

I’ve put together a dead simple tool — a Docker image that can gather any pip package and generate a .ZIP we can upload to Lambda Layers. If you want to explore the tool you can find it from LambdaZipper.

If you have Docker installed you can open your terminal and just run

docker run --rm -v $(pwd):/package tiivik/lambdazipper opencv-python

That’s it! In your current working directory you’ll find opencv-python.zip

One of the most useful serverless toolkits is serverless. However we are not going to use it in this example. Re-inventing the wheel is rarely a good idea, with an exception when you want to learn how things work under the hood. Although mature frameworks such as serverless exist, it is a good idea to dig into some of the core functionalities these frameworks abstract.

Let’s explore what the tool abstracted from us.

If you take a look at package.sh then you can see that it performed a pip install command with opencv-python argument. All that was executed in amazonlinux:2017.03 environment that, to some extent, mimics the AWS Lambda environment. You can explore the execution environment in the Dockerfile.

2. Upload the package into Lambda Layers so it can be used in any function you build

Let’s upload the opencv-python.zip to Lambda Layers so we can use that package from now on in all our functions. Think of Layers as data that can be used in any function you write. This can be Python modules, code snippets, binary files or anything.

Navigate to Layers panel in AWS Lambda and press Create layer.

Create a Highly Scalable Image Processing Service on AWS Lambda and API Gateway

Set up the layer name, description and upload the zip file. Make sure to select the correct runtime, in our case Python 3.6. Press Create layer.

Create a Highly Scalable Image Processing Service on AWS Lambda and API Gateway

As of writing this article, uploading the ZIP file from the web interface is limited to 50MB. Fortunately our opencv-python package is less than that. In case your package exceeds that you can provide the package as a link from an S3 bucket. Bear in mind that Lambda sets deployment package limit at 250MB.

After creating the function you should be greeted with a message

Successfully created layer opencv-python version 1.

Yay!

Let’s go back to our lambda-demo function and add the opencv-python layer to our function execution environment. Click on Layers > Add a layer and select your opencv-python layer.

Create a Highly Scalable Image Processing Service on AWS Lambda and API Gateway

3. Importing OpenCV

Let’s try importing the library as usual:

import json
import cv2

def lambda_handler(event, context):
    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

Now let’s click on Test. We’re provided with the response:

Response:
{
  "errorMessage": "Unable to import module 'lambda_function'"
}

For some reason Lambda wasn’t able to find our Python package. Let’s explore.

By default all Lambda layers are mounted to /opt. Let’s comment out our cv2 import and take a look what’s inside /opt.

import json
#import cv2
from os import listdir

def lambda_handler(event, context):
    # TODO implement
    print(listdir("/opt"))
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

In the function logs we can see our cv2 module in /opt.

[‘bin’, ‘cv2’, ‘numpy’, ‘numpy-1.16.2.dist-info’, ‘opencv_python-4.0.0.21.dist-info’]

By default /opt/binis added to the $PATHenvironment variable. You can reference that from AWS docs. However our layer modules exist in /opt/not in/opt/bin. So, let’s include /opt into$PATH as well so Lambda can see our package.

In Environment Variables section, add the following environment variable. Key: PYTHONPATH Value: /opt/

Create a Highly Scalable Image Processing Service on AWS Lambda and API Gateway

Typically you can just import the package without altering the path, but in this case it’s necessary for Lambda environment to detect our package.

Let’s modify our code:

import json
import cv2

def lambda_handler(event, context):
    # TODO implement
    print(cv2.__version__)
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

Save your changes and click Test. We are greeted with4.0.0 in the console, informing us the OpenCV version used.

Brilliant, Python OpenCV running in Lambda!

Let’s continue implementing the core application logic — converting images into grayscale. Let’s modify our Lambda function code:

import json
import cv2
import base64

def write_to_file(save_path, data):
  with open(save_path, "wb") as f:
    f.write(base64.b64decode(data))

def lambda_handler(event, context):
    # Write request body data into file
    write_to_file("/tmp/photo.jpg", event["body"])
    
    # Read the image
    image = cv2.imread("/tmp/photo.jpg")
    
    # Convert to grayscale
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    
    # Write grayscale image to /tmp
    cv2.imwrite("/tmp/gray.jpg", gray)
    
    # Convert grayscale image into utf-8 encoded base64
    with open("/tmp/gray.jpg", "rb") as imageFile:
      str = base64.b64encode(imageFile.read())
      encoded_img = str.decode("utf-8")
    
    # Return the data to API Gateway in base64.
    # API Gateway will handle the conversion back to binary.
    # Set content-type header as image/jpeg.
    
    return {
      "isBase64Encoded": True,
      "statusCode": 200,
      "headers": { "content-type": "image/jpeg"},
      "body":  encoded_img
    }

The API which we will set up in a moment will accept a binary image from the client. The binary image will then be converted into base64 by AWS API Gateway and passed into the Lambda.

Of course the API Gateway is not set up yet so testing this code with our current test will fail. However, before we move into setting up an API that invokes this Lambda, we can test it in the Lambda console by providing a base64 encoded image in the event body.

Reconfigure the Test with this body. In case you’re wondering, this is a base64 encoded cat photo 😺 https://imgur.com/a/0NpkzzL

{
    "body" : "/9j/4QcrRXhpZgAATU0AKgAAAAgADAEAAAMAAAABAkIAAAEBAAMAAAABAhYAAAECAAMAAAADAAAAngEGAAMAAAABAAIAAAESAAMAAAABAAEAAAEVAAMAAAABAAMAAAEaAAUAAAABAAAApAEbAAUAAAABAAAArAEoAAMAAAABAAIAAAExAAIAAAAgAAAAtAEyAAIAAAAUAAAA1IdpAAQAAAABAAAA6AAAASAACAAIAAgAFfkAAAAnEAAV+QAAACcQQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKQAyMDE5OjAzOjEwIDAwOjI3OjMwAAAEkAAABwAAAAQwMjIxoAEAAwAAAAH//wAAoAIABAAAAAEAAABAoAMABAAAAAEAAABAAAAAAAAAAAYBAwADAAAAAQAGAAABGgAFAAAAAQAAAW4BGwAFAAAAAQAAAXYBKAADAAAAAQACAAACAQAEAAAAAQAAAX4CAgAEAAAAAQAABaUAAAAAAAAASAAAAAEAAABIAAAAAf/Y/+0ADEFkb2JlX0NNAAH/7gAOQWRvYmUAZIAAAAAB/9sAhAAMCAgICQgMCQkMEQsKCxEVDwwMDxUYExMVExMYEQwMDAwMDBEMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAQ0LCw0ODRAODhAUDg4OFBQODg4OFBEMDAwMDBERDAwMDAwMEQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCABAAEADASIAAhEBAxEB/90ABAAE/8QBPwAAAQUBAQEBAQEAAAAAAAAAAwABAgQFBgcICQoLAQABBQEBAQEBAQAAAAAAAAABAAIDBAUGBwgJCgsQAAEEAQMCBAIFBwYIBQMMMwEAAhEDBCESMQVBUWETInGBMgYUkaGxQiMkFVLBYjM0coLRQwclklPw4fFjczUWorKDJkSTVGRFwqN0NhfSVeJl8rOEw9N14/NGJ5SkhbSVxNTk9KW1xdXl9VZmdoaWprbG1ub2N0dXZ3eHl6e3x9fn9xEAAgIBAgQEAwQFBgcHBgU1AQACEQMhMRIEQVFhcSITBTKBkRShsUIjwVLR8DMkYuFygpJDUxVjczTxJQYWorKDByY1wtJEk1SjF2RFVTZ0ZeLys4TD03Xj80aUpIW0lcTU5PSltcXV5fVWZnaGlqa2xtbm9ic3R1dnd4eXp7fH/9oADAMBAAIRAxEAPwDcxuntYwFrZj6StsrAScXsZLO3IHMfyf5SnVlVPOy722RLXtGjh+9tUV0WaiRYZBqmAptpLgHMLXtP5wOikarB+aT8NUUIi2Qq78Gt5lwBV303jlp+5P6T4ktIHidElOf+zqf3Qi147aoA0nhSuzKa52kPLfpEcD/ySfH3vYb7NHP0a391v/mSHELoJ4SBZf/Q6sQRHisTql1mFfWBWbq3kl1A1P8AxlT2++l62McEsBKqdUwsm3KqyMZxrsqY5g28u3Gdf6ihmNGfGdVY2Tj2NZBtrMghjiZP8h61as0iGtJa3nXnzCyqsS2msuLvUPNhOp0QbMhzb7at+rWh7HDt+7KiEiGYxBd1nUXFg3GXRqDpKo5mRU6S5zh7YLZOo/k/ylm151r3k/RMDzALvBW2N9R9rNCysgGeTI0SMiQoQALnUXm7IaxzH00FwFTHgkk/v2P/AD7Hf9BdLLQ0N8FljByxbUz1SKfUDnCOQP8ABtP5qnmnOxyXBu9nct1hPx6WT1Y8utV0f//R6nGP6MKt1LJtqs21yNAC7/yKtUfQCpdWdbXZurE726HvKhnszw3cnqmRmux/T6bkPrtDpsrMS9pHu9Pd/hGLGwL+qX5f2XNfZY41uLMhzCx25vv9LUD1G7Vsk2EEvYdx7kaH5atTvBNAvNgHpe+ZmB+c2f3Nv01EN2b6sKGXuEate/2k+SyK+sddszHs6e704e4NrdXuc6PZXdbbYNmzRdTZ1LpZoFLLGh5aII01OmihZj2VtFcgsaIDfzQB+cf3USAPHyRZ66ebcw82yGutube8NDbtrIaXAavr2/yloZFxrfuI9j4Id4yFjYFo9QMYyWjlw0kn92Vv72WM1Ac36MIjUIOkn//S62uQOPyIPUDX9ml7QddD4fkRQVV6nuON7eQZUUtizRHqDmtc5p2uDXVO7xwmfY+sH3AsMifI/m7YWRl5fUMcufjER3YRLQqWD9YOrWZL6721ei2qx5e0AHcwSxkO/wBI72KMWdiynTcN/CwMOnL9VjJaxxdjVkHbWT9Mtaf5X83+4t6u39G5trfe8QyNpdP7xH/fVzWR9Zra6z6FDfVA0a4ROsO2/wBVqzbPrT1UmDWyimwhrrWSXNn/AAm530UhGXVUpRL2jbHY7Rtd69z/AN3Qgd3emtXGeTUBumdVynR67rbDZc4vuEF7z+fH0Xaf4T+WuipuaTDzz37/ANuEBuo7P//Z/+0O4lBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAPHAFaAAMbJUccAgAAAgAAADhCSU0EJQAAAAAAEM3P+n2ox74JBXB2rq8Fw044QklNBDoAAAAAAOUAAAAQAAAAAQAAAAAAC3ByaW50T3V0cHV0AAAABQAAAABQc3RTYm9vbAEAAAAASW50ZWVudW0AAAAASW50ZQAAAABDbHJtAAAAD3ByaW50U2l4dGVlbkJpdGJvb2wAAAAAC3ByaW50ZXJOYW1lVEVYVAAAAAEAAAAAAA9wcmludFByb29mU2V0dXBPYmpjAAAADABQAHIAbwBvAGYAIABTAGUAdAB1AHAAAAAAAApwcm9vZlNldHVwAAAAAQAAAABCbHRuZW51bQAAAAxidWlsdGluUHJvb2YAAAAJcHJvb2ZDTVlLADhCSU0EOwAAAAACLQAAABAAAAABAAAAAAAScHJpbnRPdXRwdXRPcHRpb25zAAAAFwAAAABDcHRuYm9vbAAAAAAAQ2xicmJvb2wAAAAAAFJnc01ib29sAAAAAABDcm5DYm9vbAAAAAAAQ250Q2Jvb2wAAAAAAExibHNib29sAAAAAABOZ3R2Ym9vbAAAAAAARW1sRGJvb2wAAAAAAEludHJib29sAAAAAABCY2tnT2JqYwAAAAEAAAAAAABSR0JDAAAAAwAAAABSZCAgZG91YkBv4AAAAAAAAAAAAEdybiBkb3ViQG/gAAAAAAAAAAAAQmwgIGRvdWJAb+AAAAAAAAAAAABCcmRUVW50RiNSbHQAAAAAAAAAAAAAAABCbGQgVW50RiNSbHQAAAAAAAAAAAAAAABSc2x0VW50RiNQeGxAYgAAAAAAAAAAAAp2ZWN0b3JEYXRhYm9vbAEAAAAAUGdQc2VudW0AAAAAUGdQcwAAAABQZ1BDAAAAAExlZnRVbnRGI1JsdAAAAAAAAAAAAAAAAFRvcCBVbnRGI1JsdAAAAAAAAAAAAAAAAFNjbCBVbnRGI1ByY0BZAAAAAAAAAAAAEGNyb3BXaGVuUHJpbnRpbmdib29sAAAAAA5jcm9wUmVjdEJvdHRvbWxvbmcAAAAAAAAADGNyb3BSZWN0TGVmdGxvbmcAAAAAAAAADWNyb3BSZWN0UmlnaHRsb25nAAAAAAAAAAtjcm9wUmVjdFRvcGxvbmcAAAAAADhCSU0D7QAAAAAAEACQAAAAAQACAJAAAAABAAI4QklNBCYAAAAAAA4AAAAAAAAAAAAAP4AAADhCSU0EDQAAAAAABAAAAB44QklNBBkAAAAAAAQAAAAeOEJJTQPzAAAAAAAJAAAAAAAAAAABADhCSU0nEAAAAAAACgABAAAAAAAAAAI4QklNA/UAAAAAAEgAL2ZmAAEAbGZmAAYAAAAAAAEAL2ZmAAEAoZmaAAYAAAAAAAEAMgAAAAEAWgAAAAYAAAAAAAEANQAAAAEALQAAAAYAAAAAAAE4QklNA/gAAAAAAHAAAP////////////////////////////8D6AAAAAD/////////////////////////////A+gAAAAA/////////////////////////////wPoAAAAAP////////////////////////////8D6AAAOEJJTQQIAAAAAAAQAAAAAQAAAkAAAAJAAAAAADhCSU0EHgAAAAAABAAAAAA4QklNBBoAAAAAA3cAAAAGAAAAAAAAAAAAAABAAAAAQAAAACEAUwBjAHIAZQBlAG4AcwBoAG8AdAAgADIAMAAxADkALQAwADMALQAxADAAIABhAHQAIAAwADAALgAyADYALgAyADcAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAEAAAAAAABudWxsAAAAAgAAAAZib3VuZHNPYmpjAAAAAQAAAAAAAFJjdDEAAAAEAAAAAFRvcCBsb25nAAAAAAAAAABMZWZ0bG9uZwAAAAAAAAAAQnRvbWxvbmcAAABAAAAAAFJnaHRsb25nAAAAQAAAAAZzbGljZXNWbExzAAAAAU9iamMAAAABAAAAAAAFc2xpY2UAAAASAAAAB3NsaWNlSURsb25nAAAAAAAAAAdncm91cElEbG9uZwAAAAAAAAAGb3JpZ2luZW51bQAAAAxFU2xpY2VPcmlnaW4AAAANYXV0b0dlbmVyYXRlZAAAAABUeXBlZW51bQAAAApFU2xpY2VUeXBlAAAAAEltZyAAAAAGYm91bmRzT2JqYwAAAAEAAAAAAABSY3QxAAAABAAAAABUb3Ag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MCIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSIyOERCRTU2MDNFOTg1OTU0NjZEQjg5ODVFRTU2MEI0MCIgZGM6Zm9ybWF0PSJpbWFnZS9qcGVnIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiBwaG90b3Nob3A6SUNDUHJvZmlsZT0iZi5sdXggcHJvZmlsZSIgeG1wOkNyZWF0ZURhdGU9IjIwMTktMDMtMTBUMDA6MjY6MjkrMDI6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDE5LTAzLTEwVDAwOjI3OjMwKzAyOjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDE5LTAzLTEwVDAwOjI3OjMwKzAyOjAwIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MDE4MDExNzQwNzIwNjgxMTgyMkE5MEM0MkExQjY0QzAiIHN0RXZ0OndoZW49IjIwMTktMDMtMTBUMDA6Mjc6MzArMDI6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKE1hY2ludG9zaCkiIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjAyODAxMTc0MDcyMDY4MTE4MjJBOTBDNDJBMUI2NEMwIiBzdEV2dDp3aGVuPSIyMDE5LTAzLTEwVDAwOjI3OjMwKzAyOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDwvcmRmOlNlcT4gPC94bXBNTTpIaXN0b3J5PiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg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"
}

Create a Highly Scalable Image Processing Service on AWS Lambda and API Gateway

Invoking this test should now succeed with:

Response:
{
  "isBase64Encoded": true,
  "statusCode": 200,
  "headers": {
    "content-type": "image/jpeg"
  },
  "body": "/9j/4AJRgAB.....P+WqHNf//Z" <- long base64 string of black and white image here
}

We’re now ready to set up an API that invokes this Lambda function.

Setting up the API

Open AWS API Gateway console. Press Create API.

Create a Highly Scalable Image Processing Service on AWS Lambda and API Gateway

Create a new REST API, provide API name and description. In this case we’re calling our API lambda-demo.

Create a Highly Scalable Image Processing Service on AWS Lambda and API Gateway

From Resources > Actions choose Create Method and define a POST method.

Create a Highly Scalable Image Processing Service on AWS Lambda and API Gateway

For the Integration type choose Lambda Function and pick your Lambda function from the dropdown menu. Enable Use Lambda Proxy integration and hit Save.

Create a Highly Scalable Image Processing Service on AWS Lambda and API Gateway

We want our API to be able to handle binary data.

From Settings > Binary Media Types click Add Binary Media Type and define the binary types as:

image/jpeg
image/png
*/*

Press Save Changes.

Create a Highly Scalable Image Processing Service on AWS Lambda and API Gateway

Navigate back to our POST method.

Under Method Response add a Content-Type Response Header and set the type to image/jpeg:

Create a Highly Scalable Image Processing Service on AWS Lambda and API Gateway

Before publishing the API you can test it by clicking on the Client Test button:

Create a Highly Scalable Image Processing Service on AWS Lambda and API Gateway

In this case we are providing the base64 image body itself, not a json object. For convenience you can try pasting the raw base64 string from the following link into the Request Body field cat_base64_body.

Response is a base64 string of the black and white photo.

Create a Highly Scalable Image Processing Service on AWS Lambda and API Gateway

Click on Actions >Deploy API.

Create a new Deployment stage, give it a descriptive name, for example development and press Deploy.

Create a Highly Scalable Image Processing Service on AWS Lambda and API Gateway

The API is now live and functional! You’ll receive a url where your API is deployed:

[https://XXXXX.execute-api.XXXX.amazonaws.com/development](https://XXXXX.execute-api.eu-central-1.amazonaws.com/development)

Let’s try it out!

  1. Let’s download the same photo to our local environment
curl https://i.imgur.com/offvirS.jpg -o kitty.jpg

2. Post the image to our API as binary data. Result is a black and white image in your current working directory. 🎉

curl -X POST --data-binary @kitty.jpg https://XXXXX.execute-api.eu-central-1.amazonaws.com/development -o kitty_bw.jpg

Bear in mind that for the simplicity of this tutorial there is no error handling, request validation or authorization set up. For a production application these should be set up in API Gateway and your Lambda function code.

That’s it, I hope you found this guide useful!

Learn More

AWS Certified Solution Architect Associate
Computer Vision Using OpenCV
An A-Z of useful Python tricks
A Complete Machine Learning Project Walk-Through in Python
Learning Python: From Zero to Hero
Build a Serverless App with AWS Lambda - Hands On!
Amazon Web Services - Web Hosting & Cloud Computing With AWS
Amazon Web Services - Learn 7 AWS Services in 7 days
Complete Python Bootcamp: Go from zero to hero in Python 3
Machine Learning A-Z™: Hands-On Python & R In Data Science

Originally published by Rainer Selvet at https://itnext.io

Suggest