1.引言
电脑插多个USB摄像头时,当插拔或者开机之后,Opencv对应的摄像头索引会发生改变,导致Opencv打开摄像头会开错,比如笔记本自带一个摄像头,插上一个USB摄像头时,有时自带的摄像头索引是0,有时是1。十分困扰,因此本文通过获取摄像头设备的VID与PID号(这两个号是出厂自带的,不会发生改变),获取对应的Opencv索引值,打开摄像头。为了方便使用,本人将其导出动态库文件,并采用C++或Python调用该动态库。
2.C++程序导出动态库
windows下获取所有摄像头VIDPID,及其索引的程序参考【通过Opencv打开指定摄像头的方法】。本人环境为vs2019,注意在属性设置里面,高级→字符集→使用多字节字符集。
#include <iostream>
#include <string>
#include <vector>
#include <DShow.h>
#include <CString>
#include <atlstr.h>
#pragma comment(lib,"Strmiids.lib")
#define LRDLLTEST_EXPORTS //先定义宏
#ifdef LRDLLTEST_EXPORTS
#define LRDLLTEST_API __declspec(dllexport)
#else
#define LRDLLTEST_API __declspec(dllimport)
#endif
extern "C" LRDLLTEST_API int getCamIDFromPidVid(const char* pidvid);
LRDLLTEST_API int getCamIDFromPidVid(const char* pidvid)
{
// example std::string pidvid = "0001";
std::vector<std::string> devList; //设备列表
int iCameraNum = 0; //设备个数
CString str;
ICreateDevEnum* pDevEnum = NULL;
IEnumMoniker* pEnum = NULL;
HRESULT hr = NULL;
CoInitialize(NULL);
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, reinterpret_cast<void**>(&pDevEnum));
if (SUCCEEDED(hr))
{
hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum, 0);
if (hr == S_OK)
{
//枚举捕获设备
IMoniker* pMoniker = NULL;
ULONG cFetched;
while (pEnum->Next(1, &pMoniker, &cFetched) == S_OK)
{
IPropertyBag* pPropBag;
hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, reinterpret_cast<void**>(&pPropBag));
if (SUCCEEDED(hr))
{
//获取设备路径,路径里包含pidvid的信息
VARIANT varName;
varName.vt = VT_BSTR;
VariantInit(&varName);
hr = pPropBag->Read(L"DevicePath", &varName, 0); //read函数只能够读取CLSID, FriendlyName, DevicePath这三种
CString sdev(varName);
std::string strStr;
strStr = sdev.GetBuffer(0);
//std::cout << "----:\n" << strStr<<std::endl;
devList.push_back(strStr);
iCameraNum++;
pPropBag->Release();
}
pMoniker->Release();
}
}
}
//std::cout << devList.size() << std::endl;
for (int i = 0; i < devList.size(); i++) {
std::cout << devList[i] << std::endl;
}
int iRet = -1;
//对比,得到id,没有符合的就返回-1
for (int i = 0; i < devList.size(); i++)
{
int index = devList[i].find(pidvid);
if (index != -1)
{
iRet = i;
break;
}
}
CoUninitialize();
return iRet;
}
程序编写完毕之后,在配置一下属性,方便导出动态库,如下。然后生成解决方案
?生成完毕,将在x64/release文件夹下生成python_ues_cpp.dll与python_ues_cpp.lib,python_ues_cpp是我自己的名字,实际名字是根据你的方案来的。
3.python调用
查看usb设备的vid和pid,输出是这样的
?所以我们在程序中指定一些关键词就可,简单的调用程序如下:
# -*- coding: utf-8 -*-
from ctypes import *
import cv2
fileName="python_use_cpp.dll"
func=cdll.LoadLibrary(fileName)
string=bytes("vid_0001&pid_0001",encoding="utf-8")
#string="vid_0001&pid_0001".encode()
a=func.getCamIDFromPidVid(string)
print(a)
cap = cv2.VideoCapture(a,cv2.CAP_DSHOW) # 摄像头的ID不同设备上可能不同
while 1:
ret,img0=cap.read()
if not ret:
continue
cv2.imshow('result', img0)
key =cv2.waitKey(1) # 1 millisecond
if key & 0xFF == ord('q') or key == 27:
break
cv2.destroyAllWindows()
3.C++调用
#include <iostream>
#include <opencv2/opencv.hpp>
//
//#pragma comment (lib,"python_use_cpp.lib")
extern "C" __declspec(dllexport) int getCamIDFromPidVid(const char* pidvid);
int main(int avgc, char** argv) {
const char* pidvid = "vid_0001&pid_0001";
int A = getCamIDFromPidVid(pidvid);
//int A=getCamIDFromPidVid(argv[1]);
std::cout << A << std::endl;
cv::VideoCapture camera0(A, cv::CAP_DSHOW);
if (!camera0.isOpened()) {
std::cout << "can not open" << std::endl;
return 1;
}
while (true)
{
cv::Mat frame0;
camera0 >> frame0;
cv::imshow("frame", frame0);
char c = cv::waitKey(1);
if (c == 27)
{
break;
cv::destroyAllWindows();
}
}
return 0;
}
|