May 07, 2019     2min read

OpenCV 4.0 Serverless Lambda


Get the code for this post!

t04glovern/aws-opencv-serverless

OpenCV version 4.0 came out recently and It's not uncommon for me to have a use case for processing images. For this reason I worked on building a small serverless example of using OpenCV 4.0 on lambda; deployed using serverless framework.

Overview

The goal of this post is to quickly show you how to perform a simple image processing technique using OpenCV. You can see an example of what we want to achieve below.

Process an extract and detect images within the page
Process an extract and detect images within the page

Setup

The code for this post can be found at https://github.com/t04glovern/aws-opencv-serverless. I would recommend pulling it down using the following

git clone https://github.com/t04glovern/aws-opencv-serverless.git

If you are starting a version of this repo from scratch, you can go ahead an create a new serverless project by running the following

mkdir opencv-serverless
cd opencv-serverless
serverless create --template aws-python3 --name opencv-serverless

Python Requirements

In order to include OpenCV and a few other small pip packages on the lambda we'll need to make use of a nice serverless plugin called serverless-python-requirements. You can install it by running the following

serverless plugin install -n serverless-python-requirements

Next create a requirements.txt file within the opencv-serverless directory and put the following in it.

opencv-python==4.1.0.25
numpy==1.16.3

Finally add some custom parameters to serverless.yml to adjust the way python is dockerized.

...
custom:
  pythonRequirements:
    dockerizePip: non-linux
    noDeploy: []
...

OpenCV handler

Next we need to create our OpenCV code within the handler.py file that would have been created for you. Go ahead and replace the contents with the following:

import boto3
import cv2
import numpy as np
import uuid
import os

s3Client = boto3.client('s3')


def opencv(event, context):
    bucketName = event['Records'][0]['s3']['bucket']['name']
    bucketKey = event['Records'][0]['s3']['object']['key']

    download_path = '/tmp/{}{}'.format(uuid.uuid4(), bucketKey)
    output_path = '/tmp/{}'.format(bucketKey)

    s3Client.download_file(bucketName, bucketKey, download_path)

    try:
        img = cv2.imread(download_path)
        gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

        kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (8,8))
        gray = cv2.dilate(gray, kernel, iterations=1)

        ret,gray = cv2.threshold(gray, 254, 255, cv2.THRESH_TOZERO)
        ret,gray = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV)

        gray = cv2.morphologyEx(gray, cv2.MORPH_CLOSE, kernel)
        gray = cv2.morphologyEx(gray, cv2.MORPH_OPEN, kernel)

        contours, _ = cv2.findContours(gray, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

        for index in range(len(contours)):
            i = contours[index]
            area = cv2.contourArea(i)
            if area > 500:
                peri = cv2.arcLength(i,True)
                approx = cv2.approxPolyDP(i,0.1*peri,True)
                if len(approx)==4:
                        hull = cv2.convexHull(contours[index])
                        cv2.imwrite(output_path, cv2.drawContours(img, [hull], 0, (0,255,0),3))
    except Exception as e:
        print(e)
        print('Error processing file with OpenCV')
        raise e
    try:
        s3Client.upload_file(output_path, os.environ['OPENCV_OUTPUT_BUCKET'], bucketKey)
    except Exception as e:
        print(e)
        print('Error uploading file to output bucket')
        raise e
    return bucketKey

Huge thanks to lorenagdl for their C++ implementation of the code above.

The process is:

  • file will come in to a bucket
  • Bucket event fires and lambda gets the bucket name and key
  • Processes the image with OpenCV
  • Spits the output into an output bucket

Serverless Events and Resources

The final step is to add a couple things to our serverless.yml for the remaining resources, events and permissions.

custom

Although not specifically required, I'm going to hardcode the bucket names I'd like to assign to this project. For your implementation you will need to make sure you change these to something unique.

custom:
  pythonRequirements:
    dockerizePip: non-linux
    noDeploy: []
  OPENCV_PROCESS_BUCKET: devopstar-opencv-processing-bucket
  OPENCV_OUTPUT_BUCKET: devopstar-opencv-output-bucket
provider

Here we add the iamRoleStatements that allow our lambda to GET and PUT to S3. We're also defining an environment variable to pass to the lambda code.

provider:
  name: aws
  runtime: python3.7
  iamRoleStatements:
    - Effect: Allow
      Action:
        - s3:GetObject
      Resource:
        - arn:aws:s3:::${self:custom.OPENCV_PROCESS_BUCKET}/*
    - Effect: Allow
      Action:
        - s3:PutObject
      Resource:
        - arn:aws:s3:::${self:custom.OPENCV_OUTPUT_BUCKET}/*
  environment:
    OPENCV_OUTPUT_BUCKET: ${self:custom.OPENCV_OUTPUT_BUCKET}
functions

Defining a function called opencv we're also adding an event to the lambda that will fire off whenever an item is put into the bucket

functions:
  opencv:
    handler: handler.opencv
    events:
      - s3:
          bucket: ${self:custom.OPENCV_PROCESS_BUCKET}
          event: s3:ObjectCreated:*
resources

Finally we define the resource definition for the output bucket that will be receiving our processed images.

resources:
  Resources:

    S3Bucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:custom.OPENCV_OUTPUT_BUCKET}

Deploy

Deploying our new serverless function is actually super easy

serverless deploy

Testing

To test if the function is working, you can run the following command to put and retrieve images (make sure to replace the bucket names with your own).

aws s3 cp in/square-test.png s3://devopstar-opencv-processing-bucket/square-test.png
aws s3 cp s3://devopstar-opencv-output-bucket/square-test.png out/square-test.png
devopstar

DevOpStar by Nathan Glover | 2020