TensorFlowを使ったHoudiniノードを作る <C++/HDK>

:::note info
こちらは、以前（2021/01/19）に別サービスで展開した内容を Qiita に移行したものです。
:::

# はじめに

以前に TensorFlow を使ったノード作成は試していたが、ポストとしてまとめていなかったことと、Houdini のノード作成に CMake を使うので、そちらに合わせたビルド環境を構築しておきたかったため、それらをまとめる事にした。

余談だが、C++ での実装を本格的に始めようとしたきっかけは、機械学習環境の進化に対して、DCC ツール側の Python バージョンが追いついておらず、Python のみで実装を進めるには不安定な環境をユーザーに提供してしまう不安があるというのがある。また、Houdini 側での実行速度としても担保したい狙いがある。

![d45aozo17od4hcu0idh7pfbrqc76.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51857/e661e0eb-f2ab-8176-a95e-f1d250134b9d.png)


## 開発環境

> Windows 10
> Houdini 18.5.460
> TensorFlow 2.4.0 GPU
> CUDA 11
> Visual Studio 2017

## サンプルコード

https://github.com/takavfx/MLHoudiniSamples/tree/master/ML0003_TensorFlowPlugin

# 準備

## TensorFlow for C をダウンロード

TensorFlow を C++ 上で使用するには、[TensorFlow for C](https://www.tensorflow.org/install/lang_c?hl=ja) を使用する。

![](https://storage.googleapis.com/zenn-user-upload/6d4y1rhf2dxa6k5fjvcenkn31gzj)

ページ内にダウンロードリンクがあるので、任意のライブラリを選択してダウンロードしてくる。

:::note info
ダウンロードリンクを見つける際、ページの表示言語は英語に設定しておいた方が良い。英語と日本語のページだと、ダウンロードリンクにある TensorFlow のバージョンの差異がある事があり、英語表示のページの方がより最新のライブラリリンクを掲載している事があるため。
:::


## CUDA のインストール

[CUDA Toolkit](https://developer.nvidia.com/cuda-toolkit) もインストールしておく。
もし、CPU 版のライブラリを使用する場合は、こちらはスキップして良い。

TensorFlow v2.4.0 では CUDA 11 を使用する[^1]ので、その点は注意しておく。

[^1]: [TensorFlow - GPU Support - Software requirements](https://www.tensorflow.org/install/gpu#software_requirements)

# CMake

TensorFlow for C の場合、PyTorch と違って中身は非常にシンプルなため、迷うことはさほどないと思う。

しかしながら、Houdini が CMake でのビルドを推奨しており、特にGUI周りの生成の自動化などが Houdini の CMake 側の機能として用意されているため、これに従った方が得策。なので、以下に CMake を使ったビルド方法をまとめる。

## TensorFlow 側の CMake

次の様に、CMake コンフィグを用意しておく。

![zah8x07z8yc391n92g6xwux2nocf.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51857/d4566a71-cc79-f5c5-c8e1-61874489e324.png)

```cmake:TensorFlowConfig.cmake
# Find TensorFlow
#------
# This will define the following variables:
#
#   TENSORFLOW_INCLUDE_DIRS   -- The include directories for tensorflow
#   TENSORFLOW_LIBRARIES        -- Libraries to link against
#   TENSORFLOW_INSTALL_PREFIX -- Path to library root
#
# and the following imported targets:
#
#   tensorflow

if(DEFINED ENV{TENSORFLOW_INSTALL_PREFIX})
    set(TENSORFLOW_INSTALL_PREFIX $ENV{TENSORFLOW_INSTALL_PREFIX})
else()
    get_filename_component(CMAKE_CURRENT_LIST_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
    get_filename_component(TENSORFLOW_INSTALL_PREFIX "${CMAKE_CURRENT_LIST_DIR}/../" ABSOLUTE)
endif()

# Include directories
if(EXISTS "${TENSORFLOW_INSTALL_PREFIX}/include")
    set(TENSORFLOW_INCLUDE_DIRS ${TENSORFLOW_INSTALL_PREFIX}/include)
    message("-- Tensorflow include directries: ${TENSORFLOW_INCLUDE_DIRS}")
endif()

# Library dependencies.
add_library(tensorflow STATIC IMPORTED)
set(TENSORFLOW_LIBRARIES tensorflow)

find_library(TENSORFLOW_LIBRARY tensorflow PATHS "${TENSORFLOW_INSTALL_PREFIX}/lib")
list(APPEND TENSORFLOW_LIBRARIES ${TENSORFLOW_LIBRARY}) # Append TensorFlow lib
message("-- TensorFlow libraries: ${TENSORFLOW_LIBRARIES}")


set_target_properties(tensorflow PROPERTIES
    IMPORTED_LOCATION "${TENSORFLOW_LIBRARY}"
    INTERFACE_INCLUDE_DIRECTORIES "${TENSORFLOW_INCLUDE_DIRS}"
)
```

```cmake:TensorFlowConfigVersion.cmake
set(PACKAGE_VERSION "2.4.0")

if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}")
    set(PACKAGE_VERSION_COMPATIBLE FALSE)
else()
    set(PACKAGE_VERSION_COMPATIBLE TRUE)
    if("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}")
        set(PACKAGE_VERSION_EXACT TRUE)
    endif()
endif()
```

## Houdini プラグイン側の CMake

こちらは [PyTorch 版でも書いている様](https://qiita.com/takavfx/items/185e91cd6c5f5e8c5ce8)に書く。解説とかもそっちに書いているので、そちらを参考にどうぞ。

```cmake:CMakeLists.txt
cmake_minimum_required( VERSION 3.6 )

project( TensorFlowTest )

# CMAKE_PREFIX_PATH must contain the path to the toolkit/cmake subdirectory of
# the Houdini installation. See the "Compiling with CMake" section of the HDK
# documentation for more details, which describes several options for
# specifying this path.
list( APPEND CMAKE_PREFIX_PATH "$ENV{HFS}/toolkit/cmake" )

# Locate Houdini's libraries and header files.
# Registers an imported library target named 'Houdini'.
find_package( Houdini REQUIRED )
find_package( TensorFlow REQUIRED )

set( library_name SOP_TensorFlowTest )

# Code generation for the embedded DS file in SOP_TensorFlowTest.C.
houdini_generate_proto_headers( FILES SOP_TensorFlowTest.C )

# Add a library and its source files.
add_library( ${library_name} SHARED
    SOP_TensorFlowTest.C
    SOP_TensorFlowTest.h
)

# Link against the Houdini libraries, and add required include directories and
# compile definitions.
target_link_libraries( ${library_name} Houdini "${TENSORFLOW_LIBRARIES}")

# Include ${CMAKE_CURRENT_BINARY_DIR} for the generated header.
target_include_directories( ${library_name} PRIVATE
    ${CMAKE_CURRENT_BINARY_DIR}
)

# Sets several common target properties, such as the library's output directory.
houdini_configure_target( ${library_name} )


if (MSVC)
    # Make directory before copy dll
    set(binary_dir $<TARGET_FILE_DIR:${library_name}>/../bin)
    add_custom_command(TARGET ${library_name}
                        COMMAND ${CMAKE_COMMAND} -E make_directory
                        ${binary_dir})
    
    # Copy dll to install target bin directory
    file(GLOB TENSORFLOW_DLLS "${TENSORFLOW_INSTALL_PREFIX}/lib/*.dll")
    add_custom_command(TARGET ${library_name}
                        POST_BUILD
                        COMMAND ${CMAKE_COMMAND} -E copy_if_different
                        ${TENSORFLOW_DLLS}
                        ${binary_dir})
endif (MSVC)
```

# Houdini プラグインのソースの編集

例によって、SOP_Star サンプルを書き換えてテストする。

```cpp:SOP_TensorFlowTest.C
// ~ 省略 ~
#include <stdio.h>
#include <tensorflow/c/c_api.h>

// ~ 省略 ~

// ノードクック時の処理
void
SOP_TensorFlowTestVerb::cook(const SOP_NodeVerb::CookParms &cookparms) const
{
    // ~ 省略 ~
    printf("Hello from TensorFlow C library version %s\n", TF_Version());
    // ~ 省略 ~
}
```


# ビルド

## CMake ビルド

CMake のビルドと、実際の MSVC でのビルドを実行する。

```batch
cmake .. -G "Visual Studio 15 2017 Win64" -DCMAKE_PREFIX_PATH=\path\to\tesnorflow
```

```batch
cmake --build . --config Release
```

デフォルトでは、`C:\Users\<username>\Documents\houdini<houdini_version>\dso`と`C:\Users\<username>\Documents\houdini<houdini_version>\bin`以下にインストールされる。
bin ディレクトリと分けているのは、[PyTorch 版でも書いた通り](https://qiita.com/takavfx/items/185e91cd6c5f5e8c5ce8)、dso 以下には Houidni がバージョン認識できる dll のみを置く必要があり、余計な dll ファイルを置けないため。

# Houdini 上でのテスト

先に、`PATH` 環境変数と、プラグインのエラーを追うために `HOUDINI_DSO_ERROR` の環境変数設定を行う。

```batch
set PATH=C:\Users\<username>\Documents\houdini18.5\bin
set HOUDINI_DSO_ERROR=1
```

そして Houdini を起動する。
/obj/geo1 と、Geoemtry ネットワークを作成し、そこで **TensorFlowTest** ノードを作成してみる。
次の様に動いていればビルド成功。

![4q2x9xqly4vi21mq20rhh6u81c6f.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51857/dc756c95-9c20-e9e3-e362-3fe8c4c74e64.png)


# トラブルシュート

## fatal error C1083: Cannot open include file: 'tensorflow/c/c_api_macros.h': No such file or directory

### 原因

Windows 版の TensorFlow ライブラリパッケージには足りないライブラリがある。
（なぜ初歩的なエラーが公式ライブラリに……

https://github.com/tensorflow/tensorflow/issues/46343

### 解決方法

以下の2通りで解決できる。

1. Linux 版のパッケージをダウンロードし、そこから足りないファイルをコピーする。
2. Git レポジトリをクローンしてきて、そこからコピーして使用する。

今回、僕の場合は1の方を選択した。

# 最後に

これで CMake での Houdini TensorFlow ノードのビルド環境が構築できた。
また、ここら辺を行うにあたって、今回 VSCode 側でのタスクランナーの設定をしたりしたのがとても勉強になったので、そこら辺を別途まとめたい。