iOS向けopencv2.framework生成方法 with opencv_contrib

この記事は[OpenCV Advent Calendar 2015](http://qiita.com/advent-calendar/2015/opencv)の22日目の記事です．
※今回の記事はどちらかというとアプリ開発者寄りなものになっています．

# はじめに
公式がビルドしたOpenCVのiOS向けフレームワーク（opencv2.framework）は
以下のURL，配布形態で公開されています．

| 配布先 | 配布形態 |
|:--|:--|
| [SourceForge.net](http://sourceforge.net/projects/opencvlibrary/files/opencv-ios/3.0.0/) | バイナリ|

iOS上でOpenCVを使う場合，公式が配布している前述のopencv2.frameworkを
利用するのが最も敷居が低いのですが，一方で

- ビルドオプションを自分好みにカスタマイズしたい
- 最新のコンパイラを使ってビルドしたい
- 必要なモジュールだけビルドしてライブラリサイズを小さくしたい

場合などソースからビルドした方が都合が良いケースもあります．
ソースからのビルド手順（iOS向け）は[公式チュートリアル](http://docs.opencv.org/3.0.0/d5/da3/tutorial_ios_install.html)を参照ください．

しかし，この方法で生成するフレームワークは[opencv_contrib](https://github.com/itseez/opencv_contrib)の機能を含めることができないという課題があります．

一方で，[opencv_contrib](https://github.com/itseez/opencv_contrib)には比較的新しめな機能が入っているということもあり，iOS上でもそれらの機能を使いたいケースがあるため，この記事ではopencv_contribの機能を含んだiOS向けopencv2.frameworkの生成方法について紹介します．

# OpenCV 3.0のplatforms/ios/build_framework.py
OpenCV 3.0の<code>opencv-3.0.0/platforms/ios/build_framework.py</code>（iOS向けビルドスクリプト）には~~おそらく多くの人に存在が知られていないと思われる~~<code>--contrib</code>オプションが追加されています．また，この<code>--contrib</code>オプションではopencv_contribのソースがあるパスを指定できます．

以下に<code>opencv-3.0.0/platforms/ios/build_framework.py</code>の一部を抜粋します．

```py3:build_framework.py
    # path to OpenCV main repository - hardcoded ../..
    opencv_path = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "../.."))
    print "OpenCV:", opencv_path

    # path to OpenCV_contrib repository, can be empty - global variable
    if hasattr(args, "contrib") and args.contrib is not None:
        if os.path.isdir(args.contrib + "/modules"):
            opencv_contrib_path = os.path.abspath(args.contrib + "/modules")
            print "Contrib:", opencv_contrib_path
        else:
            print "Note: contrib repository is bad: modules subfolder not found"
```

一見このオプションを使うとopencv_contribありのフレームワークが生成，利用できそうなのですが実際には正しく動作しません（厳密にはフレームワーク生成まではできるのですが，インポートしても正しく動作しません）．

というわけで，OpenCV 3.0時点ではこのままだと**opencv_contrib**の機能が使えそうで使えないというなんとももどかしい状況になっています．

## 従来の回避策
とは言え，世の中にはこの問題に正面から立ち向かう人もいて，
[@shu223](https://twitter.com/shu223)さんの記事，[OpenCV 3.0 の拡張モジュール群 opencv_contrib を iOS で利用する](http://qiita.com/shu223/items/5a517bc10503278d1ff1)には
前述の問題点に対する回避策についていくつか紹介されています．

1. [How to build opencv_contrib module for iOS - OpenCV Q&A Forum](http://answers.opencv.org/question/40392/how-to-build-opencv_contrib-module-for-ios/)<br>opencv_contrib/modules 配下のソースをopencv/modules にコピーしてビルドします．<br>→手順としては簡単~~だけどかなり強引~~な回避策です・・・<br><br>

1. [opencv_contribを有効にしたopencv2.framework(for iOS)生成方法](https://github.com/atinfinity/lab/wiki/opencv_contrib%E3%82%92%E6%9C%89%E5%8A%B9%E3%81%AB%E3%81%97%E3%81%9Fopencv2.framework(for-iOS)%E7%94%9F%E6%88%90%E6%96%B9%E6%B3%95)<br>筆者が作成した回避策．<code>--contrib</code>オプションを使用すると<br>opencv_contrib分も含めて一つのopencv2.frameworkにまとめます．<br>→ビルドスクリプトの変更がありちょっと面倒かも．

# 公式とのやり取り
## プルリクエスト
「従来の回避策」で挙げたもののうち，筆者が提案する後者の方法について
[changed to build opencv2.framework with opencv_contrib #5237](https://github.com/Itseez/opencv/pull/5237)
という形でプルリクエストを行いました．

## 公式からの修正提案
その後，何度か議論を進めていく上で，公式側も現状のopencv2.frameworkの問題点を
理解してくれたようで公式側で[修正方法を提案](https://github.com/Itseez/opencv/pull/5237#issuecomment-157738040)してくれました．

## 公式側の修正マージ
最後に私の動作チェックが終わってこの件はクローズとなり，
[iOS framework with contrib #5654](https://github.com/Itseez/opencv/pull/5654)としてマージされました．

# opencv2.framework生成方法
OpenCV 3.0のコードに対してopencv_contrib-3.0.0を用いてopencv2.frameworkを生成する手順は以下の通りです．

1. [OpenCV 3.0](https://github.com/Itseez/opencv/releases/tag/3.0.0)のコードを取得して展開する．
1. [opencv_contrib-3.0.0](https://github.com/Itseez/opencv_contrib/releases/tag/3.0.0)のコードを取得して展開する．
1. <code>opencv-3.0.0/platforms/ios/build_framework.py</code>を[OpenCV 3.1](https://github.com/Itseez/opencv/releases/tag/3.1.0)のbuild_framework.pyに差し替える．
1. ターミナルで以下のコマンドを実行する．<br>
<code>python ./opencv-3.0.0/platforms/ios/build_framework.py ios --contrib ./opencv_contrib-3.0.0</code><br>
1. ビルドが完了するとiosディレクトリにopencv2.framework（opencv_contribあり）が生成されます．

※[opencv_contrib-3.0.0](https://github.com/Itseez/opencv_contrib/releases/tag/3.0.0)にあるモジュールでiOS向けビルドに失敗するモジュールがある場合は<code>opencv_contrib-3.0.0/modules</code>以下からモジュールのディレクトリを削除することでひとまずビルド対象から除外できます．

## 補足
[OpenCV 3.1のChange Logs](http://code.opencv.org/projects/opencv/wiki/ChangeLog#31)（※この記事執筆時点ではドラフト版）には以下の記載があり，OpenCV 3.1からは<code>platforms/ios/build_framework.py</code>の<code>--contrib</code>オプションが正式サポートされ，opencv2.framework（opencv_contribあり）が生成できるようになることが読み取れます．

> The iOS framework (opencv2.framework) can now be configured to include both opencv and opencv_contrib functionality. Previously, there were 2 separate frameworks, but because of the include directory names ("opencv2/...", regardless of whether some module is from opencv or from opencv_contrib), the produced opencv_contrib was unusable.

そのため，OpenCV 3.1以降は前述の「opencv2.framework生成方法」にある<code>platforms/ios/build_framework.py</code>の差し替えは不要になる見込みです．

# 注意点
前述の手順でiOS向けopencv2.framework（opencv_contribあり）を生成できるようになるわけですが，opencv_contrib全体を含めるとライセンス絡みで懸念のあるもの（SIFTやSURF等）も一緒にframeworkに含まれてしまいます．

商用で利用する際には必要に応じて<code>-DBUILD_opencv_xxx=OFF</code>（※xxxはモジュール名）とすることで不要なモジュールをビルド対象から外す必要がある点にご注意ください．

例えば**xfeatures2d**モジュールをビルド対象から外す場合には<code>-DBUILD_opencv_xfeatures2d=OFF</code>とする必要があります．具体的には，<code>platforms/ios/build_framework.py</code>にあるbuild_opencv関数の

```py3:build_framework.py
    cmakeargs = ("-GXcode " +
                "-DCMAKE_BUILD_TYPE=Release " +
                "-DCMAKE_TOOLCHAIN_FILE=%s/platforms/ios/cmake/Toolchains/Toolchain-%s_Xcode.cmake " +
                "-DCMAKE_C_FLAGS=\"-Wno-implicit-function-declaration\" " +
                "-DCMAKE_INSTALL_PREFIX=install") % (srcroot, target)
```

にてCMakeオプションの文字列を生成している箇所で<code>-DBUILD_opencv_xxx=OFF</code>（※xxxはモジュール名）を追加します．

# おわりに
この記事では，

- iOS向けopencv2.framework（opencv_contribあり）生成方法
- iOS向けopencv2.framework（opencv_contribあり）生成における注意点

について解説しました．


# 備考
筆者は以下の環境で動作確認しました．

* [OpenCV 3.0.0](https://github.com/Itseez/opencv/releases/tag/3.0.0)
* [opencv_contrib 3.0.0](https://github.com/Itseez/opencv_contrib/releases/tag/3.0.0)
* Mac OS X Yosemite
* Xcode 7.2