オーバーレイの作成

ライブ画像上に線やテキストなどのグラフィックエレメントを描画する方法について説明します。
ここでは以下の方法について説明します。

  • シンプルなテキストの描画
  • タイマーイベントを使用した現在時刻の表示
  • コールバック関数を使ったフレームカウンター
  • ライブ表示またはシンクへのオーバーレイの有効化

このサンプルは IC Imaging Controlインストールディレクトリの%TOPLEVEL%\samples\VCx\Creating an Overlayにあります。たとえばVC++2010 では%TOPLEVEL%\Samples\VC10となり、VC8では%TOPLEVEL%\Samples\VC8です。サンプルプログラムのclasslib Demoappより継承され、オーバーレイ機能を実装します。

OverlayBitmap オブジェクトを使用する

IC Imaging Control はライブ画像に図や文字を書き込める特殊なオブジェクトOverlayBitmapクラスライブラリリファレンス>クラス>OverlayBitmapを提供しています。
OverlayBitmapクラスライブラリリファレンス>クラス>OverlayBitmap はビデオフォーマット、もしくはフレームフィルタと同じサイズのbitmapを含んでいます。アプリケーションはテキストや図をこのoverlay bitmap上に描くことができ、それがビデオのの各フレームへコピーされます。ドロップアウトカラーを指定することでoverlay bitmapの特定のピクセルを透明にし、ライブ画像上で表示されないようにすることもできます。
overlay bitmapはビデオフォーマットの変更後、一度はGrabber::startLiveクラスライブラリリファレンス>クラス>Grabber>Grabber::startLive Method もしくは Grabber::prepareLiveクラスライブラリリファレンス>クラス>Grabber>Grabber::prepareLive Methodをコール済みの場合に使用可能となります。ライブ表示中のみ見えるようになっています。

オーバレイの有効、無効化(デバイスパス、シンクパス、ディスプレイパス)

IC Imaging Control version 3.0より、オーバーレイを描画する場所として、ライブ画像上、取り込み・録画された画像上、もしくはその両方というように選択可能となっています。
Grabber::setOverlayBitmapPathPositionクラスライブラリリファレンス>クラス>OverlayBitmap>Grabber::setOverlayBitmapPathPosition Method を使って tPathPositionクラスライブラリリファレンス>エニュメレーション>tPathPosition値 の論理和を設定し、OverlayBitmapクラスライブラリリファレンス>クラス>OverlayBitmapオブジェクトをどこのパスに挿入するかを指定することができます。

OverlayBitmap オブジェクトにアクセスする

OverlayBitmapのカラーモードの設定

オーバーレイのカラーモードはデフォルトでは入力される画像データのカラーフォーマットに従って自動的に選択されますが、グレースケールのライブ画像上にカラーの線やテキストを描画したい場合、OverlayBitmap::setColorModeクラスライブラリリファレンス>クラス>OverlayBitmap>OverlayBitmap::setColorMode Methodメソッドによってオーバーレイのカラーモードをカラーもしくはグレースケールに設定することができます。

シンプルなテキストの描画

まず最初に、シンプルなテキストを画面の左上のほうに表示させたいと思います。テキストはキャプチャされた画像と保存されたAVIファイルにおいても見ることができます。
アプリケーションにオーバーレイを描画させる前にデバイス、ビデオフォーマット、シンクタイプをセットアップしておく必要があります。次のコードはvoid CMainFrame::OnPreviewStart()メソッドよりコールされます。

void CMainFrame::drawOverlay( DShowLib::tPathPosition pos )
{
  smart_ptr<OverlayBitmap> ob =  m_Grabber.getOverlay(pos);

  ob->setEnable(true);
  ob->setFont("Courier",14);
   ob->drawText( RGB(255,0,0),  0, 0, "IC Imaging Control 3.2" );

  switch( pos )
  {
    case ePP_DEVICE:
       ob->drawText( RGB(0,255,0),  20, 20,  "Device Overlay Active" );
      return;
    case ePP_SINK:
       ob->drawText( RGB(0,255,0),  20, 40,  "Sink Overlay Active" );
      return;
    case ePP_DISPLAY:
       ob->drawText( RGB(0,255,0),  20, 60,  "Display Overlay Active" );
      return;
  }
}

