Look at me! Windows Image Acquisition

Published 31 October 06 06:28 PM | Coding4Fun 
  Interfacing a Webcam that supports Windows Image Acquisition (WIA) using .Net
Scott Hanselman's Computer Zen

Difficulty: Intermediate
Time Required: 1-3 hours
Cost: $50-$100
Software: Visual Studio Express Editions, Windows® Image Acquisition Automation Library v2.0 Tool: Image acquisition and manipulation component for VB and scripting
Hardware: Logitech Webcam
Download:

I love Goodwill. If you're not familiar with Goodwill, it's a chain of non-profit stores that takes donations and then resells the items back to the public. I was at Goodwill recently (we stop by at least once a week) and noticed this little Logitech Webcam for US$3.99. Frankly, I thought it was a little overpriced, but I figured I'd go for it regardless.

One of the nice things about these older Logitech Webcams is that they don't require a driver download and they support Windows Image Acquisition (WIA) directly. WIA is an API included in Windows that aims to unify the acquisition of images from all kinds of devices, including scanners and cameras. This is a pretty low-level API and a bit of a hassle to us. However, after Windows XP SP1 the WIA Automation Layer was released with a simpler COM API meant for VB6 and ASP developers. You can download this layer from MSDN and other places as it is free for redistribution. The meat of the layer is the wiaaut.dll that should be copied into the system32 directory and registered via the command "regsvr32 wiaaut.dll."

Getting to WIA from .NET

The wiaaut.dll COM automation library can be added via "Add Reference" from with Visual Studio.NET 2005 and a .NET wrapper will be automatically generated. Only devices whose drivers support WIA will be available via this interface. Most name-brand cameras will work just fine, but some no-name brands won't appear. If your device appears in the Control Panel's "Scanners and Cameras" interface, then this technique, and this program, should be able to see it. My camera is shown in the figure below.

In our code, we can add a namespace qualifying statement like using WIA in C# or imports WIA in VB.NET to name it easier to access these newly imported classes and interfaces. To start, we'll need to get a hold of a DeviceID. WIA thinks about things in terms of Devices, Commands, and Formats. Devices have types like Camera, Scanner or Video. Commands are things like "Take Picture" and Formats are JPEG or BMP, etc.

We'll create an instance of a CommandDialogClass (from the newly imported WIA namespace) and ask it to show us a select dialog so that we might select from any kind of device. You can show only Video devices or only Scanners by changing the WiaDeviceType enumeration that's passed in. We'll only show this select dialog when the user clicks "Configure" in our application, or when the application has been started with invalid configuration data.

Visual C#

CommonDialogClass class1 = new CommonDialogClass();
Device d = class1.ShowSelectDevice(WiaDeviceType.UnspecifiedDeviceType, true,false);
if (d != null)
{
settings.DeviceID = d.DeviceID;
settings.Save();
}

Visual Basic

Dim class1 As CommonDialogClass = New CommonDialogClass
Dim d As Device = class1.ShowSelectDevice(WiaDeviceType.UnspecifiedDeviceType, true, false)
If (Not (d) Is Nothing) Then
settings.DeviceID = d.DeviceID
settings.Save
End If

This select dialog is, fortunately, supplied completely by Windows and returns a Device. Each Device has a DeviceID that we will save into our User-specific settings class. This class was generated automatically by new features in Visual Studio .NET 2005 that make managing settings fantastically easy. I right-clicked on the Project from within the Visual Studio Solution Explorer and selected "Settings." After I indicated the names and data types of the settings I needed to save, Visual Studio 2005 generated a class that exposed strongly-typed properties such as DeviceID. Settings can also be saved more easily in .NET 2.0. The DeviceID for my Webcam happened to be "{6BDD1FC6-810F-11D0-BEC7-08002BE2092F}\0003" and is stored in my user's Documents And Settings\Local Settings\Application Data\BlogWebcam directory.

Taking a Picture

Once we've stored away a DeviceID in the configuration file, we'll want to connect to our device and take a picture. We'll need to find the device via it's ID without showing the dialog, connect to it and hold on the Device instance.

Visual C#

DeviceManager manager = new DeviceManagerClass();
Device d = null;
foreach (DeviceInfo info in manager.DeviceInfos)
{
if (info.DeviceID == settings.DeviceID)
{
d = info.Connect();
break;
}
}

Visual Basic

Dim manager As DeviceManager = New DeviceManagerClass
Dim d As Device = Nothing
For Each info As DeviceInfo In manager.DeviceInfos
If (info.DeviceID = settings.DeviceID) Then
d = info.Connect
Exit For
End If
Next

