iOSでCaffeの学習済みモデルを使う

最近opencv_contribにディープラーニング(dnn)モジュールが追加されたようです.
チュートリアルを見るとCaffeのモデルが使えるようなので, iOS frameworkをビルドしてGoogLeNetを使ってみました.

ググると色々とframeworkをビルドする方法が出てくるのですが, OpeCV3.0.0と現在(2015/10/17)のopencv_contribではビルドが通りませんでした. なのでframeworkのビルド方法からまとめてみようと思います.

dnnモジュールを含んだopencv_contrib frameworkのビルド

まず, githubからopencv, opencv_contribをcloneしてopencvは3.0.0タグをチェックアウトします. opencv_contribはmasterで良いと思いますが自分は9818f481bd1556ea8784faa70afeea07c52502feのコミットを使いました.

$ git clone git@github.com:Itseez/opencv.git
$ cd opencv
$ git checkout -b tag-3.0.0 refs/tags/3.0.0
$ cd ..
$ git clone git@github.com:Itseez/opencv_contrib.git
$ cd opencv_contrib
$ git checkout 9818f481bd1556ea8784faa70afeea07c52502fe

opencv_contribを含んだアプリを公開することを考えてnonfreeなもの(xfeature2dモジュール)を除外します. また, saliency, ximageprocモジュールのコンパイルに失敗してしまうのでこれらも除外します. BINGが使えないのは悲しいですが, 今回の目的はCaffeモデルを利用することなので目をつむることにしましょう.

$ git diff
diff --git a/platforms/ios/build_framework.py b/platforms/ios/build_framework.py
index 1acd2b2..f303603 100755
--- a/platforms/ios/build_framework.py
+++ b/platforms/ios/build_framework.py
@@ -53,6 +53,9 @@ def build_opencv(srcroot, buildroot, target, arch):
                 "-DCMAKE_BUILD_TYPE=Release " +
                 "-DCMAKE_TOOLCHAIN_FILE=%s/platforms/ios/cmake/Toolchains/Toolchain-%s_Xcode.cmake " +
                 "-DCMAKE_C_FLAGS=\"-Wno-implicit-function-declaration\" " +
+                "-DBUILD_opencv_saliency=OFF " +
+                "-DBUILD_opencv_ximgproc=OFF " +
+                "-DBUILD_opencv_xfeatures2d=OFF " +
                 "-DCMAKE_INSTALL_PREFIX=install") % (srcroot, target)

     if arch.startswith("armv"):

次にProtocolBuffersのコードを少し修正します. 現在のProtocolBuffersはarm64に対応しているのですが, opencv_contribに含まれているProtocolBuffersは古くarm64でクロスコンパイル時に対応してないアーキテクチャとしてコンパイルが失敗してしまいます. workaroundですが以下のように修正します. ProtocolBuffersは3rdparyライブラリとしてビルドされるだけなので, opencv_contribのビルド時にはProtocolBuffersを除外して, iOSアプリをビルドするときにCocoaPods等で追加することも可能だと思いますが試していません.

$ git diff modules/dnn/3rdparty/protobuf/src/google/protobuf/stubs/platform_macros.h
diff --git a/modules/dnn/3rdparty/protobuf/src/google/protobuf/stubs/platform_macros.h b/modules/dnn/3rdparty/protobuf/src/google/protobuf/stubs/platform_macros.h
index b1df60e..8abfd0c 100644
--- a/modules/dnn/3rdparty/protobuf/src/google/protobuf/stubs/platform_macros.h
+++ b/modules/dnn/3rdparty/protobuf/src/google/protobuf/stubs/platform_macros.h
@@ -49,6 +49,9 @@
 #elif defined(__ARMEL__)
 #define GOOGLE_PROTOBUF_ARCH_ARM 1
 #define GOOGLE_PROTOBUF_ARCH_32_BIT 1
+#elif defined(__aarch64__)
+#define GOOGLE_PROTOBUF_ARCH_AARCH64 1
+#define GOOGLE_PROTOBUF_ARCH_64_BIT 1
 #elif defined(__MIPSEL__)
 #define GOOGLE_PROTOBUF_ARCH_MIPS 1
 #define GOOGLE_PROTOBUF_ARCH_32_BIT 1

このままframeworkのビルドをすると出来上がったframeworkに含まれる静的ライブラリにはdnnモジュールのシンボルが含まれません. なのでopencv_contrib_worldモジュールにすべてのモジュールが含まれるようにCMakeLists.txtを修正します. 他のモジュールもいくつか抜けていたので, frameworkひとつにまとめるため一緒に追加してしまいます.

