ライブ表示+カメラへの外部トリガー入力で静止画保存
概要
このプログラムは、ユーザーが使用するカメラを選択すると、そのライブ映像が画面に表示されます。カメラに接続された外部信号(トリガー)の変化を検知すると、自動的に1枚の写真を撮影し、パソコンにBMP形式で保存します。ノイズなどによる誤検出を防ぐため、同じ信号が短時間に繰り返し処理されないよう、マスク(待機)時間も設定できます。
サンプルプログラム
Software | IC Imaging Control 4.0, Visual Studio™ 2022 |
---|---|
サンプル(C#) | WinForm_EventLine1Edge_cs_4.0.zip |
USBカメラ用ファームウェア (33U/37U/38U用) |
1785.zip (このプログラムをUSBカメラで使用する場合にはファームウェアをアップデートしてください) |
実行結果
マスク時間の設定
// 最終イベント受信時刻を保持する変数(クラスフィールドとして定義)
private DateTime lastRisingEventTime = DateTime.MinValue;
private DateTime lastFallingEventTime = DateTime.MinValue;
// マスク時間(デバウンス時間)を設定(ここでは10ms)
private readonly TimeSpan debounceInterval = TimeSpan.FromMilliseconds(10);
ここでは、外部からの信号による誤検出(チャタリング)や連続検出を防ぐための仕組みを準備しています。
まず、lastRisingEventTimeとlastFallingEventTimeは、前回イベント(信号の立ち上がりや立ち下がり)が発生した時刻を記録するための変数です。これらの変数はプログラム全体で共通して使うため、関数の外(クラスの中)に定義しています。debounceIntervalは「マスク時間」や「デバウンス時間」と呼ばれるもので、一度信号を検知したあと、一定時間内に同じ種類の信号がもう一度来ても無視するための待ち時間です。ここでは10ミリ秒に設定されています。
これは、センサーやスイッチからの信号が、ノイズなどで意図せずにと複数回信号が入ってしまう現象(チャタリング)を防ぐために非常に重要です。たとえば、本来1回だけ反応すればよいところで、ノイズのせいで5回も10回もイベントが発生してしまうと、意図しない処理(たとえば画像が何枚も保存される)が起きてしまいます。このマスク時間を使うことで、「前回の信号から10ミリ秒以上たっていれば処理する」「それより早ければ無視する」という判断が可能になり、安定した動作を実現できます。
アプリ起動(カメラオープン+ライブ表示+イベント監視処理追加)
private void Form1_Load(object sender, EventArgs e)
{
//
// 【1】カメラデバイスの初期化とイベント設定
//
// デバイス選択ダイアログを表示し、ユーザーにカメラを選ばせます
ic4.WinForms.Dialogs.ShowDeviceDialog(grabber, this);
// デバイスが選択されていない場合はアプリケーションを終了
if (!grabber.IsDeviceValid)
{
Close();
return;
}
// カメラ接続が切れたときのハンドラを登録
grabber.DeviceLost += Grabber_DeviceLost;
// Line1の立ち上がり/立ち下がりエッジイベントとそれぞれのタイムスタンププロパティを取得
var eventLine1RisingEdge = grabber.DevicePropertyMap.Find("EventLine1RisingEdge");
var eventLine1FallingEdge = grabber.DevicePropertyMap.Find("EventLine1FallingEdge");
var eventLine1RisingEdgeTimestamp = grabber.DevicePropertyMap.FindInteger("EventLine1RisingEdgeTimestamp");
var eventLine1FallingEdgeTimestamp = grabber.DevicePropertyMap.FindInteger("EventLine1FallingEdgeTimestamp");
// イベント通知を有効化
grabber.DevicePropertyMap.SetValue("EventSelector", "Line1RisingEdge");
grabber.DevicePropertyMap.SetValue("EventNotification", "On");
grabber.DevicePropertyMap.SetValue("EventSelector", "Line1FallingEdge");
grabber.DevicePropertyMap.SetValue("EventNotification", "On");
//
// 【2】画像受信の設定と処理
//
// ピクセルフォーマットをBGR8に固定し、バッファ数1でQueueSinkを作成
var sink = new ic4.SnapSink(ic4.PixelFormat.BGR8);
// ストリームにSinkを設定してキャプチャ開始準備完了
grabber.StreamSetup(sink,display1);
//
// 【3】ハードウェアイベントの通知ハンドラ登録
//
// Line1立ち上がりエッジのイベント通知に対して処理を登録
eventLine1RisingEdge.Notification += (s, eb) =>
{
var now = DateTime.Now;
if ((now - lastRisingEventTime) >= debounceInterval)
{
Console.WriteLine($"Line1 Rising Edge\t(Timestamp = {eventLine1RisingEdgeTimestamp.Value})");
lastRisingEventTime = now;
try
{
// 画像を1枚スナップショット取得(タイムアウト5秒)
using (var snap = sink.SnapSingle(TimeSpan.FromSeconds(5)))
{
if (snap != null)
{
string filename = $"Snap_{DateTime.Now:yyyyMMdd_HHmmss_fff}.bmp";
ic4.ImageBufferExtensions.SaveAsBitmap(snap, filename);
}
}
}
catch (Exception ex)
{
Console.WriteLine("スナップショット取得または保存中にエラー: " + ex.Message);
}
}
};
// Line1立ち下がりエッジのイベント通知に対して処理を登録
eventLine1FallingEdge.Notification += (s, eb) =>
{
var now = DateTime.Now;
if ((now - lastFallingEventTime) >= debounceInterval)
{
Console.WriteLine($"Line1 Falling Edge\t(Timestamp = {eventLine1FallingEdgeTimestamp.Value})");
lastFallingEventTime = now;
}
};
}
1.カメラデバイスの初期化とイベント設定
ここでは、最初にカメラと接続する準備をしています。アプリを起動すると、カメラを選ぶための画面(ダイアログ)が表示され、ユーザーが使用するカメラをic4.WinForms.Dialogs.ShowDeviceDialog(grabber, this);
で選択します。
もし何も選ばなければ処理を中断し、アプリケーションを終了します。その後、カメラの接続が途中で切れてしまった場合に備えた「接続エラー」のエラーハンドリングを設定しています。さらに、外部のセンサやスイッチなどから電気信号を受け取る「Line1」という端子のイベントを取得する準備もここで行います。「Line1RisingEdge」は信号がオンになったとき、「Line1FallingEdge」は信号がオフになったときに反応します。また、それぞれの発生タイミング(タイムスタンプ)も取得できるようにし、これらのイベントが有効になるよう、カメラにイベントの通知の設定もします。
2.画像受信の設定と処理
ここでは、カメラが撮影した画像を受け取るための仕組みを準備しています。今回はSnapSinkというタイプのシンク(画像受信のための受け皿)を使っており、必要なときにだけ1枚の画像を取得しています。たとえば、今回のように外部信号に応じて1枚だけ撮りたいといった用途に向いています。
次にシンクと、画面上の表示エリア(ここではdisplay1)をカメラに接続して、実際に撮影した画像が画面に表示されるようにセットアップします。これで、アプリがSnapSinkモードに設定した後に、grabber.StreamSetup(sink,display1);
でライブ表示を開始することができます。
3.ハードウェアイベントの通知ハンドラ登録
ここでは、実際にカメラの「Line1」に信号が入ったときに、どのような処理を行うかを記述しています。たとえば、Line1がオンになったとき(立ち上がりエッジ)には、現在時刻とタイムスタンプを表示し、そのあとでSnapSinkを使ってカメラから1枚だけ画像を撮影します。そしてこのサンプルの実行ファイルが置かれている場所にbmp形式で保存します。保存されるファイル名には現在の日付と時刻が含まれており、ファイルが上書きされないように工夫されています。
また、Line1がオフになったとき(立ち下がりエッジ)も検知はしていますが、この処理では単にメッセージを表示するだけで、撮影などは行っていませんが、ここにも任意の処理を追加することができます。
さらに、誤って短時間に何度もイベントが発生してしまうのを防ぐため、前回のイベントから一定時間(10ミリ秒)以上経過していない場合は、処理をスキップするようになっています。これにより、ノイズによる誤動作を防ぐことができます。
デバイスプロパティ設定
private void btnDeviceProperties_Click(object sender, EventArgs e)
{
// デバイスのプロパティマップのプロパティダイアログを表示します。
ic4.WinForms.Dialogs.ShowDevicePropertyDialog(grabber, this, ic4.WinForms.PropertyDialogFlags.AllowStreamRestart);
}
btnDeviceProperties_Clickは、ボタンを押したときにカメラ設定用のダイアログを開く処理です。ShowDevicePropertyDialogはICImagingControlが用意している便利なメソッドで、呼ぶと露光時間・ゲイン・トリガーモードなどカメラの全プロパティを一覧できる標準ダイアログが表示されます。
ユーザーが値を変更して[Close]を押すと、その設定がカメラに適用されます。フラグとしてAllowStreamRestartを付けているので、解像度変更などストリーム停止が必要な操作も自動で処理されます。
デバイスロストの設定
private void Grabber_DeviceLost(object sender, EventArgs e)
{
// DeviceLostイベントはGrabberオブジェクトによって所有されるスレッドで実行されます。
Invoke(
new Action(
() => MessageBox.Show(this, $"デバイスが失われました: {grabber.DeviceInfo.ModelName}", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error)
)
);
}
Grabber_DeviceLostはカメラが切断されたときに自動で発生するDeviceLostイベントのハンドラです。イベントは内部スレッドで呼ばれるため、そのままではMessageBoxを安全に触れません。そこでInvokeを使い、処理をメイン(UI)スレッドに転送しています。転送後、「デバイスが失われました」というメッセージボックスを表示してユーザーに知らせています。
デバイス選択
private void btnSelectDevice_Click(object sender, EventArgs e)
{
// 別のデバイスを選択するためにユーザーに許可します。
var selectedDeviceInfo = ic4.WinForms.Dialogs.ShowSelectDeviceDialog(this);
// 新しいデバイスが選択されたかどうかを確認します。
if (selectedDeviceInfo != null && selectedDeviceInfo != grabber.DeviceInfo)
{
// 現在開いているデバイスを閉じます。
grabber.DeviceClose();
// 新しく選択されたデバイスを開きます。
grabber.DeviceOpen(selectedDeviceInfo);
// 表示するためのデータストリームを開始します。
grabber.StreamSetup(display1);
}
}
btnSelectDevice_Clickは、ボタンを押したときにデバイス用の選択用のダイアログを開く処理です。
ボタンを押すとIC4の「デバイス選択」ダイアログが開き、接続されているカメラの一覧を表示します。ユーザーがカメラを選んで[OK]を押すと、そのカメラのDeviceInfoがselectedDeviceInfoに戻ります。
戻ってきた値がnullでなく、現在使っているカメラと違う場合は、まずgrabber.DeviceClose()
で今つながっているカメラを閉じ、それからgrabber.DeviceOpen(selectedDeviceInfo)
で新しく選ばれたカメラを開き直します。
最後にgrabber.StreamSetup(display1)
を呼び、ライブ映像をdisplay1コントロールと接続してライブ表示します。