Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

An example of calling AWS Rekognition HTTP API from Python

I'd like to try Rekognition's CompareFaces, but I don't see a full example of the syntax for using the HTTP API. Assuming I have two images, how would I call this API from Python to retrieve a similarity score?

like image 626
jensph Avatar asked Dec 30 '16 00:12

jensph


1 Answers

Information about the code

There is little documentation around using the HTTP API for AWS Rekognition, but it is pretty simple to use the model that most code uses to hit AWS service HTTP endpoints.

Important information about the code that follows:

  • You must have requests installed. If you don't have it, you can run the following in your shell (doing it in virtualenv is recommended).

    pip install requests
    
  • The us-east-1 region is used. Rekognition is currently supported in us-east-1, eu-west-1, and us-west-2 so you can modify the code to support different region endpoints as you wish.

  • It expects two files to exist on disk for reading, called source.jpg and target.jpg.

    As she's in the most recent movie I saw, I'm using images of Felicity Jones from Star Wars: Rogue One as my source and target.

    The source.jpg is: Felicity Jones source image

    The target.jpg is: Felicity Jones target image

  • It includes code to do signing with AWS Signature Version 4. There are libraries out there that will do the signature generation for you, but I didn't want to rely too much on third party libs in order to demonstrate a complete example.

  • The AWS credentials you're using should have a valid policy for Rekognition.

  • It was written for Python 2.7 (shouldn't be terribly difficult to move to Python 3).


The Code

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import base64
import datetime
import hashlib
import hmac
import json

import requests

# Key derivation functions
# http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-python
def sign(key, msg):
    return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()


def getSignatureKey(key, date_stamp, regionName, serviceName):
    kDate = sign(('AWS4' + key).encode('utf-8'), date_stamp)
    kRegion = sign(kDate, regionName)
    kService = sign(kRegion, serviceName)
    kSigning = sign(kService, 'aws4_request')
    return kSigning


if __name__ == '__main__':
    # Read credentials from the environment
    access_key = os.environ.get('AWS_ACCESS_KEY_ID')
    secret_key = os.environ.get('AWS_SECRET_ACCESS_KEY')

    # Uncomment this line if you use temporary credentials via STS or similar
    #token = os.environ.get('AWS_SESSION_TOKEN')

    if access_key is None or secret_key is None:
        print('No access key is available.')
        sys.exit()

    # This code shows the v4 request signing process as shown in
    # http://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html

    host = 'rekognition.us-east-1.amazonaws.com'
    endpoint = 'https://rekognition.us-east-1.amazonaws.com'
    service = 'rekognition'

    # Currently, all Rekognition actions require POST requests
    method = 'POST'

    region = 'us-east-1'

    # This defines the service target and sub-service you want to hit
    # In this case you want to use 'CompareFaces'
    amz_target = 'RekognitionService.CompareFaces'



    # Amazon content type - Rekognition expects 1.1 x-amz-json
    content_type = 'application/x-amz-json-1.1'

    # Create a date for headers and the credential string
    now = datetime.datetime.utcnow()
    amz_date = now.strftime('%Y%m%dT%H%M%SZ')
    date_stamp = now.strftime('%Y%m%d') # Date w/o time, used in credential scope

    # Canonical request information
    canonical_uri = '/'
    canonical_querystring = ''
    canonical_headers = 'content-type:' + content_type + '\n' + 'host:' + host + '\n' + 'x-amz-date:' + amz_date + '\n' + 'x-amz-target:' + amz_target + '\n'

    # list of signed headers
    signed_headers = 'content-type;host;x-amz-date;x-amz-target'

    # Our source image: http://i.imgur.com/OK8aDRq.jpg
    with open('source.jpg', 'rb') as source_image:
        source_bytes = base64.b64encode(source_image.read())

    # Our target image: http://i.imgur.com/Xchqm1r.jpg
    with open('target.jpg', 'rb') as target_image:
        target_bytes = base64.b64encode(target_image.read())

    # here we build the dictionary for our request data
    # that we will convert to JSON
    request_dict = {
            'SimilarityThreshold': 75.0,
            'SourceImage': {
                'Bytes': source_bytes
            },
            'TargetImage': {
                'Bytes': target_bytes
            }
    }

    # Convert our dict to a JSON string as it will be used as our payload
    request_parameters = json.dumps(request_dict)

    # Generate a hash of our payload for verification by Rekognition
    payload_hash = hashlib.sha256(request_parameters).hexdigest()

    # All of this is 
    canonical_request = method + '\n' + canonical_uri + '\n' + canonical_querystring + '\n' + canonical_headers + '\n' + signed_headers + '\n' + payload_hash

    algorithm = 'AWS4-HMAC-SHA256'
    credential_scope = date_stamp + '/' + region + '/' + service + '/' + 'aws4_request'
    string_to_sign = algorithm + '\n' +  amz_date + '\n' +  credential_scope + '\n' +  hashlib.sha256(canonical_request).hexdigest()

    signing_key = getSignatureKey(secret_key, date_stamp, region, service)
    signature = hmac.new(signing_key, (string_to_sign).encode('utf-8'), hashlib.sha256).hexdigest()

    authorization_header = algorithm + ' ' + 'Credential=' + access_key + '/' + credential_scope + ', ' +  'SignedHeaders=' + signed_headers + ', ' + 'Signature=' + signature

    headers = { 'Content-Type': content_type,
            'X-Amz-Date': amz_date,
            'X-Amz-Target': amz_target,

            # uncomment this if you uncommented the 'token' line earlier
            #'X-Amz-Security-Token': token,
            'Authorization': authorization_header}

    r = requests.post(endpoint, data=request_parameters, headers=headers)

    # Let's format the JSON string returned from the API for better output
    formatted_text = json.dumps(json.loads(r.text), indent=4, sort_keys=True)

    print('Response code: {}\n'.format(r.status_code))
    print('Response body:\n{}'.format(formatted_text))

Code Output

If you get the code running, it should output something like this:

Response code: 200

Response body: 
{

    "FaceMatches": [],
    "SourceImageFace": {
        "BoundingBox": {
            "Height": 0.9448398351669312,
            "Left": 0.12222222238779068,
            "Top": -0.017793593928217888,
            "Width": 0.5899999737739563
        },
        "Confidence": 99.99041748046875
    }
}

Really, just use boto3

The simplest thing you can do is to use boto3.

The code would be simplified to something like the following, as all the signature generation and JSON work become unnecessary.

Be sure that you have configured boto3 with credentials in the environment or via the configuration file, or put your credentials inline with the code. For more information, see boto3 configuration.

The code for this uses the boto3 Rekognition API.

import pprint

import boto3

# Set this to whatever percentage of 'similarity'
# you'd want
SIMILARITY_THRESHOLD = 75.0

if __name__ == '__main__':
    client = boto3.client('rekognition')

    # Our source image: http://i.imgur.com/OK8aDRq.jpg
    with open('source.jpg', 'rb') as source_image:
        source_bytes = source_image.read()

    # Our target image: http://i.imgur.com/Xchqm1r.jpg
    with open('target.jpg', 'rb') as target_image:
        target_bytes = target_image.read()

    response = client.compare_faces(
                   SourceImage={ 'Bytes': source_bytes },
                   TargetImage={ 'Bytes': target_bytes },
                   SimilarityThreshold=SIMILARITY_THRESHOLD
    )

    pprint.pprint(response)

The above boto3 example should output this:

{u'FaceMatches': [],
 'ResponseMetadata': {'HTTPHeaders': {'connection': 'keep-alive',
                                      'content-length': '195',
                                      'content-type': 'application/x-amz-json-1.1',
                                      'date': 'Sat, 31 Dec 2016 23:15:56 GMT',
                                      'x-amzn-requestid': '13edda2d-cfaf-11e6-9999-d3abf4c2feb3'},
                      'HTTPStatusCode': 200,
                      'RequestId': '13edda2d-cfaf-11e6-9999-d3abf4c2feb3',
                      'RetryAttempts': 0},
 u'SourceImageFace': {u'BoundingBox': {u'Height': 0.9448398351669312,
                                       u'Left': 0.12222222238779068,
                                       u'Top': -0.017793593928217888,
                                       u'Width': 0.5899999737739563},
                      u'Confidence': 99.99041748046875}}
like image 128
逆さま Avatar answered Oct 13 '22 09:10

逆さま