フィルタの実装
プロジェクトの作成
Visual Studioを起動し、これまで作成してきたソリューションファイル「WM_Camera.sln」を読み込んでください。次に「mono_filter」という名前でプロジェクトを新規作成し、ソリューションへ追加してください。このときプロジェクトの種類を「Win32スマートデバイスプロジェクト」、アプリケーションの種類を「DLL」とします。
フィルタ情報を定義する
作成するフィルタ情報を「mono_filter.h」へ定義していきます。フィルタクラスのクラスIDをCLSID_MonoFilter
として定義します。
#pragma once #define TEMPLATE_NAME (L"Mono_Filter") #define FILTER_NAME (TEMPLATE_NAME) // {A2A38415-10AC-4bdc-8403-5FA8E59D8A84} const GUID CLSID_MonoFilter= {0xa2a38415, 0x10ac, 0x4bdc, 0x84, 0x3, 0x5f, 0xa8, 0xe5, 0x9d, 0x8a, 0x84};
フィルタのピン情報を定義します。トランスフォームフィルタでは、入力ピンと出力ピンの2種類のピン情報を定義する必要があります。
// メディアタイプの定義 const AMOVIESETUP_MEDIATYPE sudPinTypes = { &MEDIATYPE_Video, &MEDIASUBTYPE_RGB565 }; // 入力、出力ピンの情報 const AMOVIESETUP_PIN sudPins[] = { { L"", FALSE, FALSE, // 入力ピンなので FALSE とすること FALSE, FALSE, &CLSID_NULL, 0, 1, &sudPinTypes }, { L"", FALSE, TRUE, // 出力ピンなので TRUE とすること FALSE, FALSE, &CLSID_NULL, 0, 1, &sudPinTypes }, };
AMOVIESETUP_FILTER
構造体を使ってフィルタ情報を定義します。入力ピンと出力ピン、合わせて2つのピンがあるので、ピン数を2とすることに注意してください。
// フィルタ情報 const AMOVIESETUP_FILTER afFilterInfo= { &CLSID_MonoFilter,// フィルタのCLSID FILTER_NAME, // フィルタ名 MERIT_DO_NOT_USE, // メリット値 2, // ピン数 sudPins // ピン情報 };
フィルタクラスの作成
トランスフォームフィルタを作成するには、DirectShowクラスライブラリのCTransformFilter
クラスを使うと便利です。今回は、それを派生させてCMonoFilter
クラスを作成します。クラス図を図1に示します。
CTransformFilter
で定義されている純粋仮想メソッドをCMonoFilter
に実装していきます。加えて、ピンの接続完了時に呼び出されるCompleteConnect
を実装します。
メンバ変数としては、クリティカルセクションと画像フォーマットを記憶するためのVIDEOINFOHEADER
構造体を宣言します。
class CMonoFilter : public CTransformFilter { VIDEOINFOHEADER m_Vih; public: CMonoFilter(LPUNKNOWN pUnk,HRESULT *phr); virtual ~CMonoFilter(); static CUnknown * WINAPI CreateInstance(LPUNKNOWN pUnk, HRESULT *phr); // CTransformFilter HRESULT CheckInputType(const CMediaType *mtIn); HRESULT GetMediaType(int iPosition,CMediaType *pMediaType); HRESULT CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut); HRESULT CompleteConnect(PIN_DIRECTION direction,IPin *pReceivePin); HRESULT DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *ppropInputRequest); HRESULT Transform(IMediaSample *pIn,IMediaSample *pOut); };
コンストラクタ、デストラクタ、CreateInstance
の実装については本記事の添付ファイルを参照して頂くとして、CheckInputType
から見ていきます。
CheckInputType
は、上位フィルタが入力ピンに接続しようとしたときに、メディアタイプを判別して許可するかどうかを判定します。
本フィルタではメディアタイプがMEDIATYPE_Video
、サブメディアタイプがMEDIASUBTYPE_RGB565
のみ許可します。
HRESULT CMonoFilter::CheckInputType(const CMediaType *mtIn) { OutputDebugString(TEXT("CMonoFilter::CheckInputType\n")); if(*mtIn->FormatType()!=FORMAT_VideoInfo) { return VFW_E_TYPE_NOT_ACCEPTED; } if(*mtIn->Type()!=MEDIATYPE_Video || *mtIn->Subtype()!=MEDIASUBTYPE_RGB565) { return VFW_E_TYPE_NOT_ACCEPTED; } return S_OK; }
次にGetMediaType
を実装します。このメソッドは、出力ピンがサポートするメディアタイプを1つ返します。引数にインデックス値が指定されるので、それに対応するメディアタイプを返します。
本フィルタでは、入力ピンが受けつけたメディアタイプだけを返すように実装します。つまり出力ピンから送出されるメディアタイプが入力ピンと同じになります。またインデックス値は0のみ許容します。
HRESULT CMonoFilter::GetMediaType( int iPosition,CMediaType *pMediaType) { OutputDebugString(TEXT("CMonoFilter::GetMediaType\n")); if(iPosition<0) { return E_INVALIDARG; }else if(iPosition>0) { return VFW_S_NO_MORE_ITEMS; } *pMediaType=m_pInput->CurrentMediaType(); return S_OK; }
CTransformFilter::m_pInput
というメンバ変数で表されます。出力ピンは
CTransformFilter::m_pOutput
となります。 次にCheckTransform
を実装します。このメソッドは、引数に入力メディアタイプと出力メディアタイプを渡されるので、その変換をフィルタがサポートするか判定し、結果を返します。
本フィルタでは、入力メディアタイプと出力メディアタイプが等しい場合のみサポートします。
HRESULT CMonoFilter::CheckTransform( const CMediaType *mtIn,const CMediaType *mtOut) { OutputDebugString(TEXT("CMonoFilter::CheckTransform\n")); if(*mtIn->FormatType()!=FORMAT_VideoInfo) { return VFW_E_TYPE_NOT_ACCEPTED; } if(*mtIn->Type()!=MEDIATYPE_Video || *mtIn->Subtype()!=MEDIASUBTYPE_RGB565) { return VFW_E_TYPE_NOT_ACCEPTED; } if(*mtIn!=*mtOut) { return VFW_E_TYPE_NOT_ACCEPTED; } return S_OK; }
次にCompleteConnect
を実装します。このメソッドは、ピンの接続が完了したときに呼ばれます。
本フィルタでは、入力ピンの接続が完了したとき、入力ピンのメディアタイプを取得し、画像フォーマットが格納されているVIDEOINFOHEADER
構造体をメンバ変数m_Vih
にコピーします。
HRESULT CMonoFilter::CompleteConnect( PIN_DIRECTION direction,IPin *pReceivePin) { OutputDebugString(TEXT("CMonoFilter::CompleteConnect\n")); if(direction==PINDIR_INPUT) { CMediaType &type=m_pInput->CurrentMediaType(); CopyMemory(&m_Vih,type.Format(),sizeof(VIDEOINFOHEADER)); } return S_OK; }
次にDecideBufferSize
を実装します。このメソッドは、出力ピンから下位フィルタに送る画像データ用のバッファを確保します。バッファの数とサイズをALLOCATOR_PROPERTIES
構造体で指定し、IMemAllocator::SetProperties
を呼び出すことによりバッファの確保が行われます。
本フィルタではバッファ数を1、サイズを入力メディアタイプのサンプルサイズとしています。すなわち、画像1フレーム分のバッファです。
HRESULT CMonoFilter::DecideBufferSize( IMemAllocator *pAlloc,ALLOCATOR_PROPERTIES *ppropInputRequest) { OutputDebugString(TEXT("CMonoFilter::DecideBufferSize\n")); if(m_pInput!=NULL && m_pInput->IsConnected()==FALSE) { return E_FAIL; } ppropInputRequest->cBuffers=1; ppropInputRequest->cbBuffer =m_pInput->CurrentMediaType().GetSampleSize(); ALLOCATOR_PROPERTIES Actual; HRESULT hr=pAlloc->SetProperties(ppropInputRequest,&Actual); if(FAILED(hr)) { return hr; } // 確保されたかチェック if((ppropInputRequest->cBuffers>Actual.cBuffers) || (ppropInputRequest->cbBuffer>Actual.cbBuffer)) { return E_FAIL; } return S_OK; }
最後にTransform
を実装します。このメソッドは、入力サンプルを受け取り、何らかの処理をし、出力サンプルを書き込む処理を行います。
つまり、このフィルタの肝となるモノトーン加工をここで行います。また入力サンプルのタイム情報を出力サンプルにコピーします。
HRESULT CMonoFilter::Transform( IMediaSample *pIn,IMediaSample *pOut) { LPWORD pSrc,pDest; pIn->GetPointer((BYTE**)&pSrc); pOut->GetPointer((BYTE**)&pDest); const long acSize=pIn->GetActualDataLength(); for(LONG y=0;y<m_Vih.bmiHeader.biHeight;y++) { for(LONG x=0;x<m_Vih.bmiHeader.biWidth;x++) { // 各要素を6ビットとして取り出す WORD r=(*pSrc & 0xf800) >> 10; WORD g=(*pSrc & 0x7e0) >> 5; WORD b=(*pSrc & 0x1f) << 1; // 明度を求める WORD Yg =(r*299+g*587+b*114) >> 10; WORD Yrb=Yg>>1; // 書き込む *pDest=(Yrb<<11)+(Yg<<5)+(Yrb); pSrc++; pDest++; } } pOut->SetActualDataLength(acSize); pOut->SetSyncPoint(TRUE); // 入力サンプルのタイムを出力サンプルへコピー REFERENCE_TIME start, end; pIn->GetTime(&start, &end); pOut->SetTime(&start, &end); pIn->GetMediaTime(&start, &end); pOut->SetMediaTime(&start, &end); return S_OK; }
Y = 0.299*R + 0.587*G + 0.114*B
(Yは明度。R、G、B、Yの各範囲は0以上1以下)。
本フィルタでは、RとBを2倍してGとビット数を揃えた後、次式を適用します。
Y = 299*R + 587*G + 114*B
このとき、Yの最大値は62000、16進数では0xF230です。
RとBに対しては上位5ビット残すため11ビット、Gに対しては上位6ビットを残すため10ビット右シフトしたYを、出力画像のR、G、Bそれぞれの要素へ書き込むことにより、モノトーン加工された画像が得られます。