Building a Raspberry Pi security camera, and taking it to the cloud.

Ruan de Bruyn
DVT Software Engineering
10 min readJul 30, 2020

One side effect of the Covid-19 pandemic is that most of us have a lot of extra time to be productive on things we didn’t have time for before, being on lockdown and all. Some may spend this extra time on exercise or perhaps finally getting around to learning German. Most probably just catching up on all the Netflix shows your friends have been telling you to watch, to which your response for literal years was “yeah, I’m just busy right now, but I’m putting it on the list. I’ll get around to it soon.” (but there is no list, or it’s been gathering dust for years because deep down you know you won’t get around to it before it’s been forgotten to the abyss of pop culture. Or maybe that’s just me.)

In my case I abandoned all of the aforementioned noble pursuits, and decided to build a security camera instead, allowing you to expertly monitor what’s going on in your home when you’re not there, in a time when…well, you’re probably always there.

Anyway, it’s a fun project, and IoT projects are a hobby of mine. This article will also introduce a lot of concepts in the Amazon AWS ecosystem, and cloud computing has become a very big deal. If you’re looking for a fun project that will give you an excuse to tinker with a Raspberry Pi and make it do stuff with the cloud, this post should be an enjoyable read. With all that said, let’s get into it.

The First Project, and its Glaring Shortcomings

This project is actually based on one I did four years ago when I first started playing around with the Raspberry Pi. My first security camera project was very rudimentary. It was capable of taking video footage and saving it to the onboard MicroSD card. It can only capture raw footage though, not in a web/media-player friendly format, so it had to transcode the footage to mp4 format too. Then, it hosted the videos on the local network (i.e the house’s WiFi) using a PHP web server. I mention this project because it had many shortcomings, listed below, and knowing what they are; makes the case for moving this same project to the cloud.

  • Saving footage locally for a Raspberry Pi is troublesome for many reasons. If you only have the MicroSD card, as I did, the main issue is that all of the data writes to the card will make it reach its write limit sooner, in addition to storage space being relatively small. You could always hook up an external hard drive to it that has a lot of storage space, but this consumes extra power. Even if power consumption isn’t an issue, it still makes the setup bulky, and regardless of what type of storage you use, you have to start making provisions to delete old footage once the hard drive fills up.
  • The Pi has limited processing power. At the time of writing, the newest model is the Pi 4B, which has a CPU clock speed of 1.5GHz. While this seems like a lot for a small package, this CPU, based on the ARM architecture, has very little in the way of optimizations like what you’d expect to find in higher-end desktop CPUs. My first project was on a Pi 3B+, and it was unable to record new video footage and convert previously recorded footage to mp4 format without the weight of parallel processing on its shoulders creating a supermassive black hole inside one of the CPU registers, causing the Pi to subsequently implode upon itself and erasing its very existence from the quantum universe. And, oh yeah, this is before even hosting the videos over WiFi. I was able to get around this by just straight up pausing the security camera while transcoding was taking place, but this is not exactly a perfect scenario.
  • Bonus: the webserver was working with PHP. This is likely the most crippling drawback of this entire system.

If we take this whole thing to the cloud, we can avoid almost all of these issues. Let’s dive into getting the Raspberry Pi set up.

Setting up the Raspberry Pi

For this project, I used a Raspberry Pi 3B+, along with the official Raspberry Pi Camera Module.

Pi 3B+ with the PiCamera module. The case on this Pi is the official Pi case for the 3B range, and can be taken apart as you need it. I made it completely open to allow access to the ribbon connector on the board. You get fancy cases that allow you to mount the camera inside the case itself.

If you’re new to the Pi, check out the getting started page on their official website to get going. All you’ll need is a MicroSD card to boot the OS from. This page does assume you have a monitor and an HDMI cable to connect the Pi to. If this doesn’t work (sometimes there are weird issues with HDMI output not working, and you have to edit a bunch of text files to fix it) or if you’re partial to being a 1337-anonymous-pro-hacker, follow this guide to set the Pi up with SSH when it boots up, so you can interact with the Pi via command line instead.

