之前在https://blog.csdn.net/fengbingchun/article/details/102641967中介紹過通過DShow獲取Camera視頻的實現,即調用VideoCapture類。在OpenCV的VideoCapture類中並沒有提供獲取Camera設備列表、支持的編解碼類型列表及支持的video size列表接口,這裏基於已有的VideoCapture類增加對這些的實現。
獲取Camera設備名:在videoInput類中有兩個函數,一個是getDeviceCount用於獲取設備數,一個是getDeviceName用於根據索引獲取設備名。在CvCaputreCAM_DShow類中增加函數getDevicesList,實現如下,在此函數中調用videoInput中的getDeviceCount和getDeviceName。
bool CvCaptureCAM_DShow::getDevicesList(std::map<int, std::string>& devicelist) const
{
devicelist.clear();
for (int i = 0; i < VI.getDeviceCount(); ++i) {
devicelist[i] = VI.getDeviceName(i);
}
return true;
}
增加一個對外調用的接口get_camera_names,實現如下:
bool FBC_EXPORTS get_camera_names(std::map<int, std::string>& names)
{
VideoCapture capture(0);
if (!capture.isOpened()) {
fprintf(stderr, "fail to open capture\n");
return false;
}
return capture.getDevicesList(names);
}
獲取Camera支持的編解碼類型和video size:這部分的實現參考了FFmpeg中的源碼libavdevice/dshow.c,並把其中的一些code移了過來。首先在videoInput類中增加一個變量infolist,用於存放設備索引名、編解碼類型、視頻size信息;增加一個函數getCodecAndVideoSize,用於實現獲取編解碼和video size;增加一個getCodecList用於供CvCaptureCAM_DShow調用獲取編解碼列表;增加一個getVideoSizeList用於供CvCaptureCAM_DShow調用獲取video size列表。CvCaptureCAM_DShow中也有對應的這兩個函數,用於供VideoCapture調用。它們的實現如下:執行完VD->streamConf->GetStreamCaps後,結構體scc中的MinOutputSize和MaxOutputSize成員變量值即是video size。通過計算結構體pmtConfig中的BITMAPINFOHEADER即可獲取codec type。
void videoInput::getCodecAndVideoSize(videoDevice *VD)
{
infolist[VD->myID].clear();
int iCount = 0;
int iSize = 0;
HRESULT hr = VD->streamConf->GetNumberOfCapabilities(&iCount, &iSize);
if (hr != S_OK) {
fprintf(stderr, "fail to get number of capabilities\n");
return;
}
if (iSize == sizeof(VIDEO_STREAM_CONFIG_CAPS)) {
std::map<int, std::map<int, std::set<std::vector<int>>>> info;
//For each format type RGB24 YUV2 etc
for (int iFormat = 0; iFormat < iCount; iFormat++) {
VIDEO_STREAM_CONFIG_CAPS scc;
AM_MEDIA_TYPE *pmtConfig;
hr = VD->streamConf->GetStreamCaps(iFormat, &pmtConfig, (BYTE*)&scc);
if (SUCCEEDED(hr)){
//his is how many diff sizes are available for the format
int stepX = scc.OutputGranularityX;
int stepY = scc.OutputGranularityY;
//Don't want to get stuck in a loop
if (stepX < 1 || stepY < 1) continue;
std::vector<int> size_min{ scc.MinOutputSize.cx, scc.MinOutputSize.cy }, size_max{ scc.MaxOutputSize.cx, scc.MaxOutputSize.cy };
VIDEOINFOHEADER* pVih = reinterpret_cast<VIDEOINFOHEADER*>(pmtConfig->pbFormat);
BITMAPINFOHEADER* bih = &pVih->bmiHeader;
const AVCodecTag *const tags[] = { avformat_get_riff_video_tags(), NULL };
enum AVPixelFormat pix_fmt = dshow_pixfmt(bih->biCompression, bih->biBitCount);
if (pix_fmt == AV_PIX_FMT_NONE) {
enum AVCodecID codec_id = av_codec_get_id(tags, bih->biCompression);
if (codec_id != AV_CODEC_ID_NONE) {
if (codec_id == AV_CODEC_ID_MJPEG) {
info[VD->myID][VIDEO_CODEC_TYPE_MJPEG].insert(size_min);
info[VD->myID][VIDEO_CODEC_TYPE_MJPEG].insert(size_max);
}
else if (codec_id == AV_CODEC_ID_H264) {
info[VD->myID][VIDEO_CODEC_TYPE_H264].insert(size_min);
info[VD->myID][VIDEO_CODEC_TYPE_H264].insert(size_max);
}
else if (codec_id == AV_CODEC_ID_H265) {
info[VD->myID][VIDEO_CODEC_TYPE_H265].insert(size_min);
info[VD->myID][VIDEO_CODEC_TYPE_H265].insert(size_max);
}
}
}
else {
info[VD->myID][VIDEO_CODEC_TYPE_RAWVIDEO].insert(size_min);
info[VD->myID][VIDEO_CODEC_TYPE_RAWVIDEO].insert(size_max);
}
MyDeleteMediaType(pmtConfig);
}
}
for (auto codec_id = 0; codec_id < info[VD->myID].size(); ++codec_id) {
for (auto it = info[VD->myID][codec_id].cbegin(); it != info[VD->myID][codec_id].cend(); ++it) {
std::string size = std::to_string((*it)[0]);
size += "x";
size += std::to_string((*it)[1]);
infolist[VD->myID][codec_id].push_back(size);
}
}
}
else {
fprintf(stderr, "fail to get codec and video size\n");
return;
}
}
bool videoInput::getCodecList(int device_id, std::vector<int>& codecids)
{
codecids.clear();
if (infolist[device_id].size() == 0) return false;
for (auto it = infolist[device_id].cbegin(); it != infolist[device_id].cend(); ++it) {
codecids.push_back(it->first);
}
return true;
}
bool videoInput::getVideoSizeList(int device_id, int codec_id, std::vector<std::string>& sizelist)
{
if (device_id >= getDeviceCount()) return false;
sizelist = this->infolist[device_id][codec_id];
return true;
}
OpenCV中的VideoCapture類中雖然有接收設備名的接口,但是好像並不能運行,只能通過設備索引來獲取視頻。在一臺PC機上插入多個USB攝像頭時並不清楚哪個索引對應哪個設備名,這裏對VideoCapture(const std::string& filename)的實現進行了修改,使其可以通過輸入設備名獲取視頻,實現如下:首先通過索引0獲取到設備列表,然後根據輸入的設備名在設備列表中是否可以映射到對應的索引值,如果有匹配的,則再通過對應的索引值打開設備。
bool VideoCapture::open(const std::string& filename)
{
if (isOpened()) release();
open(0);
if (!cap) return false;
std::map<int, std::string> devices_name;
cap->getDevicesList(devices_name);
if (devices_name.size() == 0) return false;
if (isOpened()) release();
int device = -1;
for (auto it = devices_name.cbegin(); it != devices_name.cend(); ++it) {
if (filename.compare((*it).second) == 0)
device = (*it).first;
}
if (device == -1) return false;
return open(device);
}
測試代碼如下:
#include "fbc_cv_funset.hpp"
#include <videocapture.hpp>
#include <opencv2/opencv.hpp>
int test_get_camera_info()
{
#ifdef _MSC_VER
std::map<int, std::string> camera_names;
fbc::get_camera_names(camera_names);
fprintf(stdout, "camera count: %d\n", camera_names.size());
for (auto it = camera_names.cbegin(); it != camera_names.cend(); ++it) {
fprintf(stdout, "camera index: %d, name: %s\n", (*it).first, (*it).second.c_str());
}
fbc::VideoCapture capture(0);
if (!capture.isOpened()) {
fprintf(stderr, "fail to open capture\n");
return -1;
}
std::vector<int> codecids;
capture.getCodecList(codecids);
fprintf(stdout, "support codec type(video_codec_type_t: 0: h264; 1: h265; 2: mjpeg; 3: rawvideo): ");
for (auto i = 0; i < codecids.size(); ++i) {
fprintf(stdout, "%d ", codecids[i]);
}
fprintf(stdout, "\n");
std::vector<std::string> sizelist;
int codec_type = fbc::VIDEO_CODEC_TYPE_MJPEG;
capture.getVideoSizeList(codec_type, sizelist);
fprintf(stdout, "codec type: %d, support video size list:(width*height)\n", codec_type);
for (auto it = sizelist.cbegin(); it != sizelist.cend(); ++it) {
fprintf(stdout, "\t%s\n", (*it).c_str());
}
return 0;
#else
fprintf(stderr, "Error: only support windows platform\n");
return -1;
#endif
}
執行結果如下: