設計方法言語に依存しないクロスプラットフォームコンピュータービジョンSDK:ハンズオンチュートリアル

(Cyrus Behroozi)(2020年10月21日)

最近、ベニスコンピュータビジョンのミートアップに参加する機会がありました。ご存じない方のために説明すると、これは Trueface が主催するイベントで、コンピュータービジョンの開発者と愛好家が、最先端のコンピュータービジョンの研究、アプリケーション、実践を紹介できます。チュートリアル。

この記事では、クロスプラットフォーム展開と最大の拡張性のために言語に依存しないコンピュータービジョンソフトウェア開発キット(SDK)を設計する方法についてのチュートリアルプレゼンテーションについて説明します。プレゼンテーションの

ライブレコーディング

を表示したい場合は、ここ。また、プロジェクト全体をオープンソース

にしたので、次のコンピュータービジョンプロジェクトのテンプレートとして自由に使用してください。

cyrusbehr / sdk_design

クロスプラットフォーム展開と最大の拡張性のために言語に依存しないSDKを設計する方法。ヴェネツィアのコンピュータビジョン…

github.com

このチュートリアルが重要な理由

私の経験では、このような包括的なガイドは見つかりませんでした。言語に依存しないクロスプラットフォームSDKを作成するために必要なすべての関連手順を要約します。私は、適切な情報についてさまざまなドキュメントをくまなく調べ、各コンポーネントを個別に学習してから、それをすべて自分で断片化する必要がありました。イライラしました。時間がかかりました。そして今、あなた、親愛なる読者は、私のすべての仕事から利益を得るようになります。先に、言語に依存しないクロスプラットフォームSDKを構築する方法を学びます。すべての必需品があります。綿毛のどれも、いくつかのミームを保存します。楽しんでください。

このチュートリアルでは、次の方法を学ぶことができます。

  • C ++で基本的なコンピュータービジョンライブラリを構築する
  • コンパイルしてクロス- AMD64、ARM64、およびARM32用のライブラリをコンパイルします
  • ライブラリとすべての依存関係を単一の静的ライブラリとしてパッケージ化します
  • 単体テストを自動化します
  • 継続的インテグレーションを設定します統合(CI)パイプライン
  • ライブラリのPythonバインディングを作成する
  • APIから直接ドキュメントを生成する

このデモのために、 MTCNNと呼ばれるオープンソースの顔検出器を使用して顔とランドマークの検出SDKを構築します。

例顔の境界ボックスと顔のランドマークの組み合わせ

API関数は画像パスを取得し、顔の境界ボックスと顔のランドマークの座標を返します。顔を検出する機能は、顔認識、年齢予測、自動顔ぼかしなど、多くのパイプラインの最初のステップであるため、コンピュータービジョンで非常に役立ちます。

注:このためチュートリアルでは、Ubuntu18.04で作業します。

ライブラリにC ++を使用する理由

効率的なC ++コードを実行すると、次のように感じることができます

ライブラリの大部分は、コンパイルされ静的に型指定された言語であるC ++で記述されます。 C ++が非常に高速なプログラミング言語であることは周知の事実です。必要な速度を提供するのに十分な低レベルであり、実行時のオーバーヘッドの追加は最小限です。

コンピュータービジョンアプリケーションでは、通常、多くの画像の操作、行列演算の実行、機械学習の推論の実行が含まれます。大量のコンピューティング。したがって、実行速度は重要です。これは、必要なフレームレートを達成するためにレイテンシを短縮する必要があるリアルタイムアプリケーションでは特に重要です。多くの場合、すべてのコードを実行するのにミリ秒しかありません。

C ++のもう1つの利点は、特定のアーキテクチャ用にコンパイルし、すべての依存関係を静的にリンクすると、追加のインタープリターやライブラリを必要とせずに、そのハードウェアで実行できます。信じられないかもしれませんが、オペレーティングシステムがなくてもベアメタル組み込みデバイスで実行できます!

ディレクトリ構造

プロジェクトでは次のディレクトリ構造を使用します。

3rdpartyには、プロジェクトに必要なサードパーティの依存関係ライブラリが含まれます。

distには、SDKのエンドユーザーに配布されるファイルが含まれます。この場合、それはライブラリ自体と関連するヘッダーファイルになります。

には、Dockerイメージの生成に使用されるDockerファイルが含まれます。 CIビルド用。

