Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Design

Time-Lapse MPEG Animations


Apr04: Programmer's Toolchest

All it takes is hardware and some open-source software

Stephen is the senior programmer/analyst at the Aerodynamics Laboratory of the Institute for Aerospace Research, National Research Council of Canada. He can be contacted at http://www.nrc.gc.ca/~jenkins.


During the early stages of the construction of the new Gas Turbine Environment Research Centre (GTERC) here at the National Research Council of Canada, I was asked if I could provide a web cam so that our staff and clients could watch the progress at the building site. Relying on experience I'd gained from a recently completed dynamic-image manipulation project, I immediately said "yes," thinking, "I'll do a lot better than just a typical web cam site—I'll give them up-to-date time-lapse animations." By the time I finished, I thought, they would see a web page that contained the most-recent static image, along with links to downloadable MPEG files showing the construction activity over the previous day, week, and month. As is often the case with projects like this, there were two minor problems—it was already behind schedule (the foundations of the building had been poured) and the budget was small.

Generating time-lapse animations is a simple, two-step process. First, you need to periodically acquire and store images in individual files. Then, after you have captured the desired number of frames, they have to be combined into a single animation file. To get the task done as quickly as possible, and in keeping with the prime virtue of Perl programmers (that is, laziness), I wanted to make use of other people's code as much as possible. Acquiring and storing the images is easy to do using the LWP::Simple module, while the ImageMagick (http://www.imagemagick.org/) image-manipulation tools can combine the frames if you have the mpeg2vidcodec installed. All that remained for me to do was write some file-handling and glue code.

Hardware