まずオーバーレイを挿入したいパスのポジションをパラメータとしてGrabber::getOverlayクラスライブラリリファレンス>クラス>OverlayBitmap>Grabber::getOverlay MethodをコールすることでOverlayBitmapクラスライブラリリファレンス>クラス>OverlayBitmapへのポインターを取得することができます。
ob->setEnable(true);の部分はオーバーレイを有効化します。オーバーレイが有効でなかった場合、ビデオストリームには描画されません。
その後、テキストのフォント名とサイズが選択されます。

ob->drawText( RGB(255,0,0), 0, 0, "IC Imaging Control 3.2" );の一行はテキストの色、座標 (0,0)、そして表示される文字を表しています。 "IC Imaging Control 3.0"という赤色の文字がビデオストリームの左上に描画されるということです。drawTextメソッドは一度だけコールします。テキストは消去されるか上書きされるまでオーバーレイ上に残ります。

挿入するパスの位置によってどのオーバーレイビットマップが描画されるかをその後のテキストが説明しています。

時間を表示させる

次に、画面の右下に現在時刻を表示するようにしたいと思います。時間は白の長方形の枠内に表示され、タイマーは1000ミリ秒単位で設定できます。タイマーイベントはCMainFrame::OnTimer( UINT nIDEvent )メソッドによって扱われ、ここではCMainFrame::displayTimeメソッドが古い時間を最新の時間に上書きしていきます。

void CMainFrame::displayTime()
{
  CString szTimeString;    // 文字列に時間を格納
  CTime cTime = CTime::GetCurrentTime();
  SIZE  StringSize;        // 文字列の表示に必要な長方形のサイズ
  smart_ptr<DShowLib::OverlayBitmap> ob;
  RECT TimeRect;
  // ライブ画像表示中は時間の表示のみ有効
  if ( m_Grabber.isLive() )
  {
    ob = m_Grabber.getOverlay();

    // beginPaintをコールすることでOverlayBitmapクラスにDC(デバイスコンテキスト)を使った
    // GDI操作を行うことを伝える。
     ob->beginPaint();                  

    // ノンプロポーショナルcharsetを使用するフォントとして設定
    ob->setFont("Courier",14);           

    // 現在の時間を文字列にコピー
    szTimeString = cTime.Format("%H:%M:%S");           

    // 時間表示に使用されるオーバーレイのサイズをピクセルで取得
    GetTextExtentPoint32( m_Grabber.getOverlay()->getDC(),szTimeString,
                      szTimeString.GetLength(), &StringSize);

    // 表示時間枠となる長方形の作成
    TimeRect.left = m_Grabber.getAcqSizeMaxX()-StringSize.cx-2;
    TimeRect.top = m_Grabber.getAcqSizeMaxY()-StringSize.cy-2;
    TimeRect.right = TimeRect.left + StringSize.cx+1 ;
    TimeRect.bottom = TimeRect.top + StringSize.cy+1 ;           

    // 画面の右下に文字列の時間を描画
    ob->drawText(RGB(255,255,255), TimeRect.left + 1, TimeRect.top + 1, (LPCTSTR)szTimeString);           

    // 時間を囲む長方形の描画
    ob->drawLine( RGB(255,255,255),  TimeRect.left,  TimeRect.top, TimeRect.right,  TimeRect.top);
    ob->drawLine( RGB(255,255,255), TimeRect.right,  TimeRect.top, TimeRect.right,  TimeRect.bottom);
    ob->drawLine( RGB(255,255,255), TimeRect.right,  TimeRect.bottom, TimeRect.left,  TimeRect.bottom);
    ob->drawLine( RGB(255,255,255),  TimeRect.left,  TimeRect.top, TimeRect.left,  TimeRect.bottom);           

    // endPaintをコールすることでDCを解放、OverlayBitmapクラスに描画が完了したことを伝える
    ob->endPaint();  
  }
}