Now we're back where we were before, with a Device instance in the variable d. We'll be connecting to the device each time our timer is started. Each device has a series of commands available to it, and these commands are well-known and identified by GUIDS both in the Registry and in the MSDN Help. We're interested in "Take Picture" which has the GUID string value "AF933CAC-ACAD-11D2-A093-00C04F72DC3C”, but the COM interface also exposes this value in the constant CommandID.wiaCommandTakePicture. When we get a hold of our Device we can spin through its available Commands until we find this one to determine if the device supports it, or we can just call it directly via Device.ExecuteCommand.

Visual C#

Item item = device.ExecuteCommand(CommandID.wiaCommandTakePicture);

Visual Basic

Dim item As Item = device.ExecuteCommand(CommandID.wiaCommandTakePicture)

Once we've called ExecuteCommand on our device an "Item" is returned. This isn't just any ordinary item, it's a WIA Item. Each WIA may include any number of image Formats. These formats are also well-known and appear in the registry. I look them up in the startup of the app just in case they change, rather than hard-coding them.

We spin through the available formats looking for JPEG. I could likely have hard-coded the JPEG GUID and avoided this quick spin, but I also wanted to illustrate how you can find your way around the WIA object model. Once we've found JPEG, we call item.Transfer and an ImageFile is returned that we can save to disk.

Visual C#

Item item = device.ExecuteCommand(CommandID.wiaCommandTakePicture);
foreach (string format in item.Formats)
{
if (format == jpegGuid)
{
WIA.ImageFile imagefile = item.Transfer(format) as WIA.ImageFile;
filename = GetFreeFileName();
if (string.IsNullOrEmpty(filename) == false)
{
imagefile.SaveFile(filename);
}
this.picLastImage.Load(filename);
return filename;
}
}

Visual Basic

Item item = device.Execute
Dim item As Item = device.ExecuteCommand(CommandID.wiaCommandTakePicture)
For Each format As String In item.Formats
If (format = jpegGuid) Then
Dim imagefile As WIA.ImageFile = CType(item.Transfer(format),WIA.ImageFile)
filename = GetFreeFileName
If (String.IsNullOrEmpty(filename) = false) Then
imagefile.SaveFile(filename)
End If
Me.picLastImage.Load(filename)
Return filename
End If
Next

Incidentally, I also load the saved image into a PictureBox on my WinForm and return it from this TakePicture() method.

Uploading the Picture to my WebLog

Now that I've taken a picture, what am I going to do to it? Well, why not upload it to a specific filename on my blog so folks can see me and my workspace; what could be more thrilling? In the past, FTP'ing a file would require a third-party library, but .NET 2.0 has extended the System.Net.WebRequest class with support for FTP.

Here we'll create an FtpWebRequest by passing an ftp:// URL to the WebRequest.Create method. For convenience I'll include the username and password in the URL like this: ftp://username:password@ftp.myurl.com/blog/webcam.jpg. Note that the URL includes the username, password, domain name and destination filename all in one string. We'll load our local file into a byte array and write it out (upload it) to the FtpWebRequest's underlying stream by retrieving it with GetRequestStream() and then Write().

Visual C#

FtpWebRequest request = (FtpWebRequest)WebRequest.Create(settings.FTPServerURL);
request.Method = WebRequestMethods.Ftp.UploadFile;
request.UseBinary = true;
FileInfo fileInfo = new FileInfo(filename);
byte[] fileContents = new byte[fileInfo.Length];
using (FileStream fr = fileInfo.OpenRead())
{
fr.Read(fileContents, 0, Convert.ToInt32(fileInfo.Length));
}
using (Stream writer = request.GetRequestStream())
{
writer.Write(fileContents, 0, fileContents.Length);
}
using (FtpWebResponse response = (FtpWebResponse)request.GetResponse())
{
}

Visual Basic

Dim request As FtpWebRequest = Nothing
request = CType(WebRequest.Create(settings.FTPServerURL),FtpWebRequest)
request.Method = WebRequestMethods.Ftp.UploadFile
request.UseBinary = true
Dim fileInfo As FileInfo = New FileInfo(filename)
Dim fileContents() As Byte = New Byte((fileInfo.Length) - 1) {}
Using fr As FileStream = fileInfo.OpenRead
fr.Read(fileContents, 0, Convert.ToInt32(fileInfo.Length))
End Using
Using writer As Stream = request.GetRequestStream
writer.Write(fileContents, 0, fileContents.Length)
End Using
Dim response As FtpWebResponse = CType(request.GetResponse,FtpWebResponse)

