MENOU Imaging Plugin SDK API ドキュメント
概要
MENOU Imaging Plugin SDK は MENOU-RN のプラグイン機能に接続するための撮像アプリケーション作成 I/F を提供する .NET ライブラリです。
対象プラットフォーム
- .NET 8.0
- .NET 6.0
- .NET Core 3.1
- .NET Framework 4.7.2
- .NET Standard 2.0
動作環境
- OS : Windows10 64bit (バージョン 1809 以降), Windows11
提供形式
MENOU Imaging Plugin SDK は Nuget パッケージ形式で配布されます。
クイックスタート
インストール
MENOU Imaging Plugin SDK は 以下のパッケージソースリポジトリに公開されている Nuget パッケージをインストールするだけで利用できます。
MENOU Imaging Plugin SDK パッケージソース URL
Visual Studio におけるパッケージソースの追加方法については、以下をご確認ください。
Nuget パッケージのインストール
「Menou.VI.SDK.Imaging.Plugin」パッケージを実行プロジェクトにインストールする必要があります。
Visual Studio Nuget パッケージマネージャー上で、パッケージソースを MENOU SDK パッケージソースに設定し、「Menou.VI.SDK」パッケージを検索、インストールしてください。
撮像アプリケーションを作成する
C#のコンソールアプリプロジェクトを新規作成し、Windowsアプリケーションを指定します。
エリアセンサカメラを使用する場合と、ラインセンサカメラを使用する場合で実装方法が異なります。 それぞれのカメラごとに MENOU Plugin SDK を利用した撮像アプリケーションの実装例を示します。
エリアセンサカメラ時
// Program.cs
// Copyright (c) 2023 MENOU. All rights reserved.
using System.Runtime.InteropServices;
using OpenCvSharp;
/// <summary>
/// プラグインデバイスのテストのホスト部(デバイス側)
/// </summary>
internal class Program
{
#region field
/// <summary>
/// カメラID
/// </summary>
private const int CameraId = 0;
/// <summary>
/// 撮像デバイス。
/// </summary>
private static VideoCapture? CaptureDevice = null;
/// <summary>
/// 撮像失敗時のエラーコード。
/// 0は正常を意味するため、0以外を指定する。
/// </summary>
private static readonly int CaptureErrorCode = -1;
#endregion
public static async Task Main(string[] args)
{
var name = args[0]; // 画像転送用のメモリマップ名
var sizeName = args[1]; // 画像サイズ転送用のメモリマップ名
var appDataDirectory = args[2]; // アプリケーションデータディレクトリ(ログ保存用)
using var pluginImageDeviceCommunicator = new PluginImageDeviceCommunicator(name, sizeName, appDataDirectory);
// 撮像デバイス初期化
CaptureDevice = new VideoCapture();
// カメラオープン
// カメラIDが重複しないようにCameraIdを設定すること。
if (CaptureDevice?.Open(CameraId) != true)
{
return;
}
// 撮像要求時のイベント登録。
pluginImageDeviceCommunicator.CaptureRequested += OnCaptureRequested;
// 終了要求時のイベント登録
pluginImageDeviceCommunicator.TerminateRequested += OnTerminateRequested;
// 開始
await pluginImageDeviceCommunicator.StartAsync();
}
/// <summary>
/// 撮像要求時の処理
/// このメソッドはサンプルコードですので、適宜変更してください。
/// </summary>
/// <param name="sender"></param>
/// <param name="eventArgs"></param>
private static void OnCaptureRequested(object? sender, CaptureEventArgs eventArgs)
{
using var frame = new Mat();
// 撮像
CaptureDevice?.Read(frame);
if (!frame.Empty()) // 撮像成功時
{
eventArgs.ImageWidth = frame.Width;
eventArgs.ImageHeight = frame.Height;
eventArgs.ImageChannels = frame.Channels();
var data = new byte[eventArgs.ImageWidth * eventArgs.ImageHeight * eventArgs.ImageChannels];
Marshal.Copy(frame.Data, data, 0, data.Length);
eventArgs.ImageBytes = data;
}
else // 撮像失敗時
{
// エラーコードを指定する
eventArgs.ErrorCode = CaptureErrorCode;
}
}
/// <summary>
/// 終了要求時の処理。
/// このメソッドはサンプルコードですので、適宜変更してください。
/// </summary>
/// <param name="sender"></param>
/// <param name="eventArgs"></param>
private static void OnTerminateRequested(object? sender, EventArgs eventArgs)
{
// 撮像デバイスの解放
DisposeCaptureDevice();
}
/// <summary>
/// 撮像デバイスの解放処理。
/// </summary>
private static void DisposeCaptureDevice()
{
CaptureDevice?.Dispose();
CaptureDevice = null;
}
}
MENOU Plugin SDK I/F を提供する名前空間をインポートします。
using Menou.VI.Core.Imaging.Plugin;
Plugin と MENOU-RN を通信する PluginImageDeviceCommunicator オブジェクトを生成します。
var name = args[0]; // 画像転送用のメモリマップ名 var sizeName = args[1]; // 画像サイズ転送用のメモリマップ名 var appDataDirectory = args[2]; // アプリケーションデータディレクトリ(ログ保存用) using var pluginImageDeviceCommunicator = new PluginImageDeviceCommunicator(name, sizeName, appDataDirectory);
PluginImageDeviceCommunicator オブジェクトは不要になったタイミングで適宜解放(Dispose)してください。
カメラを初期化、オープンします。以下はUVCカメラのオープン例です。
CaptureDevice = new VideoCapture(); CaptureDevice?.Open(CameraId);
撮像要求時に実行されるイベントを登録します。
pluginImageDeviceCommunicator.CaptureRequested += OnCaptureRequested;
撮像要求時処理では、撮像を行い、取得した撮像画像データおよび幅、高さ、チャンネル情報を CaptureEventArgs オブジェクトにセットします。
撮像に失敗した時は、任意のエラーコード(0以外)をセットします。
終了要求時に実行されるイベントを登録します。
pluginImageDeviceCommunicator.TerminateRequested += OnTerminateRequested;
終了要求時処理ではカメラのクローズ処理など、オブジェクトの解放処理を行います。
MENOU-RNとの通信を非同期で開始します。
pluginImageDeviceCommunicator.StartAsync();
ラインセンサカメラ時
// Copyright (c) 2024 MENOU. All rights reserved.
using Menou.VI.Core.Imaging.Plugin.LineCamera;
using Menou.VI.Core.Imaging.Plugin.LineCamera.OmronSentech.Test.Host;
using Menou.VI.Core.Imaging.Plugin.LineCamera.OmronSentech.Test.Host.Camera;
using Menou.VI.Core.Logging;
using System.Collections.Concurrent;
/// <summary>
/// プラグインデバイスのテストのホスト部(デバイス側)
/// </summary>
internal class Program
{
/// <summary>
/// ラインセンサカメラ撮像設定。
/// </summary>
private static LineCameraCaptureConfig? Config;
/// <summary>
/// 撮像デバイス。
/// </summary>
private static GigeCamControl? GigeCaptureDevice = null;
/// <summary>
/// エラーコード。
/// 0は正常、1は撮像画像なし、を意味するため、0,1以外を指定する。
/// </summary>
private static readonly int OpenErrorCode = -1;
private static readonly int PrmErrorCode = -2;
private static readonly int CaptureErrorCode = -3;
/// <summary>
/// 撮像画像一覧。
/// </summary>
private static ConcurrentQueue<CaptureImageEventArgs>? CaptureImages;
public static async Task Main(string[] args)
{
var name = args[0]; // メモリマップ名
var appDataDirectory = args[1]; // アプリケーションデータディレクトリ(ログ保存用)
var parentProcessIdStr = args[2]; // 呼び出し元のプロセスIDの文字列。
using var pluginImageDeviceCommunicator = new PluginLineCameraImageDeviceCommunicator(name, appDataDirectory, parentProcessIdStr);
CaptureImages = new ConcurrentQueue<CaptureImageEventArgs>();
// 初期化要求時のイベント登録。
pluginImageDeviceCommunicator.InitializeRequested += OnInitializeRequested;
// 撮像要求時のイベント登録。
pluginImageDeviceCommunicator.CaptureRequested += OnCaptureRequested;
// 撮像画像取得要求時のイベント登録。
pluginImageDeviceCommunicator.CaptureImageRequested += OnCaptureImageRequested;
// 撮像停止要求時のイベント登録。
pluginImageDeviceCommunicator.CaptureStopRequested += OnCaptureStopRequested;
// 終了要求時のイベント登録
pluginImageDeviceCommunicator.TerminateRequested += OnTerminateRequested;
// 開始
await pluginImageDeviceCommunicator.StartAsync();
}
/// <summary>
/// 初期化要求時の処理。
/// このメソッド内でカメラの初期化、カメラのオープン処理を行ってください。
/// </summary>
/// <param name="sender"></param>
/// <param name="initializeEventArgs">初期化イベント引数。</param>
/// <exception cref="NotImplementedException"></exception>
private static void OnInitializeRequested(object? sender, InitializeEventArgs initializeEventArgs)
{
if (initializeEventArgs == null)
throw new ArgumentNullException(nameof(initializeEventArgs));
// 撮像デバイス初期化
GigeCaptureDevice = new GigeCamControl();
// 撮像完了時のイベントを登録。
GigeCaptureDevice!.CaptureEndEvent += CaptureEndEvent;
// カメラオープン
string ipAddress = initializeEventArgs.IpAddress;
if (GigeCaptureDevice!.CameraOpen(ipAddress) != true)
{
LogManager.Logger.Error("カメラオープンエラー。");
// エラーコードを指定する
initializeEventArgs.ErrorCode = OpenErrorCode;
}
else // カメラオープン成功時。
{
CameraStop(); // カメラ停止。
Config = new LineCameraCaptureConfig(initializeEventArgs.CaptureCount, initializeEventArgs.ImageHeight, initializeEventArgs.OverlapImageHeight); // 設定取得。
try
{
GigeCaptureDevice?.CameraConfig(Config); // カメラ設定
}
catch (Exception ex)
{
LogManager.Logger.Error("パラメータ設定エラー", ex);
initializeEventArgs.ErrorCode = PrmErrorCode;
return;
}
try
{
CameraStart(); // カメラ開始
}
catch (Exception ex)
{
LogManager.Logger.Error("取り込み開始エラー", ex);
initializeEventArgs.ErrorCode = CaptureErrorCode;
return;
}
}
}
/// <summary>
/// 撮像要求時の処理。
/// </summary>
/// <param name="sender"></param>
/// <param name="eventArgs"></param>
private static void OnCaptureRequested(object? sender, CaptureEventArgs eventArgs)
{
LogManager.Logger.Information("撮像開始。");
GigeCaptureDevice?.Trigger();
LogManager.Logger.Information("撮像開始完了。");
}
/// <summary>
/// 撮像画像取得要求時の処理。
/// </summary>
/// <param name="sender"></param>
/// <param name="eventArgs"></param>
private static void OnCaptureImageRequested(object? sender, CaptureImageEventArgs eventArgs)
{
// 撮像画像が取得できない場合は画像なしのエラーとして終了。
if (!CaptureImages!.TryDequeue(out CaptureImageEventArgs? captureImage))
{
eventArgs.ErrorCode = CaptureImageEventArgs.NoImageError;
return;
}
if (captureImage!.ErrorCode == 0) // 撮像成功時
{
LogManager.Logger.Debug($"撮像成功。幅:{captureImage.ImageWidth}, 高さ:{captureImage.ImageHeight}, チャンネル:{captureImage.ImageChannels}, 深度:{captureImage.ImageDepth}");
eventArgs.ImageWidth = captureImage.ImageWidth;
eventArgs.ImageHeight = captureImage.ImageHeight;
eventArgs.ImageChannels = captureImage.ImageChannels;
eventArgs.ImageBytes = captureImage.ImageBytes;
eventArgs.ImageDepth = captureImage.ImageDepth; // 8bitグレースケールまたは24bitカラー画像の場合は1を指定する。
// 取り込み成功 データ作成
// オーバーラップ部
int olcount = 0;
if (OverLap != null) olcount = eventArgs.ImageWidth * (int)GigeCaptureDevice!.Overlap * eventArgs.ImageChannels * eventArgs.ImageDepth;
int imglen = eventArgs.ImageWidth * eventArgs.ImageHeight * eventArgs.ImageChannels * eventArgs.ImageDepth;
byte[] imgdata = new byte[imglen + olcount];
OverLap!.CopyTo(imgdata, 0); // 前回画像先頭に挿入
Array.Copy(eventArgs.ImageBytes, 0, imgdata, olcount, imglen); // メイン画像コピー
Array.Copy(eventArgs.ImageBytes, imglen - olcount, OverLap, 0, olcount); // 次回用に画像保存
eventArgs.ImageBytes = imgdata;
eventArgs.ImageHeight = eventArgs.ImageHeight + (int)GigeCaptureDevice!.Overlap;
}
else // 撮像失敗時
{
LogManager.Logger.Error($"撮像異常。エラーコード:{CaptureErrorCode} 幅:{captureImage.ImageWidth}, 高さ:{captureImage.ImageHeight}, チャンネル:{captureImage.ImageChannels}, 深度:{captureImage.ImageDepth}");
// エラーコード送信
eventArgs.ErrorCode = CaptureErrorCode;
}
}
/// <summary>
/// 撮像停止要求時の処理。
/// このメソッド内でカメラの停止処理を行ってください。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void OnCaptureStopRequested(object? sender, CaptureStopEventArgs eventArgs)
{
CameraStop();
if (CaptureImages != null)
{
while (true)
{
if (!CaptureImages.TryDequeue(out _))
{ // 撮影停止時にキューが残っていれば空にする
break;
}
}
}
try
{
// 撮像要求時に即座に撮像を実行できるようカメラを再始動。
CameraStart();
}
catch (Exception ex)
{
LogManager.Logger.Error("取り込み開始エラー", ex);
eventArgs.ErrorCode = CaptureErrorCode;
return;
}
}
/// <summary>
/// 終了要求時の処理。
/// このメソッド内でカメラの解放処理を行ってください。
/// </summary>
/// <param name="sender"></param>
/// <param name="eventArgs"></param>
private static void OnTerminateRequested(object? sender, TerminateEventArgs eventArgs)
{
// 撮像デバイスの解放
DisposeCaptureDevice();
}
/// <summary>
/// 撮像デバイスの解放処理。
/// </summary>
private static void DisposeCaptureDevice()
{
CameraStop(); //カメラ停止
GigeCaptureDevice?.CameraClose(); // カメラのリソース解放
}
private static int nowcnt = 0;
/// <summary>
/// 撮像転送キュー作成。
/// </summary>
/// <returns>撮像画像一覧。</returns>
private static ConcurrentQueue<CaptureImageEventArgs> Capture(ImageEventArgs frame)
{
var capturedImages = new ConcurrentQueue<CaptureImageEventArgs>();
var eventArgs = new CaptureImageEventArgs();
if (frame != null) // 撮像成功時
{
if (frame.ErrorCode == 0)
{
eventArgs.ImageWidth = frame.Width;
eventArgs.ImageHeight = frame.Height;
eventArgs.ImageChannels = frame.Channels;
eventArgs.ImageBytes = frame.Data;
eventArgs.ImageDepth = frame.Depth;
nowcnt = frame.FrameID;
}
else
{
eventArgs.ErrorCode = CaptureErrorCode;
LogManager.Logger.Error(frame.ErrorMsg, null);
}
}
else // 撮像失敗時
{
// カメラ初期化または撮像異常時はエラーコード(0以外の値)をセットし、クライアントアプリに通知する。
eventArgs.ErrorCode = CaptureErrorCode;
LogManager.Logger.Error("撮像処理異常。");
}
capturedImages.Enqueue(eventArgs);
return capturedImages;
}
/// <summary>
/// 撮像完了のコールバック
/// </summary>
/// <param name="sender"></param>
/// <param name="frame"></param>
private static void CaptureEndEvent(object? sender, ImageEventArgs frame)
{
LogManager.Logger.Debug("撮像完了コールバック処理開始。");
foreach (CaptureImageEventArgs captureImage in Capture(frame))
{
CaptureImages!.Enqueue(captureImage); // 転送キューに追加
}
LogManager.Logger.Debug("撮像完了コールバック処理完了。");
}
private static byte[]? OverLap = null;
/// <summary>
/// カメラ開始。
/// </summary>
private static void CameraStart()
{
OverLap = new byte[(int)GigeCaptureDevice!.Width * GigeCaptureDevice!.Overlap * 3 * 2]; // オーバーラップ領域初期化 カラー・深度最大領域
if (Config?.CaptureCount == 1)
{
// 1枚のみ撮像時。
GigeCaptureDevice?.CaptureSnap();
}
else
{
GigeCaptureDevice?.CaptureStart(); // カメラ再開
}
}
/// <summary>
/// カメラ停止。
/// </summary>
private static void CameraStop()
{
if (GigeCaptureDevice?.IsRun == true)
{
GigeCaptureDevice?.CaptureStop(); //カメラ停止
}
}
}
namespace Menou.VI.Core.Imaging.Plugin.LineCamera.OmronSentech.Test.Host
{
/// <summary>
/// ラインセンサカメラ撮像設定クラス。
/// </summary>
public class LineCameraCaptureConfig
{
/// <summary>
/// 撮像数。
/// 0指定時は無限長撮像。
/// </summary>
public int CaptureCount { get; set; }
/// <summary>
/// 画像の高さ(px)。
/// </summary>
public int ImageHeight { get; set; }
/// <summary>
/// 重複する画像の高さ(px)。
/// </summary>
public int OverlapImageHeight { get; set; }
/// <summary>
/// コンストラクタ。
/// </summary>
/// <param name="captureCount">撮像数。</param>
/// <param name="imageHeight">画像の高さ(px)。</param>
/// <param name="overlapImageHeight">重複する画像の高さ(px)。</param>
public LineCameraCaptureConfig(int captureCount, int imageHeight, int overlapImageHeight)
{
this.CaptureCount = captureCount;
this.ImageHeight = imageHeight;
this.OverlapImageHeight = overlapImageHeight;
}
}
}
MENOU Plugin SDK I/F を提供する名前空間をインポートします。
using Menou.VI.Core.Imaging.Plugin.LineCamera;
Plugin と MENOU-RN を通信する PluginLineCameraImageDeviceCommunicator オブジェクトを生成します。
var name = args[0]; // メモリマップ名 var appDataDirectory = args[1]; // アプリケーションデータディレクトリ(ログ保存用) var parentProcessIdStr = args[2]; // 呼び出し元のプロセスIDの文字列。 using var pluginImageDeviceCommunicator = new PluginLineCameraImageDeviceCommunicator(name, appDataDirectory, parentProcessId);
PluginLineCameraImageDeviceCommunicator オブジェクトは不要になったタイミングで適宜解放(Dispose)してください。
カメラ初期化時に実行されるイベントを登録します。
pluginImageDeviceCommunicator.InitializeRequested += OnInitializeRequested;
カメラ初期化要求時処理では、カメラオープン処理、カメラ設定処理を行います。
InitializeEventArgs オブジェクト にて MENOU-RN で設定したカメラパラメータを取得できます。
撮像要求時に実行されるイベントを登録します。
pluginImageDeviceCommunicator.CaptureRequested += OnCaptureRequested;
撮像要求時処理では、カメラのトリガーを開始するなど、撮像を開始します。
撮像画像取得要求時に実行されるイベントを登録します。
pluginImageDeviceCommunicator.CaptureImageRequested += OnCaptureImageRequested;
撮像画像取得要求時処理では、取得した撮像画像データおよび幅、高さ、チャンネル情報を CaptureImageEventArgs オブジェクトにセットします。
撮像に失敗した時は、任意のエラーコード(0、1以外)をセットします。
撮像画像がない場合は、エラーコードに NoImageError(1) をセットします。
撮像停止要求時に実行されるイベントを登録します。
pluginImageDeviceCommunicator.CaptureStopRequested += OnCaptureStopRequested;
撮像停止要求時では、カメラの撮像を停止します。
終了要求時に実行されるイベントを登録します。
pluginImageDeviceCommunicator.TerminateRequested += OnTerminateRequested;
終了処理ではカメラのクローズ処理など、オブジェクトの解放処理を行ってください。
MENOU-RNとの通信を非同期で開始します。
pluginImageDeviceCommunicator.StartAsync();
各 I/F の詳細については API リファレンス をご確認ください。