Skip to content
/

Using the OpenCV VideoCapture class with the name of a Camera

To capture images in my applications, I use the VideoCapture class of the EmguCV library, an OpenCV wrapper for .NET.

To choose a specific camera, you need to supply an index value. But getting this number is not straightforward, and as this number can change over time, it might break your application in the future.

So, what if we could use the actual name of the camera instead of the index value?

In this article I will show how to achieve this.

Capture an image

To start, this is the code to capture an image with the VideoCapture class.

using Emgu.CV;

var capture = new VideoCapture(1, VideoCapture.API.DShow);
using (var frame = capture.QueryFrame())
{
    frame.Save("frame.jpg");
}

The code saves the frame from input 1 to the frame.jpg file.

How do I know that I need the value of 1? Just by trial and error. A couple of weeks ago the value was 0. I am not sure why Windows decided to change the order of devices, but it happened, and my application broke.

Enumerate video input devices

Getting the list of video devices with .NET is not as easy as it might sound. To enumerate devices, we will have to fall back to the world of COM interop and magic values.

This is done with the System Device Enumerator. Creating the enumerator will return an object that implements the ICreateDevEnum interface. This interface needs to be declared first.

[ComImport]
[Guid("29840822-5B84-11D0-BD3B-00A0C911CE86")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface ICreateDevEnum
{
    [PreserveSig]
    int CreateClassEnumerator(
        [In] ref Guid deviceClass,
        [Out] out IEnumMoniker enumMoniker,
        [In] int flags
    );
}

It is possible to get the correct C# type using the correct GUID value. With the type available, create an instance.

var comType = Type.GetTypeFromCLSID(new Guid("62BE5D10-60EB-11D0-BD3B-00A0C911CE86"));
var systemDeviceEnumerator = (ICreateDevEnum)Activator.CreateInstance(comType);

Now it is possible to enumerate all system devices. In this case we only want video input devices. Provide the value of the video input device class to filter the enumerator.

var videoInputDeviceClass = new Guid("860BB310-5D01-11D0-BD3B-00A0C911CE86");

var hresult = systemDeviceEnumerator.CreateClassEnumerator(ref videoInputDeviceClass, out var enumMoniker, 0);
if (hresult == 0)
{
    // Successful
}

If there are no errors creating the enumerator, it is now possible to do some enumeration. Loop until there are no devices left.

var moniker = new IMoniker[1];

while (true)
{
    hresult = enumMoniker.Next(1, moniker, IntPtr.Zero);
    if (hresult == 0 && moniker[0] != null)
    {
        // moniker[0] is a pointer to a video input device
    }
}

Get the friendly name of the device

With the moniker, it is possible to get more information about the device. This information is stored in a property bag.

To access the property bag, first declare the IPropertyBag interface.

[ComImport]
[Guid("55272A00-42CB-11CE-8135-00AA004BB851")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IPropertyBag
{
    [PreserveSig]
    int Read(
        [In, MarshalAs(UnmanagedType.LPWStr)] string propertyName,
        [In, Out, MarshalAs(UnmanagedType.Struct)] ref object value,
        [In] IntPtr errorLog
    );

    [PreserveSig]
    int Write(
        [In, MarshalAs(UnmanagedType.LPWStr)] string propertyName,
        [In, MarshalAs(UnmanagedType.Struct)] ref object value
    );
}

The property bag can be bound to the moniker. Use the Read method to query for the FriendlyName property.

var bagId = typeof(IPropertyBag).GUID;

moniker.BindToStorage(null, null, ref bagId, out object bagObject);
var propertyBag = (IPropertyBag)bagObject;

object value = null;
var hresult = propertyBag.Read("FriendlyName", ref value, IntPtr.Zero);
if (hresult == 0)
{
    // value holds the friendly name
}

Put it all together

I have put all the bits together and shared this code on GitHub. I also provided this solution as a NuGet package for convenience.

As a consumer, I can use the name of my device to get the index and create an instance of the VideoCapture class.

using Emgu.CV;
using Hompus.VideoInputDevices;

VideoCapture capture;

using (var sde = new SystemDeviceEnumerator())
{
    var devices = sde.ListVideoInputDevice();

    var index = devices.First(d => d.Value == "Cam Link 4K").Key;
    capture = new VideoCapture(index, VideoCapture.API.DShow);
}

using (var frame = capture.QueryFrame())
{
    frame.Save("frame.jpg");
}

If you have any improvements on the code, please open an issue and/or submit a pull request.