You may notice the use of the "using" statements in the code snippet above. Using "using" with classes that implement IDisposable will automatically cause Dispose() to be called when then using block exits. Some folks don't like the syntax and others believe that using it even if the underlying Dispose() doesn't do anything is syntactic sugar. Personally, I really like the syntax, and in this sample the using statement is closing the FileStream, the Request stream, and the FtpWebResponse.

Conclusion

There are things that could be extended, added, and improved on with this project. Here are some ideas to get you started:

  • Interface with X10 or your doorbell to take a picture and display it on your Media Center PC.
  • Stitch together hundreds of photos, perhaps of your baby, into time-lapse videos.
  • Create a security system that detects motion by diff'ing photos and emails you with an alarm and the attached photo!
  • Upload photos to Flickr or Smugmug with their APIs.

Have fun and have no fear when faced with the words: Some Assembly Required!

Filed under: ,

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# Ben said on February 23, 2007 8:22 AM:

Great sample. I'm working on the Flcikr integration.

Does anyone know how I can open the camera device's built-in configuration properties dialog through code?

The select device dialog doesn't give me access to the manaufacturer's own properties window which I thought it would.

Interestingly, using Skype, the "video settings" option within Skype itself does open the manaufacturer's own properties dialog, so it must be possible.

When I use this sample the device settings are defaulted. Any ideas how I can access these settings and save them. I've searched everywhere and I'm stumped.

I want to set the camera to its "Outdoor" mode, as the result is far too bright.

# Faheem said on April 19, 2007 4:20 PM:

Fantastic work Scott,

Please tell me how can i choose camera with dialog box throgh code only, actually i want to switch between cameras on interval basis without user prompt of dialog.

Pleeeeeeeeeez guide :)

# John Roach said on April 27, 2007 3:20 AM:

Hi,

Neat program. However I wasn't able to work it. the WIA has been installed however I can't see no image in the image box.( I did click on configure) If you can help me it will be great!

# Neil said on May 14, 2007 7:12 PM:

The sample code will run but only ever displays a black image.

I'm using a Logitech QuickCam Chat which works perfectly in the Windows XP Scanners and Cameras control panel.

# Justin said on May 27, 2007 5:34 PM:

First I'd like to say excellent article.

Second, I'd like to.. well, second Ben's comment.  I'd also like to know if it's possible to open the device's built-in device settings dialog.

# carlos david rodriguez said on June 17, 2007 11:33 PM:

try using vfw of the windows API

im new at c# but is it possible with c++ so it should be also with c#

regards

# dc said on July 10, 2007 8:10 AM:

HOw do i know wether my webcam supports WIA or not

# JoerT said on July 11, 2007 2:15 AM:

the loading of the JPEG GUID doesn't work on my Vista Business 32bit setup. Haven't yet looked into it. I'll post back when I do.

# kirupaBlog - If it isn’t broken, take it apart and fix it! » Blog Archive » Interesting Links #3: Write Better Code, AK-47, Webcams… said on July 26, 2007 4:17 AM:

PingBack from http://blog.kirupa.com/?p=116

# Manuel said on July 30, 2007 11:24 AM:

what if i want to show the camera preview so i can decide when to take the picture?

can i do that?

# Thomas said on August 12, 2007 6:20 PM:

I Think this is great

Thanx

Excellent Article

# Manuel said on August 23, 2007 5:07 PM:

Hi, i found your article to be excellent, but i have one simple question.

I want to select a webcam and save it in my database so when i enter again to the project i can just look for it at the DB.

i thought that would be possible by just saving the device id in the DB and then just setting the DeviceID to that value when i enter the project again but it didnt work.

Could you help me there?

# Michael Schøler said on August 27, 2007 10:40 AM:

Taking a single snapshot from any webcam source takes roughly 5-10 secs. Anyway to spped things up a bit? Say 20 fps instead?

Best regards

Michael Schøler

# cazub said on September 8, 2007 4:56 PM:

