Introduction

Like videos? If you have a raspberry pi with a camera, you can easily create a fully automated timelapse. Let's walk through some setup and scripts that make this possible.

If you don't have a camera, you can pick up a 5 megapixel one online for around $10. There are 8 megapixel cameras available too, around $25. The 5MP camera gets you 2592x1944 static images, and 1080p30 video (or higher framerates with lower resolution).

This post is going to assume that other resources will be used to configure your raspberry pi, the camera, networking, and storage device.

Here's an example of the result!

Taking Pictures

Once installed and enabled, you can use the raspistill utility to take pictures. The important options for our use case are -q for quality and -o to control the output destination. Since we're going to be taking a lot of pictures, let's take a closer look at how the JPG quality option affects image size and ... image quality!

pi@pine /m/z/tmp> raspistill -q 100 -o 100-quality.jpg
pi@pine /m/z/tmp> raspistill -q 10  -o 010-quality.jpg
pi@pine /m/z/tmp> raspistill -q 5   -o 005-quality.jpg
pi@pine /m/z/tmp> raspistill -q 1   -o 001-quality.jpg

With those commands, we have a range of qualities to compare. Let's compare sizes.

pi@pine /m/z/tmp> du -h *
33K     001-quality.jpg
133K    005-quality.jpg
345K    010-quality.jpg
2.6M    100-quality.jpg

That's quite the difference! Let's do some quick math to see about how much storage we'll need for a day's worth of pictures. In reality, image size is affected greatly by the content: a picture of total darkness will be much smaller than a picture of a crowd of people, but this will give a decent back of the napkin estimate.

pi@pine /m/z/tmp> python3 -q
>>> minutes_in_a_day = 60 * 24
>>> quality_sizes = [33, 133, 345, 2.6 * 1024]
>>>
>>> # now to see kilobytes are required for a days worth of pictures, 1 picture per minute
>>> [size * minutes_in_a_day for size in quality_sizes]
[47520, 191520, 496800, 3833856.0]
>>>
>>> # let's convert to megabytes
>>> [s * minutes_in_a_day / 1024 for s in quality_sizes]
[46.40625, 187.03125, 485.15625, 3744.0]

Wow! The difference is even more obvious now. At maximum quality, we'd produce ~3.7 gigabytes of data per day. At worst quality, we'd have ~46.4 megabytes. For a year of data, the difference is 1,350 GB (best) vs 16 GB (worst). This will also affect the size of videos we create from the images later on too. Unless you have a storage array laying around, it's pretty clear some kind of compromise needs to be made.

If you don't have a monitor connected to your raspberry pi, you can use Python to start an HTTP server to view the images.

pi@pine /m/z/tmp> ls
001-quality.jpg  005-quality.jpg  010-quality.jpg  100-quality.jpg

pi@pine /m/z/tmp> python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 ...

Now you can use a web browser to view the pictures, by going to http://ip-of-your-pi:8000. Here are mine for comparison:

  • Quality 1
  • Quality 5
  • Quality 10
  • Quality 100

Quality 1 is clearly terrible, 5 is maybe okay, but there's some clear pixelation around rounded shapes (clouds in my example). 10 is not obviously better than 100 unless you zoom in. Personally, I think -q 10 strikes the best balance between size and quality. Feel free to run some more tests and choose what works for you!

Cron, automating the picture taking

Now that we can take pictures, we need to set up some automation to take them automatically on an interval. This means we need to think about organizing the output too. One picture per minute is 1440 pictures per day, one per 5 minutes is 288 per day. Both are quickly unmanageable without a naming scheme.

I chose <full-path-to-base>/<date>/<unix-epoch-time>.jpg. This looks like:

pi@pine /m/zfs> ls timelapse/
2019-06-16/  2019-06-17/  2019-06-18/  2019-06-19/  2019-06-20/  2019-06-21/  2019-06-22/

pi@pine /m/zfs> ls timelapse/2019-06-16/ | head
1560668401.jpg
1560668521.jpg
1560668641.jpg
1560668761.jpg
1560668881.jpg
1560669001.jpg
1560669121.jpg
1560669241.jpg
1560669361.jpg
1560669481.jpg

