在計算機視覺應用中,經常需要從特定的攝像頭設備獲取視頻流。例如,在多攝像頭環境中,當使用 OpenCV 的?cv::VideoCapture
?類打開攝像頭時,如果不指定攝像頭的 ID,可能會隨機打開系統中的某個攝像頭,或者按照設備連接的順序打開第一個可用的攝像頭。
比如:
? ??// 打開兩個攝像頭cv::VideoCapture?cap0(0);if?(!cap0.isOpened()) {cap0.open(0);}cv::VideoCapture?cap1(1);if?(!cap0.isOpened() || !cap1.isOpened()) {std::cerr <<?"Error: Cannot open camera"?<< std::endl;return;}
在多攝像頭環境下,這種方式可能無法滿足應用需求。此外,直接使用攝像頭 ID 的方式可能不夠穩定,因為設備的連接順序或系統分配的 ID 可能會發生變化。
那如何使用 OpenCV 打開指定的攝像頭呢?我們知道,攝像頭都會在安裝后,操作系統會生成一個設備ID信息,
操作系統就是根據攝像頭的 PID(產品 ID)和 VID(供應商 ID)來精確識別并打開某個攝像頭的。
如圖所示,對應關系分別如下:
VID_0BDA&PID_3787 (Front Camera)
VID_0BDA&PID_5846 (HBVCAM Camera)
VID_0BDA&PID_D567 (USB Camera)
解決辦法
那OpenCV是否支持在打開攝像頭時,根據個信息進行指定呢?當然可以。
在 Windows 系統中,攝像頭設備通常通過 DirectShow API 進行管理和操作。而 OpenCV 是一個功能強大的開源計算機視覺庫,提供了與攝像頭交互的接口。結合兩者的優勢,可以方便地實現對指定攝像頭的訪問。
通過以下步驟實現對指定攝像頭的打開:
1. 使用 DirectShow API 枚舉系統中的攝像頭設備,并獲取每個設備的詳細信息,包括設備路徑、PID 和 VID 等。
2. 根據用戶指定的 PID 和 VID,在設備列表中查找匹配的設備,并獲取其對應的設備 ID。
3. 使用 OpenCV 的?
cv::VideoCapture
?類,結合設備 ID 和 DirectShow API,打開指定的攝像頭設備。
以下是完整的 C++ 代碼,展示了如何使用 OpenCV 和 DirectShow API 打開指定 PID 和 VID 的攝像頭:
#include?<iostream>
#include?<vector>
#include?<string>
#include?<algorithm>
#include?<opencv2/opencv.hpp>
#include?<DShow.h>
#include?<atlstr.h>
#pragma?comment(lib,"Strmiids.lib")// 定義導出函數的宏
#ifdef?_WIN32
#define?DLL_EXPORT __declspec(dllexport)
#else
#define?DLL_EXPORT
#endif// 獲取攝像頭ID的函數
DLL_EXPORT?int?getCamIDFromPidVid(const?char* pidvid)?{std::vector<std::string> devList;?// 設備列表int?iCameraNum =?0;?// 設備個數ICreateDevEnum* pDevEnum =?NULL;IEnumMoniker* pEnum =?NULL;HRESULT hr =?CoInitialize(NULL);if?(FAILED(hr)) {std::cerr <<?"COM 初始化失敗,錯誤碼: "?<< hr << std::endl;return-1;}hr =?CoCreateInstance(CLSID_SystemDeviceEnum,?NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum,?reinterpret_cast<void**>(&pDevEnum));if?(FAILED(hr)) {std::cerr <<?"創建設備枚舉器失敗,錯誤碼: "?<< hr << std::endl;CoUninitialize();return-1;}hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum,?0);if?(hr != S_OK && hr != S_FALSE) {std::cerr <<?"枚舉視頻輸入設備類別失敗,錯誤碼: "?<< hr << std::endl;pDevEnum->Release();CoUninitialize();return-1;}if?(hr == S_FALSE) {std::cerr <<?"沒有找到視頻輸入設備"?<< std::endl;pDevEnum->Release();CoUninitialize();return-1;}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)) {VARIANT varName;VariantInit(&varName);varName.vt = VT_BSTR;hr = pPropBag->Read(L"DevicePath", &varName,?NULL);if?(SUCCEEDED(hr) && varName.vt == VT_BSTR && varName.bstrVal !=?nullptr) {std::wstring?wstrDevicePath(varName.bstrVal);std::string?strDevicePath(wstrDevicePath.begin(), wstrDevicePath.end());devList.push_back(strDevicePath);iCameraNum++;}?else?{std::cerr <<?"讀取設備路徑失敗,錯誤碼: "?<< hr << std::endl;}VariantClear(&varName);pPropBag->Release();}pMoniker->Release();}pEnum->Release();pDevEnum->Release();// 將輸入的pidvid轉換為小寫std::string lowerPidvid = pidvid;std::transform(lowerPidvid.begin(), lowerPidvid.end(), lowerPidvid.begin(), ::tolower);int?iRet =?-1;for?(int?i =?0; i < devList.size(); i++) {// 將設備路徑轉換為小寫std::string lowerDevicePath = devList[i];std::transform(lowerDevicePath.begin(), lowerDevicePath.end(), lowerDevicePath.begin(), ::tolower);if?(lowerDevicePath.find(lowerPidvid) != std::string::npos) {iRet = i;break;}}CoUninitialize();return?iRet;
}// 主函數示例
int?main()?{// 替換為你的攝像頭的PID和VID,支持大寫和小寫std::string targetPidVid =?"VID_XXXX&PID_XXXX";?// 例如:"VID_046D&PID_0825"int?camId =?getCamIDFromPidVid(targetPidVid.c_str());if?(camId ==?-1) {std::cout <<?"未找到匹配的攝像頭"?<< std::endl;return-1;}std::cout <<?"攝像頭ID: "?<< camId << std::endl;// 使用OpenCV打開攝像頭cv::VideoCapture cap;cap.open(camId, cv::CAP_DSHOW);if?(!cap.isOpened()) {std::cerr <<?"無法打開攝像頭,ID: "?<< camId << std::endl;return-1;}// 嘗試讀取一幀,驗證攝像頭是否真的可用cv::Mat frame;if?(!cap.read(frame)) {std::cerr <<?"無法從攝像頭讀取幀,ID: "?<< camId << std::endl;cap.release();return-1;}std::cout <<?"攝像頭已成功打開"?<< std::endl;while?(true) {cap >> frame;if?(frame.empty()) {std::cerr <<?"無法讀取幀"?<< std::endl;break;}cv::imshow("Camera", frame);if?(cv::waitKey(1) ==?27) {?// 按ESC鍵退出break;}}cap.release();cv::destroyAllWindows();return0;
}
注意細節
1. 確保安裝了 OpenCV 庫,并正確配置了開發環境。
2. 根據實際攝像頭的 PID 和 VID 修改代碼中的?
targetPidVid
?變量值。3. 在編譯代碼時,鏈接必要的庫文件,如?
Strmiids.lib
?和 OpenCV 相關的庫。4. 在選擇攝像頭時,我們要確保多個攝像頭要各不一樣(這樣即可保證通過VID/PID來區分攝像頭),但每一種都要采購統一(保證在不同電腦上VID/PID都一樣)。
5. 上述相關思想也可以在?*nix?等系統中使用。
通過上述代碼和方法,可以實現根據攝像頭的 PID 和 VID 精確打開指定的攝像頭設備,適用于多攝像頭環境和需要精確設備識別的場景。