ライブ画像表示中であればそこに時間を表示させるのは当然です。よってif( m_Grabber.isLive() )ステートメントが使用されます。時間の表示には"Courier"というフォントが使用されます。"Courier"はノンプロポーショナルのフォントで、このフォントの文字は全て同じサイズとなります。これによって表示する時間の文字列が前に表示されていたものより短い場合でも文字化けを防ぐことができます。時間はMFC CTimeオブジェクトで取得され、CString szTimeStringオブジェクトにフォーマットされます。GetTextExtentPoint32関数は時間文字列の幅と高さを算出します。時間を右下に表示させるために、前に計算された幅と高さの値はライブ画像の幅と高さの値から引き算されます。

アプリケーションはOverlayBitmapのデバイスコンテキスト(DC)が必要とするのでメソッドOverlayBitmap::beginPaintクラスライブラリリファレンス>クラス>OverlayBitmap>OverlayBitmap::beginPaint Method をコールしなければいけません。 OverlayBitmap::beginPaintクラスライブラリリファレンス>クラス>OverlayBitmap>OverlayBitmap::beginPaint Method がコールされていない場合、メソッド OverlayBitmap::getDCクラスライブラリリファレンス>クラス>OverlayBitmap>OverlayBitmap::getDC Method は0を返します。

GDIの最初でOverlayBitmap::beginPaintクラスライブラリリファレンス>クラス>OverlayBitmap>OverlayBitmap::beginPaint Method メソッドがコールされた場合、GDIの終わりでOverlayBitmap::endPaintクラスライブラリリファレンス>クラス>OverlayBitmap>OverlayBitmap::endPaint Methodメソッドがコールされなければいけません。

もしアプリケーションがOverlayBitmapクラスライブラリリファレンス>クラス>OverlayBitmapのデバイスコンテキスト(DC)ではなくdrawTextやdrawLineのような標準の描画メソッドのみを使用する場合、 OverlayBitmap::beginPaintクラスライブラリリファレンス>クラス>OverlayBitmap>OverlayBitmap::beginPaint MethodOverlayBitmap::endPaintクラスライブラリリファレンス>クラス>OverlayBitmap>OverlayBitmap::endPaint Methodは必要ありません。

注: もしこのコードを他のアプリケーションにコピーする際にはオーバーレイが有効であることを確認してください。そうでない場合には時間は表示されません。

オーバーレイコールバック関数を使用する

OverlayBitmapオブジェクトはイメージストリームの全フレーム対してコールバックを提供します。このコールバックによってアプリケーションはオーバーレイにフレームのカウントなどの特定のデータを書き込むことができるようになります。下記のサンプルコードではイメージストリームにシンプルなフレームカウンターを実装する方法を紹介しています。

まず最初に、コールバック用のハンドラを実装する必要がございます。これはオーバーレイアップデートイベント用のコールバックメソッドを実装する新クラスを作成することで行います。この新クラスはコールバックを実装する全クラスの基底クラスであるGrabberListenerクラスライブラリリファレンス>クラス>GrabberListenerより継承されます。新クラスは"COverlayCallback"と呼ばれる、CMainFrameクラスのメンバです。COverlayCallback のインスタンスはGrabberには新しい"listener"として登録される必要があります。これはGrabberのインスタンス化された後にCMainframeのコンストラクタ内で行われます。

m_Grabber.addListener(&m_cOverlayCallback, DShowLib::GrabberListener::eOVERLAYCALLBACK);

パラメータDShowLib::GrabberListener::eOVERLAYCALLBACKによってGrabberはoverlayCallbackメソッドだけをコールするようになります。COverlayCallbackクラスの宣言は以下の通り行われます。

class COverlayCallback : public GrabberListener
{
public:
  COverlayCallback();
  virtual ~COverlayCallback();
  virtual void overlayCallback( Grabber& param, smart_ptr<OverlayBitmap> pBitmap, const tsMediaSampleDesc& );

private:
  int    m_displayCallCount;
};

