MENOU SDK MENOU SDK
MENOU SDK MENOU SDK
MENOUフォーラム

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

    https://menoufs.blob.core.windows.net/sdk/index.json

Visual Studio におけるパッケージソースの追加方法については、以下をご確認ください。

  • NuGet パッケージ マネージャーを使用して 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;
    }
}
  1. MENOU Plugin SDK I/F を提供する名前空間をインポートします。

     using Menou.VI.Core.Imaging.Plugin;
    
  2. 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)してください。

  3. カメラを初期化、オープンします。以下はUVCカメラのオープン例です。

     CaptureDevice = new VideoCapture();
     CaptureDevice?.Open(CameraId);
    
  4. 撮像要求時に実行されるイベントを登録します。

    pluginImageDeviceCommunicator.CaptureRequested += OnCaptureRequested;
    

    撮像要求時処理では、撮像を行い、取得した撮像画像データおよび幅、高さ、チャンネル情報を CaptureEventArgs オブジェクトにセットします。

    撮像に失敗した時は、任意のエラーコード(0以外)をセットします。

  5. 終了要求時に実行されるイベントを登録します。

    pluginImageDeviceCommunicator.TerminateRequested += OnTerminateRequested;
    

    終了要求時処理ではカメラのクローズ処理など、オブジェクトの解放処理を行います。

  6. 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;
        }
    }
}
  1. MENOU Plugin SDK I/F を提供する名前空間をインポートします。

     using Menou.VI.Core.Imaging.Plugin.LineCamera;
    
  2. 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)してください。

  3. カメラ初期化時に実行されるイベントを登録します。

    pluginImageDeviceCommunicator.InitializeRequested += OnInitializeRequested;
    

    カメラ初期化要求時処理では、カメラオープン処理、カメラ設定処理を行います。

    InitializeEventArgs オブジェクト にて MENOU-RN で設定したカメラパラメータを取得できます。

  4. 撮像要求時に実行されるイベントを登録します。

    pluginImageDeviceCommunicator.CaptureRequested += OnCaptureRequested;
    

    撮像要求時処理では、カメラのトリガーを開始するなど、撮像を開始します。

  5. 撮像画像取得要求時に実行されるイベントを登録します。

    pluginImageDeviceCommunicator.CaptureImageRequested += OnCaptureImageRequested;
    

    撮像画像取得要求時処理では、取得した撮像画像データおよび幅、高さ、チャンネル情報を CaptureImageEventArgs オブジェクトにセットします。

    撮像に失敗した時は、任意のエラーコード(0、1以外)をセットします。

    撮像画像がない場合は、エラーコードに NoImageError(1) をセットします。

  6. 撮像停止要求時に実行されるイベントを登録します。

    pluginImageDeviceCommunicator.CaptureStopRequested += OnCaptureStopRequested;
    

    撮像停止要求時では、カメラの撮像を停止します。

  7. 終了要求時に実行されるイベントを登録します。

    pluginImageDeviceCommunicator.TerminateRequested += OnTerminateRequested;
    

    終了処理ではカメラのクローズ処理など、オブジェクトの解放処理を行ってください。

  8. MENOU-RNとの通信を非同期で開始します。

    pluginImageDeviceCommunicator.StartAsync();
    

各 I/F の詳細については API リファレンス をご確認ください。

© Copyright 2021 MENOU Corp.