メモリーレコーディング
(高速カメラで撮影した現象をRAMメモリー領域に保存)
メモリーレコーディング
ICImagingControlで画像データを保存するためのリングバッファを構成し、カメラから取得した複数のフレームを表示・静止画保存・動画保存をするサンプルです。100fpsを超える高速画像データを静止画ファイル保存(JPEG)のサンプルのようにHDD/SSDに書き込むと、計算上保存される枚数よりも実際に保存される画像が少ないことがあります(フレーム落ちと呼びます)。カメラから送られてくる画像データはメモリを経由してHDD/SSDに保存されますが、カメラから送られてくる画像データよりもHDD/SSDに書き込む速度が遅いため、メモリにあるデータがHDD/SSDに保存される前に上書きされてしまうことで発生しています。 このサンプルではカメラから送られてくる画像データをメモリに保存し、後処理で表示・静止画保存・動画保存ができるようになっています。 予め画像を保存するためのメモリを確保することで、高速画像データであってもメモリ上の画像データを上書きすることなく画像を保存することができます。
サンプルプログラム
Software | IC Imaging Control 3.5, Visual Studio™ 2019 |
---|---|
サンプル(C#) | Memory recording.zip |
サンプルツールの外観
このサンプルでは、キャプチャボタンが押された時点から指定の秒数まで遡った全イメージフレームを保持します。メモリに保存できる画像の枚数は【解像度】と【フレームレート】と【撮影秒数】によって変わります。上記の動画では640×480の解像度で395fpsを3秒間の画像を保存することになりますので、メモリは1,390MB(640×480×3Byte(1ピクセルごとのバイト数)×395fps×3秒)使用することになり【利用可能物理メモリ:3,747MB】よりも小さいことから全フレームを保存できます。この【利用可能物理メモリ】は使用しているPCのメモリに依存しますので、このサンプルを試す際には8GB以上のメモリを搭載したPCをご利用ください。
グローバル変数を宣言(下準備)
private void Form1_Load(object sender, EventArgs e)
{
if( !icImagingControl1.LoadShowSaveDeviceState("lastSelectedDeviceState.xml") )
{
MessageBox.Show("No device was selected.", this.Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
this.Close();
return;
}
//カメラから出力された映像をIC Imaging Contorolのコントロール表示サイズにする
icImagingControl1.LiveDisplayDefault = false;
icImagingControl1.LiveDisplaySize = icImagingControl1.Size;
icImagingControl1.LiveStart();
}
すべての画像を処理するためにカメラから受け取った画像を出力する先FrameQueueSinkクラスのsink(シンク)を定義します。カメラから送られてきたデータを受け取るごとにリングバッファに画像を入れるために_sinkListenerをグローバル変数で宣言します。また、カメラ取得した画像を変数に格納するために_bufferlistも宣言しておきます。
フォーム起動時に発生するイベント
private void Form1_Load(object sender, EventArgs e)
{
icImagingControl1.LoadDeviceStateFromFile("device.xml", true);
if (m_bFitImageToWindow)
{
// ディスプレイのサイズを調整
icImagingControl1.LiveDisplayDefault = false;
icImagingControl1.LiveDisplaySize = icImagingControl1.Size;
}
// _sinkListenerをベースにシンクを作成
// Prepareボタンをクリックしたときにリングバッファを取得します。
_sink = new FrameQueueSink(_sinkListener, MediaSubtypes.RGB32);
icImagingControl1.Sink = _sink;
UpdateControls();
}
FrameQueueSinkクラスを使うには下記のようにIFrameQueueSinkListenerを実装したクラスへの参照が必要となります。
class SinkListener : IFrameQueueSinkListener
{
public void FramesQueued(FrameQueueSink sink)
{
if( sink.InputQueueSize == 0)
{
sink.QueueBuffer(sink.PopOutputQueueBuffer());
}
}
}
[メモリに画像を格納]ボタンをクリックしたタイミングで、リングバッファを作成するために出力キューのバッファが取り出されます。リングバッファ(InputQueueSize)が空の場合、カメラから送られてきた画像から1枚を取得し(PopOutputQueueBuffer)、リングバッファに画像を入れます(QueueBuffer)。
[Start Live]ボタンクリックした時
private void StartLiveVideo()
{
icImagingControl1.LiveStart();
btnSnapEvent.Enabled = false;
_FramesToCapture = (int)icImagingControl1.DeviceFrameRate * int.Parse( txtSecond.Text); // キャプチャーする時間;
UpdateControls();
}
ライブ開始時にリングバッファに取りためる数をグローバル変数_FramesToCapture(フレームレート×設定した秒数)で計算しておきます。
[メモリに画像を格納]ボタンをクリックした時
private void btnPrepareFrameBufferQueue_Click(object sender, EventArgs e)
{
btnPrepareFrameBufferQueue.Enabled = false;
if (_bufferlist != null)
{
_bufferlist = null;
GC.Collect();
}
_sink.AllocAndQueueBuffers(_FramesToCapture);
btnSnapEvent.Enabled = true;
}
bufferlistにデータが残っていればクリアにしてGC.Collect()で強制的にガベージコレクション(占有していたメモリ領域のうち不要なメモリバッファを解放)を行い、AllocAndQueueBuffersを使って、_FramesToCapture(フレームレート×指定時間)分のリングバッファを割り当てて、カメラから送られてきた画像をリングバッファに格納します。ボタンを押した後、下図のようにPC内部の【利用可能物理メモリ】が消費されることが分かります。
[画像表示確認]ボタンをクリックした時
private void btnSnap_Click(object sender, EventArgs e)
{
btnSnapEvent.Enabled = false;
_bufferlist = _sink.PopAllOutputQueueBuffers();
if (_bufferlist.Length > 0)
{
tbImageIndex.Maximum = _FramesToCapture - 1;
tbImageIndex.Value = 0;
pictureBox1.Image = _bufferlist[0].CreateBitmapWrap();
btnSaveClip.Enabled = true;
}
btnPrepareFrameBufferQueue.Enabled = true;
}
キャプチャされたリングバッファはシンクから_bufferlistにコピーされます。
SinkListenerクラスのQueueBufferでリングバッファに保存された画像をPopAllOutputQueueBuffersを使用して_bufferlist変数に格納します。なお、PopAllOutputQueueBuffersを使用するとリングバッファは空になるので下記のようにバッファには画像が1枚も残っていない状態になります。
_bufferlist変数に格納されたバッファのイメージをCreateBitmapWrapを使って画像ボックス(pictureBox1)に引き渡し画面上に表示させます。
[メモリ開放]ボタンをクリックした時
private void BtnMemoryRelease_Click(object sender, EventArgs e)
{
_bufferlist = null;
//ガベージコレクション実行
GC.Collect();
pictureBox1.Image = null;
tbImageIndex.Maximum = 0;
tbImageIndex.Value = 0;
pictureBox1.Update();
if (icImagingControl1.LiveVideoRunning) StopLiveVideo();
}
_bufferlist変数に格納されたデータを初期化しガベージコレクションを実行します。
下図のようにPC内部の【利用可能物理メモリ】が増えたことが確認できます。
[静止画保存]ボタンをクリックした時
private void BtnSaveBmp_Click(object sender, EventArgs e)
{
string fileName;
Cursor = Cursors.WaitCursor;
for (int i = 0; i < _bufferlist.Length; i++)
{
//デバイスが生成したフレーム番号が格納されます。
string strFrameNumber = _bufferlist[i].FrameMetadata.DeviceFrameNumber.ToString();
//(有効な場合)デバイスのタイムスタンプが格納されます。
// デバイス固有のタイムソースによるタイムスタンプです。
string strDeviceTimeStamp = _bufferlist[i].FrameMetadata.DeviceTimeStamp.ToString();
//このフレームの最初のパケットをドライバが受信した時刻が格納されます。
string strSampleStartTime = _bufferlist[i].FrameMetadata.DriverFrameFirstPacketTime.ToString(@"d\_hh\_mm\_ss\_fff");
//ドライバがこのフレームに与えたフレーム番号が格納されます。
string strDriverFrameNumber = _bufferlist[i].FrameMetadata.DriverFrameNumber.ToString();
fileName = "Image " + strSampleStartTime + ".bmp";
TIS.Imaging.FrameExtensions.SaveAsBitmap(_bufferlist[i], fileName);
}
Cursor = Cursors.Default;
}
_bufferlist変数に格納された画像をSaveAsBitmapを使ってbmp保存します。HDD/SSDに書き出す速度が遅いと保存するまでに時間がかかりますので注意してください。なお、ファイル名は例としてタイムスタンプDriverFrameFirstPacketTimeの日時を指定しています。
[動画保存]ボタンをクリックした時
private void btnSaveClip_Click(object sender, EventArgs e)
{
if( saveClipDlg.ShowDialog(this) == DialogResult.OK)
{
SaveBufferList(saveClipDlg.FileName);
}
}
private void SaveBufferList(String FileName)
{
MFTestSharp.H264Writer writer;
int BITRATE = 60 * 1000000;
writer = new MFTestSharp.H264Writer(FileName, _sink.OutputFrameType, (int)icImagingControl1.DeviceFrameRate, BITRATE);
writer.Begin();
for (int i = 0; i < _bufferlist.Length; i++)
{
writer.Write(_bufferlist[i]);
}
writer.End();
}
Microsoft Media Foundationを使ってH.264コーデックでビデオファイルを保存します。
H264Writerメソッドをラップしたもので下記の4つの変数を引き渡すことでH.264コーデックにて保存することが可能です。
なお、120fpsを超えるフレームレートの場合、エラーとなりますのでご注意ください。
- FileName(ファイル名)
- OutputFrameType(解像度)
- DeviceFrameRate(フレームレート)
- BITRATE(ビットレート)
ビットレートは1秒間に何ビットのデータを保存するかを表しています。
ここでは、ビットレートは60*1,000,000(60M) bpsに設定していますので、理論的には1秒間に7.5MB(60M/8)のデータ量が作成されることになります。(ただし、撮影対象物や録画時間によって、実際の保存容量は理論値から変動します。)
ビットレートを高く設定すると、ブロックノイズが減り綺麗な動画を得られますが、録画容量が大きくなります。一方で、ビットレートを小さく設定すると、画質は犠牲になりますが、録画容量が小さくなります。従って、長時間録画したい場合は、録画容量を低く抑える為に最低限の画質を確保しながらビットレートを低く設定するようにしてください。