Hey doods, the very first bita code ( in C#) isn't workin for me, I get the old

"The name 'settings' does not exist in the current context"

in visua C# express 2005 , gota do something like

myproject1.Properties.Settings.Default.DeviceID = d.DeviceID;  

any way to make the code a little more manageable???

# Frank said on September 21, 2007 10:51 AM:

It there a way to be notified by an event when a picture is taken by a Digital Camera?

# Sunny said on September 28, 2007 12:59 AM:

Could you please help me on writing codes for zoom in/out using you lib?

# Benjamin Prince said on October 20, 2007 12:49 PM:

I wish I Can Extend this to the web and can do it using VB Clasic

# x64 user said on October 31, 2007 7:37 AM:

I use Windows XP x64, and can't get the dll registered when copied to the system32 folder.

I get the error:

LoadLibrary("WIAAut.DLL") failed - The specified module could not be found.

If I put it in another directory, I get it registered, but I get runtime errors when using it in Visual Studio 2005.

Runtime error:

Retrieving the COM class factory for component with CLSID {850D1D11-70F3-4BE5-9A11-77AA6B2BB201} failed due to the following error: 80040154.

The runtime error indicates that the class I'm trying to use, WIA.CommonDialogClass is not registered.

Is there an alternative dll or method for x64 systems ?

# Obtener im??genes de una c??mara. « Lo mejor del mundo tiene dos letras… said on November 4, 2007 10:18 PM:

PingBack from http://elmanu.wordpress.com/2007/11/05/obtener-imagenes-de-una-camara/

# John Galvin said on November 18, 2007 11:07 AM:

Thanks Scott

That example was just what I was looking for

Keep up the good work and thanks again

Regards

John

# Nick Kusters said on December 9, 2007 10:29 AM:

I have tried to use the com object, but showing the select device window fails without error (does not show). Any ideas?

# Coding4Fun said on December 9, 2007 9:58 PM:

Try DirectShow instead.  Here is an example

http://blogs.msdn.com/coding4fun/archive/2007/11/23/6440747.aspx

# Arto Kainu said on January 5, 2008 4:24 AM:

Hi! Great example, got me started pretty quick! There's this problem that if I just use the Item item = device.ExecuteCommand(CommandID.wiaCommandTakePicture); command the picture is always all black.

I've been thinking that the webcam should be "turned on" somehow first. F.ex. if I open the ShowAcquireImage -dialog, the image is first all black and after a while the video preview starts to show. Then I can take pictures I want. But the problem is that I need to take pictures automatically without user interaction but they are all black. Any suggestions?

# WIA Scanning in C# said on January 21, 2008 2:21 AM:

hi, I'm new to WIA programming, would you please post sample program using WIA (scanning) in C#.net

# Justin said on January 21, 2008 4:03 AM:

Hi. I finally got stdole to reference after re-referencing it (must have been a different version than you referenced?) anyway, I get an error 'cannot find wrapper assembly for type library stdole'. I don't know what is wronog. Could anyone please help?

# Coding4Fun said on January 22, 2008 9:26 AM:

@WIA:  The link is at the top of the page.  plus we already have a scanner example too.

http://blogs.msdn.com/coding4fun/archive/2007/09/25/5121856.aspx

# Kevin said on January 31, 2008 1:40 PM:

Thank you so much! We have a few Whitebox robots here at the school where I work. They come with a USB webcam, but only the default webcam software. No example of integrating that into a custom program out of the box. So this example you provided was JUST what I needed! Thank you!

# Vibro.NET said on March 7, 2008 12:00 PM:

Well, don't get fooled. I'm not going to make any big philosophical considerations about technology and

# Noticias externas said on March 7, 2008 12:07 PM:

Well, don't get fooled. I'm not going to make any big philosophical considerations about technology

# MSDN Blog Postings » CardSpace & surveillance said on March 7, 2008 12:33 PM:

PingBack from http://msdnrss.thecoderblogs.com/2008/03/07/cardspace-surveillance/

# Visitor(get your code and test) said on March 19, 2008 7:32 AM:

Item item = device.ExecuteCommand(CommandID.wiaCommandTakePicture);

I test code taskPicture, I see very low about 5 - 10 seconds.

I don't know what another function taskpicture faster than device.ExecuteCommand(CommandID.wiaCommandTakePicture);

# Chuky said on April 1, 2008 3:08 AM:

This works perfectly for me but the picture taken is black. Apparently the device.ExecuteCommand(CommandID.wiaCommandTakePicture); command returns a black picture. Does anyone have the fix for this.

# Coding4Fun said on April 1, 2008 4:36 PM:

@Chuky:  Switch to a DirectShow style of doing it.

# Ali Sanjaya said on April 9, 2008 2:15 AM:

how to change webcam screen resolutions?

the default webcam resolutions is 320 x 240, how to change to 640 x 480 for the screen and capturing .

thanks.

# Windows » [Ken] Coding4Fun : Look at me! Windows Image Acquisition said on May 9, 2008 2:46 AM:

PingBack from http://windows.wawblog.info/?p=13409

Leave a Comment

(required) 
(optional)
(required) 
Page view tracker