設備監視などで活用可能なコンパクトなWebカメラ
1.仕様
- 20×20×80のサイズでコンパクト、取付は1/4ネジの為三脚での固定が可能。
- 画素数最大2592×1944(MPEG)。取得可能な形式はMPEG、NV12、YUY2。
- UVCカメラであるためOpenCVでも画像取得が可能。
- 定価 32,780円だが大手ECサイトでは2万円前後。
- オートフォーカス機能あり。40㎜~無限遠
2.OpenCVでの画像取得
OpenCVでの画像取得が可能です。
但しデバイスの選択がインデックスでしかできない(配線の繋ぎ直しで変化する可能性あり)と多くのカメラプロパティを制御できない点が課題となります。(画像サイズ、露光時間は可能)
画像取得のコード
#include <opencv2/opencv.hpp>
#pragma comment(lib,"opencv_world454d.lib")
int main()
{
//キャプチャーデバイスを取得
cv::VideoCapture cap = cv::VideoCapture(0);
cv::Mat mat;
while(true){
//カメラ画像データの取得
cap.read(mat);
//ウィンドウへの画像表示
cv::imshow("MyWindow", mat);
//30ms待機、またEscキーでループ終了
if (cv::waitKey(30) == 27)
break;
}
//デバイスの開放
cap.release();
//ウィンドウを閉じる
cv::destroyAllWindows();
}
3.DirectShowでのカメラプロパティ設定
カメラのプロパティはDirectShowを使用して取得、変更します。画像の取得も可能ですが、MicrosoftがMediaFoundationでの画像取得を推奨しているのでそちらの方法で取得します。
本カメラでは以下のプロパティの設定が可能です。
- brightness(明るさ)
- contrast(コントラスト)
- hue(色相)
- saturation(色彩)
- sharpness(シャープネス)
- gamma(ガンマ補正値)
- whitebalance(ホワイトバランス)
- whitebalance_automatic(ホワイトバランス自動設定)
- backlight-compensation(逆光補正)
- gain(ゲイン値)
- exposure(露光値)
- exposure_automatic(露光値自動設定)
- focus(フォーカス)
- focus_automatic(オートフォーカス設定)
また本カメラで使用可能なビデオフォーマットも以下のようになり、
フォーマットを得る関数であるIMFMediaTypeHandler::GetMediaTypeByIndex(DWORD &pType)
で得られます。
const int EF_NV12_1920X1080 = 0; //NV12
const int EF_MJPEG_1920X1080 = 1; //MJPEG
const int EF_NV12_2592X1944 = 2; //NV12
const int EF_MJPEG_2592X1944 = 3; //MJPEG
const int EF_NV12_2048X1536 = 4; //NV12
const int EF_MJPEG_2048X1536 = 5; //MJPEG
const int EF_NV12_1600X1200 = 6; //NV12
const EF_MJPEG_1600X1200 = 7; //MJPEG
const int EF_NV12_1280X960 = 8; //NV12
const int EF_MJPEG_1280X960 = 9; //MJPEG
const EF_NV12_1280X720 = 10; //NV12
const int EF_MJPEG_1280X720 = 11; //MJPEG
const int EF_NV12_1024X768 = 12; //NV12
const int EF_MJPEG_1024X768 = 13; //MJPEG
const int EF_NV12_800X600 = 14; //NV12
const int EF_MJPEG_800X600 = 15; //MJPEG
const int EF_NV12_600X480 = 16; //NV12
const int EF_MJPEG_600X480 = 17; //MJPEG
const int EF_NV12_640X360 = 18; //NV12
const int EF_MJPEG_640X360 = 19; //MJPEG
const int _EF_NV12_1920X1080 = 20; ??
const int _EF_MJPEG_1920X1080 = 21; ??
const int EF_YUY2_1920X1080 = 22; //以下YUY2
const int EF_YUY2_1600X1200 = 23;
const int EF_YUY2_1280X960 = 24;
const int EF_YUY2_1280X720 = 25;
const int EF_YUY2_1024X768 = 26;
const int EF_YUY2_800X600 = 27;
const int EF_YUY2_640X480 = 28;
const int EF_YUY2_640X360 = 29;
DirectShow制御の為のクラス
#pragma once
#include <stdio.h>
#include <string>
#include <dshow.h>
#include <vector>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <algorithm>
struct video_formats
{
int width;
int height;
double fps;
std::string type;
bool flag = false;
};
class ClsDirectShow
{
public:
static IEnumMoniker* video_init(ICreateDevEnum* pDevEnum);
static void get_devices_list(IEnumMoniker* pClassEnum);
static void get_videoformats_list(int device_num, IEnumMoniker* pClassEnum);
static void get_camera_settings(int device_num, IEnumMoniker* pClassEnum);
private:
static void get_config(IBaseFilter* pbf);
static video_formats get_format_type(VIDEOINFOHEADER* video);
static void get_user_controls(IBaseFilter* pbf);
static void get_camera_controls(IBaseFilter* pbf);
};
#include "ClsDirectShow.h"
//選択したデバイスを返します
IEnumMoniker* ClsDirectShow::video_init(ICreateDevEnum* pDevEnum)
{
if (FAILED(CoInitialize(NULL)))
{
printf("error: COM init error");
return NULL;
}
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDevEnum));
if (SUCCEEDED(hr))
{
// Create an enumerator for the category.
IEnumMoniker* pClassEnum = NULL;
hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pClassEnum, 0);
if (hr == S_FALSE)
{
return NULL;
}
return pClassEnum;
}
return NULL;
}
//接続しているビデオデバイスを取得し列挙します
void ClsDirectShow::get_devices_list(IEnumMoniker* pClassEnum)
{
ULONG cFetched;
IMoniker* pMoniker = NULL;
int n = 0;
pClassEnum->Reset();
while (pClassEnum->Next(1, &pMoniker, &cFetched) == S_OK)
{
IPropertyBag* pP = NULL;
VARIANT var;
var.vt = VT_BSTR;
BSTR device_name;
BSTR device_path;
pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void**)&pP);
pP->Read(L"FriendlyName", &var, 0);
device_name = var.bstrVal;
VariantClear(&var);
pP->Read(L"DevicePath", &var, 0);
device_path = var.bstrVal;
VariantClear(&var);
std::wcout << device_name << ": (" << device_path << "):" << std::endl;
std::wcout << " /dev/video" << n << std::endl;
n++;
}
std::wcout << std::endl;
return;
}
//使用可能なビデオフォーマットのリストを列挙します
void ClsDirectShow::get_videoformats_list(int device_num, IEnumMoniker* pClassEnum)
{
ULONG cFetched;
IMoniker* pMoniker = NULL;
int n = 0;
pClassEnum->Reset();
while (pClassEnum->Next(1, &pMoniker, &cFetched) == S_OK)
{
if (device_num == n)
{
IBaseFilter* pbf = NULL;
pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pbf);
get_config(pbf);
return;
}
n++;
}
}
//カメラ、ユーザー制御のプロパティの設定値を取得する
void ClsDirectShow::get_camera_settings(int device_num, IEnumMoniker* pClassEnum)
{
ULONG cFetched;
IMoniker* pMoniker = NULL;
int n = 0;
pClassEnum->Reset();
while (pClassEnum->Next(1, &pMoniker, &cFetched) == S_OK)
{
if (device_num == n)
{
IBaseFilter* pbf = NULL;
pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pbf);
get_user_controls(pbf);
get_camera_controls(pbf);
return;
}
n++;
}
}
//ユーザー制御のプロパティの設定値を取得する
void ClsDirectShow::get_user_controls(IBaseFilter* pbf)
{
std::vector<std::string> proc_user = { "brightness",
"contrast",
"hue",
"saturation",
"sharpness",
"gamma",
"colorEnable",
"whitebalance",
"backlight-compensation",
"gain" };
IAMVideoProcAmp* pProcAmp = 0;
HRESULT hr = pbf->QueryInterface(IID_IAMVideoProcAmp, (void**)&pProcAmp);
if (FAILED(hr))
return;
std::cout << "User Controls" << std::endl;
std::cout << std::endl;
for (int proc_amp = 0; proc_amp < 10; proc_amp++)
{
long Min, Max, Step, Default, Flags, AutoFlags, Val;
// Get the range and default value.
hr = pProcAmp->GetRange(proc_amp, &Min, &Max, &Step, &Default, &AutoFlags);
if (SUCCEEDED(hr))
{
// Get the current value.
hr = pProcAmp->Get(proc_amp, &Val, &Flags);
}
if (SUCCEEDED(hr))
{
std::string str_proc = "";
if (AutoFlags == 3)
{
int auto_val = Flags;
if (Flags == 2)
{
auto_val = 0;
}
str_proc = "";
for (int i = int(proc_user[proc_amp].size()) + 10; i < 22; i++)
{
str_proc += " ";
}
std::cout << "\t" << str_proc << proc_user[proc_amp] << "_automatic (bool)"
<< "\t: default=" << 1 << " value=" << auto_val << std::endl;
}
str_proc = "";
for (int i = int(proc_user[proc_amp].size()); i < 22; i++)
{
str_proc += " ";
}
std::cout << "\t" << str_proc << proc_user[proc_amp] << " (int) \t: min=" << Min << " max=" << Max << " step=" << Step << " default=" << Default << " value=" << Val << std::endl;
}
}
std::cout << std::endl;
}
//カメラ制御のプロパティの設定値を取得する
void ClsDirectShow::get_camera_controls(IBaseFilter* pbf)
{
std::vector<std::string> proc_contrl = { "pan",
"tilt",
"roll",
"zoom",
"exposure",
"iris",
"focus" };
IAMCameraControl* pCamCtl = 0;
HRESULT hr = pbf->QueryInterface(IID_IAMCameraControl, (void**)&pCamCtl);
if (FAILED(hr))
return;
std::cout << "Camera Controls" << std::endl;
std::cout << std::endl;
for (int camctl_num = 0; camctl_num < 7; camctl_num++)
{
long Min, Max, Step, Default, Flags, AutoFlags, Val;
hr = pCamCtl->GetRange(camctl_num, &Min, &Max, &Step, &Default, &AutoFlags);
if (SUCCEEDED(hr))
{
hr = pCamCtl->Get(camctl_num, &Val, &Flags);
}
if (SUCCEEDED(hr))
{
std::string str_proc = "";
if (AutoFlags == 3)
{
int auto_val = Flags;
if (Flags == 2)
{
auto_val = 0;
}
str_proc = "";
for (int i = int(proc_contrl[camctl_num].size()) + 10; i < 22; i++)
{
str_proc += " ";
}
std::cout << "\t" << str_proc << proc_contrl[camctl_num] << "_automatic (bool)"
<< "\t: default=" << 1 << " value=" << auto_val << std::endl;
}
str_proc = "";
for (int i = int(proc_contrl[camctl_num].size()); i < 22; i++)
{
str_proc += " ";
}
std::cout << "\t" << str_proc << proc_contrl[camctl_num] << " (int) \t: min=" << Min << " max=" << Max << " step=" << Step << " default=" << Default << " value=" << Val << std::endl;
}
}
std::cout << std::endl;
}
//
void ClsDirectShow::get_config(IBaseFilter* pbf)
{
HRESULT a;
ICaptureGraphBuilder2* pCapture = NULL;
a=CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC, IID_ICaptureGraphBuilder2, (void**)&pCapture);
IAMStreamConfig* pConfig = NULL;
HRESULT hr = pCapture->FindInterface(&PIN_CATEGORY_CAPTURE, 0, pbf, IID_IAMStreamConfig, (void**)&pConfig);
int iCount = 0;
int iSize = 0;
hr = pConfig->GetNumberOfCapabilities(&iCount, &iSize);
std::vector<video_formats> format_list;
video_formats ret;
if (iSize == sizeof(VIDEO_STREAM_CONFIG_CAPS))
{
for (int iFormat = 0; iFormat < iCount; iFormat++)
{
VIDEO_STREAM_CONFIG_CAPS scc;
AM_MEDIA_TYPE* pmtConfig;
hr = pConfig->GetStreamCaps(iFormat, &pmtConfig, (BYTE*)&scc);
VIDEOINFOHEADER* pVih2;
if ((SUCCEEDED(hr) && pmtConfig->majortype == MEDIATYPE_Video) && (pmtConfig->formattype == FORMAT_VideoInfo) && (pmtConfig->cbFormat >= sizeof(VIDEOINFOHEADER)) && (pmtConfig->pbFormat != NULL))
{
pVih2 = (VIDEOINFOHEADER*)pmtConfig->pbFormat;
ret = get_format_type(pVih2);
if (ret.flag)
{
format_list.push_back(ret);
}
}
}
}
pConfig->Release();
pCapture->Release();
if (int(format_list.size() > 0))
{
std::string type = "";
int type_cnt = 0;
for (int i = 0; i < int(format_list.size()); i++)
{
if (i == 0)
{
std::cout << "ioctl: VIDIOC_ENUM_FMT" << std::endl;
std::cout << " Type: Video Capture" << std::endl;
std::cout << std::endl;
}
if (type != format_list[i].type)
{
type = format_list[i].type;
std::cout << " [" << type_cnt << "]: '" << type << "' (" << type << ")" << std::endl;
type_cnt++;
}
std::cout << " Size: Discrete " << format_list[i].width << "x" << format_list[i].height << std::endl;
std::cout << std::fixed;
std::cout << " Interval: Discrete " << std::setprecision(3) << 1 / format_list[i].fps << "s (" << std::setprecision(3) << format_list[i].fps << " fps)" << std::endl;
}
std::cout << std::endl;
}
}
video_formats ClsDirectShow::get_format_type(VIDEOINFOHEADER* video)
{
double ns = 100 * 1.0e-9;
double frame = 1 / (double(video->AvgTimePerFrame) * ns);
int width = video->bmiHeader.biWidth;
int height = video->bmiHeader.biHeight;
std::string format;
std::vector<std::string> format_types;
std::string line;
std::string file_path;
video_formats v_formats;
char Path[MAX_PATH + 1];
char drive[MAX_PATH + 1], dir[MAX_PATH + 1], fname[MAX_PATH + 1], ext[MAX_PATH + 1];
GetModuleFileName(NULL, Path, MAX_PATH);
_splitpath_s(Path, drive, dir, fname, ext); // パス名を構成要素に分解します
// printf("完全パス : %s\n", Path);
// printf("ドライブ : %s\n", drive);
// printf("ディレクトリ パス : %s\n", dir);
// printf("ベース ファイル名 (拡張子なし) : %s\n", fname);
// printf("ファイル名の拡張子 : %s\n", ext);
file_path = std::string(dir) + "\\format_types.txt";
std::replace(file_path.begin(), file_path.end(), '\\', '/');
std::ifstream input_file(file_path.c_str());
while (getline(input_file, line))
{
format_types.push_back(std::string(line));
}
input_file.close();
for (int i = 0; i < (int)format_types.size(); i++)
{
char c1 = (byte)format_types[i][0];
char c2 = (byte)format_types[i][1];
char c3 = (byte)format_types[i][2];
char c4 = (byte)format_types[i][3];
if (video->bmiHeader.biCompression == MAKEFOURCC(c1, c2, c3, c4))
{
format = format_types[i];
break;
}
}
v_formats.width = width;
v_formats.height = height;
v_formats.fps = frame;
v_formats.flag = true;
if (format != "")
{
v_formats.type = format;
}
else
{
v_formats.type = std::to_string(video->bmiHeader.biCompression);
}
return v_formats;
}
実行ファイル
#include <iostream>
#include "ClsDirectShow.h"
int main()
{
//デバイス列挙情報の取得
IEnumMoniker* pEnumMoniker = ClsDirectShow::video_init(NULL);
//デバイスリストをコンソールに表示する
ClsDirectShow::get_devices_list(pEnumMoniker);
//カメラのプロパティを取得しコンソールに表示する
ClsDirectShow::get_camera_settings(0, pEnumMoniker);
//使用可能なビデオフォーマットをコンソールに表示する
ClsDirectShow::get_videoformats_list(0, pEnumMoniker);
}
実行結果
4.MediaFoundationによる画像の取得→OpenCV Matへの変換
MediaFoundationは画像データの取得ができます。また画像サイズやビデオ形式の変更も対応できます。
以下コードでは画像データを取得し、OpenCVのMatオブジェクトへの変換をしています。
int main()
{
HRESULT hr = CoInitialize(NULL);
//Microsoft Media Foundation の初期化
MFStartup(MF_VERSION);
IMFSourceReader* reader;
//デバイスの取得
IMFMediaSource* source;
std::wstring cameraName = L"ELECOM 5MP Webcam";
if (SUCCEEDED(CreateVideoCaptureDevice(cameraName, &source))) {
std::cout << "Succeed creating source." << std::endl;
}
//キャプチャの作成
CreateVideoCaptureDevice(&source);
IMFMediaType* type = NULL;
//ビデオタイプの取得
GetCurrentType(source, &type);
//ピクセルサイズの取得
UINT32 uwidth= 0;
UINT32 uheight = 0;
GetFrameSize(type, &uwidth, &uheight);
//ビデオフォーマットの取得
VideoFormatType format;
GetVideoFormat(type, &format);
//ビデオフォーマットを"NV12"に設定する
PROPVARIANT val2;
PropVariantInit(&val2);
GUID guid = MFVideoFormat_NV12;
val2.vt = VT_CLSID;
val2.puuid = &guid;
hr = type->SetItem(MF_MT_SUBTYPE, val2);
if (FAILED(hr)) {
std::cout << "Error setting item." << std::endl;
return 0;
}
//ソースリーダーを作成する
hr = CreateSourceReader(source, &reader);
if (SUCCEEDED(hr))
std::cout << "Succeed creating reader." << std::endl;
//静止画の画像データを取得する
auto data = Capture(reader);
//OpenCV Matデータへ変換する NV12の場合
int width = uwidth;
int height = uheight;
cv::Mat bitmap = cv::Mat(height, width, CV_8UC3);
tbb::parallel_for(tbb::blocked_range<int>(0, height),
[&](tbb::blocked_range<int> r) {
for (int i = r.begin(); i < r.end(); ++i)
{
uchar* src_y = (uchar*)&data[i * width];
uchar* src_u = (uchar*)&data[width * height + i / 2 * width];
uchar* src_v = (uchar*)&data[width * height + i / 2 * width + 1];
uchar* dst = bitmap.ptr(i);
for (int j = 0; j < width; j++)
{
//R
dst[2] = std::min(std::max((CONVERT_R(src_y[0], src_v[0])), 0), 255);
//G
dst[1] = std::min(std::max(CONVERT_G(src_y[0], src_u[0], src_v[0]), 0), 255);
//B
dst[0] = std::min(std::max(CONVERT_B(src_y[0], src_u[0]), 0), 255);
dst = dst + 3;
src_y = src_y + 1;
if (j % 2 == 1) {
src_v = src_v + 2;
src_u = src_u + 2;
}
}
}
});
cv::imwrite("test.bmp", bitmap);
reader->Release();
MFShutdown();
CoUninitialize();
return 0;
}
使用した関数
//デバイス名からキャプチャーデバイスを取得する
HRESULT CreateVideoCaptureDevice(std::wstring& deviceName, IMFMediaSource** ppSource)
{
*ppSource = NULL;
UINT32 count = 0;
IMFActivate** ppDevices = NULL;
IMFAttributes* pConfig = NULL;
// Create an attribute store to hold the search criteria.
HRESULT hr = MFCreateAttributes(&pConfig, 1);
// Set the device type to video.
if (SUCCEEDED(hr))
{
hr = pConfig->SetGUID(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID
);
}
// Enumerate the devices,
if (SUCCEEDED(hr))
{
hr = MFEnumDeviceSources(pConfig, &ppDevices, &count);
}
if (SUCCEEDED(hr))
{
if (count > 0)
{
for (int i = 0; i < count; i++) {
wchar_t* buffer;
uint32_t length;
hr = ppDevices[i]->GetAllocatedString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &buffer, &length);
if (SUCCEEDED(hr)) {
if (buffer != NULL) {
if (buffer == deviceName) {
hr = ppDevices[i]->ActivateObject(IID_PPV_ARGS(ppSource));
std::cout << "DeviceName: ";
char deviceName[1000];
size_t origsize = wcslen(buffer) + 1;
size_t strLength = 10;
const wchar_t* test=L"test";
errno_t err=wcstombs_s(&strLength, deviceName, origsize, buffer, 1000);
std::cout << deviceName << std::endl;
break;
}
}
}
}
}
else
{
//hr = MF_E_NOT_FOUND;
}
}
std::cout << "kokomade" << std::endl;
for (DWORD i = 0; i < count; i++)
{
ppDevices[i]->Release();
}
CoTaskMemFree(ppDevices);
return hr;
}
//ソースからビデオフォーマットタイプを取得する
static HRESULT GetCurrentType(IMFMediaSource* source, IMFMediaType** type)
{
HRESULT hr;
BOOL selected;
IMFPresentationDescriptor* presDesc = NULL;
IMFStreamDescriptor* strmDesc = NULL;
IMFMediaTypeHandler* handler = NULL;
*type = NULL;
hr = source->CreatePresentationDescriptor(&presDesc);
if (FAILED(hr)) goto done;
hr = presDesc->GetStreamDescriptorByIndex(0, &selected, &strmDesc);
if (FAILED(hr)) goto done;
hr = strmDesc->GetMediaTypeHandler(&handler);
if (FAILED(hr)) goto done;
hr = handler->GetCurrentMediaType(type);
if (FAILED(hr)) goto done;
done:
SafeRelease(&presDesc);
SafeRelease(&strmDesc);
SafeRelease(&handler);
return hr;
}
//ビデオフォーマットタイプから画像サイズ(幅、高さ)を取得する
static HRESULT GetFrameSize(IMFMediaType* type,UINT32* width, UINT32* height)
{
HRESULT hr;
PROPVARIANT var;
*width = 0;
*height = 0;
PropVariantInit(&var);
hr = type->GetItem(MF_MT_FRAME_SIZE, &var);
if (FAILED(hr)) return hr;
Unpack2UINT32AsUINT64(var.uhVal.QuadPart, width, height);
PROPVARIANT var1;
PropVariantInit(&var1);
hr = type->GetItem(MF_MT_DEFAULT_STRIDE, &var1);
INT32* stride = NULL;
return hr;
}
//ソースからリーダーオブジェクトを作成する
HRESULT CreateSourceReader(IMFMediaSource* source, IMFSourceReader** reader)
{
IMFSourceReader* p;
HRESULT hr;
*reader = NULL;
hr = MFCreateSourceReaderFromMediaSource(source, NULL, &p);
if (FAILED(hr)) {
printf("Failed create IMFSourceReader object\n");
return hr;
}
*reader = p;
return S_OK;
}
//画像データ取得しをRGB形式のバイト列に変換する
std::vector<BYTE> Capture(IMFSourceReader* reader)
{
DWORD flags;
IMFSample* sample = NULL;
DWORD streamIndex = 0;
LONGLONG timeStamp = 0;
REFPROPVARIANT varPosition{};
HRESULT hr;
while (true)
{
hr = reader->ReadSample((DWORD)MF_SOURCE_READER_ANY_STREAM, 0, &streamIndex, &flags, &timeStamp, &sample);
if (FAILED(hr)) {
std::cout << "Failed reading sample." << std::endl;
return std::vector<BYTE>();
}
else {
std::cout << "Succeed reading sample." << std::endl;
}
if (sample == nullptr) {
std::cout << "Sample is nullptr" << std::endl;
//return std::vector<BYTE>();
}
else {
std::cout << "Sample isnot nullptr" << std::endl;
break;
}
}
IMFMediaBuffer* buffer;
hr = sample->ConvertToContiguousBuffer(&buffer);
if (SUCCEEDED(hr))
std::cout << "Succeed getting buffers" << std::endl;
BYTE* p;
DWORD size;
buffer->Lock(&p, NULL, &size);
std::vector<BYTE> data(size);
memcpy(data.data(), p, size);
buffer->Unlock();
buffer->Release();
sample->Release();
return data;
}