高度な画像処理を行う
このチャプターでは高度な画像処理を行う方法について説明します。
今回のサンプルプログラムのC#用のソースコードはMy Documents/IC Imaging Control 3.5内の以下ののディレクトリに格納されています。
samples\C# *\Advanced Image Processing
概要
このチャプターでは以下の操作に関して説明します。
- イメージバッファを表示する
- ライブ画像上にオーバレイを描画する
- マウスイベントを処理する
- イメージバッファ内のデータを処理する
ここで紹介するのはマウスを使ってライブ画像上に長方形を描画するプログラムです。その長方形は範囲内のライブ画像に変化があったかどうかをチェックする為に使われ、 変化が発生した時に画像は更新されます。その変化のしきい値はユーザー側で設定が可能です。
プロジェクトの新規作成
新しいプロジェクトを作成し、IC imaging Controlをフォームに追加してください。プログラムを実行する前に、はじめに: Visual Studio .NETプログラマーズガイド>Visual Studioでスタートにあるように映像デバイスの選択、入力方式、ビデオフォーマットを選択してください。もしくはデバイスを選択せずにプログラムを実行してください。その際はIC ImagingControlによってデバイス選択のダイアログが出現します。選択をせずにダイアログを閉じた場合、プログラムはエラーメッセージを表示し、終了します。
フォームにボタンを2つ追加し、Captionプロパティを Device、Settingsとします。そしてそれぞれに cmdDevice、cmdSettingsと名前を付けます。"Device"ボタンをクリックすればデバイス選択ダイアログが表示されます。有効なデバイスを選択後、UpdateDeviceSettingがコールされます。これはヘルパー関数で、画像の取り込みに使われる FrameQueueSinkクラスライブラリ リファレンス>クラス>FrameQueueSinkを立ち上げます。このシンクは5つのバッファサイズ、Y800カラーフォーマットで初期化されます。今回の例ではバッファを手動で描画するので ICImagingControl.LiveDisplayクラスライブラリリファレンス>クラス>ICImagingControl>ICImagingControl.LiveDisplay Propertyは無効にします。Settingsボタンは現在選択中のデバイスのVCDProperties を調節するためのダイアログを表示します。2つのボタンのコードは次のようになります。
[C#]private void cmdDevice_Click( object sender, EventArgs e )
{
icImagingControl1.ShowDeviceSettingsDialog();
UpdateDeviceSettings();
}
private void UpdateDeviceSettings()
{
cmdStart.Enabled = icImagingControl1.DeviceValid;
cmdSettings.Enabled = icImagingControl1.DeviceValid;
if ( !icImagingControl1.DeviceValid )
{
return;
}
// icImagingControl1のサイズを選択されたデバイスのサイズにセット
icImagingControl1.Size = icImagingControl1.VideoFormatCurrent.Size;
_userROI.TOP = 0;
_userROI.Left = 0;
_userROI.Bottom = icImagingControl1.Width;
_userROI.Right = icImagingControl1.Height
}
[C#]private void cmdSettings_Click( object sender, EventArgs e )
{
icImagingControl1.ShowPropertyDialog();
}
変数の宣言
Form クラスの最初の部分に次のコードを挿入し、RECTというユーザデータタイプを作成します。ここに長方形の座標を格納できるようにします。
[C#]private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
グローバル変数を追加します。
[C#]private IFrameQueueBuffer _currentlyDisplayedBuffer;
private RECT _userROI;
private bool _userROICommited = false;
private int _threshold = 0;
private FrameQueueSink _sink;
- _currentlyDisplayedBufferは イベントで表示されるバッファへの参照です。 新しいバッファを表示する必要がある場合、その新しいバッファがこれと比較されます。
- _userROIはユーザがマウスで指定したRegion Of Interest (関心領域)を含みます。
- _userROICommitedは描画とテストの切り替えの際に使用しま す。
- _thresholdは、比較の際のしきい値です。
- _sinkはシンクオブジェクトです。
マウスイベントプロシージャの追加
ICImagingControlのMouseDown,MouseUp, MouseMoveの 各イベント用にイベントプロシージャを追加します。次のコードを各イベントプロシージャ内に挿入してください。
MouseDown:
if ( !_userROICommited&& (e.Button == MouseButtons.Left) )
{
_userROI.Left = e.Location.X;
_userROI.Top = e.Location.Y;
}
MouseUp:
[C#]
if ( !_userROICommited && (e.Button == MouseButtons.Left) )
{
_userROI.Right = e.Location.X;
_userROI.Bottom = e.Location.Y;
}
MouseMove: [C#]
if ( !_userROICommited && (e.Button == MouseButtons.Left) )
{
_userROI.Right = e.Location.X;
_userROI.Bottom = e.Location.Y;
}
Y800をシンクのカラーフォーマットとして使用する為、マウス位置はイメージバッファにおけるピクセル位置と同一となります。よってUserROI.bottomと.topのメンバをYPosに設定できます。
RGB8のようなボトムアップ(上下逆)のカラーフォーマットをシンクで使用する際はマウス位置を ICImagingControl1.Height - YPos に変換する必要があります。
アプリケーションの機能を追加する
イベントを使ってバッファを表示させるプログラマーズガイド>イベントを使ってバッファを表示させるにあるようにフレーム取得時のイベントハンドラを記述します。
[C#]FrameQueuedResult NewBufferCallback( IFrameQueueBuffer buffer )
{
RECT region = NormalizeRect( _userROI, buffer.FrameType.Size );
if( !_userROICommited )
{
ReceiveFrameInContinuousMode( buffer, region );
}
else
{
ReceiveFrameInCompareMode( buffer, region );
}
return FrameQueuedResult.SkipReQueue;
}
このイベントハンドラは関数NormalizeRectを使うことにより長方形の座標が入れ替わらないようにします (e.g. leftの値がrightの値よりも大きくなる)。またReceiveFrameInContinuousModeメソッドをコールして長方形をイメージバッファ上に描画します。
NormalizeRectは次のようにして実装されます。
private RECT NormalizeRect( RECT val, Size fmtDim )
{
RECT r = val;
if ( r.Top > r.Bottom )
{
int Tmp = r.Top;
r.Top = r.Bottom;
r.Bottom = Tmp;
}
if ( r.Left > r.Right )
{
int Tmp = r.Left;
r.Left = r.Right;
r.Right = Tmp;
}
if ( r.Top < 0 )
{
r.Top = 0;
}
if ( r.Left < 0 )
{
r.Left = 0;
}
if ( r.Bottom >= fmtDim.Height )
{
r.Bottom = fmtDim.Height - 1;
}
if ( r.Right >= fmtDim.Width )
{
r.Right = fmtDim.Width - 1;
}
return r;
}
ReceiveFrameInContinuousModeメソッドは以下のように実装します。
[C#]private void ReceiveFrameInContinousMode( IFrameQueueBuffer buffer, RECT Region )
{
if( _currentlyDisplayedBuffer != null )
{
_sink.QueueBuffer(_currentlyDisplayedBuffer);
}
_currentlyDisplayedBuffer = buffer;
DrawRectangleY8( buffer, Region );
icImagingControl1.DisplayImageBuffer( _currentlyDisplayedBuffer );
}
まず、現在表示されているバッファがあるかどうかを確認し、FrameQueueSink.QueueBufferクラスライブラリリファレンス>クラス>FrameQueueSink>FrameQueueSink.QueueBuffer Methodを使ってSinkに キューイングします。次に現在のフレームを _currentlyDisplayedBufferに保存します。ここで、ROI領域の長方形をフレームデータに描画し、ICImagingControl.DisplayImageBufferクラスライブラリリファレンス>クラス>ICImagingControl>ICImagingControl.DisplayImageBuffer Method
を使って表示画像を更新します。
この実装によりICImagingContorlはビデオライブ中にコントロール上に長方形を描くことで領域を選択することができます。
"CompareMode" (比較モード)の挿入
ROI内において何らかの変化があった場合にだけ表示を更新する機能を加えます。cmdROICommitボタンを作成し、以下のコードをクリックイベントプロシージャに追加してください。
[C#]private void cmdROICommit_Click( object sender, EventArgs e )
{
if ( !_userROICommited )
{
_userROICommited = true;
cmdROICommit.Text = "Reset ROI";
}
else
{
_userROICommited = false;
cmdROICommit.Text = "Set current ROI";
}
}
このボタンが押された際, ROIが設定され、その領域内で何らかの変化があった時のみ表示が更新される"CompareMode"に切り替わります。このモードでは、ROI領域の中に変化があった場合にのみ表示をアップデートします。 すでに"CompareMode"で動作していた場合、ボタンにより"ContinousMode"(連続モード)に切り替わります。
イベントプロシージャのコードを更新する
次のコードをcmdStart_Clickイベントプロシージャ に追加します。
[C#]mdROICommit.Enabled = true;
ライブ画像表示の際には常にROIの描画ができるようにします。この例では"ContinousMode" と呼称しています。 では次のコードをcmdStop_Clickイベントプロシージャ に追加してください。
[C#]try
{
cmdStart.Enabled = true;
cmdStop.Enabled = false;
cmdDevice.Enabled = true;
if( _userROICommited )
{
cmdROICommit_Click( sender, e );
}
cmdROICommit.Enabled = false;
icImagingControl1.LiveStop();
_currentlyDisplayedBuffer = null;
}
catch( Exception ex )
{
MessageBox.Show( ex.Message );
}
Stopされたときに"CompareMode"から"ComtinuousMode"にリセットし、"CompareMode"をアクティブにします。 さらにForm_LoadイベントプロシージャにcmdROICommit.Enabled = Falseとします。
イベントが発生した時のコードを以下のように変更します。
FrameQueuedResult NewBufferCallback( IFrameQueueBuffer buffer )
{
RECT region = NormalizeRect( _userROI, buffer.FrameType.Size );
if( !_userROICommited )
{
ReceiveFrameInContinuousMode( buffer, region );
}
else
{
ReceiveFrameInCompareMode( buffer, region );
}
return FrameQueuedResult.SkipReQueue;
}
これで_userROICommitedに応じて適切なサブプロシージャを呼び出します。
"ReceiveFrameInCompareMode"メソッド
イベントプロシージャが使用するReceiveFrameInCompareModeメソッドを次のように実装します。
[C#]private void ReceiveFrameInCompareMode( IFrameQueueBuffer newFrame, RECT Region )
{
IFrameQueueBuffer oldBuffer = _currentlyDisplayedBuffer;
if( oldBuffer == null || CompareRegion(oldBuffer, newFrame, Region, _threshold) )
{
if( oldBuffer != null )
{
_sink.QueueBuffer(oldBuffer);
}
_currentlyDisplayedBuffer = newFrame;
DrawRectangleY8(newFrame, Region);
icImagingControl1.DisplayImageBuffer(newFrame);
}
else
{
_sink.QueueBuffer(newFrame);
}
}
このメソッドはフレームのROI領域を比較するためのCompareRegion関数をコールします。 もし十分な差異があれば、前のフレームがFrameQueueSinkの中でキューされ、newFrameにある新しいフレームがDisplayImageBufferによって表示されます。
"CompareRegion " 関数の作成
CompareRegion関数は関数は2つのイメージバッファBufとBuf2におけるROI領域を比較します。指定されたRegion(領域内)の異なるピクセル数が加算され、その結果は変数greyscaleDifferenceAccuに保存されます。 加算後, grayscallDifferenceAccuの値はその領域内の総ピクセル数で除算され しきい値と比較されます。しきい値がgrayscallDifferenceAccuよりも大きければ 領域の差は十分であり、この関数はTrueを返します。そうでなければFalseを戻します。
CompareRegionは以下のようにして実装されます。
private bool CompareRegion( IFrame buf, IFrame buf2, RECT region, int threshold )
{
int PixelCount = (region.Bottom - region.Top) * (region.Right - region.Left);
if (PixelCount <= 0)
{
return false;
}
long grayscaleDifferenceAccu = 0;
for( int y = region.Top; y <= region.Bottom; y++ )
{
for( int x = region.Left; x <= region.Right; x++ )
{
grayscaleDifferenceAccu += Math.Abs( GetY8PixelAt( buf, x, y ) - GetY8PixelAt( buf2, x, y ) );
}
}
var grayscaleDifference = grayscaleDifferenceAccu / PixelCount;
if( grayscaleDifference > threshold )
{
return true;
}
else
{
return false;
}
}