OpenFOAMでcheckMeshしてから画像を認識する

# はじめに
前回のOpenCAE勉強会@関西で話したネタです。

OpenFOAMの実行ファイルの中でTensorFlowをincludeする際の手順や注意事項などをまとめます。
（[前の記事](https://qiita.com/inabower/items/4f117c5b1d8cdd6a2f55)の清書を兼ねています。）
例としてcheckMeshの実行ファイルの中でTensorFlowのチュートリアルの一つである[label_image](https://www.tensorflow.org/versions/master/tutorials/image_recognition)というものを実行してみました。

※画像認識をする意味は特にないです。

# コンパイル手順
## ○ OpenFOAMの環境
OpenFOAMはすでにインストールされているものとする。

- Ubuntu16.04LTS
- OpenFOAM-v1712（コンパイラ：gcc）
- 場所：$HOME/OpenFOAM/

TensorFlowも同様に$HOME/OpenFOAM/にインストールする。

## ① Bazelをインストール（詳しくは[こちら](https://docs.bazel.build/versions/master/install-ubuntu.html)）
以下の手順でTensorFlowこビルドするためにBazelをインストールする。

```bash
$ sudo apt-get install openjdk-8-jdk
$ echo "deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list
$ curl https://bazel.build/bazel-release.pub.gpg | sudo apt-key add -
$ sudo apt-get update && sudo apt-get install bazel
```

## ② TensorFlowをソースからビルド
この際にc++用のsoを作成するためには以下のようなコマンドを入力する。

```bash
$ git clone tensorflow/tensorflow
$ cd tensorflow
$ ./configure
$ bazel build -c opt --copt=-march=native tensorflow:libtensorflow_cc.so
```

この./configureの時にPythonの場所やcudaを設定するかどうかを聞かれる。

## ③ ヘッダファイルをコピーして収集
includeするTensorFlowのヘッダファイルを取ってくる。
[Tensorflowをc++のcmakeしてるプロジェクトで使いたい](https://qiita.com/Tacha-S/items/5bb18c631868b6b15517)の中のスクリプトをほぼそのまま使用した。
今回のチュートリアルでは以下のようなスクリプトを実行して動作を確認した。

```bash:getHeaderTF.sh

#!/bin/bash
HEADER_DIR=$HOME/OpenFOAM/tensorflow/lnInclude　※各自のtensorflowインストール場所

if [ ! -e $HEADER_DIR ];
then
    mkdir -p $HEADER_DIR
fi

cd tensorflow

find tensorflow/core -follow -type f -name "*.h" -exec cp --parents {} $HEADER_DIR \;
find tensorflow/cc   -follow -type f -name "*.h" -exec cp --parents {} $HEADER_DIR \;
find tensorflow/c    -follow -type f -name "*.h" -exec cp --parents {} $HEADER_DIR \;

find bazel-genfiles    -follow -type f -name "*.h" -exec cp --parents {} $HEADER_DIR \;
find bazel-tensorflow    -follow -type f -name "*.h" -exec cp --parents {} $HEADER_DIR \;

find third_party/eigen3 -follow -type f -exec cp --parents {} $HEADER_DIR \;

pushd bazel-genfiles
find tensorflow -follow -type f -name "*.h" -exec cp --parents {} $HEADER_DIR \;
popd

pushd bazel-tensorflow/external/protobuf_archive/src
find google -follow -type f -name "*.h" -exec cp --parents {} $HEADER_DIR \;
popd

pushd bazel-tensorflow/external/eigen_archive
find Eigen       -follow -type f -exec cp --parents {} $HEADER_DIR \;
find unsupported -follow -type f -exec cp --parents {} $HEADER_DIR \;
popd

pushd ~/.cache/bazel/ユーザー名/謎の文字列/external
find eigen_archive -follow -type f -name "*.h" -exec cp --parents {} $HEADER_DIR \;
find protobuf_archive -follow -type f -name "*.h" -exec cp --parents {} $HEADER_DIR \;
popd
```

※最後の **ユーザー名** と **謎の文字列** はbazelでtensorflowをインストールした時に生成されるディレクトリ
tensorflow/bazel-binのリンク先から確認できる。
今回使用したスクリプトファイルは[これ](https://github.com/inabower/solvers-v1712/blob/master/getHeaderTF.sh)だが、謎の文字列の部分は変更する必要がある。

## ④ soのパスを通す
実行するためには毎回 *LD_LIBRARY_PATH* に *tensorflow_cc.so* の場所を追加する必要がある。
今回は *$(HOME)/OpenFOAM/OpenFOAM-v1712/etc/bashrc* に以下のコマンドを追加した。

```bash
$ export LD_LIBRARY_PATH="$(HOME)/OpenFOAM/tensorflow/bazel-bin/tensorflow:$LD_LIBRARY_PATH"
```

## ⑤ wmakeの設定変更
デフォルトのままでもコンパイルは通るが、コーディングルールの違いからコンパイル時に多くのエラーメッセージが出てくる。
これを解消するためにOpenFOAM側のwmakeのオプションを変更する。

今回はここに

```bash
$HOME/OpenFOAM/OpenFOAM-v1712/wmake/rules/linux64Gcc/c++
```

以下のように変更を加えた。

```diff:$HOME/OpenFOAM/OpenFOAM-v1712/wmake/rules/linux64Gcc/c++
SUFFIXES += .C .cc .cpp .cxx

- c++WARN     = -Wall -Wextra -Wold-style-cast -Wnon-virtual-dtor -Wno-unused-parameter \
-               -Wno-invalid-offsetof -Wno-attributes
+ c++WARN     = -Wall -Wextra -Wnon-virtual-dtor -Wno-unused-parameter \
+               -Wno-invalid-offsetof -Wno-attributes -Wno-sign-compare

# Suppress some warnings for flex++ and CGAL
c++LESSWARN = -Wno-old-style-cast -Wno-unused-local-typedefs -Wno-array-bounds

CC          = g++ -std=c++11 -m64

include $(DEFAULT_RULES)/c++$(WM_COMPILE_OPTION)

ptFLAGS     = -DNoRepository -ftemplate-depth-100

c++FLAGS    = $(GFLAGS) $(c++WARN) $(c++OPT) $(c++DBUG) $(ptFLAGS) $(LIB_HEADER_DIRS) -fPIC

Ctoo        = $(WM_SCHEDULER) $(CC) $(c++FLAGS) -c $< -o $@
cxxtoo      = $(Ctoo)
cctoo       = $(Ctoo)
cpptoo      = $(Ctoo)

LINK_LIBS   = $(c++DBUG)

LINKLIBSO   = $(CC) $(c++FLAGS) -shared -Xlinker --add-needed -Xlinker --no-as-needed
LINKEXE     = $(CC) $(c++FLAGS) -Xlinker --add-needed -Xlinker --no-as-needed
```

## ⑥ ユーティリティのコンパイル
今回のテストに使用するユーティリティはcheckMeshしてから画像を認識するというもの。
[GitHub/inabower](https://github.com/inabower/solvers-v1712.git)の中のossanCheckMeshがこれに該当する。
例えば以下のようなコマンドでコンパイルできる。

```bash:コンパイル例
$ git clone https://github.com/inabower/solvers-v1712.git
$ cd solvers-v1712/utilities/ossanCheckMesh
$ wmake
```

# 実行してみる
## テストケース
ケース自体は普通のpitzDaily。

<img src="https://qiita-image-store.s3.amazonaws.com/0/151450/b858f794-5ec6-e430-66f6-330bb930a6ab.png" width=60%>

Allrunファイルの中身は以下のようになっている。

```bash:Allrun
#!/bin/sh
cd ${0%/*} || exit 1                        # Run from this directory
. $WM_PROJECT_DIR/bin/tools/RunFunctions    # Tutorial run functions

runApplication blockMesh

curl -L "https://storage.googleapis.com/download.tensorflow.org/models/inception_v3_2016_08_28_frozen.pb.tar.gz" | tar -C ./data -xz
runApplication ossanCheckMesh
# runApplication $(getApplication)

#------------------------------------------------------------------------------
```

最初のcurlでgoogleの学習データをダウンロードする。

```:出力画面
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 84.5M  100 84.5M    0     0  9849k      0  0:00:08  0:00:08 --:--:-- 11.1M
```

またdataディレクトリに今回テストに使用する男性の顔写真が入っておりこれを認識することになる。
<img src="https://qiita-image-store.s3.amazonaws.com/0/151450/50d2d427-374f-8975-6ade-4f7f5253a77b.jpeg" width=20%>

## 実行結果
以下のような結果が出力される。

```:log.ossanCheckMesh
/*---------------------------------------------------------------------------*\
| =========                 |                                                 |
| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
|  \\    /   O peration     | Version:  v1712                                 |
|   \\  /    A nd           | Web:      www.OpenFOAM.com                      |
|    \\/     M anipulation  |                                                 |
\*---------------------------------------------------------------------------*/
Build  : v1712
Arch   : "LSB;label=32;scalar=64"
Exec   : ossanCheckMesh
Date   : Jan 24 2018
Time   : 19:14:10

（中略）

Checking geometry...
    Overall domain bounding box (-0.0206 -0.0254 -0.0005) (0.29 0.0254 0.0005)
    Mesh has 2 geometric (non-empty/wedge) directions (1 1 0)
    Mesh has 2 solution (non-empty) directions (1 1 0)
    All edges aligned with or perpendicular to non-empty directions.
    Boundary openness (-1.74458e-19 3.10465e-18 2.11178e-15) OK.
    Max cell openness = 2.21044e-16 OK.
    Max aspect ratio = 8.1407 OK.
    Minimum face area = 1.68589e-07. Maximum face area = 5.14451e-06.  Face area magnitudes OK.
    Min volume = 1.6902e-10. Max volume = 3.83579e-09.  Total volume = 1.4516e-05.  Cell volumes OK.
    Mesh non-orthogonality Max: 5.95045 average: 1.63034
    Non-orthogonality check OK.
    Face pyramids OK.
    Max skewness = 0.260575 OK.
    Coupled point location match (average 0) OK.

Mesh OK... but...

Checking image...
> Image : $HOME/OpenFOAM/test/ossanTest/data/grace_hopper.jpg
> Graph : $HOME/OpenFOAM/test/ossanTest/data/inception_v3_2016_08_28_frozen.pb

    83.43% : 	military uniform.
    2.19% : 	mortarboard.
    1.04% : 	academic gown.
    0.80% : 	pickelhaube.
    0.54% : 	bulletproof vest.

This might be "military uniform" !!

End
```

無事に画像認識もできた。

# 注意点
## 先にTensorFlowをincludeする
OpenFOAMの中には *TypeName* というマクロがあり、TensorFlowには *TypeName* という関数がある。
このためOpenFOAMのヘッダを読んだあとにTensorFlowのヘッダを読むと関数として出てきた「TypeName」という文字列をマクロだと認識してしまいコンパイルエラーにつながってしまう。
ユーティリティやソルバーであれば一番最初に、境界条件などのsoであればそれの一番最初にincludeする必要がある。

## namespaceに注意
例えばTensorなどの型がOpenFOAMと重複しているため *using namespace* が使いづらい。

## 未確認事項

- mpirun
- 数字や文字列や行列の受け渡し
- C++でやる意味

# 最後に
内容はGitHub（[https://github.com/inabower/solvers-v1712](https://github.com/inabower/solvers-v1712)）にあげてあります。
コメントやプルリクをお待ちしています。