フレームフィルタの記述: 二値化処理

ここではライブ画像に2値化処理を実行するフレームフィルタの作成方法について説明します。
以下の処理の手順について紹介します。

このサンプルはIC Imaging Controlインストールディレクトリの%TOPLEVEL%\samples\VCx\Binarizationにございます。VC++2010バージョンでは%TOPLEVEL%\Samples\VC10となります。

フレームフィルタの作成

フレームフィルタの実装は全てFrameFilterImplクラスライブラリリファレンス>クラス>FrameFilterImplもしくはFrameUpdateFilterImplクラスライブラリリファレンス>クラス>FrameUpdateFilterImplより派生する必要があります。二値化はイメージの全てのピクセルを変更するため、updateフィルタではなくtransformフィルタを実装したほうがいいでしょう。FrameFilterImplは派生したクラス名をテンプレートパラメータとするクラステンプレートです。

class CBinarizationFilter
  : public DShowLib::FrameFilterImpl<CBinarizationFilter>
{
public:
  CBinarizationFilter();

FrameFilterImplより派生したクラスはすべてFilterInfoクラスライブラリリファレンス>構造体>FlterInfo Structure構造体を返す静的メソッド getStaticFilterInfo を持つ必要があります。FilterInfoクラスライブラリリファレンス>構造体>FlterInfo Structure構造体はフィルタについての情報を含んでいます。

public:
  // FrameFilterImplの実装
  static DShowLib::FilterInfo getStaticFilterInfo();

フレームフィルタを生成する為には、メソッド getSupportedInputTypesクラスライブラリリファレンス>クラス>FrameFilterImpl>FrameFilterImpl::getSupportedInputTypes Method, getTransformOutputTypesクラスライブラリリファレンス>クラス>FrameFilterImpl>FrameFilterImpl::getTransformOutputTypes Method , transformクラスライブラリリファレンス>クラス>FrameFilterImpl>FrameFilterImpl::transform Method を挿入する必要があります。

public:
  // IFrameFilter の実装
  virtual    void getSupportedInputTypes( DShowLib::FrameTypeInfoArray& arr ) const;
  virtual    bool getTransformOutputTypes( const DShowLib::FrameTypeInfo& in_type,
                      DShowLib::FrameTypeInfoArray& out_types ) const;
  virtual    bool transform( const DShowLib::IFrame& src, DShowLib::IFrame& dest );

2つの変数で二値化プロセスを制御します。これら変数を変更すのに2種類の方法が用意されています。

public:
  // 二値化の有効化または無効化を行う
  void    enable( bool bEnable );
  // 二値化の際のしきい値を設定する
  void    setThreshold( int th );

private:
  bool    m_bEnabled;
  int        m_threshold;
};

フレームフィルタの実装

CBinarizationFilterのコンストラクタにおいてメンバ変数はデフォルト値に初期化されます。

CBinarizationFilter::CBinarizationFilter()     
  :    m_bEnabled( false ),
    m_threshold( 127 )
{
}

getStaticFilterInfoの実装でフィルタ名がFilterInfoクラスライブラリリファレンス>構造体>FlterInfo Structure構造体に入っていきます。 フィルタはフィルタインスペクタプログラマーズガイド>フィルタインスペクタのようなジェネリックアプリケーションで使用することはできないため、tFilterClassクラスライブラリリファレンス>エニュメレーション>tFilterClassfilterClassメンバはeFC_INTERNALに設定する必要があります。

pFilterInfo CBinarizationFilter::getStaticFilterInfo()
{
  // フィルタ名を返しそのフィルタをeFC_INTERNALとして宣言
  FilterInfo fi = { L"Binarization", L"", eFC_INTERNAL };
  return fi;
}

getSupportedInputTypesメソッドにおいては、FrameTypeInfoArrayクラスライブラリリファレンス>クラス>FrameTypeInfoArrayにフレームフィルタがインプットとして受け付けるフレームタイプが入っていきます。シンプルな形にするためにeRGB8eY800の2つの8ビットフォーマットのみ使用します。デバイスのビデオフォーマットがこれと違う場合、画像データはフレームフィルタに渡される前に自動的に変換されます。

void CBinarizationFilter::getSupportedInputTypes( DShowLib::FrameTypeInfoArray& arr ) const
{
  // このフィルタは8ビットのグレースケールイメージにのみ対応
  arr.push_back( eRGB8 );
  arr.push_back( eY800 );
}

getTransformOutputTypesメソッドはFrameTypeInfoArrayクラスライブラリリファレンス>クラス>FrameTypeInfoArray out_typesにフレームタイプを入れていきます。このフレームタイプは指定された入力フレームタイプよりフィルタが生成できるものです。二値化フィルタはフレームタイプのサイズを変更しないため、配列には入力タイプのみを挿入します。

bool CBinarizationFilter::getTransformOutputTypes( const DShowLib::FrameTypeInfo& in_type,
                              DShowLib::FrameTypeInfoArray& out_types ) const
{
  // フレームタイプのサイズの変更はないため、 output = input
  out_types.push_back( in_type );
  return true;
}

transformメソッドはフレームごとにコールされます。これはその時の設定に応じて二値化を行う役割を果たします。

transform メソッドはユーザーインターフェースとは別のスレッドで実行されるため、m_bEnabledm_thresholdのメンバ変数へのアクセス においてアプリケーションプログラムによる並行処理が起こらないようにしておく必要があります。

