LoginSignup
7
2

More than 1 year has passed since last update.

OpenCV4系をHSP3で使いたい!~HSP用DLLヘッダ定義と日本語機械翻訳リファレンスの作成~

Posted at

この記事はOpenCV Advent Calendar 2021の18日目の記事です。

記事が大変長いので、今北産業でまとめると...

HSP3 で OpenCV4系を使用できるように、 同じOpenCV4系のラッパーである OpenCvSharp を活用し、 HSP用のDLLヘッダ定義と日本語機械翻訳リファレンス(C++/HSP用)を作成した

という、お話しになります。DLLヘッダ定義などの生成物のダウンロードはこちら

対象読者

  • HSP3 で OpenCV4系を使いたい方
  • HSP3 で他言語にあるフレームワーク/ライブラリを使用できるように移植したい方
  • OpenCV が標準サポートしていない言語で使用できるように移植したい方

はじめに

2021年にリリースされた最新版の HSP 3.6 には、OpenCV の機能を使用できる hspcv.dll が同梱されています。ところが、OpenCV のバージョンが 1.0 と古く、Webカメラのキャプチャが正しく動作しなかったり、ディープラーニングによる画像認識など、新しい OpenCV の機能を HSP から使用することができません。

本記事は、OpenCV4系を HSP で使用できるように、必要なDLL定義やリファレンスを移植した際の技術メモとなります。

環境

以下の環境で動作を確認しました。

使用ツール

今回作成したもの

OpenCV4系をHSPで使用するために、以下の作業を行いました。

  1. OpenCV4系の関数をHSPから使用できるように、HSP用DLLヘッダ定義ファイルを作成
    • 32bit/64bit版 HSPランタイムに対応する
    • 今回は CV_8UC3 などの定数値の定義は作成しない
  2. OpenCV(C++) のリファレンスを日本語化(機械翻訳)
  3. OpenCV(HSP) の HSP Docs Library用の日本語リファレンスを作成

1. HSP用DLLヘッダ定義ファイルを作成

今回は、.NET実装(ラッパー)である OpenCvSharp を活用してHSPへの移植を行いました。
以下が理由として挙げられます。

  • C++版 OpenCV 4系 は C形式のインターフェースが提供されておらず、HSP から直接使用するのが困難である。何かしらのラッパーを作成しないといけない。
  • 一方で、OpenCvSharp に OpenCvSharpExtern.dll が同梱されている。これは、C++版 OpenCV を C形式のインターフェースで DLL 化したものであり、これを活用することで HSP から容易に使用することが可能。 
  • OpenCvSharp が放置されず、定期的にメンテナンスされていること。
  • C# で OpenCvSharpExtern.dll の P/Invoke 定義が記述されているのもポイント。

以降は、OpenCvSharp を活用した、HSPのヘッダーファイルの作成手順を記述します。

OpenCvSharpExtern の DLL定義の調査

HSPでは外部DLLを使用する際に、以下のような外部関数定義をあらかじめ行う必要があります。

HSP上でのDLL定義例
#uselib "DLL名"
#func HSP上の関数名 "DLL内部の関数名" 引数パラメタ...

今回は、OpenCVSharpExtern.dll を使用しますので、DLL がエクスポートしている関数定義を 2,762 個記述する必要があります。

HSP上でのDLL定義例(OpenCVSharpExtern.dll)
#uselib "OpenCvSharpExtern32.dll"     // 32bit版
#func core_Mat_new1 "core_Mat_new1" var
#func core_Mat_new2 "core_Mat_new2" int,int,int,var 
// 以降、#func が 2,760 行分続きます...

しかも、HSP上の 変数名 や 関数名 は 59文字以下 という制限がありますが、ximgproc_segmentation_SelectiveSearchSegmentationStrategyMultiple_clearStrategies() のような 59文字 を大幅に超える関数が OpenCVSharpExtern.dll にてエクスポートされています。HSP上の関数定義では短くなるように加工する必要もあります。

HSP上でのDLL定義例(長すぎるので短くした関数の例)
#uselib "OpenCvSharpExtern32.dll"     // 32bit版
#func ximgproc_seg_SeleSchSegStratMultiple_clearStrategies "ximgproc_segmentation_SelectiveSearchSegmentationStrategyMultiple_clearStrategies" sptr

はい、手でなんかでやってられません!

