An example of calling AWS Rekognition HTTP API from Python
Asked Answered
D

1

11

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?

Dayak answered 30/12, 2016 at 0:7 Comment(1)
Any reason why you want to use the HTTP API instead of boto3?Cakewalk
C
22

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}}
Cakewalk answered 30/12, 2016 at 4:48 Comment(7)
I'm trying out boto3 as it certainly looks simpler. Shouldn't the initial call be something like: client = boto3.client('rekognition', aws_access_key_id=key, aws_secret_access_key=secret, region_name=region )?Dayak
I am able to use the Rekognition demo in the AWS console, however I get an error when using boto3: An error occurred (AccessDeniedException) when calling the CompareFaces operation: User: X is not authorized to perform: rekognition:CompareFaces. Perhaps I need to double check my keys, but I wanted to check that the client is set up correctly.Dayak
@Dayak If using boto3, you can either specify the key/secret/token/region while requesting a client, or specifying them in environment variables. My code examples assume the latter. There are also other ways to provide credentials. As for the other problem you're facing, it sounds like your IAM policy doesn't grant access to use rekognition resources.Cakewalk
You were correct about the IAM policy. I've downloaded the two images you used, and when running the above boto3 example, I get: botocore.exceptions.ClientError: An error occurred (InvalidImageFormatException) when calling the CompareFaces operation: Invalid image encoding. I've additionally tried other images and formats and see the same error.Dayak
@Dayak - I updated my boto example - turns out you don't have to perform a base64 encoding of the image bytes when using boto3 like you have to in the HTTP API example.Cakewalk
This works! And for anyone wondering where the similarity score is, it's inside the empty FaceMatches list in the above output: u'FaceMatches': [{u'Face': {u'BoundingBox': {}, u'Confidence': 99.9865951538086}, u'Similarity': 0.0}]. With a score of 0, Rekognition was unable to match this challenging pair of images.Dayak
Here's an updated version for py3 gist.github.com/x011/2e881130474b39b1fac1add4e9d89698Perinephrium

© 2022 - 2024 — McMap. All rights reserved.