高度な画像処理を行う

このチャプターではどうのようにして高度な画像処理を行うかについて説明します。
今回のサンプルプログラムの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プロパティを DeviceSettingsとします。そしてそれぞれに cmdDevicecmdSettingsと名前を付けます。"Device"ボタンをクリックすればデバイス選択ダイアログが表示されます。有効なデバイスを選択後、setupDeviceが呼び出されます。こちらはヘルパープロシージャで、画像の取り込みに使われる FrameHandlerSinkクラスライブラリリファレンス>クラス>FrameHandlerSink を立ち上げます。このシンクは5リングバッファサイズ、Y800カラーフォーマットで初期化されます。今回の例ではバッファは手描きの為、 ICImagingControl.LiveDisplayクラスライブラリリファレンス>クラス>ICImagingControl>ICImagingControl.LiveDisplay Property は動作しません。Settingsボタンは現在選択中のデバイスのVCDPropertiesを調節するためのダイアログを表示します。2つのボタンのコードは次のようになります。

[VB.NET]
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
[C#]
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();
     }
}
[VB.NET]
Private Sub cmdSettings_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
  Handlese cmdSettings.Click
     IcImagingControl1.ShowPropertyDialog()
End Sub
[C#]
private void cmdSettings_Click(object sender, EventArgs e)
{
     icImagingControl1.ShowPropertyDialog();
}

グローバル変数の宣言

Form クラスの最初の部分に次のコードを挿入し、RECTというユーザデータタイプを作成し ます。ここに長方形の座標を格納できるようにします。

[VB.NET]
Private Structure RECT
     Dim Left As Integer
     Dim Top As Integer
     Dim Right As Integer
     Dim Bottom As Integer
End Structure
[C#]
private struct RECT
{
     public int Left;
     public int Top;
     public int Right;
     public int Bottom;
}

3つのグローバル変数を入力します。

[VB.NET]
Private DisplayBuffer As TIS.Imaging.ImageBuffer
Private UserROI As RECT
Private UserROICommited As Boolean
Private Threshold As Integer
[C#]
private delegate void ShowBufferDelegate(TIS.Imaging.ImageBuffer buffer);
private ImageBuffer DisplayBuffer;
private RECT UserROI;
private bool UserROICommited = false;
private int threshold = 0;

マウスイベントプロシージャの追加

ICImagingControlのMouseDown, MouseUpMouseMoveの各イベント用にイベントプロシージャを追加します。次のコードを各イベントプロシージャ内に挿入してください。

MouseDown:

[VB.NET]
If Not UserROICommited And (e.Button = Forms.MouseButtons.Left) Then
     UserROI.Left = e.X
     UserROI.Top = IcImagingControl1.Height - e.Y
End If
[C#]
if (!UserROICommited && (e.Button == MouseButtons.Left))
{
     UserROI.Left = e.Location.X;
     UserROI.Top = icImagingControl1.Height - e.Location.Y;
}

MouseUp:

[VB.NET]
If Not UserROICommited And Not (e.Button = Forms.MouseButtons.Left) Then
     UserROI.Right = e.X
     UserROI.Bottom = IcImagingControl1.Height - e.Y
End If
[C#]
[C#]
if (!UserROICommited && !(e.Button == MouseButtons.Left))
{
     UserROI.Right = e.Location.X;
     UserROI.Bottom = icImagingControl1.Height - e.Location.Y;
}

MouseMove:

[VB.NET]
If Not UserROICommited And (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;
}

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 ベントハンドラに追加します。 これによって DisplayBufferImageAvailableクラスライブラリリファレンス>クラス>ICImagingControl>ICImagingControl.ImageAvailable Eventイベントプロシージャ内 でロックされなくなります。コントロールが停止した際、 DisplayBufferのロックは解除される必要があります。
ImageAvailableクラスライブラリリファレンス>クラス>ICImagingControl>ICImagingControl.ImageAvailable Eventハンドラに次のコードを 挿入してください。

[VB.NET]
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
[C#]
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は次のようにして実装されます。

[VB.NET]
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
[C#]
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は以下の手順で実装することができます。

[VB.NET]
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
[C#]
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 によってロックされます。その後サブプロシージャDrawRectangleY8Regionによって指定された長方形をイメージバッファに書き込みます。そのバッファを表示させるために ICImagingControl.DisplayImageBufferクラスライブラリリファレンス>クラス>ICImagingControl>CImagingControl.DisplayImageBuffer Method がパラメータとしてDisplayBufferに呼び出されます。 これでバッファに現在のROIを示す白い長方形が描画されます。

"CompareMode" (比較モード)の挿入

ROI内において何らかの変化があった場合にだけ表示を更新する機能を加えます。cmdROICommitボタンを作成し、以下のコードをクリックイベントプロシ-ジャに追加してください。

[VB.NET]
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
[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"に切り替わります。すでに "CompareMode"で動作していた場合、 "ContinousMode"(連続モード)に切り替わります。

イベントプロシージャのコードを更新する

次のコードをcmdStart_Clickイベントプロシージャ に追加します。

[VB.NET]
cmdROICommit.Enabled = True
[C#]
cmdROICommit.Enabled = true;

ライブ画像表示の際には常にROIの描画ができるようにします。この例では"ContinousMode" という名前をつけています。

では次のコードをcmdStop_Clickイベントプロシージャ に追加してください。

[VB.NET]
If UserROICommited Then
     cmdROICommit_Click(sender, e) 
   ' Change CompareMode to ContinousMode.
End If
cmdROICommit.Enabled = False
[C#]
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イベントプロシージャ内のコードを以下のように変更してください。

[VB.NET]
Dim Region As RECT
Region = NormalizeRect(UserROI)


If Not UserROICommited Then
     ContinousMode(e.bufferIndex, Region)
Else
     CompareMode(e.bufferIndex, Region)
End If
[C#]
RECT Region;
Region = NormalizeRect(UserROI);

if (!UserROICommited)
{
     ContinousMode(e.bufferIndex, Region);
}
else
{
     CompareMode(e.bufferIndex, Region);
}

これはUserROICommitedに応じて適切なサブプロシージャを呼び出します。

"CompareMode" プロシージャ

新しい ImageAvailableクラスライブラリリファレンス>クラス>ICImagingControl>ICImagingControl.ImageAvailable EventイベントプロシージャがサブプロシージャCompareModeを呼び出します。次のようにして実装されます。

[VB.NET]
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
[C#]
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のロックが解除され新しいイメージバッファであるIBNewDisplayBufferにあてられます。そうすることでDisplayBufferが新しい画像を保持することになります。このDisplayBufferは上書きをふせぐためにロックされています。長方形のROIはバッファに描画され、それはICImagingControl.DisplayImageBufferクラスライブラリリファレンス>クラス>ICImagingControl>CImagingControl.DisplayImageBuffer Methodメソッドによって表示されます。

"CompareRegion " 関数の作成

CompareRegion関数は関数は2つのイメージバッファBufBuf2におけるROIの比較をします。指定されたRegion(領域内)の異なるピクセル数は加算され、その結果は変数GreyscaleDifferenceに代入されます。 加算後, GreyscaleDifferenceの値はその領域内の総ピクセル数で除算されます。その値は関数に渡されたしきい値と比較され、しきい値がGreyscaleDifferenceの値以下であった場合、その領域内で変化があったとみなしてこの関数はTrueを戻し、そうでなければFalseを戻します。
CompareRegionは以下のようにして実装されます。

[VB.NET]
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
[C#]
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;
     }
}