Capturing images in your applications with the VideoCapture
class of the EmguCV library, an OpenCV wrapper for dotnet, requires supplying an index value to choose a specific camera. However, finding this number can be challenging, and since it can change over time, it might cause your application to break.
In this article, I'll demonstrate how to use the actual name of the camera instead of the index value with OpenCV's VideoCapture class.
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.