Building the time lapse video

Before embarking on this construction project, I setup a camera to take a single photo every 10 minutes during the entire process.

Time lapse camera setup in the second floor bathroom

Now that it's had a chance to capture a reasonable amount of work, I've tasked myself with setting up a workflow to convert the images into a movie. The key steps are:

  • Remove the photos taken at night.
  • Crop the images to remove the irrelevant areas and resize to 720x480 (the native resolution is 2304x1536).
  • Rename the images to follow a sequence.
  • Encode and package a video.

My initial thought was to use the EXIF time taken data to remove the nighttime photos. The challenge with this method is that it's currently Fall and so the sunrise/sunset times are moving noticeably each week.

So I instead chose to detect the nighttime photos by looking at the average brightness of each image. The go to command line tool for such activities is ImageMagick.

The following command outputs the image mean for a folder of images:

identify -format "%[fx:mean]\n" image-folder/*

For a folder of five images, the following output is produced:

0.669545
0.286204
0.703383
0.038666
0.113007

We need a binary signal, and so:

identify -format "%[fx:mean<0.1?1:0]\n" image-folder/*

produces:

0
0
0
1
0

We can use this in a Bash script to automatically delete or move the images that we don't want.

Next we need to crop and resize. ImageMagick is our tool of choice again. Usually the convert utility would be used, but since we want to batch process, mogrify is more suitable:

mogrify -strip -crop 1440x960+600+150 -resize 720x480 -path outputdir "imagedir/*"

Note the use of quotes around the image path here. This allows ImageMagick's filename globbing methods to generate the file list. Without this, many OS shells will generate an "argument list too long" error or similar for large numbers of files, given limitations on the length of the command line.

Uncropped image from camera (resized)
Cropped image

The ultimate step prior to generating the video is to rename the resized files to sequential order. In Bash:

#!/bin/bash

resizeddir="./resized"
i=0
for jpg in $resizeddir/*JPG; do
  newname=$(printf "$resizeddir/img_%05d.jpg" "$i")
  mv -i -- "$jpg" "$newname"
  let i=i+1
done

To generate the video, I'm using ffmpeg with the libx264 codec. I initially tried 30 frames per second, but with an image only every 10 minutes, it was a tad fast. I've adjusted it to 15 frames per second which I think is more watchable. I'll probably adjust the camera settings at some point to take a photo every 6 minutes instead and re-up the frame rate.

ffmpeg -r 30 -f image2 -s 720x480 -i resized/img_%05d.JPG -vcodec libx264 -crf 25 -pix_fmt yuv420p output.mp4 

Putting it all together, our final script is:

#!/bin/bash

imagedir="./images"
resizeddir="./resized"
removeddir="./toodark"

### Move too dark images to a separate folder

echo "Evaluating brightness of images in $imagedir..."
filenames=($(ls $imagedir))
brightnessstr=$(identify -format "%[fx:mean]\n" "$imagedir/*")
IFS=$'\r\n' brightness=($brightnessstr) # \r\n as we're running on Windows
darkstring=$(identify -format "%[fx:mean<0.1?1:0]\n" "$imagedir/*")
IFS=$'\r\n' toodark=($darkstring) # \r\n as we're running on Windows

for ((i=0; i<${#filenames[@]}; ++i)); do
	if [ ${toodark[i]} -eq 1 ]; then
		printf "%s mean is %s - moving to %s\n" "${filenames[i]}" "${brightness[i]}" "$removeddir"
		mv "$imagedir/${filenames[i]}" $removeddir
	else
		printf "%s mean is %s\n" "${filenames[i]}" "${brightness[i]}"
	fi
done

### Crop and resize images

echo "Cropping and resizing remaining images..."
mogrify -strip -crop 1440x960+600+150 -resize 720x480 -path $resizeddir "$imagedir/*"

### Rename images to be sequential

echo "Renaming images to follow sequence starting at 0..."
i=0
for jpg in $resizeddir/*JPG; do
  newname=$(printf "$resizeddir/img_%05d.jpg" "$i")
  mv -i -- "$jpg" "$newname"
  let i=i+1
done

### Create video from images

echo "Creating video..."
ffmpeg -r 30 -f image2 -s 720x480 -i $resizeddir/img_%05d.JPG -vcodec libx264 -crf 25 -pix_fmt yuv420p output.mp4

Bash is far from my forte and there are likely many issues with this script, but for the purposes of quickly generating the video from the camera images, it works.

The video so far