And here's a script that will take pictures in that format. You'll want to change /mnt/zfs/timelapse to something appropriate for you setup. Possibly local storage /home/pi, or a remote file share similar to my setup.

#!/bin/bash

# this is called by cron on a raspberry pi to populate images for a timelapse
#
# quality = 10 is good enough for concatenating into an mp4
#   5 has too many artifacts
#  20 doesn't show a noticable difference except when zoomed in

folder=/mnt/zfs/timelapse/"$( date +'%F' )"
mkdir -p "$folder"
output="$folder/$( date +'%s' ).jpg"

raspistill -q 10 -o "$output"

Every time this script is called, it'll take a new picture in the format <full-path-to-base>/<date>/<unix-epoch-time>.jpg. Now, we can add a new line to our user's cron entry with crontab -e. It might look something like this, to run the script every 2 minutes. You need to provide the full path to the script, where ever you saved in on your system.

# m h  dom mon dow   command

*/2 * * * * bash /mnt/zfs/take-a-picture.sh

With that, we're off! The raspberry pi will take a picture at the interval prescribed forever (well until you change it, or turn off the device).

Making a video

Now that we have some pictures, let's make a video! ffmpeg is the tool to use. The basic command is this:

ffmpeg -y -framerate 40 -pattern_type glob -i "$input" "$output"

Framerate determines how many pictures will be 'consumed' for a single second of video. The higher this is, the smoother and shorter the video. The lower, the more distinct each frame becomes and the longer the video. Since we're dealing with hundreds or thousands of input images, -pattern_type glob is the way to go. That lets us provide all the pictures in order with -i /mnt/zfs/timelapse/<day>/*.jpg. The output can be anywhere you like with an appropriate extension. ffmpeg supports many different output formats, and looks to the extension to determine what you want.

And with that you have a video!

Night vs Day

If you have cron taking a picture every minute, every other minute, etc you may notice that you end up with a lot of pictures of darkness from each night. These aren't interesting and add a span of nothing at the beginning and end of each of our videos. How can we get rid of these?

If you look closely at the sizes of the output images over the course of a day, you'll see a pattern. This is more apparent with a quick graph.

alt text

We can easily detect pre-sunrise and post-sunset images by size. I chose 40KB as the cutoff based on the images my setup was producing. This stripped out complete night pictures, without losing sunrises and sunsets. Another value may be more appropriate for your area.

We can write another script to organize each day's images into day and night, with some buffer for error, and then only provide the day pictures to ffmpeg to create the video with. The following script gives all the images a -day.jpg or -night.jpg suffix.

#!/bin/bash

# sort images into day and night by looking at the file size. night time images
# are significantly smaller than day time images (since there's less
# information to encode)
#
# assumes
#
# /mnt/zfs/pi/timelapse
#   <date>
#     <image>.jpg
#     ...
#   ...

cd /mnt/zfs/pi/timelapse || exit 1
cutoff_in_kb=40

for directory in *; do

    # unix epochs are 10 digits (for the next 260 years at least)
    # only consider those
    for file in "$directory"/??????????.jpg; do

        # check if we actually matched anything
        [[ "$file" =~ \? ]] && continue

        size="$( du "$file" | awk '{print $1}' )"
        base="${file//.jpg/}"

        if (( size > cutoff_in_kb )); then
            mv "$file" "$base-day.jpg"
        else
            mv "$file" "$base-night.jpg"
        fi
    done

done

After this, we can run ffmpeg with -pattern_type glob -i "$directory/*-day.jpg".

Done! A little more automation with cron, and you can have this done automatically by your Raspberry Pi. I have my Pi saving the images to network attached storage, where another computer picks them up for post processing. This ensures that creating videos doesn't interrupt taking more images, and that the Raspberry Pi doesn't run out of storage if I leave it alone for too long.

You can find the full working scripts I use here:

Summary

We walked through some scripts and utilities (raspistill, cron, ffmpeg) that let us automatically create a timelapse video once per day. The result is a .mp4 playable in the browser, with extra night frames stripped out.

Enjoy your timelapse videos!