Go to Alexandria's home page
The Library of Alexandria

DualCameras

Alexandria Home | Up One Level

Last updated 8/19/2005

Table of Contents

About this Document
Choosing a Pair of Cameras
Download the Code
Using the DualCameras Component
Reference to the DualCameras Public Interface
The Video Capture API Underneath
Your Feedback

Back up to the table of contents About this Document

Recently, I've been experimenting with research into stereo vision (AKA binocular vision). I went looking all over the Web for suitable code or components. I had a hard time finding anything good. Once I finally found a pair of cameras that would work on my PC, I decided to create my own component from scratch using raw Windows API calls. I wrote it entire in C#, so it's .Net from the ground up (except for marshaled calls to the API, of course). I'm making it available here for other developers, including the full source code.

I invite you to read the rest of this document to help you avoid making many of the same mistakes I made along the way.

Back up to the table of contents Choosing a Pair of Cameras

If you search the Web, you'll find a small cadre of geeks like me who are experimenting with stereo vision. You'll also quickly find that most of them have had considerable trouble figuring out which cameras to use. I wish I could tell you that they're just fools who don't know what they're doing, but doing so would force me to admit I'm one of those fools. I tried six different camera pairs before I finally found one that didn't crash my computer or otherwise choke. Following is a table of the things I tried and what I found:

Image Name / Price Worked? Comments
No-name camera

USD $27 (pair) on eBay