オーバーレイがフレームに描画される前にGrabberがCOverlayCallback オブジェクトのメソッド
GrabberListener::overlayCallbackクラスライブラリリファレンス>クラス>GrabberListener>GrabberListener::overlayCallback Method をコールします。描画作業はこのメソッド内で行われます。コールバックメソッドは次の方法で実装されます。

void COverlayCallback::overlayCallback
       ( Grabber& param, smart_ptr<OverlayBitmap> pBitmap, const tsMediaSampleDesc &MediaSample )
{
  CString szText;

  switch( pBitmap->getPathPosition() )
  {
  case ePP_DEVICE:
    szText.Format( TEXT("Device: Frame %d   "), MediaSample.FrameNumber );
    pBitmap->drawText(RGB(255,255,0), 20, 100, (LPCTSTR)szText );
    break;
  case ePP_SINK:
    szText.Format( TEXT("Sink: Frame %d   "), MediaSample.FrameNumber );
    pBitmap->drawText(RGB(255,255,0), 20, 120, (LPCTSTR)szText );
    break;
  case ePP_DISPLAY:
    szText.Format( TEXT("Display: No Frame Number Available, called %d times"), m_displayCallCount++ );
    pBitmap->drawText(RGB(255,255,0), 20, 140, (LPCTSTR)szText );
    break;
  }
}

コールバックが実行されたオーバーレイビットマップのパス位置を取得したい場合はOverlayBitmap::getPathPositionクラスライブラリリファレンス>クラス>OverlayBitmap>OverlayBitmap::getPathPosition Methodをコールします。
このサンプルでは、現在のフレーム数を表すテキストが表示される。ディスプレイパスのオーバーレイビットマップはフレーム数へのアクセスを持っていない為、メンバ変数m_displayCallCountを使ってコール数をカウントする。

ドロップアウトカラーについて

OverlayBitmapクラスはドロップアウトカラーとして設定した色をビデオストリーム上で透明にします。ドロップアウトカラーを持つ各ピクセルはライブビデオ上にはコピーされません。
ドロップアウトカラーの設定はOverlayBitmap::setDropOutColorクラスライブラリリファレンス>クラス>OverlayBitmap>OverlayBitmap::setDropOutColor Methodをコールして行い、OverlayBitmap::getDropOutColorクラスライブラリリファレンス>クラス>OverlayBitmap>OverlayBitmap::getDropOutColor Methodをコールすることで取得することができます。オーバーレイビットマップの作成時、全ピクセルの色はデフォルトのドロップアウトカラーである黒 (RGB(0,0,0))に設定されます。

次のコードを使うことによってアプリケーションがドロップアウトカラーを変更することができます。

// ドロップアウトカラーをMagentaに設定する
m_Grabber.getOverlay()->setDropOutColor( RGB(255,0,255) );

ドロップアウトカラーをmagentaに変更したため現在ライブ画像は真っ黒になっていると思います (オーバーレイビットマップ内のピクセルの色ではありません) 。これは黒のピクセルがすべてライブ画像にコピーされているから(コピーされないのはmagentaのピクセルだけ)です。オーバーレイビットマップを全てmagentaにしたい場合には以下の1行を入れます。

// オーバーレイビットマップをmagentaで塗りつぶす(現在のドロップアウトカラー)
m_Grabber.getOverlay()->fill( RGB(255,0,255) );

オーバーレイビットマップ上に書き込んだテキストの消去には以下のコードを使用します。

// 表示される色でテキストを描く
m_Grabber.getOverlay()->drawText(RGB(255,0,0), 0, 0, "Hello World" );
// テキストを隠す場合は、ドロップアウトカラーを使用して描く
m_Grabber.getOverlay()->drawText(m_Grabber.getOverlay()->getDropOutColor(), 0, 0, "Hello World" );

これは同じテキストを同じ場所に、ドロップアウトカラーを使って描くというものです。テキストは見えなくなります。