Motion Sensing with Raspberry Pi Camera and Cat Face Scanning with AWS Lambda + OpenCV
I wrote a Python script that detects camera motion with a Raspberry Pi camera and uploads photos to S3. The frames are then searched for cat faces using OpenCV running on AWS to alert them of heightened 'cattivity' in the house.
This could also be done using Amazon’s cool new image ‘Rekognition’ service, but it would cost more if you process thousands of frames daily.
I wanted to know how often my flatmate's cat attempted to attack his mouse enclosure. Cat detection events are sent to DataDog using the StatsD protocol for alerting!
Raspberry Pi Agent Code
I am monitoring the occurrence of motion events using DataDog and uploading the frames to S3 to detect further objects in the frame, such as cats or people.
The full source code is on GitHub: timatooth/catscanface
Haar-Cascade Cat Detection with AWS Lambda
You will need to package compiled Numpy & OpenCV libraries prepared for running inside the AWS Lambda functions thanks to aeddi https://github.com/aeddi/aws-lambda-python-opencv
Update: 2019 I Believen numpy & OpenCV may work out-of-box using AWS SAM to package Lambda functions.
from __future__ import print_function import logging logger = logging.getLogger() logger.setLevel(logging.INFO) import os import os.path import numpy import cv2 import boto3 # Config s3_prefix = 'catscanface/' s3_bucket = 'timatooth' cat_cascade = cv2.CascadeClassifier('haarcascade_frontalcatface_extended.xml') s3_client = boto3.client('s3') def scan_frame(image_data): img_array = numpy.asarray(bytearray(image_data), dtype=numpy.uint8) frame = cv2.imdecode(img_array, 0) cat_faces = cat_cascade.detectMultiScale(frame, 1.3, 5) # Draw rectangle onto region where cat face is found cat_detected = False for (x, y, w, h) in cat_faces: cat_detected = True cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2) logger.info("Found cat") if cat_detected: _, buf = cv2.imencode(".jpg", frame) return buf.tobytes() def get_image(object_key): response = s3_client.get_object(Bucket=s3_bucket, Key=object_key) image_bytes = response['Body'].read() logger.info('Got {} bytes'.format(len(image_bytes))) return image_bytes def lambda_handler(event, context): object_key = event['Records'][0]['s3']['object']['key'] image_bytes = get_image(object_key) cat_image = scan_frame(image_bytes) if cat_image is not None: key = s3_prefix + 'detections/' + os.path.basename(object_key) logger.info('Saving cat detection image: {}'.format(key)) s3_client.put_object( Bucket=s3_bucket, Key=key, Body=cat_image )
CloudFormation
--- AWSTemplateFormatVersion: '2010-09-09' Description: Cat facial recognition OpenCV lambda Parameters: S3BucketName: Type: String Description: The S3 bucket which contains cat pictures S3BucketPrefix: Type: String Description: A bucket prefix where the cat pictures are found Default: /catscanface Resources: LambdaRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: "/" Policies: - PolicyName: CloudwatchLog PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: arn:aws:logs:*:*:* - PolicyName: AccessCatPictureBucket PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - s3:GetObject Resource: !Join ['', ['arn:aws:s3:::', Ref: S3BucketName, Ref: S3BucketPrefix, '/motion/*']] - PolicyName: WriteDetectedCatFrames PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - s3:PutObject Resource: !Join ['', ['arn:aws:s3:::', Ref: S3BucketName, Ref: S3BucketPrefix, '/detections/*']] Lambda: Type: AWS::Lambda::Function Properties: Code: ZipFile: 'print("hello world")' Description: Scan images uploaded to S3 for cats FunctionName: Ref: AWS::StackName Handler: catscan.lambda_handler MemorySize: 256 Role: !GetAtt LambdaRole.Arn Runtime: python2.7 Timeout: '4' Outputs: LambdaRole: Description: IAM Role for LambdaRole Value: Ref: LambdaRole Lambda: Value: !GetAtt Lambda.Arn