Sunday 19 August 2007

Camera Automation with PowerShell Part III

Camera Automation with PowerShell Part III

So far we've figured out how to do some basic automation and image comparison. Now it's time to see if we can make this do anything useful.

Our first serious attempt is going to add some basic error trapping to the various part. For starters we have to some traditional property checking to the start up code to make sure that:

  • The PC can create a functioning WIA object
  • We have at least one WIA compatible device connected
  • We can connect to the device
  • It supports the WIA Command wiaCommandTakePicture ({AF933CAC-ACAD-11D2-A093-00C04F72DC3C})

The object setup and enumeration routines don't generally throw any error exceptions but we do have to make sure that we actually have a valid device and we might as well just set things up so that all but the nastiest failures end up with the user getting some sort of sensible message at least. To do this the best approach is to set PowerShell's global error handling behavior to "SilentlyContinue" and then to check for all errors immediately after making any call that might produce a fatal result. This global behavior is controlled by the Global Automatic Variable called $ErrorActionPreference which we will set to "SilentlyContinue". We could go to some trouble here enumerating through the stack of messages in the $error error handler object however for our purposes here we're simply going to use the "$?" variable that always returns the state (Successful [True] or Failed [False]) of the last command. This turns the initialization block of code into:

$ErrorActionPreference="silentlycontinue"
$WIAManager = new-object -comobject WIA.DeviceManager
if (!$?) {
write "Unable to Create a WIA Object"
Eexit
}
$DeviceList = $WIAManager.DeviceInfos
if ($DeviceList.Count -gt 0) {
$Device=$DeviceList.item($DeviceList.Count)
} else {
write "No Device Connected"
}
$ConnectedDevice = $Device.connect()
if (!$?) {
write "Unable to Connect to Device"
Exit
}
$Commands = $ConnectedDevice.Commands
$TakeShot="Not Found"
foreach ($item in $Commands) {
if ($item.name -match "take") {
$TakeShot=$x.CommandID
}
}
if ($TakeShot -eq "Not Found") {
write "Attached Camera does not provide the ""Take Picture WIA Command"""
Exit
}

Moving on to the core working loop there are a couple of errors that arise quite often once you begin to test it.
  • Unexpected files. Since the basic function of the script means that we are creating and renaming image files all the time it is no surprise that we often find situations where files exist that we didn't expect and likewise files don't exist when we really wished they did.
  • Unexpected Camera failures. Depending on your camera you may see this a lot. My experience has been that really simple cheaper (fully automatic) cameras tend to plough along regardless of conditions but more powerful cameras tend to get easily upset and refuse to take pictures unless you have everything perfect for them. We need to code for the latter behavior as I want to use a DSLR for this, and its highly likely that it will be temperamental.

This transforms the core loop into:

$Pdir="C:\temp\capture\test"
new-item $Pdir -itemtype dir
$ICompare="c:\Program Files\ImageMagick-6.3.5-Q16\compare.exe"
$SaveCount=0
$Sensitivity=1000
for ($stage=0;$stage -lt 10;$stage++) {

if (test-path "$Pdir\temp.jpg") {
del "$Pdir\temp.jpg"
}
rename-item -force "$Pdir\lastpicture.jpg" temp.jpg
rename-item -force "$Pdir\picture.jpg" "lastpicture.jpg"
if (-not (test-path "$Pdir\lastpicture.jpg")) {
rename-item -force "$Pdir\temp.jpg" "lastpicture.jpg"
} else {
del "$Pdir\temp.jpg"
}
Write "Taking Picture # $stage. "
$Pcount=$ActiveCamera.items.Count
$TakeanImage=$ActiveCamera.ExecuteCommand($TakeShot)
if ($ActiveCamera.items.Count -gt $Pcount) {
# Camera has actually taken a picture and has it stored so attach an object to it
$Picture=$ActiveCamera.items.item($ActiveCamera.items.Count)
# Set up a storage variable to be certain we have retrieved it
$Imagefile=""
$ImageFile=$Picture.Transfer()
if ($ImageFile.FileExtension -eq "jpg") {
# If the object now contains a property called Filextension that has a value jpg we have the pic
# Make sure we have a good filename to save it to
if (test-path "$Pdir\picture.jpg") {
del "$Pdir\picture.jpg"
}
$ImageFile.SaveFile("$Pdir\picture.jpg")
# Call ImageMagic to compare it
# $compare=& $ICompare -metric MEPP $Pdir\picture.jpg $Pdir\lastpicture.jpg $Pdir\sectors.jpg 2>&1
if ($compare -match "(\d+)(\.|\s)") { $result=$matches[1] }
# If the Compare result is higher than some value then save a copy of the file
if ($result -gt $Sensitivity) {
copy-item "$Pdir\picture.jpg" "$Pdir\$SaveCount-$result-picture.jpg"
Write "Saving interesting picture: $Pdir\$SaveCount-$result-picture.jpg"
$SaveCount++
}

# We have an image on the camera that we need to tidy up
$ActiveCamera.items.Remove($ActiveCamera.items.Count)
}
}
}
This now gives us a reasonably reliable way of taking a bunch of snaps of a (slowly) changing target and seeing what the results look like. The results of that exercise were pretty good for static indoor scenes but they aren't actually all that good for the sort of thing that I want to do - compare long exposure pictures of the night sky. The sky rotates quite a bit in 30 seconds - 1/8th of a degree to be exact and that converts into up to 7 or 8 pixels near the horizon on a 10 mega pixel image for a camera with a fairly average wide angle lens (18mm on a Nikon D40x). The net result is that the ImageMagick compare metrics see the meteor as a single line covering up to 20 degrees or so of the picture (say affecting 1000-2000 pixels) but there are also many hundreds of star tracks each moving 7-8 pixels each. The end result is too complex for Imagemagick to figure out.

To make matters worse ImageMagick is a complete resource hog when dealing with DSLR size images. Comparing two 1-2Megapixel images may chew up 10-20Meg of memory and use 100% CPU to yield an answer in five to ten seconds. Bumping the resolution up to 10 Megapixels and the amount of memory jumps to 250Meg or more and will use 100% CPU for up to a minute on my laptop to yield an answer. Playing about with preparatory transforms that reduce the image resolution and change the colour space to Monochrome help the performance issues at the cost of some complexity but it is a lot of work and in the end none of the blurring and edge detection filter options I tried gave me a reliable number that could be used to detect meteors.

Tomorrow we'll look at the test images and see what we can do about the problem.

No comments: