Timothy Langer

Linux webcam timelapse

linux ubuntu project
Snapshot from my webcam, November 2019

This weekend I decided to set up an unused webcam to capture a time-lapse of London. There were several things to consider:

I connected the camera to an old laptop with Linux installed that I would be willing to keep switched on. I used guvcview to connect to the camera and check the configuration. Next, I installed and set up motion and set it to take a snapshot every 300 seconds and also stream a live feed to a locally accessible website. This naïve and simple solution worked, but there were two issues.

Firstly, my webcam of choice was the Logitech C920, which has a 3:2 2304×1636 sensor which is limited to 1080p@30fps for video, and can only manage two photos per second at the full sensor resolution. Since I configured the webcam to take photos every five minutes I had hoped to use the full sensor resolution. To my dismay, since motion’s main use case is for detecting motion (hence the name) this was impossible. Secondly, motion had a very high load on the machine and caused the fan to be noticeably louder, and I was not willing to have so much noise in the room this machine is in.

So I turned instead to a combination of ffmpeg and cron to capture photos. ffmpeg supports input from video devices on Linux, so this was a viable alternative. After spending a significant amount of time messing with the configuration options for my specific webcam, I managed to retrieve full-sensor images.

My webcam configuration command:

v4l2-ctl -c brightness=128 
\ -c contrast=128
\ -c saturation=150
\ -c sharpness=190
\ -c backlight_compensation=1
\ -c exposure_auto_priority=1
\ -c focus_absolute=0
\ -c focus_auto=0
\ -c led1_mode=0

And my ffmpeg command:

ffmpeg -f video4linux2                 # V4L2 interface
\ -input_format yuyv422 # raw video input (to access full sensor)
\ -framerate 2 # limit framerate to 2, otherwise V4L2 automatically selects 1080p instead
\ -video_size 2304x1536 -s 2304x1536 # set the desired capture size
\ -i /dev/video0 # my input device
\ -frames 1 # how many frames to take
\ -vf drawtext="fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf:text='%{localtime\:%Y-%m-%d %T}':x=20:y=20:fontcolor=white" # date + time watermark on the photo
\ /var/www/html/motion/KewBridge-$(date +%Y%m%d-%H%M%S).jpg # output filename

So putting this into cron, it looks like this (escaping the % symbol):

@reboot v4l2-ctl -c brightness=128 -c contrast=128 -c saturation=150 -c sharpness=190 -c backlight_compensation=1 -c exposure_auto_priority=1 -c focus_absolute=0 -c focus_auto=0 -c led1_mode=0
*/5 * * * * ffmpeg -f video4linux2 -input_format yuyv422 -framerate 2 -video_size 2304x1536 -s 2304x1536 -i /dev/video0 -frames 1 -vf drawtext="fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf:text='\%{localtime\:\%Y-\%m-\%d \%T}':x=20:y=20:fontcolor=white" /var/www/html/motion/KewBridge-$(date +\%Y\%m\%d-\%H\%M\%S).jpg

Using ffmpeg allows me to access the camera when it’s not taking a photo, CPU load on my machine is significantly reduced and I still capture the same number of images.

To access and present these photos, I use apache2 in combination with this lovely apache2 directory listing theme.

A small post-scriptum

The webcam struggles with handling the high dynamic range. Especially as the days get longer and there’s more sun about, often-times the whole photograph is pure white, and at night barely anything can be seen because it’s so dark. I tried writing Python in combination with OpenCV to capture multiple photographs at different exposures and combining them into a single HDR post-processed photograph. This proved to be a lot more complicated than I thought, particularly because the webcam’s exposure control interface isn’t well-equipped for this kind of abuse. I’m no computational photography expert so I’ve left the project dormant for now…