A certain engineer "COMPLEX"

開発メモ その72 dlibをCUDAを有効にしてビルドする

Problem


OpenCVには顔検出用の関数があるが、どうにも精度が良くないときがある。
そこで最近名前を聞くようになったdlibを使って見ることにする。

ただ、C#のラッパーなどは出回っておらず、C++から呼び出す泥臭いことが必要な模様。
ただ、ソースのサンプルが豊富なので、試してみるのは簡単そう....

と思っていましたがそうでは無かったです。

Preparation


ソース

まずはソースをダウンロード。

ページ左下の青いボタンがダウンロードになります。2017/07/05時点の最新版は19.4です。

ダウンロード後、任意の場所に展開します。
ここでは、D:\Works\Lib\DLib\19.4とします。
配下には下記のファイル、フォルダが展開されます。

dlib
docs
examples
python_examples
tools
CMakeLists.txt
documentation.html
MANIFEST.in
README.md
setup.py

CUDA

dlibはCUDAを利用することで性能を大幅に向上させることが出来ます。
ただし、利用できるCUDAは7.5以降になります。今回は8.0を選択。

ダウンロードは下記になります。

今回はWindows 10用のインストーラを入手します。

パッチもダウンロードできるようなので、そちらもダウンロードします。
バージョンは異なりますが、インストールは下記を参考にできます。

cuDNN

CUDAを利用するにはcuDNNが必要です。
インストールしたCUDAに合わせたバージョンを使います。6.0はdlibで利用できないので、5.1を利用します。

ダウンロードは下記になります。
NVIDIAの開発者登録が必要ですので、登録を済ませておいてください。

入手や展開は下記を参考にできます。

今回は、D:\Works\Lib\NVIDIA\cuDNNに展開します。

OpenCV

下記からインストーラをダウンロードしてインストールしておきます。2017/07/05の時点で最新は3.2.0になります。

今回は、D:\Works\Lib\OpenCV\opencv-3.2.0に展開します。

CMake

Visual Studioのソリューションファイルを生成するために必要です。
下記でダウンロードしインストールします。2017/07/05時点の最新の安定版は3.8.2です。

インストール時、CMakeを環境変数PATHに追加するか選択できますが、そこは好みで。

Visual Studio

ビルドに使います。当たり前かもしれませんが、これがくせ者。
まず、dlibが対応しているCUDAは7.5以降になります。今回は8.0にします。
しかし、8.0に対応しているのはVisual Studio 2015以前です。
2017は対応していません

Build with CUDA


まずコマンドプロンプトで、dlibの展開フォルダを開きます。
なお、buildフォルダの名前は自由です。

しかし、Cmakeが失敗します。
ネットを見ると、-DCMAKE_PREFIX_PATHcuDNNのルートフォルダを指定する、みたいなことが書いてありますが、これでは私の環境ではダメでした。
解決策は、cuDNN内の

cuda\include\cudnn.h
cuda\lib\x64\cudnn.lib

を、それぞれ

C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\lib\x64

にコピーします。
その後、buildフォルダ内を削除し、先のコマンドを実行します。

CMakeでVisual Studioのソリューションファイルの生成に成功しましたので、下記のようにビルドします。

Build without CUDA


もし、CUDAを無効にしたいなら

とします。
この際、cmakeを実行するフォルダをbuildフォルダと別名にしておかないと、CUDAを有効にしたバイナリが上書きされますので注意です。
ビルドは同様に、

とします。

開発メモ その71 NLogでLoggerの読み込みに失敗する

Problem


NLogはシンプルで使いやすいです。
個人的にはlog4netよりもシンプルで使いやすくて好きです。

そんなNLogで、複数のLoggerを準備している際に問題が起きました。
下記のようなNLog.configです。

このファイルをアプリと一緒に読み込み、LogManager.GetLogger("GHILogger");と実行してもログを書き出すことができません。
しかもエラーも発生しません。

Solution


ポイントは、targets内の、targetセクションにおいて、rules内のloggerセクションのwriteTo属性と対応付けすることが出来ないtargetが存在することになります。
つまり、DEF_がそれです。
このため、NLogの内部では処理がおかしくなっているのか、ABCLoggerは読み込めますが、次のDEFLoggerは対応付くDEFが存在しないため読み込めません。
さらに不味いのは、この状態だと後続のGHILoggerも読めません。
途中で対応付けに失敗すると、それ以降の対応付けに失敗あるいは対応付けそのものを中止してしまうのでしょう。

対応は簡単です。

targetのname属性またはloggerのwriteTo属性を正しく対応付けるように修正するだけです。
この場合は、2番目のtargetのname属性をDEFに修正することで正しく動きます。

仕事で、ログが出力されなくて悩みましたが、一見無関係に見える、使っていないLoggerが影響するとは思いませんでした。

.NETでLinuxと遊んでみる Visual Studio Code編 第5回

Introduction


前回は、System.IO.FileStreamを使ってファイルのアクセス権限を確認してみました。

今回は少し高度なファイルへのアクセス可否についてです。

P/Invoke


