Thursday, July 17, 2008

Java Image Comparison / Motion Detection

I've recently purchased a wireless security camera which is very conveniently smaller than a cubic inch. Well the imagination runs wild once you've got this device,... Who can I spy on? What can I monitor? What does the domestic worker actually do when I'm at work?.. and so the list continues.

I played around in java's JMF for a day and soon had a small app that recorded one still frame per second. Now I could review the recorded images when getting home at the end of the day. Hovever, 1 frame per second equates to 36000 images over the 10 hour work day that I'm not at home! I don't really want to record motion video - yet. I'm still happy with working with images,.. albeit a lot of them. So now I've got a haystack of images, and presumably somewhere inside of them is the needle of interesting footage.

I searched pretty hard for a Java image comparison class or library and could not find anything. Just a whole bunch of forum posts declaring how advanced the topic can get. So sleeves up, I created my own rudimentary ImageCompare class. (Download Link). It's not bulletproof, and its not lightspeed, but for my purposes it's done the job perfectly.

The class breaks up the images into smaller regions and compares the brightness of each corresponding region. If any particular pair of regions are vastly different then something must have changed in that part of the image.

How to use:

// Create a compare object specifying the 2 images for comparison.
ImageCompare ic = new ImageCompare("c:\\test1.jpg", "c:\\test2.jpg");
// Set the comparison parameters.
// (num vertical regions, num horizontal regions, sensitivity, stabilizer)
ic.setParameters(8, 6, 5, 10);
// Display some indication of the differences
in the image.
ic.setDebugMode(2);
// Compare.
ic.compare();
// Display if these images are considered a match according to our parameters.
System.out.println("Match: " + ic.match());
// If its not a match then write a file to show changed regions.
if (!ic.match()) saveJPG(ic.getChangeIndicator(), "c:\\changes.jpg");


The setParameters() accepts four parameters.
  1. The number of vertical columns in the comparison grid.
  2. The number of horizontal rows in the comparison grid.
  3. A threshold value. If the difference in brightness exceeds this then the region is considered different.
  4. A stabilization factor. In future I will calculate this automatically since it is proportional to parameters 1 and 2.
setDebugMode() is completely optional and only really usefull while designing your parameters.

After running compare(), match() will hold the result.

Example:
The above code was run on the following two images:




The console output looked like this:
|0,0,0,0,0,0,1,0|
|0,0,0,0,0,0,0,1|
|0,0,0,0,0,0,0,3|
|0,0,0,0,0,1,1,8|
|0,0,0,0,0,0,0,8|
|0,0,0,0,0,1,0,0|
Match: false
... and the image output (changes.jpg) looked like this:



On my todo list:
  • Provide an array/vector of changed regions, or number of changed regions.
  • Heuristically determine a stabilizing factor, thus not having to specifying one.
  • Allow for the input of area vectors before comparison for the purpose of excluding or only checking these areas.
  • I'd also like to integrate this into a more real time solution that then exposes events and the like.

What you see here is a very alpha stage development. Take it and use it if you like, but do so at your own risk,.. or frustration. I'll try post some updates to this code as and when significant changes have been made.

Follow-up post: http://mindmeat.blogspot.com/2008/11/java-image-comparison.html