The final system has only two hardware components—a camera and computer. To make the programming easier and the physical installation process simpler, I decided to use an AXIS 2100 Network camera (http://www.axis.com/products/cam_2100/index.htm) rather than a traditional web cam. This device contains an Apache web server running on an embedded version of Linux and requires only power and a network connection to be fully operational. For the computer, I opted for a generic PC with a 1.6-GHz P4 processor, 80-GB hard drive, and 1 GB of RAM. The processor speed and hard-drive size constrain, respectively, the time required to generate the MPEG animations, and the number and size of stored images. The memory size imposes a limit on the maximum number of frames in the generated MPEG files. Since all access to the computer is handled via the Internet, I didn't even need a monitor, mouse, or keyboard after the software had been installed.

Support Software

All of the supporting software used for this system is open source. First, I installed Linux from a RedHat distribution CD, then downloaded and installed Perl modules LWP::Bundle and Mail::Mailer from the Comprehensive Perl Archive Network (CPAN). ImageMagick, which can be accessed via C, C++, Perl, Python, Java, and other languages, was downloaded and built with the PerlMagick option enabled. Finally, I installed the mpeg2vidcodec package. (While the source code for all of these components is freely available and, thus, it should be possible to get everything to work on any modern operating system, I have not attempted to use any OS other than Linux.)

Proof-of-Concept

While waiting for the hardware to arrive, I wrote Listing One as a proof-of-concept. The first part of the program is an initialization section. In the first few lines, warnings and strict naming are enabled, the LWP::Simple module is loaded, and output buffering is turned off. The next line sets a handler for the SIGINT signal, causing the program to execute the catch_int() subroutine when users enter a Ctrl-C. In the last two lines of the initialization section, variables are created that contain the desired wait time between image captures and the URL of the web camera's image file. Since I didn't have my own camera yet, I had to find one that was publicly accessible. This wasn't as easy as it sounds because most web cams have terms-of-use that specifically prohibit both continuous sampling and the storing of images. In the end, I decided to use a camera (owned and operated by another government department) that looks towards the Canadian Parliament Buildings.

The image-capture section of the program begins by setting up an infinite loop. Inside this loop, the current date and time are read and are used to create an image filename of the form imgYYYYMMDD-hhmm-ss.jpg. The mirror() routine from LWP::Simple is called with the image URL and the new filename as arguments. It acquires the JPEG image via the Web and saves it on the local hard drive. The program waits for the interval time to pass, then repeats the capture process until interrupted.

The final section is the signal handler subroutine that sets up the creation of the animation file. It begins by asking if users want an animation file to be created; if "yes," the current date and time are used to create a filename with a .mpg extension. Then, the convert program from the ImageMagick package is executed using the following arguments: the -adjoin option, a wildcard filename to match all of the JPEGs, and the desired name for the MPEG output file. The convert program determines the input- and output-file formats from their extensions and automatically makes use of the mpeg2vidcodec (including creating a default configuration file for it). After the convert program completes the generation of the animation file, the test program exits.

While simple, this test program not only provided proof that the method worked, but also let me experiment with various image capture rates and animation file sizes. I first tried a 30-second pause between images. While this generated a very smooth animation, I decided that it was impractical because of the large number of images involved. Each day would generate, on average, 12 hours × 60 minutes × 2 images per minute, or 1440 files. Since each image is approximately 50 KB, that would mean 72 MB of disk space per day for a project that was expected to run for 10-12 months. Also, when converted into an MPEG at one image per frame (25 frames/second), each animation file would require about 8 MB of disk space and would run for approximately 58 seconds. I realized that this size of animation file was too large, both from the point of view of download size and running time. After trying several combinations of capture rate and animation file sizes, I opted to use a 60-second interimage delay, and constructed each MPEG from 400 images, yielding a 2.2-MB, 15-second animation. Since I was capturing about 700 images to disk every day, I could always generate new animations with a larger number of frames.

Although this test program worked well for its intended purpose, several things about it are inappropriate for an automated system expected to run unattended for many months, generating well over 100,000 files. The three most obvious shortcomings are: human intervention required for starting and stopping, a flat file-storage system, and inadequate error reporting.

Final System

For the final system, I created two small programs: one called "tlget.pl" to capture and store the images, and the other called "tlmpeg.pl" to combine groups of images into time-lapse animations. I opted to use the UNIX cron utility to schedule the execution of both programs. This provided me with an easy way of acquiring images only when construction activity was likely to occur (weekdays during daylight hours, for instance). Also, because I expected the system to run unattended, I decided to use the Perl Mail::Mailer module, to have both programs e-mail all error messages to me.

When executed by cron, tlget.pl builds a name of the form TLyyymmdd using the current date, and creates a directory of that name to hold the day's files. It then begins acquiring images and places them in that directory using the same type of filename described in the test program. To make the most recent image available to users, it is also copied to a web-accessible location using the name tlcurrent.jpg.

The animation-generating program, tlmpeg.pl, runs every night after tlget.pl has finished. Using File::Find, a hash table is created whose keys are all of the daily directory names, and whose values are pointers to arrays of all of the image file names for that day. After this data hash has been constructed, its keys are reverse sorted to produce an array of directory names, where the first element contains the past day's directory, the first five (five working days per week) elements make up the past week, and the first 20 make up the past month. These three arrays of directories are then used with the master data hash to produce arrays of the filepaths for the images that were captured over each of the three time periods. Since the target number of frames for each MPEG was 400, each of these lists is culled to contain 400 approximately equally temporally spaced images. These final lists are then passed to the convert program one at a time to create the three animation files. These three files are stored in the current day's directory and are also copied to a web-accessible location and renamed past_day.mpg, past_week.mpg, and path_month.mpg, respectively. To make tlmpeg.pl more flexible, I added an option to allow the date to be overridden from the command line. This lets me run the program in batch mode if I ever need to regenerate any of the animation files (to get MPEGs with longer running times, for example).

To users, the end result of this process is a simple web page (Figure 1) that shows the most recently acquired still image along with links to the most recently generated set of animations. Since tlget.pl and tlmpeg.pl change the contents of the files that that web page refers to, the GTERC cam homepage can be a static HTML document rather than a CGI program. The four metatags in Example 1 need to be included in the header portion of the web cam page to ensure proper behavior. The first line causes the browser to reload the page every 60 seconds. Since there isn't a single standard command that disables caching in all browsers, the next three lines are all needed to force the image to be reloaded as well.

To make it easier to quickly access any of the animation or image files, I enabled the Indexes option in the Apache server's httpd.conf file for a select few people. This lets these users view the contents of any of the daily image directories from their browsers. This simple thing can make you look like a hero when the "big boss" comes in and says: "Can I see the animation for last Thursday?"

I only encountered two problems of any significance during the creation and commissioning of this system—one software and one hardware. The first was a problem with ImageMagick that I discovered while working on the test program. The convert command seems to consume a significant amount of memory. With 1 GB of RAM, the largest animation files that I could generate were about 450 frames long. When I tried longer MPEGs, the OS began swapping, which eventually lead to serious disk thrashing. Because time was limited, and I had already decided on shorter animations, I didn't pursue the problem further. The hardware problem was one of camera placement. My supervisor and I originally decided to mount the camera inside a building just to the north of the construction site. Unfortunately, we found that the window we were looking through caused severe reflection problems, leading to very poor images during low light conditions. Our second choice was to put the camera in a heated enclosure mounted on the roof of the same building (Figure 2). While this solved the reflection problem, we found that as winter progressed, the combination of snow on the ground and the sun low on the horizon lead to problems with glare and lens flare at certain times of the day. We decided not to make any other changes, since by this time, we had already captured several weeks worth of images and didn't want to have to restart the animation sequences from another, possibly less suitable, location.

Conclusion

The feedback that I've received has been unanimously positive; people seem to enjoy being able to watch the construction of the building at fast-forward speeds. Once construction is complete, I'll generate one last animation file from images captured at the same time (say noon) every day. An unexpected side benefit of this project is that the experience I gained has already been put to use to produce flow visualization animations from experiments in our laboratory's water tunnel. In all, the project has been extremely successful for a little system that required about three weeks from concept to commission—and only cost about $750.00 U.S.

DDJ

Listing One

#!/usr/bin/perl
use warnings;
use strict;
use LWP::Simple;
$|++;

$SIG{INT} = \&catch_int;

my $interval = 60;
my $camURL = 'http://parliamenthill.gc.ca/text/newhillcam.jpg';

# capture the images
while(1) {
    my @t = localtime(time);
    my $imgname = sprintf("img%04d%02d%02d-%02d%02d-%02d.jpg",
                          $t[5]+1900,$t[4]+1,$t[3],$t[2],$t[1],$t[0]);
    print "Getting $imgname ... ";
    mirror($camURL, $imgname) || warn "Oops: $!";
    print "done!\n";
    sleep($interval);
}
# generate the animation
sub catch_int{
    print "\n\nDo you want me to create an mpeg? ";
    if( (<STDIN>) =~ m/y/i ){
        my @t = localtime(time);
        my $mpegname = sprintf("%04d%02d%02d-%02d%02d.mpg",
                               $t[5]+1900,$t[4]+1,$t[3],$t[2],$t[1]);
        print `convert -adjoin *.jpg $mpegname`;
    }
    exit(0);
} 

Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.