docsには、ヘッダーファイルから直接ドキュメントを生成するために必要なビルドスクリプトが含まれます。

にはパブリックAPIのインクルードファイルが含まれます。

には顔検出ディープラーニングモデルファイルが含まれます。

pythonには、pythonバインディングを生成するために必要なコードが含まれます。

には、コンパイルされるcppファイルが含まれます。また、SDKで配布されないヘッダーファイル(内部ヘッダーファイル)も含まれます。

にはユニットテストが含まれます。

toolsには、クロスコンパイルに必要なCMakeツールチェーンファイルが含まれます。

依存関係ライブラリのインストール

このプロジェクトでは、サードパーティ必要な依存関係ライブラリは、 ncnn 、軽量の機械学習推論ライブラリ、 OpenCV です。画像拡張ライブラリ、 Catch2 、単体テストライブラリ、最後に pybind11 、ライブラリPythonバインディングの生成に使用されます。最初の2つのライブラリはスタンドアロンライブラリとしてコンパイルする必要がありますが、後の2つはヘッダーのみであるため、ソースのみが必要です。

これらのライブラリをプロジェクトに追加する1つの方法は、gitサブモジュールを使用することです。 。このアプローチは機能しますが、私は個人的に、ソースコードをプルして、目的のプラットフォーム(この場合はAMD64、ARM32、およびARM64)用にビルドするシェルスクリプトを使用するのが好きです。

これがその例です。これらのビルドスクリプトは次のようになります。

スクリプトは非常に単純です。それは、gitリポジトリから目的のリリースソースコードをプルすることから始まります。次に、CMakeを使用してビルドを準備し、makeを呼び出してコンパイラを駆動し、ソースコードをビルドします。

AMD64ビルドとARMビルドの主な違いは次のとおりです。 ARMビルドは、CMAKE_TOOLCHAIN_FILEと呼ばれる追加のCMakeパラメーターを渡します。この引数は、ビルドターゲットアーキテクチャ(ARM32またはARM64)がホストアーキテクチャ(AMD64 / x86_64)とは異なることをCMakeに指定するために使用されます。したがって、CMakeは、選択したツールチェーンファイル内で指定されたクロスコンパイラを使用してライブラリを構築するように指示されます(ツールチェーンファイルについては、このチュートリアルの後半で詳しく説明します)。このシェルスクリプトが機能するためには、Ubuntuマシンに適切なクロスコンパイラがインストールされている必要があります。これらは

を使用して簡単にインストールでき、その方法の説明はここに示されています。

ライブラリAPI

ライブラリAPIは次のようになります:

私は非常にクリエイティブなので、SDKにMySDKという名前を付けることにしました。 APIには、ErrorCodeという列挙型、Pointという構造体、そして最後に、という1つのパブリックメンバー関数があります。 getFaceBoxAndLandmarks。このチュートリアルの範囲では、SDKの実装の詳細については説明しません。要点は、OpenCVを使用して画像をメモリに読み込み、オープンソースモデルでncnnを使用して機械学習推論を実行し、顔の境界ボックスとランドマークを検出することです。実装について詳しく知りたい場合は、ここで行うことができます。