幸いなことに、OpenCvSharp 内に OpenCvSharpExtern.dll がエクスポートしている関数の定義情報(P/Invoke定義)を記載した、以下のような C# のファイルが多数存在しています。

opencvsharp-master\src\OpenCvSharp\Internal\PInvoke\NativeMethods\core\NativeMethods_core_Mat.cs
[Pure, DllImport(DllExtern, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern ExceptionStatus core_Mat_new1(out IntPtr returnValue);

[Pure, DllImport(DllExtern, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern ExceptionStatus core_Mat_new2(int rows, int cols, int type, out IntPtr returnValue);

OpenCvSharp 内の P/Invoke定義が記述されたソースコードを活用して、HSP用のDLL定義に変換すれば良さそうです。

C# のソースコードのパース方法ですが、正規表現で頑張るという方法も考えましたが、今回は Microsoft.CodeAnalysis.CSharp を使用しました。HTMLのDOM操作の感覚でC#のソースコードのパースが行えるので、とても便利でした!

なお、変換部分のソースはごっちゃごっちゃしているので、現状非公開とさせていただきます。

作成したHSP用の OpenCvSharpExtern.dll 定義ファイルはこちらにあります。
また、変換にあたって作成した資料はこちらにまとめて置いてあります。

移植にあたってのビット数に関する注意点

基本的には HSP は 32bit アプリケーションですが、64bit 版 HSPランタイムもベータ版的扱いで存在します。両ビットを考慮して作成はしていますが、64bit版HSPランタイムに関しては 2021年12月現在、HSP側の制限事項が多いため、本移植が不完全な実装であることをご了承ください。

詳しくは省略しますが、Windows アプリケーションの32bit/64bitではポインタサイズや呼び出し規約が異なっています。その影響により、HSPからDLL内の関数を呼び出す際にビット数によって引数の数が異なる場合があります。特に OpenCV には構造体の値渡しを行うパラメータが多くあり、ビット数によってHSP上でのパラメータの指定方法に顕著な差が出てきます。

移植にあたっては、ビット数の差も考慮に入れる必要があります。
今回、OpenCVで使用される引数の型とビット数ごとのデータサイズをまとめた一覧を作成しました。
image.png

作成資料
- OpenCVで使用される引数型一覧.pdf
- OpenCVで使用される引数型一覧.xlsx

移植例

core_Mat_new3()を例にしますと、C++ 上ではパラメータが 5つ になります。
HSPの 32bit版 では 8つ になりますが、64bit版 では 5つ になります。

OpenCVSharpExtern_(C++)
struct MyCvScalar
{
    double val[4];
};

// 引数は5つ
CVAPI(ExceptionStatus) core_Mat_new3(int rows, int cols, int type, MyCvScalar scalar, cv::Mat **returnValue)
{
    BEGIN_WRAP
    *returnValue = new cv::Mat(rows, cols, type, cpp(scalar));
    END_WRAP
}
32bit版HSPの場合
#include "OpenCvSharpExtern32.as"
#define CV_8UC3             16

pMat = 0
ddim scalar, 4
scalar = 0.0, 0.0, 0.0, 0.0     // BGRA

// 引数は8つ
// #func global core_Mat_new3 "core_Mat_new3" int,int,int,double,double,double,double,var
core_Mat_new3 480, 640, CV_8UC3, scalar(0), scalar(1), scalar(2), scalar(3), pMat
64bit版HSPの場合
#include "hsp3_64.as"
#include "hspint64.as"
#include "OpenCvSharpExtern64.as"
#define CV_8UC3             16

pMat = int64(0)
ddim scalar, 4
scalar = 0.0, 0.0, 0.0, 0.0     // BGRA

// 引数は5つ
// #func global core_Mat_new3 "core_Mat_new3" int,int,int,var,var
core_Mat_new3 480, 640, CV_8UC3, scalar, pMat

2. OpenCV(C++) のリファレンスを日本語化(機械翻訳)

公式でもリファレンスが提供されていますが、英語のみとなっています。
また、OpenCV.jp による正確な日本語翻訳が提供されていますが、2.2系 と少し古くなっています。

インターネット上の OpenCV を使用した参考資料やソースコードは C++ や Python が多いので、HSPの利用者が移植の手助けになるように、OpenCV 4.5.3(C++) のリファレンスを日本語機械翻訳しました。
機械翻訳ですので文章の正確性は欠けますが、OpenCV でやりたいことの手がかりを日本語で探すという観点では有用と考えています。

OpenCV(C++) リファレンスの機械翻訳手順

今回は、DeepL Pro を使用して機械翻訳を実施しました。
無料版では翻訳可能な文字数に制限があり、リファレンスを丸ごと翻訳するのには向いていないため、有料版を使用しています。

OpenCV は Doxygen で出力したHTML形式リファレンスを提供していますが、DeepL は HTMLファイルをそのまま渡して翻訳させることができません。ですので、HTMLファイル内から翻訳すべき文字列を抽出し、DeepLにて翻訳したのちに、元のHTMLに翻訳結果を書き戻すという作業を実施しました。

image.png

HTMLのパースは AngleSharp を使用しました。

作成した対訳辞書は CSV形式 でこちらにあります。
なお、実際のHTMLパース処理、対訳辞書の作成機能、翻訳処理部分のソースは、現状非公開とさせていただきます。

作成したOpenCV 4.5.3のリファレンス(日本語機械翻訳)はこちら
image.png

3. OpenCV(HSP) の HSP Docs Library用の日本語リファレンスを作成

HSP には、HSP Docs Library (以下、HDL)という HSP のドキュメントやリファレンスをまとめて 検索・閲覧する仕組みがあり、専用のHS形式を作成することで検索対象として追加することができます。OpenCvSharp4HSP 用のHSファイルを作成して、HDL上でOpenCV4の関数を日本語で検索できるようにしてみました。
image.png

OpenCV(HSP) リファレンスの作成手順

OpenCV(C++)の日本語訳のリファレンスは、2. OpenCV(C++) のリファレンスを日本語化(機械翻訳) にて作成しておりますので、これを元ネタとしてHSP用のリファレンスを作成します。

ところが… 1つ大きな問題点があり、OpenCVのC++クラス名/関数名からHSP上の関数名を求めるには(逆の場合も同様)、OpenCvSharpExtern.dll 上の関数名 と OpenCVのC++クラス名の対応をマッピングした辞書(図中はC/C++マッピング辞書)が中間に必要になります。

image.png

そして、C/C++マッピング辞書を作成するには、実際に OpenCVSharpExtern 内の実装を確認しないといけません。
以下のような感じですべての関数が実装されていれば、素直に抽出できそうですが…

C側関数名 C++側クラス名/関数名/変数名
aruco_drawAxis()関数 cv::aruco クラス drawAxis()関数
OpenCVSharpExtern_(C++)
CVAPI(ExceptionStatus) aruco_drawAxis(
    cv::_InputOutputArray *image,
    cv::_InputArray *cameraMatrix,
    cv::_InputArray *distCoeffs,
    cv::_InputArray *rvec,
    cv::_InputArray *tvec,
    float length)
{
    BEGIN_WRAP
    cv::aruco::drawAxis(*image, *cameraMatrix, *distCoeffs, *rvec, *tvec, length);
    END_WRAP
}

以下のように、引数にクラスインスタンスを渡して処理をしている場合もあり、手動できちんと中身を確認しないといけません。

C側関数名 C++側クラス名/関数名/変数名
aruco_Dictionary_setMaxCorrectionBits()関数 cv::aruco::Dictionary クラス maxCorrectionBits 変数
OpenCVSharpExtern_(C++)
CVAPI(ExceptionStatus) aruco_Dictionary_setMaxCorrectionBits(cv::aruco::Dictionary *obj, int value)
{
    BEGIN_WRAP
    obj->maxCorrectionBits = value;
    END_WRAP
}

OpenCVSharpExtern.dll には 2,762 個の外部エクスポート関数があるんだよなぁ…
というわけで頑張って、全関数を手動で確認しました!!
image.png

作成資料
- 関数名マッピング一覧(日本語ヘルプ作成用).pdf
- 関数名マッピング一覧(日本語ヘルプ作成用).xlsx

作成したOpenCV 4.5.3のHSP用リファレンス(日本語機械翻訳)はこちら
image.png

実際に動かしてみた!

Darknet + YOLO による物体検出が HSP でもできるようになります!
少し前に Twitter で話題になりました、ウマ娘の画像をディープラーニングで判定してみた もHSP で出来るように!!

↓↓↓ HSP での実行例 ↓↓↓

画像上にテキストを表示する機能に不具合があるため、別ウィンドウ上に結果を表示しています。
サンプルコードはこちら
image.png
「ウマ娘 プリティーダービー」をプレイする際の配信ガイドライン に従い、スクリーンショット画像を利用しております。

お客様は本コンテンツから取り込んだゲームプレイの動画や静止画を、共有サイトに投稿することができます。

OpenCV 内部例外について

OpenCV 内部(OpenCVSharpExtern.dll)ではエラー時にC++例外(cv::Exception)を送出するようになっています。ところが、HSPのランタイム内部でもC++例外の仕組みを使用したエラー管理システムが存在していますが、例外の取得処理が catch-all になっているため、OpenCV内部で例外が送出されるとHSPのランタイムはシステムエラー扱いで強制終了してしまいます。
image.png

抜本的対策は、HSPランタイムの修正かOpenCVSharpExtern.dllにて例外を送出しないように修正することになります。今回は、例外時の詳細メッセージを取得することを行いたいので、Windows ベクトル化例外処理の仕組み(AddVectoredExceptionHandler)を活用して、HSPランタイムを修正することなく例外時のOpenCVのエラーメッセージを取得できるように対応してみました。
(スローされた例外をなかったことにはしていないので、結局強制終了してしまいますが…)

詳しい説明に関しましては省略しますが、以下 C++ソースをDLL化し、HSPで呼ばれるようにしました。ビルド済みのものはこちら

HSPError.cpp
// 例外発生時処理関数 (32bit版しか考慮していません)
static LONG WINAPI TopLevelExceptionFilter(EXCEPTION_POINTERS* pExceptionInfo)
{
    // https://devblogs.microsoft.com/oldnewthing/20100730-00/?p=13273
    const auto& eCode = (pExceptionInfo->ExceptionRecord)->ExceptionCode;

    // C++ 例外
    if ( eCode == 0xE06D7363)
    {
        // 32bit (64bitは別対応が必要)
        const auto& prm2 = (pExceptionInfo->ExceptionRecord)->ExceptionInformation[2];

        if (!prm2)
        {
            return EXCEPTION_EXECUTE_HANDLER;
        }

        const auto& a = (int*)prm2;
        const auto& b = a[3];
        const auto& c = (int*)b;
        const auto& d = c[1];
        const auto& e = (int*)d;
        const auto& f = e[1];
        const auto& g = (char*)f + 0x8 + 0x1;       // 64bit = 0x10 + 0x1

        char szName[256 + 1] = { 0 };
        ::UnDecorateSymbolName(g, szName, 256, UNDNAME_NO_ARGUMENTS);

        std::string strName(szName);
        if ( strName == "class cv::Exception")
        {
            // OpenCVっぽい?
            const auto& ex = reinterpret_cast <std::exception*>
                ((pExceptionInfo->ExceptionRecord)->ExceptionInformation[1]);
            ::MessageBoxA(::GetActiveWindow(), ex->what(), "OpenCV Exception!!", MB_OK | MB_ICONERROR);
            return EXCEPTION_EXECUTE_HANDLER;
        }
        else if (strName == "enum HSPERROR")
        {
            return EXCEPTION_CONTINUE_SEARCH;       // HSPエラーは転送する
        }
        else {
            // それ以外の C++ 例外
            ::MessageBoxA(::GetActiveWindow(), szName, "C++ Exception", MB_OK | MB_ICONERROR);
            return EXCEPTION_EXECUTE_HANDLER;
        }
    }

    return EXCEPTION_EXECUTE_HANDLER;
}

BOOL Register()
{
    ::AddVectoredExceptionHandler(0, TopLevelExceptionFilter);
    return TRUE;
}
HSPError32.hsp
#include "OpenCvSharpExtern32.as"
#include "hsperror_patch32.as"

// OpenCVの例外メッセージ取得するように登録
hsperror_patch_Register

// 例外を起こすような処理
pNet = 0
dnn_readNetFromDarknet_Windows "存在しないパス", "存在しないパス", pNet

OpenCV内部で例外スロー時、HSPエラーより先に例外メッセージを取得するように対応してみた図
image.png

生成物

アルファ版ということで、仕様/名称などが大きく変更される場合がございます。 予めご了承ください。定数値は未実装です。 また、64bit版のHSPランタイム対応は不完全です。

参考サイト

おわりに

盛りすぎたと若干後悔…

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2