问题背景:
根据摄像头序列号来获得Open(index)。
使用Opencv打开多个摄像头的时候,一般都是用VideoCapture.open(index)的方式。这种方式虽然很方便,但是并不能传入其他信息来指定打开哪一个摄像头。
windows平台下,Opencv打开USB摄像头的逻辑代码主要在cap_dshow.cpp中,阅读源码发现其打开设备的顺序,只与枚举顺序有关,使用的是DirectShow中的IPropertyBag类,可以获取DevicePath,FriendName等属性,而其中没有关于序列号(iserialNumber)的方法。而在实际使用过程中,一般我们用的摄像头都是一个厂商的,设备实例路径,设备名都一样,有工具能修改或者厂商可以修改设备名来加以区分,而设备名是不可靠的,会根据插拔的顺序和Hub变化,不是与设备唯一绑定的。在厂商提供了修改序列号的工具后,发现序列号是与设备唯一绑定的,是可靠的,可以用来区分USB摄像头。故我们只要找到能获取序列号的办法,和Opencv的directShow代码结合考虑,可以获取固定序列号的固定index,再用videoCapture打开该index设备。其中需要注意的一点是,只有camera所在的hub序列号可以修改,本身无法修改,所以我们在获取序列号的过程中,需要获取父设备。
代码如下:
预解释:这里用了4位的序列号,格式为[L,R][版本号][行][列],例如L000,R010。程序中CheckSerialNumber即是对序列号的校验。代码分为2步,第一步利用directShow枚举设备,获得设备实例路径,第二步是获得实例路径之后,采用windows Api二次枚举,获取父设备的序列号,得到一个对应关系,例如L001,应该是第二个打开,他的Opencv枚举Index可能为3,那么我在打开这个摄像头的时候,就会Open(3)。
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
//
#pragma comment (lib, "setupapi.lib")
#pragma comment (lib, "cfgmgr32.lib")
#pragma comment (lib, "strmiids.lib")
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "DistinguishCamera.h"
#include
#define GUID_CAMERA_STRING L"{65e8773d-8f56-11d0-a3b9-00a0c9223196}"
static char* GuidToString(const GUID &guid)
{
int buf_len = 64;
char *buf = (char *)malloc(buf_len);
_snprintf(
buf,
buf_len,
"{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
guid.Data1, guid.Data2, guid.Data3,
guid.Data4[0], guid.Data4[1],
guid.Data4[2], guid.Data4[3],
guid.Data4[4], guid.Data4[5],
guid.Data4[6], guid.Data4[7]);
//printf("%s\n",buf);
return buf;
}
static BOOL GetParentDeviceInstanceId(_Out_ PWCHAR pszParentDeviceInstanceId, _Out_ PDEVINST phParentDeviceInstanceId, _In_ DEVINST hCurrentDeviceInstanceId) {
// Handle to parent Device Instance ID
BOOL result = CM_Get_Parent(phParentDeviceInstanceId, hCurrentDeviceInstanceId, 0);
if (result == CR_SUCCESS) {
// Device ID as String
memset(pszParentDeviceInstanceId, 0, MAX_DEVICE_ID_LEN);
result = CM_Get_Device_IDW(*phParentDeviceInstanceId, pszParentDeviceInstanceId, MAX_DEVICE_ID_LEN, 0);
if (result == CR_SUCCESS) {
return TRUE;
}
}
return FALSE;
}
//检验序列号是否合法,规则是最后四位为"C[Version][L|R][int][int]",输出次序值
static int CheckSerialNumber(string serialNumber)
{
int k = serialNumber.length() - 1;
while (serialNumber[k] != '\\') k--;
if (serialNumber[k + 1] == 'C' && (serialNumber[k + 2] >= '0' && serialNumber[k + 2] <= '9') &&(serialNumber[k + 3] == 'L' || serialNumber[k + 3] == 'R'))
{
int id = (serialNumber[k + 4] - '0') * 10 + (serialNumber[k + 5] - '0') * 2;
if (serialNumber[k + 3] == 'R') id++;
return id;
}
else
return -1;//illegal
}
static int getSerialNumber(string childDistanceId)
{
USES_CONVERSION;
// GUID to match devices by class
GUID guid;
CLSIDFromString(GUID_CAMERA_STRING, &guid);
// Get matching devices info
HDEVINFO devInfo = SetupDiGetClassDevs(&guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);// DIGCF_ALLCLASSES);
// Device Instance ID as string
WCHAR szDeviceInstanceId[MAX_DEVICE_ID_LEN];
if (devInfo != INVALID_HANDLE_VALUE) {
DWORD devIndex = 0;
SP_DEVINFO_DATA devInfoData;
devInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
// Loop over devices found in SetupDiGetClassDevs
while (SetupDiEnumDeviceInfo(devInfo, devIndex, &devInfoData))
{
// Read Device Instance ID of current device
memset(szDeviceInstanceId, 0, MAX_DEVICE_ID_LEN);
SetupDiGetDeviceInstanceIdW(devInfo, &devInfoData, szDeviceInstanceId, MAX_PATH, 0);
//compare the device's instanceId
char *szDeviceInstId = W2A(szDeviceInstanceId);
transform(childDistanceId.begin(), childDistanceId.end(), childDistanceId.begin(), ::toupper);
if (childDistanceId.compare(szDeviceInstId) == 0)
{
// Handle of current defice instance id
DEVINST hCurrentDeviceInstanceId = devInfoData.DevInst;
// Handle of parent Device Instance ID
DEVINST hParentDeviceInstanceId;
// Parent Device Instance ID as string
WCHAR pszParentDeviceInstanceId[MAX_DEVICE_ID_LEN];
// Initialize / clean variables
memset(szDeviceInstanceId, 0, MAX_DEVICE_ID_LEN);
hParentDeviceInstanceId = NULL;
if (GetParentDeviceInstanceId(pszParentDeviceInstanceId, &hParentDeviceInstanceId, hCurrentDeviceInstanceId))
{
string parentDeviceInstanceId = W2A(pszParentDeviceInstanceId);
return (CheckSerialNumber(parentDeviceInstanceId));
//if (DeviceIdMatchesPattern(pszParentDeviceInstanceId, pszParentDeviceInstanceIdPattern)) {
// // Parent Device Instance ID matches given regexp - print it out and exit
// wprintf(L"%s\n", pszParentDeviceInstanceId);
// getchar();
// return 0;
//}
// Parent Device Instance ID does not match the pattern - check parent's parent
//hCurrentDeviceInstanceId = hParentDeviceInstanceId;
break;
}
else {
// There is no parent. Stop the loop.
break;
}
}
devIndex++;
}
}
return 0;
}
bool getCameraOrderBySerialNumber(vector *order, int numOfCamera)
{
int numOfFoundedCamera = 0;
USES_CONVERSION;
// Init COM
HRESULT hr = NULL;
hr = CoInitialize(NULL);
if (FAILED(hr)){
printf("Error, Can not init COM.");
return false;
}
// printf("===============Directshow Filters ===============\n");
ICreateDevEnum *pSysDevEnum = NULL;
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pSysDevEnum);
if (FAILED(hr)){
return hr;
}
IEnumMoniker *pEnumCat = NULL;
//Category
/************************************************************************
Friendly Name CLSID
-------------------------------------------------------------------------
Audio Capture Sources CLSID_AudioInputDeviceCategory
Audio Compressors CLSID_AudioCompressorCategory
Audio Renderers CLSID_AudioRendererCategory
Device Control Filters CLSID_DeviceControlCategory
DirectShow Filters CLSID_LegacyAmFilterCategory
External Renderers CLSID_TransmitCategory
Midi Renderers CLSID_MidiRendererCategory
Video Capture Sources CLSID_VideoInputDeviceCategory
Video Compressors CLSID_VideoCompressorCategory
WDM Stream Decompression Devices CLSID_DVDHWDecodersCategory
WDM Streaming Capture Devices AM_KSCATEGORY_CAPTURE
WDM Streaming Crossbar Devices AM_KSCATEGORY_CROSSBAR
WDM Streaming Rendering Devices AM_KSCATEGORY_RENDER
WDM Streaming Tee/Splitter Devices AM_KSCATEGORY_SPLITTER
WDM Streaming TV Audio Devices AM_KSCATEGORY_TVAUDIO
WDM Streaming TV Tuner Devices AM_KSCATEGORY_TVTUNER
WDM Streaming VBI Codecs AM_KSCATEGORY_VBICODEC
************************************************************************/
//hr = pSysDevEnum->CreateClassEnumerator(CLSID_VideoCompressorCategory, &pEnumCat, 0);
hr = pSysDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnumCat, 0);
//hr = pSysDevEnum->CreateClassEnumerator(CLSID_AudioCompressorCategory, &pEnumCat, 0);
//hr = pSysDevEnum->CreateClassEnumerator(CLSID_AudioInputDeviceCategory, &pEnumCat, 0);
//hr = pSysDevEnum->CreateClassEnumerator(CLSID_MediaMultiplexerCategory, &pEnumCat, 0);
//hr = pSysDevEnum->CreateClassEnumerator(CLSID_LegacyAmFilterCategory, &pEnumCat, 0);
if (hr != S_OK) {
pSysDevEnum->Release();
return false;
}
// Obtain a class enumerator for the video input category.
IMoniker *pMoniker = NULL;
ULONG monikerFetched;
int deviceCounter = 0;
//Filter
while (pEnumCat->Next(1, &pMoniker, &monikerFetched) == S_OK)
{
IPropertyBag *pPropBag;
VARIANT varName;
IBaseFilter *pFilter = NULL;
hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);
if (FAILED(hr))
{
pMoniker->Release();
continue;
}
VariantInit(&varName);
// hr = pPropBag->Read(L"FriendlyName", &varName, 0);
hr = pPropBag->Read(L"DevicePath", &varName, 0);
//"FriendlyName": The name of the device.
//"Description": A description of the device.
//Filter Info================
//修改格式使其和com产生的串格式对应
string varNameBak = W2A(varName.bstrVal);
while (varNameBak[0] != 'u')
varNameBak.erase(0, 1);//remove one char
int k = 0;
while (varNameBak[k] != '{')
{
if (varNameBak[k] == '#')
{
varNameBak.erase(k, 1);
varNameBak.insert(k, "\\");
}
k++;
}
varNameBak.erase(k - 1); //remove all char in id > k
//printf("[%s]\n", W2A(varName.bstrVal));
//cout << varNameBak << endl;
int index = getSerialNumber(varNameBak);
if (index != -1)
{
order->at(index) = deviceCounter;
numOfFoundedCamera++;
}
VariantClear(&varName);
pPropBag->Release();
pMoniker->Release();
deviceCounter++;
}
pEnumCat->Release();
pSysDevEnum->Release();
//printf("=================================================\n");
CoUninitialize();
//检测所有摄像头是否合法
//ID连续且以组划分
if (numOfFoundedCamera == numOfCamera)
{
for (int i = 0; i < numOfCamera; i++)
if (order->at(i) == -1) return false;
return true;
}
else
{
return false;
}
}
//int main()
//{
// vector *order = new vector(10, -1);
// getCameraOrderBySerialNumber(order);
// return 0;
//}