├── doorman ├── __init__.py ├── unknown.py ├── guess.py └── train.py ├── package.json ├── Pipfile ├── .gitignore ├── readme.md ├── serverless.yml ├── Pipfile.lock ├── find_person.py └── handler.py /doorman/__init__.py: -------------------------------------------------------------------------------- 1 | from .guess import guess 2 | from .train import train 3 | from .unknown import unknown 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-python3", 3 | "description": "", 4 | "version": "0.1.0", 5 | "dependencies": {}, 6 | "devDependencies": { 7 | "serverless-python-requirements": "^3.0.11" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | 3 | url = "https://pypi.python.org/simple" 4 | verify_ssl = true 5 | name = "pypi" 6 | 7 | 8 | [packages] 9 | 10 | "boto3" = "*" 11 | requests = "*" 12 | 13 | 14 | [dev-packages] 15 | 16 | 17 | 18 | [requires] 19 | 20 | python_version = "3.6" 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Distribution / packaging 2 | *.pyc 3 | .Python 4 | .env 5 | node_modules 6 | env/ 7 | build/ 8 | develop-eggs/ 9 | dist/ 10 | downloads/ 11 | eggs/ 12 | .eggs/ 13 | lib/ 14 | lib64/ 15 | parts/ 16 | sdist/ 17 | var/ 18 | *.egg-info/ 19 | .installed.cfg 20 | *.egg 21 | 22 | # Serverless directories 23 | .serverless 24 | -------------------------------------------------------------------------------- /doorman/unknown.py: -------------------------------------------------------------------------------- 1 | import json 2 | import boto3 3 | import requests 4 | import hashlib 5 | import os 6 | from urllib.parse import parse_qs 7 | 8 | bucket_name = os.environ['BUCKET_NAME'] 9 | slack_token = os.environ['SLACK_API_TOKEN'] 10 | slack_channel_id = os.environ['SLACK_CHANNEL_ID'] 11 | rekognition_collection_id = os.environ['REKOGNITION_COLLECTION_ID'] 12 | 13 | 14 | def unknown(event, context): 15 | key = event['Records'][0]['s3']['object']['key'] 16 | 17 | data = { 18 | "channel": slack_channel_id, 19 | "text": "I don't know who this is, can you tell me?", 20 | "attachments": [ 21 | { 22 | "image_url": "https://s3.amazonaws.com/%s/%s" % (bucket_name, key), 23 | "fallback": "Nope?", 24 | "callback_id": key, 25 | "attachment_type": "default", 26 | "actions": [{ 27 | "name": "username", 28 | "text": "Select a username...", 29 | "type": "select", 30 | "data_source": "users" 31 | }, 32 | { 33 | "name": "discard", 34 | "text": "Ignore", 35 | "style": "danger", 36 | "type": "button", 37 | "value": "ignore", 38 | "confirm": { 39 | "title": "Are you sure?", 40 | "text": "Are you sure you want to ignore and delete this image?", 41 | "ok_text": "Yes", 42 | "dismiss_text": "No" 43 | } 44 | } 45 | ] 46 | } 47 | ] 48 | } 49 | print(data) 50 | foo = requests.post("https://slack.com/api/chat.postMessage", headers={'Content-Type':'application/json;charset=UTF-8', 'Authorization': 'Bearer %s' % slack_token}, json=data) 51 | 52 | print(foo.json()) 53 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Doorman 2 | ------- 3 | This will greet your coworkers on slack when they enter the office. 4 | 5 | This was send in as part of the [AWS Deeplens Hackaton](https://devpost.com/software/doorman-a1oh0e) 6 | 7 | 8 | Setup 9 | ----- 10 | Quite a few steps, needs cleanup, most of it can be automated. 11 | 12 | - Create a bucket, remember the name, make sure that your deeplens user can write to this bucket. 13 | - Create a Rekognition collection (and note the collecition id) 14 | - Be sure to have the following env vars in your environment: 15 | - BUCKET_NAME=your-bucket-name 16 | - SLACK_API_TOKEN=your-slack-token 17 | - SLACK_CHANNEL_ID="slack-channel-id" 18 | - REKOGNITION_COLLECTION_ID="your-collection-id" 19 | 20 | - Deploy the lambda functions with Serverles (eg: `sls deploy`), this will create a CF stack with your functions. Note the api gateway endpoint, you'll need it later 21 | 22 | - Go into the deeplens console, and create a project, select the "Object detection" model 23 | - Remove the `deeplens-object-detection` function, and add a function for `find_person` 24 | - Deploy the application to your deeplens 25 | 26 | - Go to the [slack api](https://api.slack.com/apps), and click "create a new app". 27 | - Give a name, and select your workspace 28 | - Activate: 29 | - Incoming webhooks 30 | - Interactive components (use the api gateway endpoint that you noted before, ignore `Load URL`) 31 | - Permissions: Install the app in your workspace, and note the token. You'll need `chat:write:bot`, `channels:read` and `incoming-webhook`. 32 | - Deploy the app again with the new environment variables 33 | 34 | That should be it. Whenever the Deeplens rekognizes someone, it will upload into the S3 bucket. Which will trigger the other lambda functions. 35 | 36 | Architecture 37 | ------------ 38 | ![Architecture](https://challengepost-s3-challengepost.netdna-ssl.com/photos/production/software_photos/000/602/534/datas/gallery.jpg) 39 | 40 | Video 41 | ----- 42 | [![Video](https://img.youtube.com/vi/UXVD22jDbu8/0.jpg)](https://www.youtube.com/watch?v=UXVD22jDbu8) 43 | -------------------------------------------------------------------------------- /doorman/guess.py: -------------------------------------------------------------------------------- 1 | import json 2 | import boto3 3 | import requests 4 | import hashlib 5 | import os 6 | 7 | bucket_name = os.environ['BUCKET_NAME'] 8 | slack_token = os.environ['SLACK_API_TOKEN'] 9 | slack_channel_id = os.environ['SLACK_CHANNEL_ID'] 10 | rekognition_collection_id = os.environ['REKOGNITION_COLLECTION_ID'] 11 | 12 | 13 | def guess(event, context): 14 | client = boto3.client('rekognition') 15 | key = event['Records'][0]['s3']['object']['key'] 16 | event_bucket_name = event['Records'][0]['s3']['bucket']['name'] 17 | image = { 18 | 'S3Object': { 19 | 'Bucket': event_bucket_name, 20 | 'Name': key 21 | } 22 | } 23 | # print(image) 24 | 25 | resp = client.search_faces_by_image( 26 | CollectionId=rekognition_collection_id, 27 | Image=image, 28 | MaxFaces=1, 29 | FaceMatchThreshold=70) 30 | 31 | s3 = boto3.resource('s3') 32 | 33 | if len(resp['FaceMatches']) == 0: 34 | # no known faces detected, let the users decide in slack 35 | print("No matches found, sending to unknown") 36 | new_key = 'unknown/%s.jpg' % hashlib.md5(key.encode('utf-8')).hexdigest() 37 | s3.Object(bucket_name, new_key).copy_from(CopySource='%s/%s' % (bucket_name, key)) 38 | s3.ObjectAcl(bucket_name, new_key).put(ACL='public-read') 39 | s3.Object(bucket_name, key).delete() 40 | else: 41 | print ("Face found") 42 | print (resp) 43 | # move image 44 | user_id = resp['FaceMatches'][0]['Face']['ExternalImageId'] 45 | new_key = 'detected/%s/%s.jpg' % (user_id, hashlib.md5(key.encode('utf-8')).hexdigest()) 46 | s3.Object(bucket_name, new_key).copy_from(CopySource='%s/%s' % (event_bucket_name, key)) 47 | s3.ObjectAcl(bucket_name, new_key).put(ACL='public-read') 48 | s3.Object(bucket_name, key).delete() 49 | 50 | # fetch the username for this user_id 51 | data = { 52 | "token": slack_token, 53 | "user": user_id 54 | } 55 | print(data) 56 | resp = requests.post("https://slack.com/api/users.info", data=data) 57 | print(resp.content) 58 | print(resp.json()) 59 | username = resp.json()['user']['name'] 60 | 61 | data = { 62 | "channel": slack_channel_id, 63 | "text": "Welcome @%s" % username, 64 | "link_names": True, 65 | "attachments": [ 66 | { 67 | "image_url": "https://s3.amazonaws.com/%s/%s" % (bucket_name, new_key), 68 | "fallback": "Nope?", 69 | "attachment_type": "default", 70 | } 71 | ] 72 | } 73 | resp = requests.post("https://slack.com/api/chat.postMessage", headers={'Content-Type':'application/json;charset=UTF-8', 'Authorization': 'Bearer %s' % slack_token}, json=data) 74 | return {} 75 | -------------------------------------------------------------------------------- /doorman/train.py: -------------------------------------------------------------------------------- 1 | import json 2 | import boto3 3 | import requests 4 | import hashlib 5 | import os 6 | from urllib.parse import parse_qs 7 | 8 | bucket_name = os.environ['BUCKET_NAME'] 9 | slack_token = os.environ['SLACK_API_TOKEN'] 10 | slack_channel_id = os.environ['SLACK_CHANNEL_ID'] 11 | rekognition_collection_id = os.environ['REKOGNITION_COLLECTION_ID'] 12 | 13 | 14 | def train(event, context): 15 | # print(event['body']) 16 | data = parse_qs(event['body']) 17 | data = json.loads(data['payload'][0]) 18 | print(data) 19 | key = data['callback_id'] 20 | 21 | # if we got a discard action, send an update first, and then remove the referenced image 22 | if data['actions'][0]['name'] == 'discard': 23 | message = { 24 | "text": "Ok, I ignored this image", 25 | "attachments": [ 26 | { 27 | "image_url": "https://s3.amazonaws.com/%s/%s" % (bucket_name, key), 28 | "fallback": "Nope?", 29 | "attachment_type": "default", 30 | } 31 | ] 32 | } 33 | print(message) 34 | 35 | requests.post( 36 | data['response_url'], 37 | headers={ 38 | 'Content-Type':'application/json;charset=UTF-8', 39 | 'Authorization': 'Bearer %s' % slack_token 40 | }, 41 | json=message 42 | ) 43 | s3 = boto3.resource('s3') 44 | s3.Object(bucket_name, key).delete() 45 | 46 | 47 | if data['actions'][0]['name'] == 'username': 48 | user_id = data['actions'][0]['selected_options'][0]['value'] 49 | new_key = 'trained/%s/%s.jpg' % (user_id, hashlib.md5(key.encode('utf-8')).hexdigest()) 50 | 51 | message = { 52 | "text": "Trained as %s" % user_id, 53 | "attachments": [ 54 | { 55 | "image_url": "https://s3.amazonaws.com/%s/%s" % (bucket_name, new_key), 56 | "fallback": "Nope?", 57 | "attachment_type": "default", 58 | } 59 | ] 60 | } 61 | print(message) 62 | requests.post(data['response_url'], headers={'Content-Type':'application/json;charset=UTF-8', 'Authorization': 'Bearer %s' % slack_token}, json=message) 63 | 64 | # response is send, start training 65 | client = boto3.client('rekognition') 66 | resp = client.index_faces( 67 | CollectionId=rekognition_collection_id, 68 | Image={ 69 | 'S3Object': { 70 | 'Bucket': bucket_name, 71 | 'Name': key, 72 | } 73 | }, 74 | ExternalImageId=user_id, 75 | DetectionAttributes=['DEFAULT'] 76 | ) 77 | 78 | # move the s3 file to the 'trained' location 79 | s3 = boto3.resource('s3') 80 | s3.Object(bucket_name, new_key).copy_from(CopySource='%s/%s' % (bucket_name, key)) 81 | s3.ObjectAcl(bucket_name, new_key).put(ACL='public-read') 82 | s3.Object(bucket_name, key).delete() 83 | 84 | return { 85 | "statusCode": 200 86 | } 87 | -------------------------------------------------------------------------------- /serverless.yml: -------------------------------------------------------------------------------- 1 | # Welcome to Serverless! 2 | # 3 | # This file is the main config file for your service. 4 | # It's very minimal at this point and uses default values. 5 | # You can always add more config options for more control. 6 | # We've included some commented out config examples here. 7 | # Just uncomment any of them to get that config option. 8 | # 9 | # For full config options, check the docs: 10 | # docs.serverless.com 11 | # 12 | # Happy Coding! 13 | 14 | service: doorman 15 | 16 | provider: 17 | name: aws 18 | runtime: python3.6 19 | 20 | # you can overwrite defaults here 21 | stage: dev 22 | # region: us-east-1 23 | 24 | # you can add statements to the Lambda function's IAM Role here 25 | iamRoleStatements: 26 | - Effect: "Allow" 27 | Action: 28 | - "rekognition:DetectFaces" 29 | - "rekognition:SearchFacesByImage" 30 | - "rekognition:IndexFaces" 31 | Resource: "*" 32 | - Effect: "Allow" 33 | Action: 34 | - "s3:PutObject" 35 | - "s3:PutObjectAcl" 36 | - "s3:GetObject" 37 | - "s3:DeleteObject" 38 | Resource: 39 | - "arn:aws:s3:::${env:BUCKET_NAME}/*" 40 | 41 | # - Effect: "Allow" 42 | # Action: 43 | # - "s3:ListBucket" 44 | # Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ] } 45 | # - Effect: "Allow" 46 | # Action: 47 | # - "s3:PutObject" 48 | # Resource: 49 | # Fn::Join: 50 | # - "" 51 | # - - "arn:aws:s3:::" 52 | # - "Ref" : "ServerlessDeploymentBucket" 53 | # - "/*" 54 | 55 | # you can define service wide environment variables here 56 | environment: 57 | BUCKET_NAME: ${env:BUCKET_NAME} 58 | SLACK_API_TOKEN: ${env:SLACK_API_TOKEN} 59 | SLACK_CHANNEL_ID: ${env:SLACK_CHANNEL_ID} 60 | REKOGNITION_COLLECTION_ID: ${env:REKOGNITION_COLLECTION_ID} 61 | # you can add packaging information here 62 | #package: 63 | # include: 64 | # - include-me.py 65 | # - include-me-dir/** 66 | # exclude: 67 | # - exclude-me.py 68 | # - exclude-me-dir/** 69 | 70 | 71 | functions: 72 | guess: 73 | handler: handler.guess 74 | events: 75 | - s3: 76 | bucket: ${env:BUCKET_NAME} 77 | event: s3:ObjectCreated:* 78 | rules: 79 | - prefix: incoming/ 80 | unknown: 81 | handler: handler.unknown 82 | events: 83 | - s3: 84 | bucket: ${env:BUCKET_NAME} 85 | event: s3:ObjectCreated:* 86 | rules: 87 | - prefix: unknown/ 88 | train: 89 | handler: handler.train 90 | events: 91 | - http: 92 | path: faces/train 93 | method: post 94 | 95 | # find person, function for greengrass device 96 | find-person: 97 | handler: find_person.function_handler 98 | 99 | # resources: 100 | # Resources: 101 | # S3BucketDoormanfaces: 102 | # Type: AWS::S3::Bucket 103 | # Properties: 104 | # BucketName: ${env:BUCKET_NAME} 105 | # # add additional custom bucket configuration here 106 | # 107 | plugins: 108 | - serverless-python-requirements 109 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "8b6508c5f8321ab2813a2954b0920a96ee2eaece402ab41e821566d1548ada7d" 5 | }, 6 | "host-environment-markers": { 7 | "implementation_name": "cpython", 8 | "implementation_version": "3.6.3", 9 | "os_name": "posix", 10 | "platform_machine": "x86_64", 11 | "platform_python_implementation": "CPython", 12 | "platform_release": "17.3.0", 13 | "platform_system": "Darwin", 14 | "platform_version": "Darwin Kernel Version 17.3.0: Thu Nov 9 18:09:22 PST 2017; root:xnu-4570.31.3~1/RELEASE_X86_64", 15 | "python_full_version": "3.6.3", 16 | "python_version": "3.6", 17 | "sys_platform": "darwin" 18 | }, 19 | "pipfile-spec": 6, 20 | "requires": { 21 | "python_version": "3.6" 22 | }, 23 | "sources": [ 24 | { 25 | "name": "pypi", 26 | "url": "https://pypi.python.org/simple", 27 | "verify_ssl": true 28 | } 29 | ] 30 | }, 31 | "default": { 32 | "boto3": { 33 | "hashes": [ 34 | "sha256:08fa311b5d6189a76517fbf68a52a2dd5354a6b482cb9e82a1d72b9658e23821", 35 | "sha256:e14fa322f40c34d345a4f2e653c38554df9d389713c31f7a4c1658b504abfeee" 36 | ], 37 | "version": "==1.5.1" 38 | }, 39 | "botocore": { 40 | "hashes": [ 41 | "sha256:abb64765d1048fcdb2862e87158af8c5ac3f325d14c229cd4d455df4c1150ab2", 42 | "sha256:291c961de571d0771bd93b536335064a8b3932152ae07ff344b02da4f73346ea" 43 | ], 44 | "version": "==1.8.15" 45 | }, 46 | "certifi": { 47 | "hashes": [ 48 | "sha256:244be0d93b71e93fc0a0a479862051414d0e00e16435707e5bf5000f92e04694", 49 | "sha256:5ec74291ca1136b40f0379e1128ff80e866597e4e2c1e755739a913bbc3613c0" 50 | ], 51 | "version": "==2017.11.5" 52 | }, 53 | "chardet": { 54 | "hashes": [ 55 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691", 56 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae" 57 | ], 58 | "version": "==3.0.4" 59 | }, 60 | "docutils": { 61 | "hashes": [ 62 | "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6", 63 | "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6", 64 | "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274" 65 | ], 66 | "version": "==0.14" 67 | }, 68 | "idna": { 69 | "hashes": [ 70 | "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4", 71 | "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f" 72 | ], 73 | "version": "==2.6" 74 | }, 75 | "jmespath": { 76 | "hashes": [ 77 | "sha256:f11b4461f425740a1d908e9a3f7365c3d2e569f6ca68a2ff8bc5bcd9676edd63", 78 | "sha256:6a81d4c9aa62caf061cb517b4d9ad1dd300374cd4706997aff9cd6aedd61fc64" 79 | ], 80 | "version": "==0.9.3" 81 | }, 82 | "python-dateutil": { 83 | "hashes": [ 84 | "sha256:95511bae634d69bc7329ba55e646499a842bc4ec342ad54a8cdb65645a0aad3c", 85 | "sha256:891c38b2a02f5bb1be3e4793866c8df49c7d19baabf9c1bad62547e0b4866aca" 86 | ], 87 | "version": "==2.6.1" 88 | }, 89 | "requests": { 90 | "hashes": [ 91 | "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", 92 | "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e" 93 | ], 94 | "version": "==2.18.4" 95 | }, 96 | "s3transfer": { 97 | "hashes": [ 98 | "sha256:23c156ca4d64b022476c92c44bf938bef71af9ce0dcd8fd6585e7bce52f66e47", 99 | "sha256:10891b246296e0049071d56c32953af05cea614dca425a601e4c0be35990121e" 100 | ], 101 | "version": "==0.1.12" 102 | }, 103 | "six": { 104 | "hashes": [ 105 | "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb", 106 | "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9" 107 | ], 108 | "version": "==1.11.0" 109 | }, 110 | "urllib3": { 111 | "hashes": [ 112 | "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", 113 | "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f" 114 | ], 115 | "version": "==1.22" 116 | } 117 | }, 118 | "develop": {} 119 | } 120 | -------------------------------------------------------------------------------- /find_person.py: -------------------------------------------------------------------------------- 1 | import os 2 | from threading import Timer 3 | import time 4 | import datetime 5 | import awscam 6 | import cv2 7 | from botocore.session import Session 8 | from threading import Thread 9 | 10 | # Setup the S3 client 11 | session = Session() 12 | s3 = session.create_client('s3') 13 | s3_bucket = 'doorman-faces' 14 | 15 | # setup the camera and frame 16 | ret, frame = awscam.getLastFrame() 17 | ret,jpeg = cv2.imencode('.jpg', frame) 18 | Write_To_FIFO = True 19 | class FIFO_Thread(Thread): 20 | def __init__(self): 21 | ''' Constructor. ''' 22 | Thread.__init__(self) 23 | 24 | def run(self): 25 | # write to tmp file for local debugging purpose 26 | fifo_path = "/tmp/results.mjpeg" 27 | if not os.path.exists(fifo_path): 28 | os.mkfifo(fifo_path) 29 | f = open(fifo_path,'w') 30 | 31 | # yay, succesful, let's start streaming to the file 32 | while Write_To_FIFO: 33 | try: 34 | f.write(jpeg.tobytes()) 35 | except IOError as e: 36 | continue 37 | 38 | def greengrass_infinite_infer_run(): 39 | try: 40 | modelPath = "/opt/awscam/artifacts/mxnet_deploy_ssd_resnet50_300_FP16_FUSED.xml" 41 | modelType = "ssd" 42 | input_width = 300 43 | input_height = 300 44 | max_threshold = 0.60 # raise/lower this value based on your conditions 45 | outMap = { 1: 'aeroplane', 2: 'bicycle', 3: 'bird', 4: 'boat', 5: 'bottle', 6: 'bus', 7 : 'car', 8 : 'cat', 9 : 'chair', 10 : 'cow', 11 : 'dinning table', 12 : 'dog', 13 : 'horse', 14 : 'motorbike', 15 : 'person', 16 : 'pottedplant', 17 : 'sheep', 18 : 'sofa', 19 : 'train', 20 : 'tvmonitor' } 46 | results_thread = FIFO_Thread() 47 | results_thread.start() 48 | 49 | # Load model to GPU 50 | mcfg = {"GPU": 1} 51 | model = awscam.Model(modelPath, mcfg) 52 | 53 | # try to get a frame from the camera 54 | ret, frame = awscam.getLastFrame() 55 | if ret == False: 56 | raise Exception("Failed to get frame from the stream") 57 | 58 | yscale = float(frame.shape[0]/input_height) 59 | xscale = float(frame.shape[1]/input_width) 60 | 61 | doInfer = True 62 | while doInfer: 63 | # Get a frame from the video stream 64 | ret, frame = awscam.getLastFrame() 65 | 66 | # Raise an exception if failing to get a frame 67 | if ret == False: 68 | raise Exception("Failed to get frame from the stream") 69 | 70 | # Resize frame to fit model input requirement 71 | frameResize = cv2.resize(frame, (input_width, input_height)) 72 | 73 | # Run model inference on the resized frame 74 | inferOutput = model.doInference(frameResize) 75 | 76 | # Output inference result to the fifo file so it can be viewed with mplayer 77 | parsed_results = model.parseResult(modelType, inferOutput)['ssd'] 78 | label = '{' 79 | for obj in parsed_results: 80 | if obj['prob'] > max_threshold: 81 | xmin = int( xscale * obj['xmin'] ) + int((obj['xmin'] - input_width/2) + input_width/2) 82 | ymin = int( yscale * obj['ymin'] ) 83 | xmax = int( xscale * obj['xmax'] ) + int((obj['xmax'] - input_width/2) + input_width/2) 84 | ymax = int( yscale * obj['ymax'] ) 85 | 86 | # if a person was found, upload the target area to S3 for further inspection 87 | if outMap[obj['label']] == 'person': 88 | 89 | # get the person image 90 | person = frame[ymin:ymax, xmin:xmax] 91 | 92 | # create a nice s3 file key 93 | s3_key = datetime.datetime.utcnow().strftime('%Y-%m-%d_%H_%M_%S.%f') + '.jpg' 94 | encode_param=[int(cv2.IMWRITE_JPEG_QUALITY), 90] # 90% should be more than enough 95 | _, jpg_data = cv2.imencode('.jpg', person, encode_param) 96 | filename = "incoming/%s" % s3_key # the guess lambda function is listening here 97 | response = s3.put_object(ACL='public-read', Body=jpg_data.tostring(),Bucket=s3_bucket,Key=filename) 98 | 99 | # draw a rectangle around the designated area, and tell what label was found 100 | cv2.rectangle(frame, (xmin, ymin), (xmax, ymax), (255, 165, 20), 4) 101 | label += '"{}": {:.2f},'.format(outMap[obj['label']], obj['prob'] ) 102 | label_show = "{}: {:.2f}%".format(outMap[obj['label']], obj['prob']*100 ) 103 | cv2.putText(frame, label_show, (xmin, ymin-15),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 165, 20), 4) 104 | label += '"null": 0.0' 105 | label += '}' 106 | 107 | global jpeg 108 | ret,jpeg = cv2.imencode('.jpg', frame) 109 | 110 | except Exception as e: 111 | print "Crap, something failed: %s" % str(e) 112 | 113 | # Asynchronously schedule this function to be run again in 15 seconds 114 | Timer(15, greengrass_infinite_infer_run).start() 115 | 116 | # Execute the function above 117 | greengrass_infinite_infer_run() 118 | 119 | 120 | # This is a dummy handler and will not be invoked 121 | # Instead the code above will be executed in an infinite loop for our example 122 | def function_handler(event, context): 123 | return 124 | -------------------------------------------------------------------------------- /handler.py: -------------------------------------------------------------------------------- 1 | import json 2 | import boto3 3 | import requests 4 | import hashlib 5 | import os 6 | from urllib.parse import parse_qs 7 | 8 | bucket_name = os.environ['BUCKET_NAME'] 9 | slack_token = os.environ['SLACK_API_TOKEN'] 10 | slack_channel_id = os.environ['SLACK_CHANNEL_ID'] 11 | rekognition_collection_id = os.environ['REKOGNITION_COLLECTION_ID'] 12 | 13 | from doorman import guess 14 | from doorman import train 15 | from doorman import unknown 16 | # 17 | # 18 | # if __name__ == "__main__": 19 | # print(unknown( 20 | # { 21 | # "Records": [ 22 | # { 23 | # "eventVersion": "2.0", 24 | # "eventTime": "1970-01-01T00:00:00.000Z", 25 | # "requestParameters": { 26 | # "sourceIPAddress": "127.0.0.1" 27 | # }, 28 | # "s3": { 29 | # "configurationId": "testConfigRule", 30 | # "object": { 31 | # "eTag": "0123456789abcdef0123456789abcdef", 32 | # "sequencer": "0A1B2C3D4E5F678901", 33 | # "key": "detected/U033PFSFB/1ef4dfe223eec2b6801aa4873cd3e350.jpg", 34 | # "size": 1024 35 | # }, 36 | # "bucket": { 37 | # "arn": "arn:aws:s3:::doorman-faces", 38 | # "name": "doorman-faces", 39 | # "ownerIdentity": { 40 | # "principalId": "EXAMPLE" 41 | # } 42 | # }, 43 | # "s3SchemaVersion": "1.0" 44 | # }, 45 | # "responseElements": { 46 | # "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH", 47 | # "x-amz-request-id": "EXAMPLE123456789" 48 | # }, 49 | # "awsRegion": "us-east-1", 50 | # "eventName": "ObjectCreated:Put", 51 | # "userIdentity": { 52 | # "principalId": "EXAMPLE" 53 | # }, 54 | # "eventSource": "aws:s3" 55 | # } 56 | # ] 57 | # }, {}) 58 | # ) 59 | # 60 | # # print(train({ 61 | # # "resource": "/", 62 | # # "path": "/", 63 | # # "httpMethod": "POST", 64 | # # "headers": { 65 | # # "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", 66 | # # "Accept-Encoding": "gzip, deflate, br", 67 | # # "Accept-Language": "en-GB,en-US;q=0.8,en;q=0.6,zh-CN;q=0.4", 68 | # # "cache-control": "max-age=0", 69 | # # "CloudFront-Forwarded-Proto": "https", 70 | # # "CloudFront-Is-Desktop-Viewer": "true", 71 | # # "CloudFront-Is-Mobile-Viewer": "false", 72 | # # "CloudFront-Is-SmartTV-Viewer": "false", 73 | # # "CloudFront-Is-Tablet-Viewer": "false", 74 | # # "CloudFront-Viewer-Country": "GB", 75 | # # "content-type": "application/x-www-form-urlencoded", 76 | # # "Host": "j3ap25j034.execute-api.eu-west-2.amazonaws.com", 77 | # # "origin": "https://j3ap25j034.execute-api.eu-west-2.amazonaws.com", 78 | # # "Referer": "https://j3ap25j034.execute-api.eu-west-2.amazonaws.com/dev/", 79 | # # "upgrade-insecure-requests": "1", 80 | # # "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", 81 | # # "Via": "2.0 a3650115c5e21e2b5d133ce84464bea3.cloudfront.net (CloudFront)", 82 | # # "X-Amz-Cf-Id": "0nDeiXnReyHYCkv8cc150MWCFCLFPbJoTs1mexDuKe2WJwK5ANgv2A==", 83 | # # "X-Amzn-Trace-Id": "Root=1-597079de-75fec8453f6fd4812414a4cd", 84 | # # "X-Forwarded-For": "50.129.117.14, 50.112.234.94", 85 | # # "X-Forwarded-Port": "443", 86 | # # "X-Forwarded-Proto": "https" 87 | # # }, 88 | # # "queryStringParameters": "", 89 | # # "pathParameters": "None", 90 | # # "stageVariables": "None", 91 | # # "requestContext": { 92 | # # "path": "/dev/", 93 | # # "accountId": "125002137610", 94 | # # "resourceId": "qdolsr1yhk", 95 | # # "stage": "dev", 96 | # # "requestId": "0f2431a2-6d2f-11e7-b75152aa497861", 97 | # # "identity": { 98 | # # "cognitoIdentityPoolId": None, 99 | # # "accountId": None, 100 | # # "cognitoIdentityId": None, 101 | # # "caller": None, 102 | # # "apiKey": "", 103 | # # "sourceIp": "50.129.117.14", 104 | # # "accessKey": None, 105 | # # "cognitoAuthenticationType": None, 106 | # # "cognitoAuthenticationProvider": None, 107 | # # "userArn": None, 108 | # # "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", 109 | # # "user": None 110 | # # }, 111 | # # "resourcePath": "/", 112 | # # "httpMethod": "POST", 113 | # # "apiId": "j3azlsj0c4" 114 | # # }, 115 | # # "body": "payload=%7B%22type%22%3A%22interactive_message%22%2C%22actions%22%3A%5B%7B%22name%22%3A%22discard%22%2C%22type%22%3A%22select%22%2C%22selected_options%22%3A%5B%7B%22value%22%3A%22U033PFSFB%22%7D%5D%7D%5D%2C%22callback_id%22%3A%22unknown%5C%2Ftest.jpg%22%2C%22team%22%3A%7B%22id%22%3A%22T02HUP4V7%22%2C%22domain%22%3A%22unitt%22%7D%2C%22channel%22%3A%7B%22id%22%3A%22C8HLA7R63%22%2C%22name%22%3A%22whodis%22%7D%2C%22user%22%3A%7B%22id%22%3A%22U033PFSFB%22%2C%22name%22%3A%22svdgraaf%22%7D%2C%22action_ts%22%3A%221513682920.702541%22%2C%22message_ts%22%3A%221513654753.000074%22%2C%22attachment_id%22%3A%221%22%2C%22token%22%3A%22QfG0e3Guj5VqB8FYRu6t6hsG%22%2C%22is_app_unfurl%22%3Afalse%2C%22original_message%22%3A%7B%22text%22%3A%22Whodis%3F%22%2C%22username%22%3A%22Doorman%22%2C%22bot_id%22%3A%22B8GHY4N9G%22%2C%22attachments%22%3A%5B%7B%22fallback%22%3A%22Nope%3F%22%2C%22image_url%22%3A%22https%3A%5C%2F%5C%2Fs3.amazonaws.com%5C%2Fdoorman-faces%5C%2Funknown%5C%2Ftest.jpg%22%2C%22image_width%22%3A720%2C%22image_height%22%3A480%2C%22image_bytes%22%3A85516%2C%22callback_id%22%3A%22unknown%5C%2Ftest.jpg%22%2C%22id%22%3A1%2C%22color%22%3A%223AA3E3%22%2C%22actions%22%3A%5B%7B%22id%22%3A%221%22%2C%22name%22%3A%22username%22%2C%22text%22%3A%22Select+a+username...%22%2C%22type%22%3A%22select%22%2C%22data_source%22%3A%22users%22%7D%5D%7D%5D%2C%22type%22%3A%22message%22%2C%22subtype%22%3A%22bot_message%22%2C%22ts%22%3A%221513654753.000074%22%7D%2C%22response_url%22%3A%22https%3A%5C%2F%5C%2Fhooks.slack.com%5C%2Factions%5C%2FT02HUP4V7%5C%2F288116281232%5C%2FqaNuNww7z6pywPrk3jIHHcHF%22%2C%22trigger_id%22%3A%22288701768947.2606786993.b007d1a39a5076df879809512ef1d063%22%7D", 116 | # # "isBase64Encoded": False 117 | # # }, {})) 118 | # 119 | # # print(guess({'Records': [{'eventVersion': '2.0', 'eventSource': 'aws:s3', 'awsRegion': 'us-east-1', 'eventTime': '2017-12-19T12:04:09.653Z', 'eventName': 'ObjectCreated:Put', 'userIdentity': {'principalId': 'AWS:AROAIHE5RTNGNONLAZCZS:AssumeRoleSession'}, 'requestParameters': {'sourceIPAddress': '63.228.166.237'}, 'responseElements': {'x-amz-request-id': '839246E5C0E2300C', 'x-amz-id-2': 'nZdfLBl9JptArE5YNYgD5vJhHjXSXZHPD8jFKSneIIJ8HWM4wiFvETRixxOVSJGenFGpqCFjckw='}, 's3': {'s3SchemaVersion': '1.0', 'configurationId': 'ca169f58-28da-4b01-a2c2-cdbbad9081c7', 'bucket': {'name': 'doorman-faces', 'ownerIdentity': {'principalId': 'AUAL9TOIHMDDI'}, 'arn': 'arn:aws:s3:::doorman-faces'}, 'object': {'key': 'detected/U033PFSFB/ac8b9fe608d6ec59871f5d2e2bcb8edd.jpg', 'size': 81715, 'eTag': '112ba09a09910f3d45508e31398b0517', 'sequencer': '005A39003941D63E04'}}}]} 120 | # # ,{})) 121 | --------------------------------------------------------------------------------