ファイルに実際にアクセスしてみて、アクセスできるかどうかを試すって、結構良くないと思います。
かといって、Windowsの.NET Frameworkにアクセスできるかどうかを試すAPIは無かったはずです。

ですが、Linuxのシステムコールを使えば、それが実現可能です。
システムコールの呼び出しは当然P/Invokeで実現します。

Linuxのシステムコールで、ファイルに対して、読み込みができるか、書き込みが出来るか、実行できるか、という状態を調べるには、accessを呼び出します。

大抵のシステムコールはlibcに定義されています。
実際に、libcに定義されている関数の一覧からaccessを探してみます。
そのために、nmコマンドに-Dオプションを渡します。

これでlibcにaccessが定義されていることがわかりました。

次にaccessの使い方です。
定義は下記です。

第一引数はファイルパスです。
第二引数は調べるモードを表します。

意味
F_OK 存在するか
R_OK 読み込み可能か
W_OK 書き込み可能か
X_OK 実行可能か

第二引数のモードを組み合わせることで、アクセス可否をチェックします。
指定したモードを満たせば、0を返します。
満たさない場合は-1を返します。

Try


今回は、/etc/passwdを調べてみます。

root以外は、読み込みしかできません。

では、これをコードで調べてみます。

引数で指定したファイルの読み込み、書き込み、実行の可否を調べます。

前回同様、まずは、一般ユーザで確認します。
whichでdotnetコマンドの場所を調べているのは、rootユーザでdotnetまでのパスが通っていなかったので、実施しているだけです。
パスが通っているなら不要です。
引数のファイルパスはdotnet runの直後に追記することで指定できます。

読み込みしかできないようです。
ls -laで調べたとおりです。

続いて、rootで確認します。

読み書きができて、実行できないことが確認できました。
これも、ls -laで調べたとおりです。

では、存在しないファイルを指定してみます。

きちんと存在しないことを確認してくれます。

フォルダも調べることが出来ます。

Conclusion


Windows同様、P/Invokeが利用可能であることを確認できました。

.NETでLinuxと遊んでみる Visual Studio Code編 第4回

Introduction


前回は、System.IO.DirectoryInfoを使って、ファイルの一覧を列挙してみました。

今回はファイルへのアクセス可否についてです。

May I open this file?


Windows上のC#なら、ファイルに書き込みできるかどうか等は、実際にファイルへのストリームを作成してみたりします。

では、Linuxでこれを実施するとどうなるでしょう?
ありがちなのは、所有者がrootになっていて、root以外が読み込みも出来ないパターンです。
例えば、/etc/sudo.confがそうです。

なので、こんなソースを用意してみました。

引数で指定したファイルを開くことが出来れば、'' can be opened!!と表示されます。
ファイルを開こうとして、例外を投げれば、開けなかった理由が表示される、というシンプルなテストです。

ソースを記述したら、プロジェクトフォルダで

を実行します。
これでビルドが成功するはずです。

まずは、一般ユーザで確認します。
whichでdotnetコマンドの場所を調べているのは、rootユーザでdotnetまでのパスが通っていなかったので、実施しているだけです。
パスが通っているなら不要です。
引数のファイルパスはdotnet runの直後に追記することで指定できます。

アクセスが拒否されています。

続いて、rootで確認します。

無事にアクセスできました。

Conclusion


基本的なアクセス権も.NET Coreで対処できそうです。
次回は、もう少し高度な方法で読み書きできるかを調べてみたいと思います。

.NETでLinuxと遊んでみる Visual Studio Code編 第3回

Introduction


前回は、デバッグ時に引数を指定して実行してみました。

今回はファイルパスを使ったAPIのメモです。

System.IO.DirectoryInfo


相対パス扱いなの?

前回、デバッグ引数として、~/を指定しました。
Linuxにおいてこのパスは、実行ユーザのホームディレクトリを示します。
(~も同様。)

これを使って、下記のコードを実行します。

よくある、指定したパスの配下のファイルを列挙するプログラムです。

これを実行すると下記の例外を投げます。

Could not find a part of the path '/home/XXXXXX/git/Demo/DotNetCoreLinux3/~/'という記述から、指定したパスは、カレントディレクトリからの相対パス扱いになってしまいました。
なので、System.IO.Path.GetFullPathメソッドで絶対パスに変換してみます。

変わらず同じ例外が出力されます。
デバッグコンソールの出力は下記のようになります。

なので、~はダメな模様。

ルートから指定してみる

Windowsでも、パス指定の際の基準はカレントディレクトリになりました。
でも、フルパスを指定したなら、それはフルパスとして解釈されます。
間違っても、<カレントディレクトリ>\<フルパス>みたいな解釈はされません。

Linuxでもそうなるでしょうか?
Linuxにおけるルートは/になります。

次は、デバッグ引数に/を指定して、実行してみます。
無事に最後まで実行できています。
また、/はGetFullPathでも/として認識されています。

きちんとルートディレクトリの内容と比較してみます。
同じ内容であることがわかります。

Conclusion


少しずつ基本的なAPIを使えるようになってきました。
Windows/Mac/Linuxでの挙動の違いを比較してみたいですね。