pix2pix-tensorflow付属のツールで画像を線画に変換する(Holistically-Nested Edge Detection)

線画が猫になる、というところで非常に話題になったpix2pixのTensorFlow実装ですが、残念ながらこれを手元で試す手段は用意されていません。猫以外の訓練用画像と事前学習モデルであれば配布はされています。

* [Datasets and Trained Models](https://github.com/affinelayer/pix2pix-tensorflow#datasets-and-trained-models)

しかしながら、自分でデータセットを用意して処理させる手順についてもある程度記述があります。tools/process.pyでいくつかの処理ができるようになっていますが、このドキュメントに書かれていない機能の一つとして、画像の線画化があります。

これを使って線画画像を生成する手順について記録してゆきます。Debian 8(jessie)環境での作業記録ですので、他の環境については適時読み替えてください。

## Dockerを使う方法

docker/Dockerfileを使えば、必要なものが一通り揃います。ただ、このためだけにDockerを使うのも大仰なので、ここではDockerを使わない方法について解説します。

また、自分の環境ではdocker上で不定期にsegfaultを起こすという現象が発生していたのも、この記事を書いた理由の一つです。

## 用いるソフトウェア

* caffe/pycaffe
 * [Holistically-Nested Edge Detection](https://github.com/s9xie/hed) (hed)
 * CMake
 * curl
* octave
 * imageパッケージ
 * unzip

### caffe/pycaffe/HEDのインストール

tools/setup.pyは/opt/caffe以下にインストールされていることを期待しているので、そこにコードを展開します。HEDはCaffe本体を含んでいるので、これをcloneします。特定のコミットバージョン(9e74dd710773d8d8a469ad905c76f4a7fa08f945)を必要とするので、checkoutをします。
python/requirements.txtにあるパッケージをインストールします。
必要のないexamples以下を処理の対象から外し、include/caffe/loss_layers.hppの一部のプロトタイプ宣言を有効化します。
ビルド作業用ディレクトリを作り、CPUモードでビルドします。
ビルドされたpycaffeがimportできるよう、PYTHONPATHを設定します。
最後に、事前学習されたHEDのモデルをダウンロードします。

```shell-session
# mkdir /opt/caffe
# cd /opt/caffe
# pip install --upgrade pip
# pip install -r python/requirements.txt
# git clone https://github.com/s9xie/hed .
# git checkout 9e74dd710773d8d8a469ad905c76f4a7fa08f945
# sed -i "s|add_subdirectory(examples)||g" CMakeLists.txt
# sed -i "647s|//||" include/caffe/loss_layers.hpp
# sed -i "648s|//||" include/caffe/loss_layers.hpp
# mkdir build
# cd build
# cmake -DCPU_ONLY=1 ..
# make
# export PYTHONPATH=/opt/caffe/python
# cd /opt/caffe
# curl -O http://vcl.ucsd.edu/hed/hed_pretrained_bsds.caffemodel
```

### octaveのインストール

octaveは最新のものを入れた方が良いようです。自分はDebian 8(jessie)環境下のUbuntu dockerコンテナでoctave 4.0.0を使ってみたところ、不定期にsegfaultを起こすのでnativeに最新(4.2.1)をインストールしました。
jessie-backportsには4.0.3があるのですが、こちらも不安定でした。
/opt/octave/binをパスに追加し、forgeからimageパッケージをインストールします。debian 8のoctave-imageは古すぎるようです。

```shell-session
# mkdir /opt/octave
# cd /opt/octave
# apt-get build-dep octave
# curl -O https://ftp.gnu.org/pub/gnu/octave/octave-4.2.1.tar.gz
# tar xvf octave-4.2.1.tar.gz
# cd octave-4.2.1
# ./configure --prefix /opt/octave
# make
# make install
# PATH=/opt/octave/bin:$PATH
# octave --eval "pkg install -forge image"
```

### piotr toolboxのインストール

PiotrさんのCV toolbox [https://pdollar.github.io/toolbox/](https://pdollar.github.io/toolbox/)をインストールします。この辺りはDockerfileで行っている作業とほぼ同じです。
最後にimageパッケージを自動的にロードするようにします。

```shell-session
# work=/root
# mkdir -p $work
# cd $work
# apt-get install -y --no-install-recommends unzip
# curl -O https://pdollar.github.io/toolbox/archive/piotr_toolbox.zip
# unzip piotr_toolbox.zip
# octave --eval "addpath(genpath('$work/toolbox')); savepath;" 
# echo "#include <stdlib.h>" > wrappers.hpp
# cat $work/toolbox/channels/private/wrappers.hpp >> wrappers.hpp 
# mv wrappers.hpp $work/toolbox/channels/private/wrappers.hpp 
# mkdir $work/mex 
# cd $work/toolbox/channels/private 
# mkoctfile --mex -DMATLAB_MEX_FILE -o $work/mex/convConst.mex convConst.cpp 
# mkoctfile --mex -DMATLAB_MEX_FILE -o $work/mex/gradientMex.mex gradientMex.cpp 
# mkoctfile --mex -DMATLAB_MEX_FILE -o $work/mex/imPadMex.mex imPadMex.cpp 
# mkoctfile --mex -DMATLAB_MEX_FILE -o $work/mex/imResampleMex.mex imResampleMex.cpp 
# mkoctfile --mex -DMATLAB_MEX_FILE -o $work/mex/rgbConvertMex.mex rgbConvertMex.cpp 
# octave --eval "addpath('$work/mex'); savepath;"
# curl -O https://raw.githubusercontent.com/pdollar/edges/master/private/edgesNmsMex.cpp 
# octave --eval "mex edgesNmsMex.cpp"
# mv edgesNmsMex.mex $work/mex/
# echo pkg load image >> ~/.octaverc
```

## エッジ検出処理

以下のようにして使います。

```
$ python tools/process.py \
 --input_dir /path/to/images \
 --output_dir /path/to/edged \
 --operation edges
```

内部でtf.imageを呼んでいるので、TensorFlowが必要です。

### 適用例

元画像
![ベンガルキャット](https://knok.github.io/images/bengal_cat.jpg)

エッジ画像
![ベンガルエッジ](https://knok.github.io/images/bengal_cat_edges.jpg)

出典: https://ja.wikipedia.org/wiki/%E3%83%99%E3%83%B3%E3%82%AC%E3%83%AB_(%E3%83%8D%E3%82%B3)#/media/File:Bengal_cat_catcrest3.jpg

## Holistisically-Nested Edge Detectionについて

論文もある[Holistically-Nested Edge Detection](https://arxiv.org/abs/1504.06375)のですが、きちんと読んでいません…いずれちゃんと理解したいと思います。

### 6/9追記

ざっと読みました。画像の構造的な特徴(Structured Feature)をEnd-to-Endで学習し、境界の曖昧な部分も含めて学習する、という感じのようです。
画像全体からEnd-to-Endで学ぶという点を強調する"Holistic"、特徴を階層的に捉えて学習するという点を強調する"Nested"という単語を選んだとのことです。

具体的には、入力画像をVGGNetの事前学習モデルに入力し、複数の中間層からの出力をそれぞれ取り出して、それらの入力から実際のエッジ部分を予測するモデルを学習するそうです。

![HED構造](https://knok.github.io/images/hed-network.jpg)

他のDNNベースの手法と比較しても高速(論文中では0.4秒/枚と書かれている)、高精度(BSD500でF値0.782, NYUデータセットでF値0.746)なのだそうです。
手元のマシンにて、CPUで256x256の画像を処理させた限りでは数秒程度かかる感じでした。

## 他のエッジ検出手法

Qiita上では複数のエッジ検出手法が紹介されています。自分は最初gimpのラプラシアンフィルタを使っていたのですが、途中でpix2pix-tensorflow自身にエッジ検出のコードが入っていることに気づいてこちらを使うようにしました。HEDは太さが1ピクセルになるという点が気に入っています。

* [OpenCVでエッジ検出してみる](http://qiita.com/hitomatagi/items/2c3a2bfefe73ab5c86a4)
* [python+OpenCVでエッジ抽出(Sobelフィルタ、ラプラシアンフィルタ)](http://qiita.com/shim0mura/items/3ab2a67c78eafd456c32)
* [そこそこな線画を目指す OpenCV](http://qiita.com/khsk/items/6cf4bae0166e4b12b942)
* [画像から線画をDeepLearningで抽出してみた](http://qiita.com/derui@github/items/9719efa14f44a792362b)

HEDの論文中ではCanny detectorとの比較もあります。

## 今後

実際に猫の画像を収集してedges2catsモデルを構築、公開するところまで持ってゆきたいと思っています。~~画像はWikipediaから収集しています。~~

Wikipediaの画像は必ずしも記事本体と同じ(GFDL/CC-BY-SA 3.0)とは限らないとのことで、[Pixabay](https://pixabay.com/)から収集を試みています。このサイトの画像はすべてCC0(パブリックドメイン)です。REST APIも用意されています。
前景抽出をできるだけ少ない手間でやる方法を今は模索しています。