この記事で扱っていること
- トリガを受ける前後の画像を撮影する方法
を紹介しています。
注意:すべてのエラーを確認しているわけではないので、記事の内容を実装する際には自己責任でお願いします。また、エラー配線は適当な部分があるので適宜修正してください。
LabVIEWでに追加できるVision Acquisition Softwareというアドオンを使用することでカメラを使った撮影をすることができます。
CameraLinkのカメラではない、例えばUSBカメラの場合にIMAQdxという関数群を用いて撮影を行い、単純な撮影だけならシンプルに関数をいくつか置くだけで構成できます。
撮った画像はフロントパネルに表示させるのはもちろん、画像ファイルとして出力することもできます。
通常、画像を取得する関数と保存する関数はセットで使用することが多いので、「今撮影した画像」をそのまま保存することになるのですが、場合によってはある特定のイベント、例えばトリガが来たらその時から画像を取得したいという場合もあると思います。
これはこれで、トリガが来たら画像保存の関数が実行されるように組むだけですが、さらに付け加えてトリガが来る前の画像も何枚か保存しておくとなると、それらトリガ前の画像も一時的に保存しておく必要があります。
この記事では一時的に保存しておくための方法として画像を配列データ化して残しておく方法をとってみました。
どんな結果になるか
フロントパネル上には、カメラを指定する部分と、画像の保存先フォルダの指定用パス制御器、そして画像保存を開始するためのボタンなど用意しています。
プログラム実行時には、トリガが来る前の画像として、どの程度のフレーム分保存したいかを指定しておきます。
実行後、LEDの部分が光ったら指定したフレーム分は確保できた合図となり、以降トリガ(このサンプルでは開始ボタンを押すことをトリガとしています)が来るとそのトリガが来たタイミングの前の指定したフレーム分プラス停止ボタンが押されるまでのフレーム分の画像を保存します。
プログラムの構造
プログラムはキューを使用した生産者消費者のデザインパターンをベースに、消費者の方はステートマシンチックな構造をとりいれてみました。
ステートマシンのステートの種類は3種類で、トリガ前の画像をためておくステート、トリガが来てから貯めていた画像をまとめてファイル出力するステート、そしてトリガ以降の画像を都度ファイル出力するステートです。
画像データは関数によって2次元配列に変換することになります。この変換は生産者ループで行っておき、消費者ループにキューを介して渡してやります。
なお、一番上にあるWhileループではキューステータスを確認して要素数を調べています。これは配列に変換された画像を後でファイルに出力するときなどに処理に時間がかかりキューにデータがたまってしまうかどうかを確認するためのもので、特に処理に時間がかかりすぎない(キューがたまらない)ことが分かれば消してしまっても構いません。
上の図で赤枠で囲った部分については以下のような目的があります。
一つの画像に対して「1つの」2次元配列が対応しますが、こういった画像を複数ためておくために、もう1次元追加して3次元配列を使用します。消費者ループではこの3次元配列に対して実際の画像データを当てはめていくことになります。具体的な置き換えについては後でもう少し詳しく説明します。
このようにして予め配列初期化の関数で3次元配列を用意しておくことで、restoreのステートで配列置換の関数を使用することでこの用意した3次元配列の要素を置き換えます。
ここでは置き換える際のポイントとして、置き換える要素番号が順繰りになるように工夫します。
説明のため、仮にトリガ前の4つのフレームだけを保存する場合を考えます。配列初期化の関数で3次元配列を用意し、トリガが来る前はこの配列初期化の関数に次から次へと実際の画像データを入れていきます。
全ての要素に入れ終わった後も、トリガが来ない限りは新しい画像の配列データがやってきます。このとき、一番古いデータを消して新しいデータとする必要があります。
そのための工夫として、「割り算の余り」を使用しています。割り算の「割られる数」はWhileループの反復端子、「割る数」は配列のサイズとし、「余り」の数を部分配列置換の「どの2次元配列を置換するか」の要素番号の指定に使用しています。
例えば消費者ループの反復端子が3の場合、配列のサイズが4なので、3÷4の余りは3、つまり配列初期化で用意した配列の要素番号3を最新の画像データで変換します。生産者ループから新しい画像がやってくると消費者ループの反復端子が4となり、4÷4で余りが0となるので、配列初期化で用意した配列の要素番号0を最新の画像データで変換する、といった具合です。
こうすることで常に指定した範囲の数字を0から繰り返すことができます。
restoreのステートを抜ける条件は、開始のボタンが押されたときです。開始ボタンが押されるとTRUEが生産者ループから渡されるので、消費者ループで受け取ったら次のステートに移り、ここでためておいた画像を保存します。
複数の画像を取得するため、「増分する接尾辞と一緒にファイルを作成」関数を使用しています。ベースのファイル名はpretrigger.bmpとしていますが、もちろんこうである必要はありません。(トリガ後の画像と区別さえつけば)
また、bmpファイルである必要もありません。IMQA Write FIle2.viでPNGやJPEGを指定する場合にはベースのファイル名の拡張子も変えることに注意します。
なお、このステートが実行された際に生産者ループから送られてきた画像データは改めて次のステートで使用するため、先頭にエンキューしてキューの中にためておきます。
画像保存時の注意としては、今までの画像をためていた3次元配列を「直前にためていた要素番号の次の番号」から取り出してファイル出力する必要があります。
この「次の番号」は、消費者ループの反復端子がちょうど該当するので、あとはこの端子を使用してためていた画像すべてを出力させます。
これ以降は単にキューの中身の画像データをどんどんファイル出力していくだけです。
トリガ前の画像と区別するためにベースのファイル名はposttrigger.bmpとしています。
指定したフレーム数がたまらない状態でトリガする場合
上で紹介したプログラム、そのままだと「指定したフレーム数がたまっていない」状態では意図した結果になりません。指定したフレーム数に達していない場合、配列初期化で用意した配列のうち、実際の画像データになっていない部分が存在しこれが画像化されようとするからです。
これを避ける手段としては
- そもそも指定したフレーム数たまるまで開始ボタンが押せないようにする
- 指定したフレーム以下であっても対応するようにプログラムを改変する
のどちらかの策をとります。
開始ボタンが押せないようにするのはプロパティノードを使用すれば簡単です。ただこの方法は、そもそものトリガが「フロントパネルのボタンを押す」ことになっているのが前提であり、これ以外のトリガ(ハードウェアからの信号など)を使用している場合には選択肢にはなりません。
一方、指定したフレーム以下でも対応できるようにするためには、取得したフレーム数と指定フレーム数の大小を調べてケースストラクチャで場合分けし、例えば以下のように組むことで対応します。
トリガ後のフレーム数も指定する場合
今回のプログラム、開始ボタンが押されて以降は停止ボタンが押されるまで画像ファイルを延々出力し続けます。トリガ前にしているのと同様、トリガ後も指定フレーム分しか欲しくない、という場合には、トリガ後から数え始めて指定したフレーム分たまったら生産者ループが終了するように組みます。
グレースケール画像の場合
これまで紹介していたプログラムは、カラー画像を取得するカメラを前提としていました。そのため、カラー画像を配列に、あるいは配列をカラー画像に変換するにはそれ専用の関数を使用していました。
もしカメラがカラー対応ではなくグレースケールの画像を扱う場合には使用する関数も変える必要があります。下の図の赤枠と青枠の部分をそれぞれ置換します(青枠で囲った、配列を画像に変換する場所はプログラム中で二か所あることに注意)。
また、IMAQ Createで作成している画像リファレンスのImage Typeも、RGBではなくGrayscaleに変更する必要があります。
今回の記事では、トリガがかかった前後の画像をファイル出力する方法を紹介しました。一時的に画像を保存する際の方法は数値の配列でなくても、バリアントの配列やIMAQ Createで作成したリファレンスの配列にするなどといった選択肢もあると思うので、組みやすいものを選択すればいいと思いますが、ベースの組み方は同じだと思うので参考になるとうれしいです。
ここまで読んでいただきありがとうございました。
コメント