查询资料后发现,MFC并没有像Qt那样提供了关于图像的类,而借助OpenCV
可以捕捉摄像头图像并显示。因此本demo最终目的是通过MFC的下拉框部件实现不同相机切换的显示效果。OpenCV
欲读取摄像头图像时,需先创建一个VideoCapture
对象,并传入一个index
参数。
Videocapture cap;
cap.open(index);
但是,当存在多个摄像头时,并不能事先知道拟打开摄像头的序号是多少。因此,若是能将摄像头的设备列表与序号对应起来,便可以随心所欲控制想要打开的摄像头进而捕捉其图像。因此,我们需要这样一个数据类型,根据设备字符串便可索引到设备序号。
std::map<std::string, int> cameraID;
获取摄像头列表
但现在的难题在于怎么获取设备列表,幸运的是,网上已经有答案了,参考opencv获取多个摄像头名字和编号
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include "windows.h"
#include "dshow.h"
#include <iostream>
#pragma comment(lib, "strmiids.lib")
#pragma comment(lib, "quartz.lib")
using namespace cv;
using namespace std;
int listDevices(vector<string>& list)
{
//COM Library Initialization
//comInit();
//if (!silent) DebugPrintOut("\nVIDEOINPUT SPY MODE!\n\n");
ICreateDevEnum* pDevEnum = NULL;
IEnumMoniker* pEnum = NULL;
int deviceCounter = 0;
CoInitialize(NULL);
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,
CLSCTX_INPROC_SERVER, IID_ICreateDevEnum,
reinterpret_cast<void**>(&pDevEnum));
if (SUCCEEDED(hr))
{
// Create an enumerator for the video capture category.
hr = pDevEnum->CreateClassEnumerator(
CLSID_VideoInputDeviceCategory,
&pEnum, 0);
if (hr == S_OK) {
printf("SETUP: Looking For Capture Devices\n");
IMoniker* pMoniker = NULL;
while (pEnum->Next(1, &pMoniker, NULL) == S_OK) {
IPropertyBag* pPropBag;
hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag,
(void**)(&pPropBag));
if (FAILED(hr)) {
pMoniker->Release();
continue; // Skip this one, maybe the next one will work.
}
// Find the description or friendly name.
VARIANT varName;
VariantInit(&varName);
hr = pPropBag->Read(L"Description", &varName, 0);
if (FAILED(hr)) hr = pPropBag->Read(L"FriendlyName", &varName, 0);
if (SUCCEEDED(hr))
{
hr = pPropBag->Read(L"FriendlyName", &varName, 0);
int count = 0;
char tmp[255] = {
0 };
//int maxLen = sizeof(deviceNames[0]) / sizeof(deviceNames[0][0]) - 2;
while (varName.bstrVal[count] != 0x00 && count < 255)
{
tmp[count] = (char)varName.bstrVal[count];
count++;
}
list.push_back(tmp);
//deviceNames[deviceCounter][count] = 0;
//if (!silent) DebugPrintOut("SETUP: %i) %s\n", deviceCounter, deviceNames[deviceCounter]);
}
pPropBag->Release();
pPropBag = NULL;
pMoniker->Release();
pMoniker = NULL;
deviceCounter++;
}
pDevEnum->Release();
pDevEnum = NULL;
pEnum->Release();
pEnum = NULL;
}
//if (!silent) DebugPrintOut("SETUP: %i Device(s) found\n\n", deviceCounter);
}
//comUnInit();
return deviceCounter;
}
int main()
{
vector<string> list;
int capid0 = 0, capid1 = 0;//摄像头ID
int divice_num = 0;
divice_num = listDevices(list);//设备列表
cout << "divice_num:" << divice_num << endl;
for (int i = 0; i < list.size(); i++)
{
//if (list[i] == "HIK 1080P Camera")
//{
// capid0 = i;
// cout << "capid0 = " << capid0 << endl;
// cout << list[i]<< " is HIK 1080P Camera"<<endl;
//}
//if (list[i] == "2K USB Camera")
//{
// capid1 = i;
// cout << "capid1 = " << capid1 << endl;
// cout << list[i] << " is 2K USB Camera" << endl;
//}
cout << "Device[" << i << "]:" << list[i] << endl;
}
VideoCapture cap;
cout << "capid = " << capid0 << endl;
cap.open(1);
Mat frame;
while (1)
{
cap >> frame;
imshow("capshow", frame);
if (waitKey(30) == 27)
{
break;
}
}
return 0;
}
经测试发现确实可行
SETUP: Looking For Capture Devices
divice_num:2
Device[0]:HIK 1080P Camera
Device[1]:2K USB Camera
capid = 0
MFC搭建界面
基于对话框创建一个MFC应用
将环境设置成Release x64
,并设置好OpenCV
环境。拖入一个Static Text
控件,修改其名字为:相机。
再拖入Combo Box
控件,修改其属性中的ID
为IDC_CAMERA
并为该控件关联变量m_camera
在MFCCameraDlg.h
中添加头文件
#include <vector>
#include <string>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <map>
在CMFCCameraDlg
类声明中添加两个私有变量:
cv::VideoCapture cap; //相机捕获对象
std::map<std::string, int> cameraID; //相机列表序号映射表
转到MFCCameraDlg.cpp
中,先添加需要的头文件及lib
库
#include "windows.h"
#include "dshow.h"
#pragma comment(lib, "strmiids.lib")
#pragma comment(lib, "quartz.lib")
在CMFCCameraDlg::OnInitDialog()
函数中添加窗口初始化代码
// TODO: 在此添加额外的初始化代码
//获取所有摄像头设备信息
std::vector<std::string> list;
int divice_num = listDevices(list);//设备列表
for (int i = 0; i < divice_num; i++) {
//构建映射表
cameraID.insert(std::pair<std::string, int>(list[i], i));
}
for (int i = 0; i < divice_num; i++) {
//添加下拉框选项
m_camera.AddString(StoWs(list[i]));
}
由于类中缺少listDevices
和StoWs
这两个函数,为其添加好(,h
中声明,.cpp
中实现)
public:
int listDevices(std::vector<std::string>& list);
LPCTSTR StoWs(const std::string& s); //将std::string转成LPCTSTR
int CMFCCameraDlg::listDevices(std::vector<std::string>& list) {
//COM Library Initialization
//comInit();
//if (!silent) DebugPrintOut("\nVIDEOINPUT SPY MODE!\n\n");
ICreateDevEnum* pDevEnum = NULL;
IEnumMoniker* pEnum = NULL;
int deviceCounter = 0;
CoInitialize(NULL);
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,
CLSCTX_INPROC_SERVER, IID_ICreateDevEnum,
reinterpret_cast<void**>(&pDevEnum));
if (SUCCEEDED(hr))
{
// Create an enumerator for the video capture category.
hr = pDevEnum->CreateClassEnumerator(
CLSID_VideoInputDeviceCategory,
&pEnum, 0);
if (hr == S_OK) {
printf("SETUP: Looking For Capture Devices\n");
IMoniker* pMoniker = NULL;
while (pEnum->Next(1, &pMoniker, NULL) == S_OK) {
IPropertyBag* pPropBag;
hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag,
(void**)(&pPropBag));
if (FAILED(hr)) {
pMoniker->Release();
continue; // Skip this one, maybe the next one will work.
}
// Find the description or friendly name.
VARIANT varName;
VariantInit(&varName);
hr = pPropBag->Read(L"Description", &varName, 0);
if (FAILED(hr)) hr = pPropBag->Read(L"FriendlyName", &varName, 0);
if (SUCCEEDED(hr))
{
hr = pPropBag->Read(L"FriendlyName", &varName, 0);
int count = 0;
char tmp[255] = {
0 };
//int maxLen = sizeof(deviceNames[0]) / sizeof(deviceNames[0][0]) - 2;
while (varName.bstrVal[count] != 0x00 && count < 255)
{
tmp[count] = (char)varName.bstrVal[count];
count++;
}
list.push_back(tmp);
//deviceNames[deviceCounter][count] = 0;
//if (!silent) DebugPrintOut("SETUP: %i) %s\n", deviceCounter, deviceNames[deviceCounter]);
}
pPropBag->Release();
pPropBag = NULL;
pMoniker->Release();
pMoniker = NULL;
deviceCounter++;
}
pDevEnum->Release();
pDevEnum = NULL;
pEnum->Release();
pEnum = NULL;
}
//if (!silent) DebugPrintOut("SETUP: %i Device(s) found\n\n", deviceCounter);
}
//comUnInit();
return deviceCounter;
}
LPCTSTR CMFCCameraDlg::StoWs(const std::string& s) {
int len;
int slength = (int)s.length() + 1;
len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0);
wchar_t* buf = new wchar_t[len];
MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, buf, len);
LPCTSTR r(buf);
delete[] buf;
return r;
}
运行代码,下拉框中便出现可选选项
最后实现选择对应选项的消息处理函数,双击控件即可进入
void CMFCCameraDlg::OnCbnSelchangeCamera()
{
// TODO: 在此添加控件通知处理程序代码
//1.获取当前选中的选项
int index = m_camera.GetCurSel();
//2.根据字符串索引相机设备的序号
CString str;
m_camera.GetLBText(index, str);
std::string strStr(CW2A(str.GetString())); //将CString转成std::string
int cameraIndex = cameraID[strStr];
//3.将序号传入VideoCapture对象
cap.open(cameraIndex);
cv::Mat frame;
while (1)
{
cap >> frame;
imshow("capshow", frame);
if (cv::waitKey(30) == 27)
{
break;
}
}
}
实际测试效果如下
默认显示的图像分辨率是 640 × 480 640\times480 640×480,这是OpenCV
默认的
想要修改分辨率,可以添加代码
//设置分辨率,编码格式
cap.set(cv::CAP_PROP_FRAME_WIDTH, 1920);
cap.set(cv::CAP_PROP_FRAME_HEIGHT, 1080);
cap.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'));