Capturing an Image from a WIA-compatible Digital Camera

We’ve had a research project requiring a fair amount of image acquisition and processing, requiring  higher resolutions than most industrial cameras can offer. As a result, we’ve tried at least three different digital cameras (Canon PowerShot S3is, Nikon D40, and Canon PowerShot SD780is). Each of them has their own advantages and disadvantages:

  • S3is advantages:  good control with Breezesys’ PSRemote, including a pretty complete DLL that we can call from our LabVIEW code. Disadvantages: larger aperture sizes reduce the depth of field, and PSRemote can’t toggle macro and super-macro modes.
  • D40 advantages: complete manual control when needed, huge range of apertures including ones that allow for good depth of field, easy to grab pictures in PTP mode from Windows Explorer. Disadvantages: Breezesys’ NKRemote for Nikon doesn’t support the D40.
  • SD780is advantages: ridiculously high resolution (12MP) in a tiny camera. Disadvantages: no PSRemote support, and little manual control of settings.

So it came down to needing LabVIEW to acquire images from whatever camera automatically. We had gotten it working with PSRemote some time back for a different class of pictures, but the ones we needed now went beyond PSRemote’s and the S3is’ ability to focus in on close distances. And the SD780is was out entirely. So that left the Nikon.

Previous testing with the Nikon generally consisted of putting it in PTP mode, opening up Windows Explorer, hitting the “Take a new picture” link, and then copying over the newest image to the local drive. Great, except for the clicking and dragging. The obvious solution would be to automate the process via Win32 COM programming. After a few hours with the Python docs and MSDN, a workable Python script was born:

import win32com.client, time, os

WIA_COM = "WIA.CommonDialog"

WIA_DEVICE_UNSPECIFIED = 0
WIA_DEVICE_CAMERA = 2

WIA_INTENT_UNSPECIFIED = 0

WIA_BIAS_MIN_SIZE = 65536
WIA_BIAS_MAX_QUALITY = 131072

WIA_IMG_FORMAT_PNG = "{B96B3CAF-0728-11D3-9D7B-0000F81EF32E}"

WIA_COMMAND_TAKE_PICTURE="{AF933CAC-ACAD-11D2-A093-00C04F72DC3C}"

def acquire_image_wia():
    wia = win32com.client.Dispatch(WIA_COM) # wia is a CommonDialog object
    dev = wia.ShowSelectDevice()
    for command in dev.Commands:
        if command.CommandID==WIA_COMMAND_TAKE_PICTURE:
            foo=dev.ExecuteCommand(WIA_COMMAND_TAKE_PICTURE)

    i=1
    for item in dev.Items:
        if i==dev.Items.Count:
            image=item.Transfer(WIA_IMG_FORMAT_PNG)
            break
        i=i+1

    fname = 'wia-test.jpg'
    if os.path.exists(fname):
        os.remove(fname)
    image.SaveFile(fname)

os.chdir('c:/temp')
acquire_image_wia()

Things I like about this:

  • snaps the camera shutter, grabs the last image from the card, and stashes it on the local drive, no questions asked. Since I’ll probably set the camera settings once and leave it on manual focus, this is all I needed.
  • easily converted into an executable with py2exe.
  • roughly 3.2 seconds to acquire and save the image, with around 2 seconds of that spent on the ExecuteCommand() line with a 0.25 second shutter speed.

Things I don’t like about this:

  • Windows COM programming makes my brain hurt.
  • To make things entirely hands-off, I had to disable my Webcam. I’m sure there’s a way to make WIA connect to a named device, but ShowSelectDevice() was all I found ready documentation for. With multiple cameras available, it always asked which one I wanted to acquire from. With only one camera available, it just went on and snapped the picture.
  • I couldn’t find a good way of jumping to the end of the list of items stored on the camera. I can count them, I can iterate over them, but I’m having to iterate over each element until I get to the last one, and then I can transfer it over.

Someone may have a better solution to the last two problems, but this should get people started.

Update — Leaner, meaner code to grab the last image off one specified camera — thanks to Janzert in the comments below:

import win32com.client, time, os

