From 3b63728b9b35c096f7801388a3c13e1508956eb0 Mon Sep 17 00:00:00 2001 From: Tadas Baltrusaitis Date: Fri, 26 Jan 2018 07:59:10 +0000 Subject: [PATCH] Starting to add support for proper webcam listing. --- .../CameraEnumerator/CameraEnumerator.vcxproj | 135 ++++++++++++ .../CameraEnumerator.vcxproj.filters | 20 ++ .../CameraEnumerator/DeviceEnumerator.cpp | 121 +++++++++++ .../CameraEnumerator/DeviceEnumerator.h | 31 +++ .../OpenCVDeviceEnumerator.cpp | 36 ++++ lib/3rdParty/CameraEnumerator/readme.md | 32 +++ lib/local/CppInerop/SequenceReader.h | 194 ++++++++++++++---- 7 files changed, 527 insertions(+), 42 deletions(-) create mode 100644 lib/3rdParty/CameraEnumerator/CameraEnumerator.vcxproj create mode 100644 lib/3rdParty/CameraEnumerator/CameraEnumerator.vcxproj.filters create mode 100644 lib/3rdParty/CameraEnumerator/DeviceEnumerator.cpp create mode 100644 lib/3rdParty/CameraEnumerator/DeviceEnumerator.h create mode 100644 lib/3rdParty/CameraEnumerator/OpenCVDeviceEnumerator.cpp create mode 100644 lib/3rdParty/CameraEnumerator/readme.md diff --git a/lib/3rdParty/CameraEnumerator/CameraEnumerator.vcxproj b/lib/3rdParty/CameraEnumerator/CameraEnumerator.vcxproj new file mode 100644 index 0000000..3be56a7 --- /dev/null +++ b/lib/3rdParty/CameraEnumerator/CameraEnumerator.vcxproj @@ -0,0 +1,135 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {50B7D4BF-E33B-41D0-AA89-76BBA57BF5CC} + Win32Proj + CameraEnumerator + 8.1 + + + + StaticLibrary + true + v140 + Unicode + + + StaticLibrary + false + v140 + true + Unicode + + + StaticLibrary + true + v140 + Unicode + + + StaticLibrary + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + + + Windows + + + + + + + Level3 + Disabled + _DEBUG;_LIB;%(PreprocessorDefinitions) + + + Windows + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + + + Windows + true + true + + + + + Level3 + + + MaxSpeed + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + + + Windows + true + true + + + + + + + + + \ No newline at end of file diff --git a/lib/3rdParty/CameraEnumerator/CameraEnumerator.vcxproj.filters b/lib/3rdParty/CameraEnumerator/CameraEnumerator.vcxproj.filters new file mode 100644 index 0000000..34c1b07 --- /dev/null +++ b/lib/3rdParty/CameraEnumerator/CameraEnumerator.vcxproj.filters @@ -0,0 +1,20 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + \ No newline at end of file diff --git a/lib/3rdParty/CameraEnumerator/DeviceEnumerator.cpp b/lib/3rdParty/CameraEnumerator/DeviceEnumerator.cpp new file mode 100644 index 0000000..02f4776 --- /dev/null +++ b/lib/3rdParty/CameraEnumerator/DeviceEnumerator.cpp @@ -0,0 +1,121 @@ +#include "DeviceEnumerator.h" + +std::map DeviceEnumerator::getVideoDevicesMap() { + return getDevicesMap(CLSID_VideoInputDeviceCategory); +} + +std::map DeviceEnumerator::getAudioDevicesMap() { + return getDevicesMap(CLSID_AudioInputDeviceCategory); +} + +// Returns a map of id and devices that can be used +std::map DeviceEnumerator::getDevicesMap(const GUID deviceClass) +{ + std::map deviceMap; + + HRESULT hr = CoInitialize(nullptr); + if (FAILED(hr)) { + return deviceMap; // Empty deviceMap as an error + } + + // Create the System Device Enumerator + ICreateDevEnum *pDevEnum; + hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDevEnum)); + + // If succeeded, create an enumerator for the category + IEnumMoniker *pEnum = NULL; + if (SUCCEEDED(hr)) { + hr = pDevEnum->CreateClassEnumerator(deviceClass, &pEnum, 0); + if (hr == S_FALSE) { + hr = VFW_E_NOT_FOUND; + } + pDevEnum->Release(); + } + + // Now we check if the enumerator creation succeeded + int deviceId = -1; + if (SUCCEEDED(hr)) { + // Fill the map with id and friendly device name + IMoniker *pMoniker = NULL; + while (pEnum->Next(1, &pMoniker, NULL) == S_OK) { + + IPropertyBag *pPropBag; + HRESULT hr = pMoniker->BindToStorage(0, 0, IID_PPV_ARGS(&pPropBag)); + if (FAILED(hr)) { + pMoniker->Release(); + continue; + } + + // Create variant to hold data + VARIANT var; + VariantInit(&var); + + std::string deviceName; + std::string devicePath; + + // Read FriendlyName or Description + hr = pPropBag->Read(L"Description", &var, 0); // Read description + if (FAILED(hr)) { + // If description fails, try with the friendly name + hr = pPropBag->Read(L"FriendlyName", &var, 0); + } + // If still fails, continue with next device + if (FAILED(hr)) { + VariantClear(&var); + continue; + } + // Convert to string + else { + deviceName = ConvertBSTRToMBS(var.bstrVal); + } + + VariantClear(&var); // We clean the variable in order to read the next value + + // We try to read the DevicePath + hr = pPropBag->Read(L"DevicePath", &var, 0); + if (FAILED(hr)) { + VariantClear(&var); + continue; // If it fails we continue with next device + } + else { + devicePath = ConvertBSTRToMBS(var.bstrVal); + } + + // We populate the map + deviceId++; + Device currentDevice; + currentDevice.id = deviceId; + currentDevice.deviceName = deviceName; + currentDevice.devicePath = devicePath; + deviceMap[deviceId] = currentDevice; + + } + pEnum->Release(); + } + CoUninitialize(); + return deviceMap; +} + +/* +This two methods were taken from +https://stackoverflow.com/questions/6284524/bstr-to-stdstring-stdwstring-and-vice-versa +*/ + +std::string DeviceEnumerator::ConvertBSTRToMBS(BSTR bstr) +{ + int wslen = ::SysStringLen(bstr); + return ConvertWCSToMBS((wchar_t*)bstr, wslen); +} + +std::string DeviceEnumerator::ConvertWCSToMBS(const wchar_t* pstr, long wslen) +{ + int len = ::WideCharToMultiByte(CP_ACP, 0, pstr, wslen, NULL, 0, NULL, NULL); + + std::string dblstr(len, '\0'); + len = ::WideCharToMultiByte(CP_ACP, 0 /* no flags */, + pstr, wslen /* not necessary NULL-terminated */, + &dblstr[0], len, + NULL, NULL /* no default char */); + + return dblstr; +} diff --git a/lib/3rdParty/CameraEnumerator/DeviceEnumerator.h b/lib/3rdParty/CameraEnumerator/DeviceEnumerator.h new file mode 100644 index 0000000..6e4716c --- /dev/null +++ b/lib/3rdParty/CameraEnumerator/DeviceEnumerator.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +#pragma comment(lib, "strmiids") + +#include +#include + +struct Device { + int id; // This can be used to open the device in OpenCV + std::string devicePath; + std::string deviceName; // This can be used to show the devices to the user +}; + +class DeviceEnumerator { + +public: + + DeviceEnumerator() = default; + std::map getDevicesMap(const GUID deviceClass); + std::map getVideoDevicesMap(); + std::map getAudioDevicesMap(); + +private: + + std::string ConvertBSTRToMBS(BSTR bstr); + std::string ConvertWCSToMBS(const wchar_t* pstr, long wslen); + +}; diff --git a/lib/3rdParty/CameraEnumerator/OpenCVDeviceEnumerator.cpp b/lib/3rdParty/CameraEnumerator/OpenCVDeviceEnumerator.cpp new file mode 100644 index 0000000..8c72181 --- /dev/null +++ b/lib/3rdParty/CameraEnumerator/OpenCVDeviceEnumerator.cpp @@ -0,0 +1,36 @@ +#include +#include + +#include "DeviceEnumerator.h" + +int main() +{ + + /* + The id field of the Device struct can be used with an OpenCV VideoCapture object + */ + + DeviceEnumerator de; + + // Audio Devices + std::map devices = de.getAudioDevicesMap(); + + // Print information about the devices + for (auto const &device : devices) { + std::cout << "== AUDIO DEVICE (id:" << device.first << ") ==" << std::endl; + std::cout << "Name: " << device.second.deviceName << std::endl; + std::cout << "Path: " << device.second.devicePath << std::endl; + } + + // Video Devices + devices = de.getVideoDevicesMap(); + + // Print information about the devices + for (auto const &device : devices) { + std::cout << "== VIDEO DEVICE (id:" << device.first << ") ==" << std::endl; + std::cout << "Name: " << device.second.deviceName << std::endl; + std::cout << "Path: " << device.second.devicePath << std::endl; + } + +} + diff --git a/lib/3rdParty/CameraEnumerator/readme.md b/lib/3rdParty/CameraEnumerator/readme.md new file mode 100644 index 0000000..bfbfa94 --- /dev/null +++ b/lib/3rdParty/CameraEnumerator/readme.md @@ -0,0 +1,32 @@ +# Device Enumerator for OpenCV + +This project contains a *C++* class that allows the enumeration of devices using DirectShow in Windows, in order to select and obtain the ID that needs to be used with OpenCV when creating, for example, a VideoCapture object to grab frames from a camera. I decided to put this up as "How to get the ID of a device to use inside OpenCV?" is a question that pops up continuously. + +## Contents + +The project contains the class itself (DeviceEnumerator.h and DeviceEnumerator.cpp) and also an example of how to use the class (OpenCVDeviceEnumerator.cpp) + +## License + +MIT License + +Copyright (c) 2018 David Gil de Gómez Pérez (Studiosi) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/lib/local/CppInerop/SequenceReader.h b/lib/local/CppInerop/SequenceReader.h index ef9f939..98fcdb7 100644 --- a/lib/local/CppInerop/SequenceReader.h +++ b/lib/local/CppInerop/SequenceReader.h @@ -49,6 +49,8 @@ #include #include +#include "DeviceEnumerator.h" + #include "SequenceCapture.h" #pragma managed @@ -211,68 +213,176 @@ namespace UtilitiesOF { } }; - // A utility for listing the currently connected cameras together with their ID, subset of supported resolutions and a thumbnail - static List^>^, OpenCVWrappers::RawImage^>^>^ GetCameras() - { - auto managed_camera_list = gcnew List^>^, OpenCVWrappers::RawImage^>^>(); - - // Go through camera id's untill no new cameras are found - int num_cameras = 0; - while (true) - { - cv::VideoCapture cap(num_cameras); - if (cap.isOpened()) - num_cameras++; - else - break; + static void split(const std::string &s, char delim, std::vector &elems) { + std::stringstream ss; + ss.str(s); + std::string item; + while (std::getline(ss, item, delim)) { + elems.push_back(item); } + } + + // Camera listing is camera name and supported resolutions + static Dictionary^>^>^ GetListingFromFile(std::string filename) + { + // Check what cameras have been written (using OpenCVs XML packages) + cv::FileStorage fs_read(filename, cv::FileStorage::READ); + + auto managed_camera_list_initial = gcnew Dictionary^>^>(); + + cv::FileNode camera_node_list = fs_read["cameras"]; + + // iterate through a sequence using FileNodeIterator + for (size_t idx = 0; idx < camera_node_list.size(); idx++) + { + std::string camera_name = (std::string)camera_node_list[idx]["name"]; + + cv::FileNode resolution_list = camera_node_list[idx]["resolutions"]; + auto resolutions = gcnew System::Collections::Generic::List^>(); + for (size_t r_idx = 0; r_idx < resolution_list.size(); r_idx++) + { + string res = resolution_list[r_idx]["res"]; + + std::vector elems; + split(res, 'x', elems); + + int x = stoi(elems[0]); + int y = stoi(elems[1]); + resolutions->Add(gcnew System::Tuple(x, y)); + } + managed_camera_list_initial[gcnew System::String(camera_name.c_str())] = resolutions; + } + fs_read.release(); + return managed_camera_list_initial; + } + + static void WriteCameraListingToFile(System::Collections::Generic::Dictionary^>^>^ camera_list, std::string filename) + { + cv::FileStorage fs("camera_list.xml", cv::FileStorage::WRITE); + + fs << "cameras" << "["; + for each(System::String^ name_m in camera_list->Keys) + { + + std::string name = msclr::interop::marshal_as(name_m); + + fs << "{:" << "name" << name; + fs << "resolutions" << "["; + auto resolutions = camera_list[name_m]; + for (int j = 0; j < resolutions->Count; j++) + { + stringstream ss; + ss << resolutions[j]->Item1 << "x" << resolutions[j]->Item2; + + fs << "{:" << "res" << ss.str(); + fs << "}"; + } + fs << "]"; + fs << "}"; + } + fs << "]"; + fs.release(); + } + + // A utility for listing the currently connected cameras together with their ID, name, subset of supported resolutions and a thumbnail + static List^>^, OpenCVWrappers::RawImage^>^>^ GetCameras(System::String^ root_directory_m) + { + auto managed_camera_list = gcnew List^>^, OpenCVWrappers::RawImage^>^>(); + + DeviceEnumerator de; + + // Get a listing of all connected video devices + std::map cameras = de.getVideoDevicesMap(); + + // Print information about the devices + for (auto const &device : cameras) { + std::cout << "== VIDEO DEVICE (id:" << device.first << ") ==" << std::endl; + std::cout << "Name: " << device.second.deviceName << std::endl; + std::cout << "Path: " << device.second.devicePath << std::endl; + } + + size_t num_cameras = cameras.size(); + + // Pre-load supported camera resolutions if already computed + std::string root_directory = msclr::interop::marshal_as(root_directory_m); + auto camera_resolution_list = GetListingFromFile(root_directory + "camera_list.xml"); for (size_t i = 0; i < num_cameras; ++i) { - //std::string name = cameras[i].name(); - //System::String^ name_managed = gcnew System::String(name.c_str()); - - auto resolutions = gcnew List^>(); - - // A common set of resolutions for webcams - std::vector> common_resolutions; - common_resolutions.push_back(std::pair(320, 240)); - common_resolutions.push_back(std::pair(640, 480)); - common_resolutions.push_back(std::pair(1280, 960)); - - // Grab some sample images and confirm the resolutions - cv::VideoCapture cap1(i); - + // Thumbnail to help with camera selection cv::Mat sample_img; OpenCVWrappers::RawImage^ sample_img_managed = gcnew OpenCVWrappers::RawImage(); - // Go through resolutions if they have not been identified - for (auto beg = common_resolutions.begin(); beg != common_resolutions.end(); ++beg) - { - auto resolution = gcnew System::Tuple(beg->first, beg->first); + auto resolutions = gcnew List^>(); + // Before trying the resolutions, check if the resolutions have already been computed for the camera of interest + std::string device_name = cameras[i].deviceName; + System::String^ device_name_m = gcnew System::String(device_name.c_str()); + if (camera_resolution_list->ContainsKey(device_name_m)) + { + resolutions = camera_resolution_list[device_name_m]; + + // Grab a thumbnail from mid resolution + cv::VideoCapture cap1(i); + + auto resolution = resolutions[(int)(resolutions->Count / 2)]; cap1.set(CV_CAP_PROP_FRAME_WIDTH, resolution->Item1); cap1.set(CV_CAP_PROP_FRAME_HEIGHT, resolution->Item2); - // Add only valid resolutions as API sometimes provides wrong ones - int set_width = cap1.get(CV_CAP_PROP_FRAME_WIDTH); - int set_height = cap1.get(CV_CAP_PROP_FRAME_HEIGHT); - // Read several frames, as the first one often is over-exposed for (int k = 0; k < 2; ++k) cap1.read(sample_img); + } + else + { - resolution = gcnew System::Tuple(set_width, set_height); - if (!resolutions->Contains(resolution)) + // A common set of resolutions for webcams + std::vector> common_resolutions; + common_resolutions.push_back(std::pair(320, 240)); + common_resolutions.push_back(std::pair(640, 480)); + common_resolutions.push_back(std::pair(960, 720)); + common_resolutions.push_back(std::pair(1280, 720)); + common_resolutions.push_back(std::pair(1280, 960)); + common_resolutions.push_back(std::pair(1920, 1080)); + + // Grab some sample images and confirm the resolutions + cv::VideoCapture cap1(i); + + // Go through resolutions if they have not been identified + for (size_t i = 0; i < common_resolutions.size(); ++i) { - resolutions->Add(resolution); + auto resolution = gcnew System::Tuple(common_resolutions[i].first, common_resolutions[i].second); + + cap1.set(CV_CAP_PROP_FRAME_WIDTH, resolution->Item1); + cap1.set(CV_CAP_PROP_FRAME_HEIGHT, resolution->Item2); + + // Add only valid resolutions as API sometimes provides wrong ones + int set_width = cap1.get(CV_CAP_PROP_FRAME_WIDTH); + int set_height = cap1.get(CV_CAP_PROP_FRAME_HEIGHT); + + // Grab a thumbnail from mid resolution + if(i == (int)common_resolutions.size()/2) + { + // Read several frames, as the first one often is over-exposed + for (int k = 0; k < 2; ++k) + cap1.read(sample_img); + } + + resolution = gcnew System::Tuple(set_width, set_height); + if (!resolutions->Contains(resolution)) + { + resolutions->Add(resolution); + } } + cap1.~VideoCapture(); + + // Ass the resolutions were not on the list, add them now + camera_resolution_list[device_name_m] = resolutions; + WriteCameraListingToFile(camera_resolution_list, root_directory + "camera_list.xml"); } sample_img.copyTo(sample_img_managed->Mat); - cap1.~VideoCapture(); - - managed_camera_list->Add(gcnew System::Tuple^>^, OpenCVWrappers::RawImage^>(i, resolutions, sample_img_managed)); + managed_camera_list->Add(gcnew System::Tuple^>^, OpenCVWrappers::RawImage^>(i, device_name_m, resolutions, sample_img_managed)); } return managed_camera_list;