A "mon" monitor to detect motion in images using pnmpsnr

Mon Apr 16 20:38:45 EDT 2007
Jim Trocki (trockij@arctic.org)

Here is a nifty tool from netpbm called "pnmpsnr":

     pnmpsnr  reads two PBM, PGM, or PPM files, or PAM equivalents, as input
     and prints the peak signal-to-noise ratio (PSNR) difference between the
     two  images.  This metric is typically used in image compression papers
     to rate the distortion between original and decoded image.
Feed it two images and it shows the difference in dB for each color element (Y, Cb, and Cr). The change in Y (luma) between comparing image "A" to "B", then "B" to "C", seems to give a good enough indication of something substantial changing in the image.

For example, compare the ouptut of pnmpsnr between these three pairs of images. Three pairs are shown so you can compare the sameness as well as the difference.

You can move the mouse over the left image in each pair to see how the images change:

$ /usr/bin/pnmpsnr a0.pnm a1.pnm
pnmpsnr: PSNR between a0.pnm and a1.pnm:
pnmpsnr: Y  color component: 34.94 dB
pnmpsnr: Cb color component: 41.38 dB
pnmpsnr: Cr color component: 40.54 dB

$ /usr/bin/pnmpsnr a1.pnm a2.pnm
pnmpsnr: PSNR between a1.pnm and a2.pnm:
pnmpsnr: Y  color component: 34.22 dB
pnmpsnr: Cb color component: 41.37 dB
pnmpsnr: Cr color component: 40.54 dB

$ /usr/bin/pnmpsnr a2.pnm a3.pnm
pnmpsnr: PSNR between a2.pnm and a3.pnm:
pnmpsnr: Y  color component: 23.58 dB
pnmpsnr: Cb color component: 40.04 dB
pnmpsnr: Cr color component: 37.81 dB
In summary:
Y (a->b): 34.94 dB
Y (b->c): 34.22 dB (-0.72 dB, tiny change)
Y (c->d): 23.58 dB (-10.64 dB, large change)
Therefore, writing a mon script to detect a substantial change in an image is trivial:

Here's a first-try implementation that actually works. For illustration, some almost pseudocode so you can get the idea about how it operates:

$SECS = 5;
$COUNT = 20;

$lastY = undef;
$Y = undef;

$prev_img = getimage;
sleep $SECS;

# this will run until $COUNT images are sampled or until a change
# in the image is identified
for ($i = 0; $i < $COUNT; $i++)
   $cur_img = getimage;
   $output = `pnmpsnr $prev_img $cur_img`;
   ($Y) = ($output =~ /Y.*component: (\d+(\.\d+)?)/)

   if (defined $lastY && abs ($Y - $lastY) > $TOLERANCE)

   sleep $SECS;

# eventually give the mon server a status update
exit ($MOVEMENT);

Of course for the real application you'll want something more substantial, like logging the images and maybe doing some fancier comparisons using some statistical process control tricks (running weighted average), but you get the idea.