$ git diff modules/contrib_world/CMakeLists.txt modules/dnn/CMakeLists.txt modules/saliency/CMakeLists.txt modules/ximgproc/CMakeLists.txt
diff --git a/modules/contrib_world/CMakeLists.txt b/modules/contrib_world/CMakeLists.txt
index dafe6d5..e063525 100644
--- a/modules/contrib_world/CMakeLists.txt
+++ b/modules/contrib_world/CMakeLists.txt
@@ -5,11 +5,15 @@ set(BUILD_opencv_contrib_world_INIT OFF) # disabled by default

 # add new submodules to this list
 set(OPENCV_MODULE_CHILDREN
+  adas
+  aruco
   bgsegm
   bioinspired
   ccalib
   cvv
   datasets
+  dnn
+  dpm
   face
   latentsvm
   line_descriptor
@@ -17,6 +21,8 @@ set(OPENCV_MODULE_CHILDREN
   reg
   rgbd
   saliency
+  stereo
+  structured_light
   surface_matching
   text
   tracking
diff --git a/modules/dnn/CMakeLists.txt b/modules/dnn/CMakeLists.txt
index c2fdea9..91f5f5e 100644
--- a/modules/dnn/CMakeLists.txt
+++ b/modules/dnn/CMakeLists.txt
@@ -1,6 +1,5 @@
 cmake_minimum_required(VERSION 2.8)
 set(the_description "Deep neural network module. It allows to load models from different frameworks and to make forward pass")
-set(OPENCV_MODULE_IS_PART_OF_WORLD OFF)

 ocv_add_module(dnn opencv_core opencv_imgproc WRAP python matlab)
 ocv_warnings_disable(CMAKE_CXX_FLAGS -Wno-shadow -Wno-parentheses -Wmaybe-uninitialized -Wsign-promo -Wmissing-declarations -Wmissing-prototypes)
@@ -58,4 +57,4 @@ if(${the_module}_BUILD_TORCH_TESTS)
                         COMMAND th ${CMAKE_CURRENT_SOURCE_DIR}/testdata/dnn/torch/torch_gen_test_data.lua
                         WORKING_DIRECTORY  $ENV{OPENCV_TEST_DATA_PATH}/dnn/torch )
     add_definitions(-DENABLE_TORCH_TESTS=1)
-endif()
\ No newline at end of file
+endif()
diff --git a/modules/saliency/CMakeLists.txt b/modules/saliency/CMakeLists.txt
index 31f09f9..ba6b216 100644
--- a/modules/saliency/CMakeLists.txt
+++ b/modules/saliency/CMakeLists.txt
@@ -1,3 +1,2 @@
 set(the_description "Saliency API")
-set(OPENCV_MODULE_IS_PART_OF_WORLD OFF)
 ocv_define_module(saliency opencv_imgproc opencv_highgui opencv_features2d WRAP python)
diff --git a/modules/ximgproc/CMakeLists.txt b/modules/ximgproc/CMakeLists.txt
index 91ed65e..7348690 100644
--- a/modules/ximgproc/CMakeLists.txt
+++ b/modules/ximgproc/CMakeLists.txt
@@ -1,5 +1,4 @@
 set(the_description "Extended image processing module. It includes edge-aware filters and etc.")
-set(OPENCV_MODULE_IS_PART_OF_WORLD OFF)
 ocv_define_module(ximgproc opencv_imgproc opencv_core opencv_highgui opencv_calib3d WRAP python)

 target_link_libraries(opencv_ximgproc)

ここでframeworkのビルドコマンドを実行します.

$ python opencv/platforms/ios/build_framework.py --contrib opencv_contrib opencv_framework

opencv_frameworkディレクトリにframeworkが吐かれます. ですが, opencv2_contrib.frameworkとして吐かれるため, Xcodeにframeworkを追加した時ヘッダをopencv2から始まる相対パスで参照することができず, ヘッダが存在しないエラーの嵐に悩まされることになります. これを回避するためヘッダファイル中の#includeを書き換えます. また, ProtocolBuffersはUniversalバイナリとしてまとめられないので, 利用しやすいようにlipoでまとめてしまいます. これらを行うため以下のシェルスクリプトを作りました. opencv_frameworkディレクトリで実行してください.

#!/bin/bash

# Replace include path

modules=(
  adas
  aruco
  bgsegm
  bioinspired
  ccalib
  cvv
  datasets
  dnn
  dpm
  face
  latentsvm
  line_descriptor
  optflow
  reg
  rgbd
  saliency
  stereo
  structured_light
  surface_matching
  text
  tracking
  xfeatures2d
  ximgproc
  xobjdetect
  xphoto
)

headers=`find ./opencv2_contrib.framework/Headers/* -name *.hpp`

for module in ${modules[@]}; do
  echo $module
  sed -i "" -e "s/opencv2\/${module}/opencv2_contrib\/${module}/g" $headers
done


# Make universal library

lipo -create ./build/iPhoneOS-armv7/3rdparty/lib/Release/liblibprotobuf.a ./build/iPhoneOS-armv7s/3rdparty/lib/Release/liblibprotobuf.a ./build/iPhoneOS-arm64/3rdparty/lib/Release/liblibprotobuf.a ./build/iPhoneSimulator-i386/3rdparty/lib/Release/liblibprotobuf.a ./build/iPhoneSimulator-x86_64/3rdparty/lib/Release/liblibprotobuf.a -output libprotobuf.a

わりと力技な感があるので, dnnを含んだopencv_contrib frameworkをもっとスマートにビルドする方法を知っている方がいればぜひ教えていただきたいです.

dnnモジュールを利用する

上記の手順を行うとopencv_frameworkディレクトリlibprotobuf.aとopncv2_contrib.frameworkが作成されていると思うので, これをアプリのXcodeプロジェクトに追加してください. また, ここから3.0.0のopencv.frameworkをダウンロードし一緒にプロジェクトに追加します. ダウンロードしたopencv.frameworkに含まれる静的ライブラリにはLLVMビットコードが含まれていないので, ビルドするときはBuild SettingsからEnable BitcodeをNoにしてください.

サンプルコードはチュートリアルに載っているものとほとんど同じなので割愛します. 自分はSwiftで利用したかったので, Objective-C++でラッパークラスを作ってBridging Headerを追加しました. ひとつ注意なのはUIImageToMat()でUIImageからcv::Matに変換する際, カラー画像は4チャネルのMatに変換されることです. GoogleLeNeは3チャネルのBlobを入力とするので, BGRフォーマットに変換する必要があります.

試しに認識させる画像と分類結果を表示するアプリを作ってiPhone6実機で動かしてみました. iOS Simulatorでも問題なく動きます.
IMG_0313
以上です.

広告
カテゴリー: Code, Computer Vision, Machine Learning, OpenCV パーマリンク

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中