With the Pi booted up, run sudo apt update && sudo apt upgrade from a terminal, to make sure the OS is up to date. Then run sudo apt install git . After git is installed, clone the repo from my github page. Nowcd into the repo’s directory. You’ll notice that there’s a requirements.txt file, which holds all the dependencies the Python script needs to run. To get these, do

pip3 install -r requirements.txt

and run python3 SecurityCamera.py -a to let the camera run. It will take a second to start up, and then it will start printing values to indicate that everything’s up and running.

This Python code is mostly self-explanatory if you take a second to look at it. It uses Amazon’s boto3 library to interact with the AWS ecosystem. It then continuously takes photos, comparing the last two it took by their pixel values, looking for movement. This is done by converting every image to grayscale, and then, since the images are now simple two-dimensional matrices, subtracts them from each other and looks at what remains. If enough of the entries in the resulting matrix is large, it means that the pixel content of the images are different (beyond what you’d expect from camera noise, at any rate). If the pixels are this different, then something must have happened, right? The assumption is that movement must have been happening in front of the camera, and this triggers a recording. This prevents the Pi from taking tons of useless footage. Another cool feature is that this script never writes anything to the MicroSD card. All images are kept in memory, and the recorded video is saved to a byte stream before being uploaded to the cloud. This extends the MicroSD card’s life, as well as netting faster performance.

The video byte stream recording brings us to the most important snippet of code, in the S3Database class, listed below.

def save_footage(self, footage_stream, filename: str):
self.s3_client.upload_fileobj(
footage_stream,
'input-bucket',
filename
)

We’re going to be uploading all of the footage we have to an Amazon S3 bucket. This method uses the upload_fileobj method from the boto3 library to upload the footage. A more common method is upload , where you point it to a saved file on the disk, which is then uploaded. We need the former method because our content is a stream, not a file that’s been persisted anywhere. Check out the official boto3 docs for incredibly useful code snippets and examples.

About that S3 Bucket

S3 buckets are really easy to make, take a look at this tutorial to make one. No special options were selected in making this bucket, just make sure to set access privileges to private, so only you can access it. Whenever your Pi records a video and uploads it, you should see it listed in the bucket shortly afterwards.

This is a screenshot of input-bucket, with four raw video files saved inside, with timestamps as filenames.

Now we come to the second problem I listed earlier. The video footage from the Pi has to be transcoded into a browser-friendly format. In my first project, the Pi did the transcoding. How do we go about converting the raw footage now that it’s on a bucket? To do that, we’ll make use of an AWS Lambda.

Creating a Lambda

AWS Lambdas take their name from lambda functions in a general sense; these are small, convenient functions that can be triggered by certain events. A lambda provides an easy way to make small and repetitive things happen on the AWS cloud and can be coded in a huge variety of languages. Now, what we’d like to do here is to create a Lambda that converts raw footage from our input bucket, transcode the videos into mp4 format, and then put these new videos in a new bucket. We can then consume these newly created web-friendly videos from here. Of course, the first step would be to make a function. Follow this guide to get it set up, and make its trigger an upload event to input-bucket. When prompted about the code, choose the option to start the lambda from scratch, and choose Python as the programming language.

As you can see, I take great pride in giving my things unique, easily distinguishable names. This is how my lambda function looks in the AWS management console after adding the trigger from the S3 bucket.

Now for the code. We’ll use the below script to make the magic happen.

import json
import boto3
import io
import subprocess
FFMPEG_STATIC = '/var/task/ffmpeg'def lambda_handler(event, context):
print(event)
s3_client = boto3.client('s3')

filekey = event['Records'][0]['s3']['object']['key']
if not filekey.split('.')[1] == 'h264':
return {
'statusCode': 500,
'body': json.dumps(
'File not .h264; aborted conversion.'
)
}
output_filename = '{}.mp4'.format(filekey.split('.')[0])
input_filename = '/tmp/{}'.format(filekey)
s3_client.download_file(
'input-bucket',
filekey,
input_filename
)
subprocess.call([
FFMPEG_STATIC, '-i', input_filename,
'/tmp/{}'.format(output_filename)
])
s3_client.upload_file(
'/tmp/{}'.format(output_filename),
'output-bucket',
output_filename
)
return {
'statusCode': 201,
'body': json.dumps('Conversion successful.')
}

