如何使用 OpenCV 打开指定摄像头

在计算机视觉应用中,经常需要从特定的摄像头设备获取视频流。例如,在多摄像头环境中,当使用 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 等。
    1. 根据用户指定的 PID 和 VID,在设备列表中查找匹配的设备,并获取其对应的设备 ID。
    1. 使用 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 库,并正确配置了开发环境。
    1. 根据实际摄像头的 PID 和 VID 修改代码中的 targetPidVid 变量值。
    1. 在编译代码时,链接必要的库文件,如 Strmiids.lib 和 OpenCV 相关的库。
    1. 在选择摄像头时,我们要确保多个摄像头要各不一样(这样即可保证通过VID/PID来区分摄像头),但每一种都要采购统一(保证在不同电脑上VID/PID都一样)。
    1. 上述相关思想也可以在 *nix 等系统中使用。

通过上述代码和方法,可以实现根据摄像头的 PID 和 VID 精确打开指定的摄像头设备,适用于多摄像头环境和需要精确设备识别的场景。

相关推荐
Gyoku Mint3 小时前
深度学习×第10卷:她用一块小滤镜,在图像中找到你
人工智能·python·深度学习·神经网络·opencv·算法·cnn
海绵波波10721 小时前
opencv、torch、torchvision、tensorflow的区别
人工智能·opencv·tensorflow
顾随1 天前
(三)OpenCV——图像形态学
图像处理·人工智能·python·opencv·计算机视觉
jndingxin2 天前
OpenCV直线段检测算法类cv::line_descriptor::LSDDetector
人工智能·opencv·算法
Mikowoo0072 天前
03_opencv_imwrite()函数
opencv·计算机视觉
ddfa12342 天前
opencv 值类型 引用类型
人工智能·opencv·计算机视觉
金山几座2 天前
揭开图像的秘密:OpenCV直方图入门详解
opencv·计算机视觉
批量小王子3 天前
2025-07-15通过边缘线检测图像里的主体有没有出血
人工智能·opencv·计算机视觉
Java与Android技术栈3 天前
macOS 为 Compose Desktop 构建跨平台图像库:OpenCV + libraw + libheif 实践
人工智能·opencv·macos·计算机视觉