MY_CAMERA="D40"
WIA_IMG_FORMAT_PNG = "{B96B3CAF-0728-11D3-9D7B-0000F81EF32E}"
WIA_COMMAND_TAKE_PICTURE="{AF933CAC-ACAD-11D2-A093-00C04F72DC3C}"

def acquire_image_wia():
    # Find the camera
    devman=win32com.client.Dispatch("WIA.DeviceManager")
    for info in devman.DeviceInfos:
        for prop in info.Properties:
            if prop.Name=="Name" and prop.Value==MY_CAMERA:
                dev = info.Connect()

    # Snap picture
    foo=dev.ExecuteCommand(WIA_COMMAND_TAKE_PICTURE)
    # Transfer last image (doesn't actually use PNG format, but this
    # still is valid syntax).
    image=dev.Items[dev.Items.count].Transfer(WIA_IMG_FORMAT_PNG)
    # Save into file
    fname = 'wia-test.jpg'
    if os.path.exists(fname):
        os.remove(fname)
    image.SaveFile(fname)

os.chdir('c:/temp')
acquire_image_wia()

Join the Conversation

6 Comments

  1. Thanks, I just started a project to automate some functions of a scanner which also uses WIA and this was quite helpful. Here is some stuff that might help solve the last two things you don’t like currently.

    Instead of using WIA.CommonDialog and the select device dialog, use WIA.DeviceManager and then DeviceInfos to find the device you want. Something like this:

    devman = win32com.client.Dispatch(“WIA.DeviceManager”)
    for info in devman.DeviceInfos:
    for prop in info.Properties:
    if prop.Name == “Name” and prop.Value == “Camera I Want”:
    device = info.Connect()

    Also instead of iterating through Items (or DeviceInfos or Properties) they can also be accessed directly by index (e.g. dev.Items[1] or dev.Items[dev.Items.Count]). The one thing to watch out for though is that Items and DeviceInfos seem to use a 1 based indexing and Properties use a 0(zero) based index at least for my scanner.

  2. Does the Nikon D40 allow you to plug the camera in and use the WIA-compatible Digital Camera? Which when tethered to the PC allow me to use the WIA window to take photo?

    Thanks for any information.

  3. (Sorry for the delay, just went back through the pending comments folder.)

    As long as the camera shows up in the “Scanners and Cameras” part of the My Computer folder, and has a “Take a picture” or similar menu item on the left pane, then you can at least take snaps from it. You may have to put the camera into PTP mode, but googling “g9 ptp” indicates that it’s supported. Check your manual.

    I won’t claim full control of all the camera features, but if you set all the exposures, apertures, and such ahead of time, you can definitely control the snapping of the shutter. The manual (or a forum for the g9) would say what else can be controlled remotely.

  4. Dear Mike,

    I tried your leaner version of your code with Python2.7 and the Win32 extensions for Python. I am using Windows XP, SP3. I get the following error when trying to compile:
    ________
    Traceback (most recent call last):
    File “tester.py”, line 26, in
    acquire_image_wia()
    File “tester.py”, line 8, in acquire_image_wia
    devman=win32com.client.Dispatch(“WIA.DeviceManager”)
    File “C:\Python27\lib\site-packages\win32com\client\__init__.py”, line 95, in Dispatch
    dispatch, userName = dynamic._GetGoodDispatchAndUserName(dispatch,userName,clsctx)
    File “C:\Python27\lib\site-packages\win32com\client\dynamic.py”, line 108, in _GetGoodDispatchAndUserName
    return (_GetGoodDispatch(IDispatch, clsctx), userName)
    File “C:\Python27\lib\site-packages\win32com\client\dynamic.py”, line 85, in _GetGoodDispatch
    IDispatch = pythoncom.CoCreateInstance(IDispatch, None, clsctx, pythoncom.IID_IDispatch)
    pywintypes.com_error: (-2147221005, ‘Invalid class string’, None, None)

    _______

    Please advise if you can. Thanks kindly.

Leave a comment

Leave a Reply to Mike Renfro Cancel reply

Your email address will not be published. Required fields are marked *