This script uses the same boto3 API to interact with AWS, and FFMpeg for video conversion. It starts with a simple print statement, printing the JSON event that AWS sends the lambda as part of the trigger. It’s possible to set up monitoring for Lambdas on the AWS CloudWatch service, and if you do so, the print function will show you the structure of the JSON event in the logs, so you can figure out what’s going on and play around with it if you want.

There’s a quick check to make sure that the file ends with .h264 — if the file is anything else, like an image or something, the Lambda shouldn’t even try to continue. That being said, you can set the trigger to only happen with files that fit a certain suffix or prefix. In my case, I set it to only spin up the Lambda on files that end with .h264, but I thought I’d err on the side of caution.

You may also notice that all of the files are written and read from the /tmp directory. This is how permissions in the system are set up. While a lambda somewhat resembles a normal Linux filesystem, your code runs as a user that has limited privileges, so keep any created files in the temporary directory given to you.

There are a few interesting things going on with the FFMpeg call, too. In order to use the FFMpeg tool to do video conversion for you, you need to actually package the whole thing along with your code. The lightweight Lambda doesn’t have these kinds of tools by default. To do this, download the Linux FFMPeg binary from https://ffbinaries.com/downloads. Just put it next to your Python script. Unfortunately, the binary exceeds the file-size limit for uploading directly into the Lambda, so you’ll have to zip it and upload it to input-bucket instead, and then point the Lambda to it (the archive.zip file in the first image is my Lambda’s source code).

How the lambda works, is that when it spins up, the code you uploaded for it to run is located in the /var/task directory. If this code is in a zip file, the contents are extracted there. Hence the FFMPEG_STATIC variable at the top, pointing to the location of the binary. Later in the script, we use Python’s subprocess call to make a new process in the system, calling the array of arguments we give it. This is pretty much equivalent to typing something like

/var/task/ffmpeg -i /tmp/myvideo.h264 /tmp/myconvertedvideo.mp4

into the command line. Of course, we don’t have a terminal open to a lambda, so this makes it happen for us. This will convert the video to .mp4 format for us, and then upload it to the output-bucket . Needless to say, you’ll need this bucket to be created as well, so just follow the same steps you did earlier.

This is what the contents of output-bucket look like after the Lambda we just made converts all the videos. Notice that the generic file icon on the files from input-bucket is now replaced with a video icon, indicating that AWS recognizes the files as actual videos.

And there you have it

We have a Raspberry Pi security camera, uploading footage to the cloud, which we can ultimately access as videos in mp4 format. Doing a quick recap of the problems discussed with the original project, we’re not limited to the amount of local storage we have anymore; the S3 Buckets solve that. The Pi doesn’t have to try and record new footage while transcoding old footage anymore; the Lambda solves that, while the Pi can just record and upload footage. The system is slick, and (once you get familiar with the AWS ecosystem and how things work) really flexible. I believe a project like this showcases some of the strengths of cloud-based architecture, and how we can use it to improve our software applications.

The only thing that remains is to make a website from which we can view the footage. This is a little out of the scope of this article, but there are already tons of resources out there on how to do this. The easiest would be to take a look at static website hosting on the Amazon user guides and deploy an Angular app on S3 that displays your video footage and allows you to play it from the web browser. Or, if you’re looking for an excuse to explore the AWS ecosystem a bit more, you can set up an EC2 Instance and host a web server with whatever stack you prefer. Granted, this is a little overkill, akin to ramming the side of your house with a bulldozer if you can’t get in the front door. That being said, just like learning how to use a bulldozer, knowing how to set up an EC2 instance is an indispensable real-world skill, worthy of picking up. Whichever option you choose, I hope you enjoyed this post. Happy coding!

--

--