Plugin API ドキュメント
概要
MENOU SDK にて提供されるプラグイン機能を使用した撮像アプリケーションの実装方法について説明します。
プラグイン機能は MENOU-RN から外部の撮像アプリケーションを呼び出し、撮像アプリケーションで撮像した画像データを MENOU-RN に転送する機能です。
撮像アプリケーション実装方法
新規プロジェクト作成
C#のコンソールアプリプロジェクトを新規作成し、Windowsアプリケーションを指定します。
エリアセンサカメラを使用する場合と、ラインセンサカメラを使用する場合で実装方法が異なります。
エリアセンサカメラ時
// Program.cs
// Copyright (c) 2025 MENOU. All rights reserved.
using Menou.Menoute.Runtime.Plugin.AreaCamera;
using OpenCvSharp;
using System.Runtime.InteropServices;
/// <summary>
/// エリアカメラ用テストアプリ。
/// 任意のUVCカメラを使用する。
/// </summary>
internal class Program
{
/// <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;
private static async Task Main(string[] args)
{
#if DEBUG
// デバッガーによるプロセスアタッチがされるまで待つ。
while (!System.Diagnostics.Debugger.IsAttached)
{
Thread.Sleep(1);
}
#endif
var name = args[0]; // 画像転送用のメモリマップ名
var sizeName = args[1]; // 画像サイズ転送用のメモリマップ名
var appDataDirectory = args[2]; // アプリケーションデータディレクトリ(ログ保存用)
string? parentProcessIdStr = null;
if (args.Length > 3)
{
parentProcessIdStr = args[3]; // 呼び出し元のプロセスIDの文字列。
}
using var pluginImageDeviceCommunicator = new PluginImageDeviceCommunicator(name, sizeName, appDataDirectory, parentProcessIdStr);
// 撮像デバイス初期化
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, ICaptureEventArgs 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 I/F を提供する名前空間をインポートします。
using Menou.Menoute.Runtime.Plugin.AreaCamera;Plugin と MENOU-RN を通信する PluginImageDeviceCommunicator オブジェクトを生成します。
var name = args[0]; // 画像転送用のメモリマップ名 var sizeName = args[1]; // 画像サイズ転送用のメモリマップ名 var appDataDirectory = args[2]; // アプリケーションデータディレクトリ(ログ保存用) string? parentProcessIdStr = null; if (args.Length > 3) { parentProcessIdStr = args[3]; // 呼び出し元のプロセスIDの文字列。 } using var pluginImageDeviceCommunicator = new PluginImageDeviceCommunicator(name, sizeName, appDataDirectory, parentProcessIdStr);PluginImageDeviceCommunicator オブジェクトは不要になったタイミングで適宜解放(Dispose)してください。
カメラを初期化、オープンします。以下はUVCカメラのオープン例です。
CaptureDevice?.Open(CameraId)撮像要求時に実行されるイベントを登録します。
pluginImageDeviceCommunicator.CaptureRequested += OnCaptureRequested;撮像要求時処理では、撮像を行い、取得した撮像画像データおよび幅、高さ、チャンネル情報を ICaptureEventArgs オブジェクトにセットします。
撮像に失敗した時は、任意のエラーコード(0以外)をセットします。
終了要求時に実行されるイベントを登録します。
pluginImageDeviceCommunicator.TerminateRequested += OnTerminateRequested;終了要求時処理ではカメラのクローズ処理など、オブジェクトの解放処理を行います。
MENOU-RNとの通信を非同期で開始します。
await pluginImageDeviceCommunicator.StartAsync();
ラインセンサカメラ時
// Copyright (c) 2025 MENOU. All rights reserved.
using LineCamera.Omronsentech.Camera;
using Menou.Menoute.Runtime.Plugin.LineCamera;
using Menou.VI.Core.Logging;
using System.Collections.Concurrent;
namespace LineCamera.Omronsentech;
/// <summary>
/// プラグインデバイスのテストアプリ。
/// Omron Sentech社製ラインセンサカメラ用。
/// </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<CapturedImage>? CaptureImages;
public static async Task Main(string[] args)
{
#if DEBUG
// デバッガーによるプロセスアタッチがされるまで待つ。
while (!System.Diagnostics.Debugger.IsAttached)
{
Thread.Sleep(1);
}
#endif
var name = args[0]; // メモリマップ名
var appDataDirectory = args[1]; // アプリケーションデータディレクトリ(ログ保存用)
var parentProcessIdStr = args[2]; // 呼び出し元のプロセスIDの文字列。
using var pluginImageDeviceCommunicator = new PluginLineCameraImageDeviceCommunicator(name, appDataDirectory, parentProcessIdStr);
CaptureImages = new ConcurrentQueue<CapturedImage>();
// 初期化要求時のイベント登録。
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, IInitializeEventArgs 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, ICaptureEventArgs 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, ICaptureImageEventArgs eventArgs)
{
// 撮像画像が取得できない場合は画像なしのエラーとして終了。
if (!CaptureImages!.TryDequeue(out CapturedImage? captureImage))
{
eventArgs.ErrorCode = CapturedImage.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;
// 確認画像保存用
//if (FrameCnt <= 2)
//{
// Bitmap m_Bitmap = null;
// if (captureImage.ImageChannels == 3)
// {
// m_Bitmap = new Bitmap((int)captureImage.ImageWidth, (int)captureImage.ImageHeight, PixelFormat.Format24bppRgb);
// }
// else
// {
// m_Bitmap = new Bitmap((int)captureImage.ImageWidth, (int)captureImage.ImageHeight, PixelFormat.Format8bppIndexed);
// ColorPalette palette = m_Bitmap.Palette;
// for (int i = 0; i < 256; ++i) palette.Entries[i] = Color.FromArgb(i, i, i);
// m_Bitmap.Palette = palette;
// }
// BitmapData bmpData = m_Bitmap.LockBits(new Rectangle(0, 0, m_Bitmap.Width, m_Bitmap.Height), ImageLockMode.WriteOnly, m_Bitmap.PixelFormat);
// // Place the pointer to the buffer of the bitmap.
// IntPtr ptrBmp = bmpData.Scan0;
// Marshal.Copy(captureImage.ImageBytes, 0, ptrBmp, captureImage.ImageBytes.Length);
// m_Bitmap.UnlockBits(bmpData);
// string filename = string.Format("{0}.bmp", FrameCnt);
// m_Bitmap.Save(filename);
//}
}
else // 撮像失敗時
{
LogManager.Logger.Error($"撮像異常。エラーコード:{CaptureErrorCode} 幅:{captureImage.ImageWidth}, 高さ:{captureImage.ImageHeight}, チャンネル:{captureImage.ImageChannels}, 深度:{captureImage.ImageDepth}");
// エラーコード送信
eventArgs.ErrorCode = CaptureErrorCode;
}
}
/// <summary>
/// 撮像停止要求時の処理。
/// このメソッド内でカメラの停止処理を行ってください。
/// </summary>
/// <param name="sender"></param>
/// <param name="eventArgs"></param>
private static void OnCaptureStopRequested(object? sender, ICaptureStopEventArgs 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, ITerminateEventArgs eventArgs)
{
// 撮像デバイスの解放
DisposeCaptureDevice();
}
/// <summary>
/// 撮像デバイスの解放処理。
/// </summary>
private static void DisposeCaptureDevice()
{
CameraStop(); //カメラ停止
GigeCaptureDevice?.CameraClose(); // カメラのリソース解放
}
private static int nowcnt = 0;
/// <summary>
/// 撮像転送キュー作成。
/// </summary>
/// <returns>撮像画像一覧。</returns>
private static ConcurrentQueue<CapturedImage> Capture(ImageEventArgs frame)
{
var capturedImages = new ConcurrentQueue<CapturedImage>();
var capturedImage = new CapturedImage();
if (frame != null) // 撮像成功時
{
if (frame.ErrorCode == 0)
{
capturedImage.ImageWidth = frame.Width;
capturedImage.ImageHeight = frame.Height;
capturedImage.ImageChannels = frame.Channels;
capturedImage.ImageBytes = frame.Data;
capturedImage.ImageDepth = frame.Depth;
nowcnt = frame.FrameID;
}
else
{
capturedImage.ErrorCode = CaptureErrorCode;
LogManager.Logger.Error(frame.ErrorMsg, null);
}
}
else // 撮像失敗時
{
// カメラ初期化または撮像異常時はエラーコード(0以外の値)をセットし、クライアントアプリに通知する。
capturedImage.ErrorCode = CaptureErrorCode;
LogManager.Logger.Error("撮像処理異常。");
}
capturedImages.Enqueue(capturedImage);
return capturedImages;
}
/// <summary>
/// 撮像完了のコールバック
/// </summary>
/// <param name="sender"></param>
/// <param name="frame"></param>
private static void CaptureEndEvent(object? sender, ImageEventArgs frame)
{
LogManager.Logger.Debug("撮像完了コールバック処理開始。");
foreach (var 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(); //カメラ停止
}
}
}
// Copyright (c) 2025 MENOU. All rights reserved.
namespace LineCamera.Omronsentech;
/// <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)
{
CaptureCount = captureCount;
ImageHeight = imageHeight;
OverlapImageHeight = overlapImageHeight;
}
}
// Copyright (c) 2025 MENOU. All rights reserved.
namespace LineCamera.Omronsentech;
/// <summary>
/// 撮像画像クラス。
/// </summary>
public class CapturedImage
{
/// <summary>
/// 画像取得できなかった場合のエラーコード。
/// </summary>
public const int NoImageError = 1;
/// <summary>
/// 取得画像の幅[px]。
/// </summary>
public int ImageWidth { get; set; }
/// <summary>
/// 取得画像の高さ[px]。
/// </summary>
public int ImageHeight { get; set; }
/// <summary>
/// 取得画像のチャンネル数。
/// </summary>
public int ImageChannels { get; set; }
/// <summary>
/// 取得画像の深度。
/// 8bit画像:1
/// 16bit画像:2
/// 32bit画像:4
/// </summary>
public int ImageDepth { get; set; }
/// <summary>
/// 画像データ。
/// </summary>
public byte[]? ImageBytes { get; set; }
/// <summary>
/// エラーコード。
/// </summary>
public int ErrorCode { get; set; }
}
前準備として、撮像アプリケーションのexeファイルが格納されるフォルダ内に "Config\ImagingApp.config" ファイルを作成し、以下の内容を記述してください。
{ "isLineCamera": true, "isValidInfiniteLength": true }"isLineCamera" はラインセンサカメラを使用する場合に true に設定してください。 "isValidInfiniteLength" は無限長撮像を有効にする場合に true に設定してください。
Plugin I/F を提供する名前空間をインポートします。
using Menou.Menoute.Runtime.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, parentProcessIdStr);PluginLineCameraImageDeviceCommunicator オブジェクトは不要になったタイミングで適宜解放(Dispose)してください。
カメラ初期化時に実行されるイベントを登録します。
pluginImageDeviceCommunicator.InitializeRequested += OnInitializeRequested;カメラ初期化要求時処理では、カメラオープン処理、カメラ設定処理を行います。
IInitializeEventArgs オブジェクト にて MENOU-RN で設定したカメラパラメータを取得できます。
撮像要求時に実行されるイベントを登録します。
pluginImageDeviceCommunicator.CaptureRequested += OnCaptureRequested;撮像要求時処理では、カメラのトリガーを開始するなど、撮像を開始します。
撮像画像取得要求時に実行されるイベントを登録します。
pluginImageDeviceCommunicator.CaptureImageRequested += OnCaptureImageRequested;撮像画像取得要求時処理では、取得した撮像画像データおよび幅、高さ、チャンネル情報を ICaptureImageEventArgs オブジェクトにセットします。
撮像に失敗した時は、任意のエラーコード(0、1以外)をセットします。
撮像画像がない場合は、エラーコードに NoImageError(1) をセットします。
撮像停止要求時に実行されるイベントを登録します。
pluginImageDeviceCommunicator.CaptureStopRequested += OnCaptureStopRequested;撮像停止要求時では、カメラの撮像を停止します。
終了要求時に実行されるイベントを登録します。
pluginImageDeviceCommunicator.TerminateRequested += OnTerminateRequested;終了処理ではカメラのクローズ処理など、オブジェクトの解放処理を行ってください。
MENOU-RNとの通信を非同期で開始します。
await pluginImageDeviceCommunicator.StartAsync();
各 I/F の詳細については API リファレンス をご確認ください。