No Given the tempting price of $9.99 (plus S&H), I was game for trying. They're not bad if you're OK with about 1 frame per second. Try turning off the auto exposure to increase the frame rate and they are way too dim to be useful. The vendor was rude (eBay vendors have been great to me, otherwise) and was only willing to return the $9.99, despite the bogus claim of 30 fps. (And you know it didn't cost $15 to ship and $2 to insure a $10 product.) My advice: don't buy cameras online unless you are absolutely sure they'll work or can guarantee getting your money back. More geek junk to add to my graveyard.
GE EasyCam

USD $20 at Target

No Nice, low cost, and I was able to return it. Quality was not very good. And when the second camera is plugged in, GE's driver recognizes the camera, but reports that it can't connect to it. Crashed my computer every time I tried to use both cameras.
GE EasyCam Pro

USD $30 at Target

No A little higher cost, but the higher quality of image is worth it. The graininess of the above ones is almost completely gone. Same darn driver, though, so it has the same problems. Crashed my computer without fail.
Micro Webcam Basic

USD $12 at Walmart

No Barn floor scrapings. Actually, the box shows the picture you see here, but the camera looks totally different. The focus rim on one was disconnected. The image was grainy. And, finally, the two would not work together. Actually, I suspect that the hardware and drivers are the same as for the no-name penguin thing above.
Logitech QuickCam Express

USD $40 (pair) at Walmart

No Given the good luck Bob Mottram seems to have had with an older version of these, I was pretty sure they would work for me, too. Alas, they didn't. The quality's not all that great, but it was good enough and I was hopeful.
Creative WebCam Instant

USD $30 at Walmart

Yes I finally settled on two of these. The cameras work great and they will work together. They don't work with an external hub for me, though. I have to plug them directly into my laptop's USB ports. The folding stand / clip is annoying for my needs; but hey, nothing's perfect. Still, hooray for Creative Labs!

Let me make a strong suggestion. You may be tempted to use the demo program included in the download package to test your cameras to see if they'll work, but I would advise you not to. Instead, just use some off-the-shelf video capture programs that show previews. The cameras you buy should come with at least one such program. Windows XP comes with the Windows Movie Maker program (found in Start > Programs > Accessories). The two programs can be different, if necessary, or the same. You'll find you can't generally have two programs capturing from the same device at once, so you'll be forced to select two different cameras and thus verify that they work as you hope. Only once you have verified in this way should you move on to testing out with the DualCameras component and / or its demo program.

I'm not suggesting that my component will cause problems. It's just another unknown, because it attempts to deal simultaneously with two camearas, and you're better off running your tests with as few unknowns as possible.

Back up to the table of contents Download the Code

Download Version 2.3
  • Uploaded 8/19/2005.
  • Default values for properties were not properly set on initialization.
  • Download Version 2.2
  • Uploaded 8/7/2005.
  • Added .AutoPaintPreviews property to allow finer control of the preview panels.
  • Download Version 2.1
  • Uploaded 8/6/2005.
  • Fixed bug where blank bitmap is returned when a camera is not really ready.
  • Added bool return value to TakeSnapshot() to simplify checking.
  • Added .EyePanelLeft and .EyePanelRight properties.
  • Reduced redundant code.
  • Download Version 2.0
  • Uploaded 8/2/2005.
  • Original version.
  • You are welcome to download and use my component and source code however you see fit. The package includes:

    Back up to the table of contents Using the DualCameras Component

    I'm not the sort to shrink away from understanding how to code something, but I have to admit I was daunted by a lot of the sample code I found out there for capturing from multiple cameras. It's not as trivial as it sounds. I wanted to make sure my component made the task about as simple as possible. Let me briefly describe how you use it, including sample code.

    First, how to instantiate the control. One way to do it is to add it to Visual Studio .Net's toolbox, where all the other controls are. To do so, do the following:

    1. Right-click somewhere on the toolbox.
    2. Select "Add/Remove Items..." from the drop-down menu.
    3. Click the "Browse..." button on the "Customize Toolbox".
    4. Find the "Carnell.DualCameras.dll" file and select it.
    5. Click "OK" on the "Customize Toolbox" to finish.
    6. You should now see "DualCameras" at the bottom of the controls list.

    Actually, though, I generally don't like this method when I'm developing controls because it doesn't follow along with your latest version of code. There's a better way, in my opinion, but it requires a little finessing.

    The trick used in the sample program you can download here is the one I'll describe now:

    1. Add the "Carnell.DualCameras.csproj" project to your solution.
    2. Add a reference to the "Carnell.DualCameras" project to your own project.
    3. Create an ordinary "Panel" control on your form.
    4. Find the "private System.Windows.Forms.Panel panel1;" statement in the source code.
    5. In it, replace "System.Windows.Forms.Panel" with "Carnell.DualCameras".
    6. Find the "this.panel1 = new System.Windows.Forms.Panel();" statement.
    7. Perform the same replacement.
    8. Return to the designer. You should see the control has changed appearance. That's it.

    One cool thing about this approach is that you can debug the component at the same time you debug your application code, which you can't easily do with the other way. Another is that all you have to do is hit CTRL-SHIFT-B to rebuild the solution and changes to the component will immediately be reflected in your program.

    One pitfall to be aware of, however, is that if the component project doesn't compile properly, you may get all sorts of compiler errors in your sample program until the component project is corrected. You may also see the control in your form designer might disappear. Don't get scared, though. Just plod along with fixing the control's code. When that's done and you rebuild, it should be fine again. If you seem to get stuck, try closing and reopening the solution.

    Moving on, here's a screen shot of the demo program that's included in the download package:

    Figure: Screen shot of the included demo program in the designer (left) and running (right).
    Figure: Screen shot of the included demo program in the designer (left) and running (right).

    The upper third of the form has an instance of the DualCameras control in it, while the lower shows an ordinary Panel control that the demo paints to. I have to admit I was just having fun when I put the turned-off TV screen graphics in the control, but it just looks cool and gives a clue about what you're seeing.

    The right-hand image shows what the sample program looks like when it's running on my computer and the cameras are looking at me. The DualCameras control, naturally, is showing scaled-down thumbnails of the captured images. The panel contains a blended graphic constructed by simply halving the RGB pixel values of the two source images and adding them together. And no, it's not useful at all; just a demonstration.

    Here's all the code the demo needs to start up and shut down the component:

            private void Form1_Load(object sender, System.EventArgs e) {
                dualCameras1.StartCapturing();
                timer1.Enabled = true;
            }
    
    
            private void Form1_Closing(object sender, CancelEventArgs e) {
                dualCameras1.StopCapturing();
            }
    

    What's going on here? The DualCameras.StartCapturing() method reaches out to MS Windows' Video Capture API. It's a bit older than DirectX, but has pretty much everything needed. It knows what it can capture from, so it's not necessary to search through the myriad media hard and soft devices, as you have to with DirectShow. On the other hand, you can't easily codify the selection.

    The first time you run this, you will probably be prompted for the first and/or second device to capture from. You should see this dialog:

    Figure: Selecting a capture device.
    Figure: Selecting a capture device.

    Chances are pretty good it has already selected its first capture source. Your job, then, is to select the other camera. You should see them both here. Sadly, the chances are pretty good that the one selected by default is the one that was already allocated to as the left camera. So you should almost always try selecting the other one when you see this dialog. If you chose wrong, don't worry. You'll see a message from DualCameras saying that the right camera couldn't be initialized and the program will end. Just run it and try again; you should get another chance to select.

    Once you get it right, though, and the program runs, you may never again need to use the Video Source dialog to select cameras on that machine, unless you move the cameras to other USB ports. You can unplug and re-plug your cameras in as often as you like, though; Windows should save your settings for the next time you run the program.

    You may have noticed the timer1.Enabled = true; statement in Form1_Load. You've probably already guessed that this component does not shove snapshots out at you. It just sits in wait for you to ask it for a pair (left and right) of snapshots. That's what the timer in this sample program is for. You can use threads or other events if you'd like, but you'll probably find timers work great. Following is a simplified version of the .timer1_Tick() event handler in the demo program:

            private void timer1_Tick(object sender, System.EventArgs e) {
                Bitmap bmpLeft, bmpRight;
    
                // Be sure to temporarily disable this timer so we don't get a
                // flood of its Tick events before this code can catch up.
                timer1.Enabled = false;
    
                try {
    
                    // Here's the real workhorse of the DualCameras component.
                    if (!dualCameras1.TakeSnapshot(out bmpLeft, out bmpRight)) {
                        // This typically occurs when one of the cameras is not 
                        // really ready to take a picture.
                        timer1.Enabled = true;
                        return;
                    };
    
                    Demo: create a new image that's a blend of the two sources.
    
                    // Be sure to dispose of old images.  This frees up Windows 
                    // resources (usually memory) and can make your programs faster.  
                    // The DualCameras control doesn't do this for you because it 
                    // doesn't assume that you make your own copies.
                    if (bmpLeft != null) bmpLeft.Dispose();
                    if (bmpRight != null) bmpRight.Dispose();
    
                    // Re-enable the timer so we'll get the next tick after a 
                    // measured delay.
                    timer1.Enabled = true;
    
                } catch (Exception ex) {
    
                    // Note: you don't have to close your program like this, but 
                    // it's safe at least to stop capturing and perhaps restart.  
                    // Calling .StopCapturing() should cause a proper shut-down 
                    // and release the cameras.  But what if the problem is that 
                    // the cameras aren't configured properly?  Be prepared for 
                    // many headaches and don't just ignore capture errors.
                    dualCameras1.StopCapturing();
                    MessageBox.Show(ex.Message + "\r\n\r\n" + ex.StackTrace);
                    Close();
    
                }
    
            }
    

    It looks like a lot of code, but most of it is just air and comments. The most important part is the call to DualCameras.TakeSnapshot(). You pass in references to some empty Bitmap variables and when this returns, they have new Bitmap objects attached. There's a little exception handling you should do for good measure, but otherwise, you can go to town with the two bitmaps.

    The only other thing worth pointing out in this sample code is how we disable the timer immediately upon entry and then re-enable it when we're done. The reason for this should be obvious. The timer may just keep on firing away faster than we can process requests. The .Net framework is usually pretty good about dealing with this, but I did notice one odd side effect of not disabling the timer during processing. The timer events would continue firing, but my form would stop visibly responding to it and even to mouse events. This convinced me of the value of disabling the timer.

    So that's it. The only three public methods available in the DualCameras component are to start and stop the camera and take a single pair of snapshots. That's it.

    Back up to the table of contents Reference to the DualCameras Public Interface

    Following is a basic technical reference to the public interface members (methods, properties, events, etc.) of the Carnell.DualCameras component.

    Back up to Reference to the DualCameras Public Interface Public Properties

    bool AutoPaintPreviews

    Access: Read / write
    Summary: Should I automatically paint the camera images to the preview panels? Otherwise, you can use the EyePanelLeft and EyePanelRight properties to gain access to repaint the panels as you see fit.

    bool AutoSize

    Access: Read / write
    Summary: Should the control automatically resize itself so the viewers are exactly the same size as the cameras' output images?

    int CameraHeight

    Access: Read-only
    Summary: How tall the cameras' output images are in pixels.

    int CameraIndexLeft

    Access: Read / write
    Summary: Given that there is a list of devices that includes at least two with the same name, which index among those that match .CameraNamesInclude will the left camera be associated with?

    int CameraIndexRight

    Access: Read / write
    Summary: Given that there is a list of devices that includes at least two with the same name, which index among those that match .CameraNamesInclude will the right camera be associated with?

    int CameraWidth

    Access: Read-only
    Summary: How wide the cameras' output images are in pixels.

    int CaptureTimeOut

    Access: Read / write
    Summary: How many milliseconds should we allow .TakeSnapshot() to wait before we assume it's going to fail and just time out? Use zero to turn off the timeout feature.

    bool Capturing

    Access: Read-only
    Summary: Indicates whether .StartCapturing() has been called and the camera resources are now allocated to this control.

    bool EyePanelLeft

    Access: Read-only
    Summary: Reference to the Panel control that contains the left camera's preview.

    bool EyePanelRight

    Access: Read-only
    Summary: Reference to the Panel control that contains the right camera's preview.

    Back up to Reference to the DualCameras Public Interface Public Methods

  • BmpLeft: Once called, this will contain the full size image captured by the left-hand camera or null if there was a problem. Be sure to test for null after each call.

  • BmpRight: Once called, this will contain the full size image captured by the right-hand camera or null if there was a problem. Be sure to test for null after each call.

    bool TakeSnapshot(out Bitmap BmpLeft, out Bitmap BmpRight)

    Summary: Once .StartCamera() is called, this directs both cameras to take single snapshots immediately and return them as Bitmap objects.

    void StartCapturing()

    Summary: Kicks off the capturing process by connecting to the two cameras.

    void StopCapturing()

    Summary: Disconnects and cleans up after .StartCapturing() and once capturing is no longer desired.

    Back up to Reference to the DualCameras Public Interface Public Events

    None

    Back up to the table of contents The Video Capture API Underneath

    I imagine most people who download this component will just use it as is. For those of you who are curious how it works, I encourage you to dig into the source code, included with the package. I've worked hard to add lots of in-line comments and trim out the junk I inevitably wrote as I tried to understand the process. I've even purposely left in a few gems you may find interesting to play with.

    I'll explain briefly how this component works, though, if you're not willing to wade into the code.

    Microsoft Windows XP (and other recent versions) come with a Video Capture API. It's not a simple COM or .Net component -- wouldn't that be nice? It's made to cater to the lowest common denominator: Windows applications written in C.

    The API is in your Windows\System32 directory as "avicap32.dll", and is loaded into memory with the process that wants access to the cameras, as well as a bunch of other multimedia resources.

    The essential techniques used to communicate with the API are as follows:

    There are a bunch of windows messages that the API defines for its own use, such as "WM_CAP_DRIVER_CONNECT" and "WM_CAP_SET_PREVIEW". (The "WM_CAP_" prefix is used specifically for the video capture functionality, whereas other features have different prefixes.) The API was actually made to support video capturing, but I only needed features for supporting capturing single frames.

    One of the important challenges to overcome is getting the raw binary output into a .Net Bitmap object. The good news is that the data is actually 24-bit RGB values, so its basically compatible. The technique for getting it in, then, is to call Bitmap.LockBits() so we can gain access to its raw binary data and then use Marshal.WriteByte() to copy each red, green, and blue value for each pixel from the raw buffer into the new Bitmap object. When that's done, we call Bitmap.UnlockBits() to suture up the wound and send our patient on out to recovery.

    Back up to the table of contents Your Feedback

    I encourage you to let me know what you think of this project or if you intend to use this in your own projects. Does this help solve some problems for you? What limitations get in the way of your research? To be sure, I don't really want to be a help desk for the software, but you're welcome to ask if you want some advice on how to apply these concepts to your work. Drop me a line.

    Send me email.


    Go to Alexandria's home page Copyright © 2012 The Library of Alexandria. All rights reserved.
    Produced in cooperation with Carnell Information Systems, Inc.