リングバッファを使用したイメージシーケンスの取得と再生
ICImagingControlでリングバッファを構成し、取得した複数のフレームを表示するサンプルです。
Software | IC Imaging Control 3.4, Visual Studio™ 2019 |
---|---|
サンプル(C#) | iterate_an_image_sequence_cs_3.4.zip |
サンプル(VB.NET) | iterate_an_image_sequence_vb_3.4.zip |
このサンプルでは、キャプチャボタンが押された時点から2秒前まで遡った全イメージフレームを保持します。その後、ユーザーは自動または手動で保持されたフレームを表示する事ができます。イベントレコーダーなど、様々な用途で利用する事ができるメソッドです。
たとえばスポーツ競技では、着地、跳躍など様々な瞬間的なフォームの確認に利用する事ができます。
もちろん、ゴルフやテニスのスイング、機械のエラーの瞬間など、様々なシーンで利用できるでしょう。
このサンプルの主な構成は以下となります。
ImageBuffersから迅速にデータを引き出す方法
ImageBuffer.SampleStartTimeを使って、リングバッファ中の最も古いイメージを見つける方法
ImageAvailableイベントからボタンを有効にするためにどのように Delegateを使用するのか
アプリケーションが開始されると、Deviceボタンによりデバイスの選択ができます。Start Liveボタンによりライブ表示が開始されます。ライブ開始から2秒間はリングバッファを埋めていきます。2秒後にライブバッファが一杯になり、キャプチャボタンが有効となります。
ユーザーがCaptureボタンをクリックすると、ライブビデオストリームが停止され、リングバッファにある最も古いイメージが表示されます。ウィンドウ下にあるスライドバーにより、リングバッファの古いイメージから新しいイメージまでを見ることができます。Auto Repeat ボタンをクリックすると、リングバッファに保持されたイメージを連続してループ再生します。
アプリケーションのメインクラスが使用する変数
Captureボタンがクリックされると、変数 StartIndexはリングバッファの最も古いインデックスを格納します。この最も古いイメージはアプリケーションウィンドウにあるスライダーの最も左側に割り当てられます。
変数Imageはイメージシーケンスが含まれるリングバッファへの参照を格納します。これにより、リングバッファに直接、とても高速にアクセスする事ができるようになります。
変数FrameCountにはライブビデオが開始されてからのフレーム数のカウントに使用されます。リングバッファが一杯になったとき、Captureボタンを有効にします。これにより、アプリケーションは保存されたシーケンスイメージの表示が可能となります。
TimerCurrentImageは自動表示ループのフレームインデックスとして使用されます。このタイマーイベントにより自動的にフレームが更新されます。このイベントの中で TimerCurrentImageを増やしていくことで、表示すべきリングバッファを選択しています。
変数Secondsによりキャプチャするイメージシーケンスの長さを定義しています。このサンプルでは2を指定しています。この値を変更する事は可能ですが、指定できる最大の値は利用できるメモリー領域やビデオキャプチャデバイスのフレームレートなどに依存します。
// 変数 "StartImage" はリングバッファを最も古いインデックスに保存するのに使用されます
int StartImage;
// 変数 "TimerCurrentImage" イメージシーケンスの自動リピート再生の際、現在表示するイメージのインデックスとして使用されます
int TimerCurrentImage;
// "Images" にはイメージリングバッファのコピーが格納されます。これにより、イメージに高速にアクセスが可能となります。
TIS.Imaging.ImageBuffer[] Images;
// 変数 "FrameCount" はキャプチャフレームのカウントに使用されます。この"FrameCount" がリングバッファサイズよりも大きくなると
// "btnCapture" が有効となり、ユーザーはリングバッファが一杯になった事を知る事ができます。
// FrameCount will be set to 0 in btnLiveVideo_Click の中で FrameCount は0にセットされ、ImageAvailableイベント
// ハンドラーの中で増加させていきます。
int FrameCount = 0;
// "Seconds" リングバッファの長さを、物理量である”秒”で指定します。
int Seconds = 2;
' 変数 "StartImage" はイメージをリングバッファの最も古いインデックスに保存するのに使用されます
Dim StartImage As Integer
' 変数 "TimerCurrentImage" イメージシーケンスの自動リピート再生の際、現在表示するイメージのインデックスとして使用されます
Dim TimerCurrentImage As Integer
' "Images" にはイメージリングバッファのコピーが格納されます。これにより、イメージに高速にアクセスが可能となります。
Dim Images() As TIS.Imaging.ImageBuffer
' 変数 "FrameCount" はキャプチャフレームのカウントに使用されます。この"FrameCount" がリングバッファサイズよりも大きくなると
' "btnCapture" が有効となり、ユーザーはリングバッファが一杯になった事を知る事ができます。
' FrameCount will be set to 0 in btnLiveVideo_Click の中で FrameCount は0にセットされ、ImageAvailableイベント
' ハンドラーの中で増加させていきます。
Dim FrameCount As Integer
' "Seconds" リングバッファの長さを、物理量である”秒”で指定します。
Dim Seconds As Integer
リングバッファサイズの計算
リングバッファのサイズはビデオキャプチャデバイスのフレームレートと保存する時間により定義されます。これらを掛け合わせた数字が保存に必要なバッファ量とされ、icImagingControl1.ImageRingBufferSize にアサインされます。この為にはこの計算はビデオキャプチャデバイスが指定されている必要があります。このサンプルでは、btnDevice_Click ボタンハンドラーの中で行われています。以下のコードではどのようにデバイスを選択して、リングバッファのサイズが決定されているかを示しています。
icImagingControl1.ShowDeviceSettingsDialog();
if (icImagingControl1.DeviceValid)
{
// ここでデバイスのフレームレート × 設定時間により、確保するバッファ数を指定します。
icImagingControl1.ImageRingBufferSize = Convert.ToInt32(icImagingControl1.DeviceFrameRate * Seconds);
' デバイス設定ダイアログを表示します
IcImagingControl1.ShowDeviceSettingsDialog()
If IcImagingControl1.DeviceValid Then
' ここでデバイスのフレームレート × 設定時間により、確保するバッファ数を指定します。
IcImagingControl1.ImageRingBufferSize = Convert.ToInt32(IcImagingControl1.DeviceFrameRate * Seconds)
"Capture" イベントのハンドリング
Capture ボタンがクリックされると、ライブビデオを停止します。必要であれば、icImagingControl1.LiveStopをコールする前にSleepをコールする事で、ボタンクリック後にフレーム取得が終わるまで、時差を置く事ができます。全てのフレームがリングバッファに自動的に保存されますので、取得された最後の2秒間のイメージがリングバッファに保持されています。このリングバッファに高速にアクセスする為にそれぞれのリングバッファへの参照が変数Imagesにコピーされます。
// "Capture"ボタンがクリックされた後も少しの間フレーム取得をしたい場合、"LiveStop()" の前に "Sleep()" を挿入する事ができます。
// System.Threading.Thread.Sleep(1000);
// これにより、アプリケーションはボタンがクリックされた後も1秒間だけフレームの取得を続けます。
icImagingControl1.LiveStop();
btnLiveVideo.Text = "Start Live";
// 取得されたイメージバッファへのアクセスを高速にするために、それぞれのバッファへのコピーを生成します
Images = icImagingControl1.ImageBuffers;
' "Capture"ボタンがクリックされた後も少しの間フレーム取得をしたい場合、"LiveStop()" の前に "Sleep()" を挿入する事ができます。
' System.Threading.Thread.Sleep(1000)
' これにより、アプリケーションはボタンがクリックされた後も1秒間だけフレームの取得を続けます。
IcImagingControl1.LiveStop()
btnLiveVideo.Text = "Start Live"
' 取得されたイメージバッファへのアクセスを高速にするために、それぞれのバッファへのコピーを生成します
Images = IcImagingControl1.ImageBuffers
リングバッファはイメージバッファの配列です。ICImagingControlはビデオキャプチャデバイスから送られて来た最初のフレームをリングバッファの最初のエレメントに保存します。次のフレームは2番目に、その次は3番目に・・・と連続して保存していきます。リングバッファの最後のエレメントにイメージが入れられると、次のバッファは一番最初の(最も古い)エレメントが上書きされます。そのため、最も古いイメージがどこにあるのかを知る事はとても重要です。これはSampleStartTimeを使う事で解決できます。これはリングバッファで利用できるプロパティの一つで、リングバッファに入っているイメージがシステムに到着した時間情報が保持されています。ここでは、最も小さな(古い) SampleStartTimeを探しています。このイメージバッファのインデックスは変数StartIndex に格納され、保存されたキャプチャシーケンスの最も最初に表示されます。
// リングバッファの最も古いイメージをみつけ、そのインデックスを変数"StartImage"に保存します。
double MinStartTime;
StartImage = 0;
MinStartTime = icImagingControl1.ImageBuffers[StartImage].SampleStartTime;
for (int i = 1; i < (icImagingControl1.ImageRingBufferSize - 1); i++)
{
if (Images[i].SampleStartTime < MinStartTime)
{
StartImage = i;
MinStartTime = Images[StartImage].SampleStartTime;
break;
}
}
' リングバッファの最も古いイメージをみつけ、そのインデックスを変数"StartImage"に保存します。
Dim i As Integer
Dim MinStartTime As Double
StartImage = 0
MinStartTime = IcImagingControl1.ImageBuffers(StartImage).SampleStartTime
For i = 1 To IcImagingControl1.ImageRingBufferSize - 1
If Images(i).SampleStartTime < MinStartTime Then
StartImage = i
MinStartTime = Images(StartImage).SampleStartTime
End If
Next
リングバッファで取得されたイメージを正確に順番に表示するために、StartIndexを基準としたインデックスを算出する必要があります。たとえば、最初に表示されるべき最も古いイメージが全部で30このエレメントがあるリングバッファの25番目にあったとします。アプリケーションが最初のイメージとしてインデックス0を表示したい場合、リングバッファの25番目を表示します。次のイメージ。インデックス1はリングバッファの26番目にあります。つまり、StartIndex (この場合25)にインデックスの番号をプラスする事で表すことができます。
もしインデックスが5以上になった場合、リングバッファの上限を超えてしまいます(今回の例の場合30が上限ですが、リングバッファのインデックスは0から始まりますので、0~29までの範囲となります)。したがってアプリケーションではこれを超えた場合をハンドルする必要があります。今回は計算されたリングバッファのインデックスからリングバッファの枚数分を引く事で解決できます。たとえば、11番目のインデックスのイメージを表示したい場合、11+25(StartIndex)で36番目のリングバッファとなります。36はリングバッファの量30よりも大きいため、36から30を引く必要があります。この例ではリングバッファの6番目を表示した場合に、リングバッファ中のもっとも古いイメージから11番目の画像が表示されることとなります。
private void DisplayTheImage(int Index)
{
int i;
if (!icImagingControl1.LiveVideoRunning)
{
i = StartImage + Index;
// iがリングバッファサイズを超えた場合のハンドリング
if (i >= icImagingControl1.ImageRingBufferSize)
{
i = i - icImagingControl1.ImageRingBufferSize;
}
icImagingControl1.DisplayImageBuffer(Images[i]);
}
}
Private Sub DisplayTheImage(ByVal Index As Integer)
Dim i As Integer
With IcImagingControl1
If Not .LiveVideoRunning Then
i = StartImage + Index
' iがリングバッファサイズを超えた場合のハンドリング
If i >= .ImageRingBufferSize Then
i = i - .ImageRingBufferSize
End If
.DisplayImageBuffer(Images(i))
End If
End With
End Sub
"Capture" ボタンを有効にする
最後に Capture ボタンを有効にする方法を示します。このボタンはリングバッファが一杯になったときに有効にします。変数 FrameCountはライブストリームが開始されたときに 0 にセットされます。ICImagingControlの ImageAvailableイベントの中で FrameCountは増やされていきます。これがリングバッファエレメント数よりも多くなったとき、リングバッファは新しいイメージで一杯になったとわかります。そこでCapture ボタンをアクティブにします。
ImageAvailable イベントの中で直接ボタンの状態を変更するのはお勧めできません。Delegateを使用すべきです。これは、それぞれ異なるスレッドで動作するからです。ImageAvailableイベントハンドラーはICImagingControlのGrabbingスレッドから呼び出されます。ボタンの状態変更などはアプリケーションのスレッドで動作します。このようなクロススレッドはアプリケーションハングアップの原因となり得ます。
まず、ボタンを有効にする機能を作成し、
private void EnableCaptureButton()
{
btnCapture.Enabled = true;
}
Private Sub EnableCaptureButton()
btnCapture.Enabled = True
End Sub
次にDelegateを記述します。
public delegate void EnableCaptureDelegate();
Private Delegate Sub EnableCaptureDelegate()
ImageAvailable イベントの中で、変数 FrameCountをリングバッファのエレメント数よりも大きくなるまで増加させていきます。リングバッファが一杯になったら、BeginInvokeによって Delegateをコールします。
private void icImagingControl1_ImageAvailable(object sender, TIS.Imaging.ICImagingControl.ImageAvailableEventArgs e)
{
if (btnCapture.Enabled == false)
{
if (FrameCount > icImagingControl1.ImageRingBufferSize)
{
// ここでキャプチャボタンを有効にします
BeginInvoke(new EnableCaptureDelegate(EnableCaptureButton));
}
else
{
FrameCount++;
}
}
}
Private Sub IcImagingControl1_ImageAvailable(ByVal sender As System.Object, ByVal e As TIS.Imaging.ICImagingControl.ImageAvailableEventArgs) Handles IcImagingControl1.ImageAvailable
If btnCapture.Enabled = False Then
If FrameCount > IcImagingControl1.ImageRingBufferSize Then
' ここでキャプチャボタンを有効にします
BeginInvoke(New EnableCaptureDelegate(AddressOf EnableCaptureButton))
Else
FrameCount = FrameCount + 1
End If
End If
End Sub