USBカメラの監視と再接続処理
概要
デバイスハードウェアのハングアップなどでフレームが停止し、アプリケーションの再起動などでデバイスの再復帰などができず、デバイスへの電源再起動でしか復帰できない場合があります。ICImagingControl4.0(以下IC4)では、USBデバイスをカーネルレベルで再起動するAPI(Device Reset)を用意しています。このAPIをエラー処理として用いる事によってUSBデバイスの半永久的な安定動作を実現します。
サンプルプログラム
利用した開発環境 | Visual Studio™ 2022 |
---|---|
SDK | IC Imaging Control 4 SDK |
デバイスドライバ | GenTL Producer for USB3 Vision Cameras |
デバイス | TIS USBカメラ全般 |
サンプル(C#) | IC4_WinForm_DeviceReset_cs_4.0.zip |
サンプル(VB.NET) | ー |
exeファイル アプリケーション |
ー |
別途ファイル | ー |
関連参照URL | ー |
サンプルツールの外観
解説
フォーム立ち上げ時の処理
private ic4.Grabber grabber = new ic4.Grabber();
static QueueSink sink;
private void Form1_Load(object sender, EventArgs e)
{
// ユーザーに接続カメラを選択してもらうダイアログを表示
ic4.WinForms.Dialogs.ShowDeviceDialog(grabber, this);
// ユーザーがキャンセルした/有効なデバイスが無いときは安全にアプリ終了。
if (!grabber.IsDeviceValid)
{
Close();
return;
}
// 現在の「接続デバイス+各種設定」を device.xml に保存。
// 次回以降は grabber.DeviceOpenFromState("device.xml") で自動復元できます。
grabber.DeviceSaveState("device.xml");
// 物理的な抜け(USB抜け)や通信断が起きたときに呼ばれるイベントを登録。
grabber.DeviceLost += Grabber_DeviceLost;
// QueueSinkを作成。
sink = new ic4.QueueSink(ic4.PixelFormat.BGR8);
// フレームが届いたときに通知されるイベント
sink.FramesQueued += FramesQueued;
// ライブスタート開始
grabber.StreamSetup(sink, display1);
}
フォーム立ち上げ時に、カメラ選択ダイアログからカメラを選択し、選択したカメラと現在の設定をdevice.xmlへ保存します。その後、USB抜け等の切断を検知するDeviceLostイベントを登録し、sinkとしてQueueSinkを生成し、フレーム到着時のコールバック関数としてFramesQueuedを登録します。最後にStreamSetupでgrabberとsinkとGUI上の表示(display1)を紐づけてライブスタートを開始します。
[Select Device]ボタンを押下したときの処理
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);
}
}
ボタンを押すとカメラ選択ダイアログを表示します。現在のカメラと異なる機種が選ばれた場合のみ、現在の接続を停止・閉じてから新しいカメラを開き、表示の準備(StreamSetup)を行います。
[Deviece Properties]ボタンを押下したときの処理
private void btnDeviceProperties_Click(object sender, EventArgs e)
{
ic4.WinForms.Dialogs.ShowDevicePropertyDialog(grabber, this, ic4.WinForms.PropertyDialogFlags.AllowStreamRestart);
}
カメラのプロパティダイアログ画面を開きます。AllowStreamRestartを付けているので、設定した内容がリアルタイムで設定内容が反映されるようになっています。
タイマーでフレームが止まっていないか監視
// クラスのメンバ変数として以下を宣言
private ulong _lastFramesDelivered = 0;
private int _noChangeCount = 0; // 変化なしのカウント
private const int MaxNoChangeTicks =1; // 例:1回連続で変化がなければフラグを立てる
private static ulong currentFramesDelivered = 10;
private void timer1_Tick(object sender, EventArgs e)
{
if (grabber.IsDeviceValid && grabber.IsStreaming)
{
if (currentFramesDelivered == _lastFramesDelivered)
{
_noChangeCount++;
if (_noChangeCount >= MaxNoChangeTicks)
{
exceptionError();
_lastFramesDelivered = 0;
return;
}
}
else
{
_noChangeCount = 0;
}
_lastFramesDelivered = currentFramesDelivered;
}
}
ここでは「フレームが流れているか」をここでは1000ms(1秒)毎監視し、止まったら復旧処理(exceptionError)を呼んできます。FramesQueued側で更新している最新フレーム番号currentFramesDeliveredを、タイマー(timer1_Tick)で定期チェックします。前回記録_lastFramesDeliveredと同じなら「増えていない=新規フレームなし」とみなし、復旧処理(exceptionError)を呼び出します。最後に今回のフレーム番号を_lastFramesDeliveredに記録して次回比較に使います。
FrameQueuedのコールバック関数
static void FramesQueued(object sender, EventArgs e)
{
// TryPopOutputBufferはキューが空なら false を即返します。
// 画像を取得したら、最後に必ず Dispose()してバッファを破棄してください。
while (sink.TryPopOutputBuffer(out ic4.ImageBuffer buffer))
{
try
{
// カメラ側で付いたフレーム連番(オーバーフローや開始値は機種依存)
currentFramesDelivered = buffer.MetaData.DeviceFrameNumber;
// 受け取った枚数のカウンタを更新(多スレッドで触るなら Interlocked.Increment などを検討)
counter++;
}
finally
{
// 例外があっても必ず破棄
buffer.Dispose();
}
}
}
この関数は、カメラから新しいフレームが届いたときに呼ばれます。出力キューに残っているフレームをTryPopOutputBufferで順番に取り出し、出力キューが空ならwhile文を抜けだすことができます。取り出した bufferからbuffer.MetaData.DeviceFrameNumberでフレーム番号を読み取りcurrentFramesDeliveredに反映します。なお、currentFramesDeliveredは先述のtimer1_Tickの監視で「配信が止まっていないか」を判定するために使います。
デバイスロストイベント
private void Grabber_DeviceLost(object sender, EventArgs e)
{
// もしまだライブスタート中なら止める
if (grabber.IsStreaming)
grabber.AcquisitionStop();
// デバイスが開いていれば一旦閉じる(状態を初期に戻す)
if (grabber.IsDeviceOpen)
grabber.DeviceClose();
// 念のため Grabber を作り直す(古い内部状態を引きずらないため)
grabber = new ic4.Grabber();
// USBの抜き差し直後はOS側でデバイスがまだ見つからないことがある。
// DeviceOpenFromStateが成功するまで短い待ちを挟みながらリトライする。
while (!grabber.IsDeviceValid)
{
try
{
// 保存しておいた状態ファイルから再オープン(デバイス+設定を一括復元)
grabber.DeviceOpenFromState("device.xml");
// 再オープン後、切断検知のイベントを再登録(新しい Grabber に対して)
grabber.DeviceLost += Grabber_DeviceLost;
LogToCsv("Grabber_DeviceLost 成功");
}
catch
{
// 失敗時はOS側でデバイス認識するまで待つ(100ms〜適宜調整)
System.Threading.Thread.Sleep(100);
}
}
// 再オープンが成功したら、ストリームを再設定
grabber.StreamSetup(sink, display1);
}
USB抜けなどでカメラが切断されたときに呼び出されるコールバック関数で自動的に復旧しています。 まず開いている接続をDeviceClose()で確実に閉じ、new Grabber()で内部状態をリセットし、OSがデバイスを再認識するまで待ちながら、DeviceOpenFromState("device.xml")で最初に開いたカメラを繰り返し開きなおします。カメラをオープン出来たら新しいGrabberに対して再度DeviceLostを登録し、最後にStreamSetup(sink, display1)でライブスタートを開始します。
デバイスリセットの処理(USBのみ)
private void exceptionError()
{
if (grabber.IsStreaming)
grabber.AcquisitionStop();
if(grabber.IsDeviceOpen)
grabber.DeviceClose();
//カメラ再オープンする
grabber.DeviceOpenFromState("device.xml");
if (!grabber.IsDeviceValid)
{
LogToCsv("exceptionError DeviceOpen 失敗: デバイスが見つかりません。");
return;
}
grabber.StreamSetup(sink);
System.Threading.Thread.Sleep(100);
// デバイスリセットをする
try
{
grabber.DevicePropertyMap.ExecuteCommand("DeviceReset");
if (grabber.IsDeviceOpen) grabber.DeviceClose();
}
catch
{
}
System.Threading.Thread.Sleep(1000);
grabber = new ic4.Grabber();
try
{
while (!grabber.IsDeviceValid)
{
try
{
grabber.DeviceOpenFromState("device.xml");
grabber.DeviceLost += Grabber_DeviceLost;
LogToCsv("DeviceOpen 成功");
}
catch
{
System.Threading.Thread.Sleep(100);
}
}
grabber.StreamSetup(sink, display1);
LogToCsv("exceptionError LiveStart 再開");
}
catch (Exception reconnectEx)
{
LogToCsv("exceptionError 再接続中にエラー: " + reconnectEx.Message);
}
}
private string logFilePath = "camera_log.csv";
void LogToCsv(string message)
{
string line = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff},{message}";
File.AppendAllText(logFilePath, line + Environment.NewLine, new UTF8Encoding(true));
}
この関数は、OS上ではカメラが見えているのにソフト側でフレームが進まないときの復旧手順です。まず取り込みを止めて接続を閉じ、保存済みの状態ファイル(device.xml)から開き直します。続いて一度StreamSetupでカメラを開いてから、カメラ本体にDeviceResetを実行しデバイスリセットを実行します。1秒ほど待ってからGrabberを作り直し、OSがカメラを再認識するまで100ms間隔で待ちながらDeviceOpenFromStateでカメラを開きなおします。再接続できたらDeviceLostを再登録し、StreamSetup(sink, display1)でライブスタートを開始します。なお、LogToCsvは時刻付きメッセージを1行ずつCSVに追記する簡単なロガーです。 このようにして、USB接続のキャプチャデバイスが一時的にフリーズした場合でも、自動的に復旧処理を行うことが可能になります。