注意してほしいのは、使用しているデザインパターン。 実装へのポインタまたは略して pImpl と呼ばれる手法を使用しています。これは、基本的に、クラスの実装の詳細を別のクラスに配置することで削除します。上記のコードでは、これはImplクラスを前方宣言し、このクラスにプライベートメンバー変数としてunique_ptrを設定することで実現されています。そうすることで、エンドユーザーの詮索好きな目から実装を隠すだけでなく(商用SDKでは非常に重要になる可能性があります)、APIヘッダーが依存するヘッダーの数を減らします(したがって、 #include依存関係ライブラリヘッダーからのAPIヘッダー)。

モデルファイルに関する注意

ここでは説明しません。実装の詳細ですが、言及する価値があると思うことがあります。デフォルトでは、MTCNNと呼ばれる、使用しているオープンソースの顔検出器が実行時に機械学習モデルファイルを読み込みます。これは、モデルをエンドユーザーに配布する必要があることを意味するため、理想的ではありません。この問題は、ユーザーがこれらのモデルファイルに無料でアクセスできるようにしたくない商用モデルではさらに重要です(これらのモデルのトレーニングに費やされた数え切れないほどの時間を考えてみてください)。 1つの解決策は、これらのモデルのファイルを暗号化することです。これは絶対にお勧めします。ただし、これは、SDKと一緒にモデルファイルを出荷する必要があることを意味します。最終的には、ユーザーに送信するファイルの数を減らして、ソフトウェアを使いやすくしたいと考えています(ファイルが少ないほど、問題が発生する場所が少なくなります)。したがって、以下に示す方法を使用して、モデルファイルをヘッダーファイルに変換し、実際にSDK自体に埋め込むことができます。

xdd bashコマンドは、16進ダンプの生成に使用され、バイナリファイルからヘッダーファイルを生成するために使用できます。したがって、通常のヘッダーファイルのようにモデルファイルをコードにインクルードして、メモリから直接ロードできます。このアプローチの制限は、コンパイル時にメモリを大量に消費するため、非常に大きなモデルファイルでは実用的ではないことです。代わりに、ldなどのツールを使用して、これらの大きなモデルファイルをオブジェクトファイルに直接変換できます。

CMakeとライブラリのコンパイル

これで、CMakeを使用してプロジェクトのビルドファイルを生成できます。ご存じない方のために説明すると、CMakeはビルドプロセスの管理に使用されるビルドシステムジェネレーターです。以下に、ルートCMakeLists.txt(CMakeファイル)のどの部分がどのように見えるかを示します。

基本的に、実装を含む2つのソースファイルmy_sdk.cpp<を使用して

という静的ライブラリを作成します。 / div>および

。静的ライブラリを作成する理由は、私の経験では、静的ライブラリをユーザーに配布する方が簡単であり、組み込みデバイスに対してより使いやすいためです。上で述べたように、実行可能ファイルが静的ライブラリに対してリンクされている場合、オペレーティングシステムさえも備えていない組み込みデバイスで実行できます。これは、ダイナミックライブラリでは不可能です。さらに、ダイナミックライブラリでは、依存関係のバージョンについて心配する必要があります。ライブラリに関連付けられたマニフェストファイルが必要になる場合もあります。静的にリンクされたライブラリは、動的なライブラリよりもパフォーマンスプロファイルがわずかに優れています。

CMakeスクリプトで次に行うことは、ソースファイルに必要なインクルードヘッダーファイルの場所をCMakeに指示することです。注意点:ライブラリはこの時点でコンパイルされますが、ライブラリに対して(実行可能ファイルなどを使用して)リンクしようとすると、シンボルエラーへの未定義の参照が大量に発生します。これは、依存関係ライブラリをリンクしていないためです。したがって、実行可能ファイルをlibmy_sdk_static.aに対して正常にリンクしたい場合は、すべての依存関係ライブラリ(OpenCVモジュール、ncnnなど)も追跡してリンクする必要があります。動的ライブラリとは異なり、静的ライブラリは独自の依存関係を解決できません。これらは基本的に、アーカイブにパッケージ化されたオブジェクトファイルのコレクションにすぎません。

このチュートリアルの後半で、すべての依存関係ライブラリを静的ライブラリにバンドルして、ユーザーが行う必要がないようにする方法を示します。依存関係ライブラリへのリンクについて心配してください。

ライブラリとツールチェーンファイルのクロスコンパイル

エッジコンピューティングはとても…エッジの効いた

多くのコンピュータービジョンアプリケーションがエッジにデプロイされています。これは一般的に通常ARMCPUを搭載した低電力の組み込みデバイスでコードを実行する必要があります。 C ++はコンパイルされた言語であるため、アプリケーションが実行されるCPUアーキテクチャ用にコードをコンパイルする必要があります(アーキテクチャごとに異なるアセンブリ命令を使用します)。

詳しく説明する前に、 ARM32とARM64の違い。AArch32とAArch64とも呼ばれます。 AArch64は、ARMアーキテクチャの64ビット拡張機能を指し、CPUとオペレーティングシステムの両方に依存します。したがって、たとえば、Raspberry Pi4には64ビットのARMCPUが搭載されていますが、デフォルトのオペレーティングシステムであるRaspbianは32ビットです。したがって、このようなデバイスにはAArch32でコンパイルされたバイナリが必要です。このPiデバイスで

Gentoo などの64ビットオペレーティングシステムを実行する場合は、AArch64でコンパイルされたバイナリが必要になります。人気のある組み込みデバイスのもう1つの例は、オンボードGPUを備え、AArch64を実行するNVIDIAJetsonです。

クロスコンパイルするには、CMakeに、のアーキテクチャ用にコンパイルしていないことを指定する必要があります。現在構築中のマシン。したがって、CMakeが使用するクロスコンパイラを指定する必要があります。 AArch64の場合は

コンパイラを使用し、AArch32の場合はarm-linux-gnuebhif-g++コンパイラを使用します(hfはハードフロート)。

以下は、ツールチェーンファイルの例です。ご覧のとおり、AArch64クロスコンパイラを使用するように指定しています。

ルートに戻る

、できます次のコードをファイルの先頭に追加します。

基本的に、CMakeオプションを追加します。クロスコンパイルするためにコマンドラインから有効にすることができます。 BUILD_ARM32または

オプションのいずれかを有効にすると、適切なツールチェーンファイルが選択され、クロスコンパイル用にビルドが構成されます。

依存ライブラリを使用したSDKのパッケージ化

前述のように、開発者がこの時点でライブラリに対してリンクする場合は、すべてのシンボルを解決するために、すべての依存ライブラリに対してもリンクする必要があります。依存関係ライブラリ。私たちのアプリは非常にシンプルですが、すでに8つの依存関係ライブラリがあります!最初はncnnで、次に3つのOpenCVモジュールライブラリがあり、次にOpenCVで構築された4つのユーティリティライブラリ(libjpeg、libpng、zlib、libtiff)があります。ユーザーに依存関係ライブラリを自分で作成するように要求したり、ライブラリと一緒に出荷したりすることもできますが、最終的にはユーザーの作業が増え、使用の障壁を下げることになります。理想的な状況は、標準のシステムライブラリ以外のすべてのサードパーティの依存関係ライブラリとともに、ライブラリを含む単一のライブラリをユーザーに出荷できる場合です。これは、CMakeマジックを使用して実現できることがわかりました。

まず、カスタムターゲットをに追加します。 CMakeLists.txtをクリックしてから、いわゆるMRIスクリプトを実行します。このMRIスクリプトは

bashコマンドに渡されます。このコマンドは、基本的にすべての静的ライブラリを1つのアーカイブに結合します。この方法の優れている点は、元のアーカイブから重複するメンバー名を適切に処理するため、そこでの競合について心配する必要がないことです。このカスタムターゲットを構築すると、libmy_sdk.aが生成されます。これにはSDKとすべての依存関係アーカイブが含まれます。

ちょっと待ってください:私たちが何をしているのかを調べてみましょう。これまでに完了しました。

息を呑む。おやつをつかむ。お母さんに電話してください。

この時点で、SDKとを含むlibmy_sdk.aという静的ライブラリがあります。単一のアーカイブにパッケージ化したすべての依存関係ライブラリ。また、すべてのターゲットプラットフォーム用に(コマンドライン引数を使用して)コンパイルおよびクロスコンパイルする機能もあります。

単体テスト

初めてユニットテストを実行するとき

おそらく必要ありません単体テストが重要である理由を説明しますが、基本的には、開発者がSDKがインデントされたとおりに機能していることを確認できるようにするSDK設計の重要な部分です。さらに、重大な変更が将来的に行われた場合、それらを追跡し、修正をより迅速にプッシュするのに役立ちます。

この特定のケースでは、単体テストの実行可能ファイルを作成することで、意図したとおりに正しくリンクできるようにするために作成したばかりの結合ライブラリ(そして、これらの厄介な未定義のシンボル参照エラーが発生しないようにします)。

単体テストフレームワークとしてCatch2を使用しています。 。構文の概要は次のとおりです。

Catch2の仕組みは、

という別のマクロ。 SECTIONごとに、TEST_CASEが最初から実行されます。したがって、この例では、mySdkが最初に初期化され、次に「顔以外の画像」という名前の最初のセクションが実行されます。次に、mySdkが再構築される前に分解され、「Facesinimage」という名前の2番目のセクションが実行されます。これは、セクションごとに操作する新しいMySDKオブジェクトがあることを保証するので素晴らしいです。次に、REQUIREなどのマクロを使用してアサーションを作成できます。

CMakeを使用して、。以下の3行目の

の呼び出しでわかるように、リンクする必要があるライブラリはlibmy_sdk.aだけで、他にはありません。依存関係ライブラリ。

ドキュメント

ユーザーだけがいまいましいドキュメントを読む場合。

doxygen を使用して、ヘッダーファイルから直接ドキュメントを生成します。以下のコードスニペットに示す構文を使用して、パブリックヘッダーにすべてのメソッドとデータ型を文書化できます。関数のすべての入力パラメーターと出力パラメーターを必ず指定してください。

実際にドキュメントを生成するには、doxyfileと呼ばれるものが必要です。これは、基本的にdoxygenにドキュメントの生成方法を指示するための青写真です。システムにdoxygenがインストールされていると仮定して、ターミナルでdoxygen -gを実行することにより、汎用doxyfileを生成できます。次に、doxyfileを編集できます。少なくとも、出力ディレクトリと入力ファイルを指定する必要があります。

この場合、APIヘッダーファイルからのみドキュメントを生成する必要があるため、インクルードディレクトリを指定しました。最後に、CMakeを使用して実際にドキュメントを作成します。これは、のように行うことができます

Pythonバインディング

準関連のgifを見るのにもううんざりしていませんか?ええ、私もそうです。

正直に言いましょう。 C ++は、開発が最も簡単または最も使いやすい言語ではありません。したがって、開発者が使いやすいように、ライブラリを拡張して言語バインディングをサポートしたいと考えています。 Pythonを使用してこれをデモンストレーションします。これは、人気のあるコンピュータービジョンのプロトタイピング言語ですが、他の言語バインディングも同様に簡単に記述できます。これを実現するためにpybind11を使用しています:

Python内からインポートステートメントが発行されたときに呼び出される関数を作成するマクロ。したがって、上記の例では、Pythonモジュール名はmysdkです。次に、pybind11構文を使用してクラスとそのメンバーを定義できます。

注意事項:C ++では、読み取りアクセスと書き込みアクセスの両方を許可する可変参照を使用して変数を渡すのが一般的です。これは、faceDetectedおよび

パラメーターを使用してAPIメンバー関数で行ったこととまったく同じです。 Pythonでは、すべての引数は参照によって渡されます。ただし、boolなど、特定の基本的なPythonタイプは不変です。偶然にも、faceDetectedパラメーターは可変参照によって渡されるブール値です。したがって、上記のコードの31行目から34行目に示されている回避策を使用して、Pythonラッパー関数内でboolを定義し、それをC ++関数に渡してから、変数をタプルの一部として返す必要があります。

Pythonバインディングライブラリを構築したら、次のコードを使用して簡単に利用できます:

継続的インテグレーション

継続的インテグレーションパイプラインには、Githubと直接統合するため、私が本当に気に入っているCircleCIというツールを使用します。コミットをプッシュするたびに、新しいビルドが自動的にトリガーされます。開始するには、CircleCI ウェブサイトにアクセスしてGithubアカウントに接続し、追加するプロジェクトを選択します。追加したら、プロジェクトのルートに.circleciディレクトリを作成し、そのディレクトリ内にconfig.ymlというファイルを作成する必要があります。

なじみのない人のために、YAMLは設定ファイルに一般的に使用されるシリアル化言語です。これを使用して、CircleCIに実行させたい操作を指示できます。以下のYAMLスニペットでは、最初に依存関係ライブラリの1つを構築し、次にSDK自体を構築し、最後に単体テストを構築して実行する方法を確認できます。

インテリジェントな場合(そして、これまでに達成した場合はそうだと思います)、キャッシュを使用してビルド時間を大幅に短縮できます。たとえば、上記のYAMLでは、ビルドスクリプトのハッシュをキャッシュキーとして使用して、OpenCVビルドをキャッシュします。このように、OpenCVライブラリは、ビルドスクリプトが変更された場合にのみ再ビルドされます。それ以外の場合は、キャッシュされたビルドが使用されます。もう1つ注意すべき点は、選択したDockerイメージ内でビルドを実行していることです。すべてのシステム依存関係をインストールしたカスタムDockerイメージ(ここはDockerfile)を選択しました。

Fin。

これで完了です。他の適切に設計された製品と同様に、私たちは最も需要の高いプラットフォームをサポートし、最も多くの開発者が簡単に使用できるようにしたいと考えています。上記のチュートリアルを使用して、複数の言語でアクセスでき、複数のプラットフォームにデプロイできるSDKを構築しました。また、pybind11のドキュメントを自分で読む必要もありませんでした。このチュートリアルがお役に立てて面白いと思います。幸せな建物。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です