Tuesday 23 February 2016

Making a timelapse video with a webcam and Linux

Out of my window I can see a building site on the site of the old BBC studios on Oxford Road, Manchester. As there's going to be a few years building work, I thought it would be interesting to produce some sort of time-lapse film of the works from my office window.

This post explains how I did it.

I have an existing Ubuntu Linux system which runs 24x7 doing a variety of different things, and so I decided as this was likely to have a far better uptime than my Windows system to use this to do the hard work.

First connect your webcam

So the first thing I did was to install a (cheap) USB web-cam bought from my local PC dealer for about a tenner. Make sure that the webcam you get is either marked as Linux compatible or compliant with the UVC standard. Mine just worked straight out of the box.

The most difficult thing was physically fixing the webcam in place. Most of them come with a fairly flimsy plastic clip designed to secure it to the top of a monitor, but that was unsuitable for my purposes. As I want to leave it in fundamentally the same position for several months, I also needed to be confident that it was moved, say for cleaning, then I could reposition it reasonably accurately.

I ended up securing the webcam to the top of a old tin of Chinese tea with elastic bands and wedging the entire thing against the window fittings, so it shouldn't move overly much. Not particularly elegant. But it works beautifully.

Now select your software

I want the webcam to take a photo every minute or so and tried various bits of client software to do this. The one I selected as having the best combination of features for my requirements. Eventually I settled on the fswebcam client, which can be installed using the command

$ sudo apt-get install fswebcam

After a bit of experimentation I found the command that gave me the best results was

$ /usr/bin/fswebcam  --background --device /dev/video0 --loop 60 --resolution 1280x720 --skip 10 --frames 3 --delay 5 --bottom-banner /home/tim/archive/tim/pix/\%Y-\%m-\%d_\%H:\%M:\%S.jpeg

The options do the following things:

--background flag tells the device to run in the background
--device specifies which device to use for capture, in this case the (rather obvious) /dev/video0
--loop makes the camera fire every 60 seconds
--resolution sets the image resolution. 1280 x 720 results in JPEG image of anywhere between about 40 and 140 KB depending on how much the image can be compressed
--skip 10 --frames 3 --delay 5 makes the camera makes the camera delay for 5 seconds, then skip the first 10 frames, then take an average image over the next 3 frames. I did this because I found that the (cheap) camera I had used had difficulty coping with changing light levels, and so, for examples, photos taken in the morning as the sun rose were completely overexposed. I recommend playing around with these settings until you get something which suits your requirements.
--bottom-banner produces a timestamped banner at the bottom of the image.

This is the sort of image it produces.



So I now had something which was taking an image every minute and placing it in the /home/tim/archive/pix directory with a filename something like 2016-02-23_10:12:00.jpeg (this is important as we want to be able to stitch many images together sequentially).

Note, however, that a 140 KB file being generated every minute does tend to chew your disk up: it will generate about 200 MB of output a day or, if you prefer, about 70 GB a year. My /home/tim/archive directory is actually a mount-point for a 1 TB disk, and so there's plenty of room, but if you have limited space then either reduce the image resolution or the frequency with which images are captured. I also periodically manually thin out the images (using rm), discarding, for example, any which are taken between 18:00 and 08:00 the following day as there is no building work going on during these periods. This reduces the daily output from 200 MB to about 80 MB.

Note also that if the frequency with which you want to take photos is measured in minutes, then rather than using the --loop option you could just as easily do it with a cron job, by creating a crontab using the

$ crontab -e

command. This would have the advantage that you could have much finer-grained control over the times at which pictures were taken - e.g. Monday to Friday, 08:00 to 18:00.

Although my system typically has uptimes of several months, it is rebooted occasionally. So I wanted to make sure that the image capture started automatically when it did so. I therefore put the fswebcam command above in the /etc/rc.local configuration file.

One bit of strangeness is that fswebcam doesn't work if you aren't logged in, even if it run as a cron job or with something like nohup. This doesn't matter on my system as I am automatically logged in at boot, but be aware of it and, if you find a solution please let me know!

Now stitch your frames together


I think that, really, most people won't be interested in a time-lapse video longer than about one or two minutes duration. So assuming that we're showing maybe 24 frames per second (FPS), then that's about 1,440 frames per minute. Now, taking a picture every minute for eight hours a day, means that per day my setup produces about 480 frames per day, or, if you prefer 20 seconds of video if I were to show all the frames.

Assuming that the timelapse is to be over a longer period, then it's necessary either to take fewer frames in the first place, by controlling the frequency with which fswebcam acquires images as discussed above or, alternatively, only selecting certain images. I chose this latter option, as I have plenty of storage space I didn't know if, at some stage, I might want to do a more granular video.

Now, my preferred way of stitching these frames together is ffmpeg. This is no longer included in the standard Ubuntu distribution and, instead, there is a fork of it called avconv

This has a really cool feature which allows sequentially numbered frames to be stitched together in to a video. Now as I only want to select some frames, I wrote a very simple Python script which will select the frames I want between certain hours of the day, days of the week and also introduce a "gap", so only selecting one frame in (say) 20 in order to keep things manageable.

The script creates sequentially numbered symbolic links to the image files in a separate directory for them to be subsequently stitched together using avconv. As I want to keep this blog post short, please contact me if you want a copy of the Python script.

Once the appropriate frames have been selected and sequentially numbered and placed in a subdirectory called enumerated, the separate images are then combined using avconv as follows:

$ /usr/bin/avconv -y -i enumerated/%06d.jpeg -r 25 -c:v libx264 -crf 20 -pix_fmt yuv420p output.mp4

I then use the wonderfully easy to use OpenShot 1.4.3 video editing suite. I've actually found that although the interface isn't nearly as slick-looking as OpenShot 2.0, the older version is much easier to use and so have reverted to using that. The advantage of using something like OpenShot is that it's also possible to add things like credits, background music and other effects as well as being able to save the resultant film in a number of formats or even upload it directly to YouTube.

No comments:

Post a Comment