最初に IFrameFilter::beginParamTransferクラスライブラリリファレンス>クラス>IFrameFilter>IFrameFilter::beginParamTransfer Method がコールされます。内部的にはこのメソッドがクリティカルセクションに入っていきます。パラメータを設定したい場合にはbeginParamTransferもコールする必要があります。
IFrameFilter::endParamTransferクラスライブラリリファレンス>クラス>IFrameFilter>IFrameFilter::endParamTransfer Method がクリティカルセクションを出ることになります。メンバ変数の値はローカルスタック変数にコピーされ、それらの値は変換の過程において定数のままでいることになります。

2値化自体はそれほど複雑な処理ではありません: 各ピクセルのグレー値をしきい値と比較していきます。もしそのグレー値が閾値よりも大きければそのピクセルの値を最大グレー値に設定し、逆であればゼロを設定します。

bool CBinarizationFilter::transform( const DShowLib::IFrame& src, DShowLib::IFrame& dest )
{
  // デスティネーションフレームが利用可能かのチェック
  if( dest.getPtr() == 0 ) return false;       

  BYTE* pIn = src.getPtr();
  BYTE* pOut = dest.getPtr();

  // メンバ変数を関数のスタックにコピーしsetThreshold()などへのコールによる
  // 並列処理での上書きを防ぐ
  //
  // beginParamTransfer/endParamTransferが各メンバ変数からの値に矛盾が
  // ないことを確認。※ユーザーはbeginParamTransfer/endParamTransferにも
  // ライティングパラメータへのアクセスを入れる必要があるため
  beginParamTransfer();
  bool enabled = m_bEnabled;
  int threshold = m_threshold;
  endParamTransfer();       

  // 二値化が有効かどうかをチェック
  if( m_bEnabled )
  {
    // 入力バッファ内の各バイトに対してしきい値以上かどうかをチェック
    int bufferSize = src.getFrameType().buffersize;
    while( bufferSize-- > 0 )
    {
      if( *pIn++ >= threshold )
      {
        *pOut++ = 255;
      }
      else
      {
        *pOut++ = 0;
      }
    }
  }
  else
  {
    // 二値化は無効: 画像データをそのままコピー
    memcpy( pOut, pIn, src.getFrameType().buffersize );
  }
  return true;
}

フレームフィルタを使う

CBinarizationFilterのインスタンスを作成し、ダイアログクラスm_binarizationFilterのメンバ変数とします。
Grabberクラスライブラリリファレンス>クラス>Grabber オブジェクトが初期化されると、Grabber::setDeviceFrameFiltersクラスライブラリリファレンス>クラス>Grabber>Grabber::setDeviceFrameFilters Method がフレームフィルタ設定のためにコールされます。

// ライブビデオのアウトプットウィンドウを設定
m_Grabber.setHWND( GetDlgItem( IDC_DISPLAY )->GetSafeHwnd() );

// オーバーレイを無効化する
m_Grabber.setOverlayBitmapPathPosition( ePP_NONE );

// デバイスのフレームフィルタとして二値化フィルタを設定
m_Grabber.setDeviceFrameFilters( &m_binarizationFilter );

// デバイス設定ダイアログを表示
m_Grabber.showDevicePage( GetSafeHwnd() );

// デバイスの選択後ライブモードを開始する
if( m_Grabber.isDevValid() )
{
  m_Grabber.startLive();
}

ライブモードが開始されると、デバイスから送られて来る全てのイメージがフレームフィルタによって変換されます。

フレームフィルタパラメータへのアクセス

二値化の有効/無効化を切り替えるチェックボックスのイベントハンドラではCBinarizationFilter::enableがコールされます。
フィルタパラメータの調整を行うメソッドへのコールはIFrameFilter::beginParamTransferクラスライブラリリファレンス>クラス>IFrameFilter>IFrameFilter::beginParamTransfer MethodIFrameFilter::endParamTransferクラスライブラリリファレンス>クラス>IFrameFilter>IFrameFilter::endParamTransfer Method の間に挟む必要があります。

void CBinarizationDlg::OnBnClickedBinarizeEnable()
{
  // 現在のチェック状態を読み取る
  bool bEnable = m_BinarizeEnable.GetCheck() == BST_CHECKED;       

  // フレームフィルタパラメータへのアクセスをbeginParamTransferとendParamTransferの間に入れる。
  m_binarizationFilter.beginParamTransfer();
  m_binarizationFilter.enable( bEnable );
  m_binarizationFilter.endParamTransfer();       

  // しきい値スライダの利用状況を設定
  m_BinarizeThreshold.EnableWindow( bEnable );
  m_BinarizeThresholdStatic.EnableWindow( bEnable );
}

しきい値スライダのイベントハンドラではCBinarizationFilter::setThresholdがコールされます。
このコールも同様にIFrameFilter::beginParamTransferクラスライブラリリファレンス>クラス>IFrameFilter>IFrameFilter::beginParamTransfer MethodIFrameFilter::endParamTransferクラスライブラリリファレンス>クラス>IFrameFilter>IFrameFilter::endParamTransfer Methodの間に挟む必要があります。

void CBinarizationDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
  // 現在のスライダの位置を読み取る
  int th = m_BinarizeThreshold.GetPos();       

  // フレームフィルタパラメータへのアクセスをbeginParamTransferとendParamTransferの間に入れる。
  m_binarizationFilter.beginParamTransfer();
  m_binarizationFilter.setThreshold( th );
  m_binarizationFilter.endParamTransfer();       

  // しきい値を静的ウィンドウ内に表示させる
  char buf[20];
  m_BinarizeThresholdStatic.SetWindowText( itoa( th, buf, 10 ) );       

  CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
}