高度な画像処理を行う
このチャプターではどうのようにして高度な画像処理を行うかについて説明します。
今回のサンプルプログラムのVB.NET 、C#用のソースコードはMy Documents/IC Imaging Control 3.4内の以下ののディレクトリに格納されています。
samples\VB *\Advanced Image Processing
samples\C# *\Advanced Image Processing
概要
このチャプターでは以下の操作に関して説明します。
- イメージバッファを表示する
- ライブ画像上にオーバレイを描画する
- マウスイベントを処理する
- イメージバッファ内のデータを処理する
ここで紹介するのはマウスを使ってライブ画像上に長方形を描画するプログラムです。その長方形は範囲内のライブ画像に変化があったかどうかをチェックする為に使われ、 変化が発生した時に画像は更新されます。その変化のしきい値はユーザー側で設定が可能です。
プロジェクトの新規作成
新しいプロジェクトを作成し、IC imaging Controlをフォームに追加してください。プログラムを実行する前に、 はじめに: Visual Studio .NETプログラマーズガイド>Visual Studioでスタート にあるように映像デバイスの選択、入力方式、ビデオフォーマットを選択してください。もしくはデバイスを選択せずにプログラムを実行してください。その際はIC Imaging Controlによってデバイス選択のダイアログが出現します。選択をせずにダイアログを閉じた場合、プログラムはエラーメッセージを表示し、終了します。
フォームにボタンを2つ追加し、Captionプロパティを Device、Settingsとします。そしてそれぞれに cmdDevice、cmdSettingsと名前を付けます。"Device"ボタンをクリックすればデバイス選択ダイアログが表示されます。有効なデバイスを選択後、setupDeviceが呼び出されます。こちらはヘルパープロシージャで、画像の取り込みに使われる FrameHandlerSinkクラスライブラリリファレンス>クラス>FrameHandlerSink を立ち上げます。このシンクは5リングバッファサイズ、Y800カラーフォーマットで初期化されます。今回の例ではバッファは手描きの為、 ICImagingControl.LiveDisplayクラスライブラリリファレンス>クラス>ICImagingControl>ICImagingControl.LiveDisplay Property は動作しません。Settingsボタンは現在選択中のデバイスのVCDPropertiesを調節するためのダイアログを表示します。2つのボタンのコードは次のようになります。
Private Sub cmdDevice_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handlese cmdDevice.Click
With IcImagingControl1
If .LiveVideoRunning Then
.LiveStop()
End If
.ShowDeviceSettingsDialog()
If .DeviceValid Then
cmdStart.Enabled = True
cmdSettings.Enabled = True
MakeDeviceSettings()
End If
End With
End Sub
private void cmdDevice_Click(object sender, EventArgs e)
{
if (icImagingControl1.LiveVideoRunning)
{
icImagingControl1.LiveStop();
}
icImagingControl1.ShowDeviceSettingsDialog();
if (icImagingControl1.DeviceValid)
{
cmdStart.Enabled = true;
cmdSettings.Enabled = true;
MakeDeviceSettings();
}
}
Private Sub cmdSettings_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handlese cmdSettings.Click
IcImagingControl1.ShowPropertyDialog()
End Sub
private void cmdSettings_Click(object sender, EventArgs e)
{
icImagingControl1.ShowPropertyDialog();
}
グローバル変数の宣言
Form クラスの最初の部分に次のコードを挿入し、RECTというユーザデータタイプを作成し ます。ここに長方形の座標を格納できるようにします。
Private Structure RECT
Dim Left As Integer
Dim Top As Integer
Dim Right As Integer
Dim Bottom As Integer
End Structure
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
3つのグローバル変数を入力します。
Private DisplayBuffer As TIS.Imaging.ImageBuffer
Private UserROI As RECT
Private UserROICommited As Boolean
Private Threshold As Integer
private delegate void ShowBufferDelegate(TIS.Imaging.ImageBuffer buffer);
private ImageBuffer DisplayBuffer;
private RECT UserROI;
private bool UserROICommited = false;
private int threshold = 0;
- DisplayBufferは ImageAvailableクラスライブラリリファレンス>クラス>ICImagingControl>ICImagingControl.ImageAvailable Event イベントで表示されるバッファへの参照を含みます。 新しいバッファを表示する必要があるかどうかを決定する際、そのバッファと DisplayBuffer を比較します。
- UserROIはユーザがマウスで指定したRegion Of Interest (関心領域) を含みます。
- UserROICommitedは描画とテストの切り替えの際に使用します。
マウスイベントプロシージャの追加
ICImagingControlのMouseDown, MouseUp, MouseMoveの各イベント用にイベントプロシージャを追加します。次のコードを各イベントプロシージャ内に挿入してください。
MouseDown:
If Not UserROICommited And (e.Button = Forms.MouseButtons.Left) Then
UserROI.Left = e.X
UserROI.Top = IcImagingControl1.Height - e.Y
End If
if (!UserROICommited && (e.Button == MouseButtons.Left))
{
UserROI.Left = e.Location.X;
UserROI.Top = icImagingControl1.Height - e.Location.Y;
}
MouseUp:
If Not UserROICommited And Not (e.Button = Forms.MouseButtons.Left) Then
UserROI.Right = e.X
UserROI.Bottom = IcImagingControl1.Height - e.Y
End If
[C#]
if (!UserROICommited && !(e.Button == MouseButtons.Left))
{
UserROI.Right = e.Location.X;
UserROI.Bottom = icImagingControl1.Height - e.Location.Y;
}
MouseMove:
If Not UserROICommited And (e.Button = Forms.MouseButtons.Left) Then
UserROI.Right = e.X
UserROI.Bottom = IcImagingControl1.Height - e.Y
End If
if (!UserROICommited && (e.Button == MouseButtons.Left))
{
UserROI.Right = e.Location.X;
UserROI.Bottom = icImagingControl1.Height - e.Location.Y;
}
Y800をシンクのカラーフォーマットとして使用する為、マウス位置はイメージバッファにおけるピクセル位置と同一となります。よってUserROI.bottomと.topのメンバをYPosに設定できます。
RGB8のようなボトムアップ(上下逆)のカラーフォーマットをシンクで使用する際はマウス位置を ICImagingControl1.Height - YPos に変換する必要があります。
アプリケーションの機能を追加する
ImageAvailableイベントによってバッファを表示するプログラマーズガイド>ImageAvailableイベントによってバッファを表示させる にあるように, 2つのボタン ( Start / Stop )、 Form_Loadイベントプロシージャ、 ImageAvailableクラスライブラリリファレンス>クラス>ICImagingControl>ICImagingControl.ImageAvailable Eventイベントハンドラを作成します。 DisplayBuffer.ForceUnlockクラスライブラリリファレンス>クラス>ImageBuffer>ImageBuffer.ForceUnlock Method の一行を Stop_Click ベントハンドラに追加します。 これによって DisplayBufferが ImageAvailableクラスライブラリリファレンス>クラス>ICImagingControl>ICImagingControl.ImageAvailable Eventイベントプロシージャ内 でロックされなくなります。コントロールが停止した際、 DisplayBufferのロックは解除される必要があります。
ImageAvailableクラスライブラリリファレンス>クラス>ICImagingControl>ICImagingControl.ImageAvailable Eventハンドラに次のコードを 挿入してください。
Private Sub IcImagingControl1_ImageAvailable(ByVal sender As Object,
ByVal e As TIS.Imaging.ICImagingControl.ImageAvailableEventArgs) Handles IcImagingControl1.ImageAvailable
Try
Dim Region As RECT
Region = NormalizeRect(UserROI)
ContinousMode(e.bufferIndex, Region)
Catch ex As Exception
System.Diagnostics.Trace.WriteLine(ex.Message)
End Try
End Sub
private void icImagingControl1_ImageAvailable(object sender,
TIS.Imaging.ICImagingControl.ImageAvailableEventArgs e)
{
try
{
RECT Region;
Region = NormalizeRect(UserROI)
ContinousMode(e.bufferIndex, Region)
}
catch(Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}
このイベントハンドラは関数NormalizeRectを使うことにより長方形の座標が入れ替わらないようにします (e.g. leftの値が rightの値よりも大きくなる)。また同時にサブルーチンContinousModeを呼び出しその長方形をイメージバッファ上に描画します。
NormalizeRectは次のようにして実装されます。
Private Function NormalizeRect(ByRef val As RECT) As RECT
Dim Tmp As Integer
Dim r As RECT
r = val
If r.Top > r.Bottom Then
Tmp = r.Top
r.Top = r.Bottom
r.Bottom = Tmp
End If
If r.Left > r.Right Then
Tmp = r.Left
r.Left = r.Right
r.Right = Tmp
End If
If r.Top < 0 Then
r.Top = 0
End If
If r.Left < 0 Then
r.Left = 0
End If
If r.Bottom >= IcImagingControl1.ImageHeight Then
r.Bottom = IcImagingControl1.ImageHeight - 1
End If
If r.Right >= IcImagingControl1.ImageWidth Then
r.Right = IcImagingControl1.ImageWidth - 1
End If
NormalizeRect = r
End Function
private RECT NormalizeRect(RECT val)
{
int Tmp;
RECT r;
r = val;
if (r.Top > r.Bottom)
{
Tmp = r.Top;
r.Top = r.Bottom;
r.Bottom = Tmp;
}
if (r.Left > r.Right)
{ 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 >= icImagingControl1.ImageHeight)
{
r.Bottom = icImagingControl1.ImageHeight - 1;
}
if (r.Right >= icImagingControl1.ImageWidth)
{
r.Right = icImagingControl1.ImageWidth - 1;
}
return r;
}
サブルーチン ContinousModeは以下の手順で実装することができます。
Private Sub ContinousMode(ByVal BufferIndex As Integer, ByVal Region As RECT)
DisplayBuffer = IcImagingControl1.ImageBuffers(BufferIndex)
DisplayBuffer.Lock()
DrawRectangleY8(DisplayBuffer, Region)
IcImagingControl1.DisplayImageBuffer(DisplayBuffer)
DisplayBuffer.Unlock()
End Sub
private void ContinousMode(int BufferIndex, RECT Region)
{
//DisplayBuffer.Unlock();
DisplayBuffer = icImagingControl1.ImageBuffers[BufferIndex];
//DisplayBuffer.Lock();
DrawRectangleY8(DisplayBuffer, Region);
this.BeginInvoke(new ShowBufferDelegate(ShowImageBuffer), DisplayBuffer);
}
最初にDisplayBufferのロック解除が行います。バッファへの書き込みを有効にしておくためにも、不必要なロックは基本的に解除しておくことが推奨されます。続いてImageBuffersクラスライブラリリファレンス>クラス>FrameHandlerSink>FrameHandlerSink.ImageBuffers Property コレクションのBufferIndexによって指定されるイメージバッファがDisplayBufferに割り当てられます。 このバッファは上書きを防止するためにDisplayBuffer.Lockクラスライブラリリファレンス>クラス>ImageBuffer>ImageBuffer.Lock Method によってロックされます。その後サブプロシージャDrawRectangleY8がRegionによって指定された長方形をイメージバッファに書き込みます。そのバッファを表示させるために ICImagingControl.DisplayImageBufferクラスライブラリリファレンス>クラス>ICImagingControl>CImagingControl.DisplayImageBuffer Method がパラメータとしてDisplayBufferに呼び出されます。 これでバッファに現在のROIを示す白い長方形が描画されます。
"CompareMode" (比較モード)の挿入
ROI内において何らかの変化があった場合にだけ表示を更新する機能を加えます。cmdROICommitボタンを作成し、以下のコードをクリックイベントプロシ-ジャに追加してください。
Private Sub cmdROICommit_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles cmdROICommit.Click
If Not UserROICommited Then
UserROICommited = True
cmdROICommit.Text = "Reset ROI"
Else
UserROICommited = False
cmdROICommit.Text = "Set current ROI"
End If
End Sub
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"に切り替わります。すでに "CompareMode"で動作していた場合、 "ContinousMode"(連続モード)に切り替わります。
イベントプロシージャのコードを更新する
次のコードをcmdStart_Clickイベントプロシージャ に追加します。
cmdROICommit.Enabled = True
cmdROICommit.Enabled = true;
ライブ画像表示の際には常にROIの描画ができるようにします。この例では"ContinousMode" という名前をつけています。
では次のコードをcmdStop_Clickイベントプロシージャ に追加してください。
If UserROICommited Then
cmdROICommit_Click(sender, e)
' Change CompareMode to ContinousMode.
End If
cmdROICommit.Enabled = False
if( UserROICommited )
{
cmdROICommit_Click(sender, e);
// Change CompareMode to ContinousMode.
}
cmdROICommit.Enabled = false;
"CompareMode" の時に"Stop" ボタンがクリックされた際、"ContinousMode" に変更されるようになります。 さらにForm_LoadイベントプロシージャにcmdROICommit.Enabled = Falseと加えます。
ImageAvailableクラスライブラリリファレンス>クラス>ICImagingControl>ICImagingControl.ImageAvailable Eventイベントプロシージャ内のコードを以下のように変更してください。
Dim Region As RECT
Region = NormalizeRect(UserROI)
If Not UserROICommited Then
ContinousMode(e.bufferIndex, Region)
Else
CompareMode(e.bufferIndex, Region)
End If
RECT Region;
Region = NormalizeRect(UserROI);
if (!UserROICommited)
{
ContinousMode(e.bufferIndex, Region);
}
else
{
CompareMode(e.bufferIndex, Region);
}
これはUserROICommitedに応じて適切なサブプロシージャを呼び出します。
"CompareMode" プロシージャ
新しい ImageAvailableクラスライブラリリファレンス>クラス>ICImagingControl>ICImagingControl.ImageAvailable EventイベントプロシージャがサブプロシージャCompareModeを呼び出します。次のようにして実装されます。
Private Sub CompareMode(ByVal BufferIndex As Integer, ByVal Region As RECT)
Dim IBOld, IBNew As TIS.Imaging.ImageBuffer
IBOld = DisplayBuffer
IBNew = IcImagingControl1.ImageBuffers(BufferIndex)
If CompareRegion(IBOld, IBNew, Region, Threshold) Then
DisplayBuffer.Unlock()
DisplayBuffer = IBNew
DisplayBuffer.Lock()
DrawRectangleY8(IBNew, Region)
IcImagingControl1.DisplayImageBuffer(DisplayBuffer)
End If
End Sub
private void CompareMode(int BufferIndex, RECT Region)
{
TIS.Imaging.ImageBuffer IBOld, IBNew;
IBOld = DisplayBuffer;
IBNew = icImagingControl1.ImageBuffers[BufferIndex];
if (CompareRegion(IBOld, IBNew, Region, threshold) )
{
//DisplayBuffer.Unlock()
DisplayBuffer = IBNew;
//DisplayBuffer.Lock()
DrawRectangleY8(IBNew, Region);
this.BeginInvoke(new ShowBufferDelegate(ShowImageBuffer), DisplayBuffer);
}
}
このサブルーチンはイメージバッファのROIを比較するサブ関数CompareRegionを呼び出します。その2つの間で違いが出た場合、古いDisplayBufferのロックが解除され新しいイメージバッファであるIBNewがDisplayBufferにあてられます。そうすることでDisplayBufferが新しい画像を保持することになります。このDisplayBufferは上書きをふせぐためにロックされています。長方形のROIはバッファに描画され、それはICImagingControl.DisplayImageBufferクラスライブラリリファレンス>クラス>ICImagingControl>CImagingControl.DisplayImageBuffer Methodメソッドによって表示されます。
"CompareRegion " 関数の作成
CompareRegion関数は関数は2つのイメージバッファBufとBuf2におけるROIの比較をします。指定されたRegion(領域内)の異なるピクセル数は加算され、その結果は変数GreyscaleDifferenceに代入されます。 加算後, GreyscaleDifferenceの値はその領域内の総ピクセル数で除算されます。その値は関数に渡されたしきい値と比較され、しきい値がGreyscaleDifferenceの値以下であった場合、その領域内で変化があったとみなしてこの関数はTrueを戻し、そうでなければFalseを戻します。
CompareRegionは以下のようにして実装されます。
Private Function CompareRegion(ByVal Buf As TIS.Imaging.ImageBuffer,
ByVal Buf2 As TIS.Imaging.ImageBuffer, ByVal Region As RECT, ByVal Threshold As Integer) As Boolean
Dim x, y As Integer
Dim GreyscaleDifference As Integer
Dim PixelCount As Integer
PixelCount = (Region.Bottom - Region.Top) * (Region.Right - Region.Left)
If PixelCount > 0 Then
GreyscaleDifference = 0
For y = Region.Top To Region.Bottom
For x = Region.Left To Region.Right
GreyscaleDifference = GreyscaleDifference + Math.Abs(CInt(Buf(x, y)) - CInt(Buf2(x, y)))
Next x
Next y
GreyscaleDifference = GreyscaleDifference / PixelCount
If GreyscaleDifference > Threshold Then
CompareRegion = True
Else
CompareRegion = False
End If
Else
CompareRegion = False
End If
End Function
private bool CompareRegion(TIS.Imaging.ImageBuffer Buf, TIS.Imaging.ImageBuffer Buf2, RECT Region, int Threshold) {
int x, y;
int GreyscaleDifference;
int PixelCount;
PixelCount = (Region.Bottom - Region.Top) * (Region.Right - Region.Left);
if (PixelCount > 0)
{
GreyscaleDifference = 0;
for (y = Region.Top; y <= Region.Bottom; y++)
{
for (x = Region.Left; x <= Region.Right; x++)
{
GreyscaleDifference = GreyscaleDifference + Math.Abs((int)(Buf[x, y]) - (int)(Buf2[x, y]));
}
}
GreyscaleDifference = GreyscaleDifference / PixelCount;
if (GreyscaleDifference > Threshold)
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}