OpenCVをRubyMotionから使う方法

OpenCVをRubyMotionから使う方法

RubyMotionからOpenCVを使えるようにするの(MotionCV)を今年ずっとやってましたのでAdvent Calendarではその解説を行います。

# MotionCVの仕組み

RubyMotionから使えるようにOpenCVをビルドするのは[このエントリー](http://www.iwazer.com/~iwazawa/diary/2013/08/build-opencv-to-be-usable-from-rubymotion.html)にも書いているのですが、OpenCVはC++で書かれているためiOS用にビルドされたライブラリをそのまま使えません。

OpenCVのiOS用ビルド環境を使いながら、RubyMotionからC++が見えないようにObjective-Cの薄いラッパーを作りそれを通して使います。

## ビルド方法

ラッパープログラムはOpenCVのmodulesの[一部として配備](https://github.com/iwazer/opencv/tree/motioncv/modules/motion)して、RakefileからOpenCVのiOS用ビルドスクリプトの中で一緒にビルドします。

具体的には私の[OpenCVのForkのmotioncvブランチ](https://github.com/iwazer/opencv)をcloneしてrakeします。

```
git clone https://github.com/iwazer/opencv.git
cd opencv
rake
```

ビルドが終わると`gem/framework/opencv2.framework`が生成され、これがそのままRubyMotionプロジェクトの3rd Party Frameworkとして使用できます。

# Objective-Cによるラッパー

OpenCVは非常に多くの機能を持っていますが、現在のMotionCVは[core](http://opencv.jp/opencv-2svn/cpp/core._the_core_functionality.html)、[imgproc](http://opencv.jp/opencv-2svn/cpp/imgproc._image_processing.html)のごく一部のみ実装しています。

coreは画像などを表現する基本的な構造体やクラス、それに対してビット操作したり描画したりするための基本的な機能の集合です。

imgprocは画像処理のためのビルトインフィルターやより複雑な変換、フィルターを自分で作るための機能の集合です。

MotionCVでは`modules/motion`配下でObjective-Cのヘッダファイルと実装ファイルとしてラップします。

```
$ tree modules/motion
modules/motion
├── CMakeLists.txt
├── include
│   └── opencv2
│       └── motion
│           ├── MotionCV_capi.h
│           ├── MotionCV_core.h
│           ├── MotionCV_helper.h
│           ├── MotionCV_imgproc.h
│           ├── MotionMat.h
│           └── core_base.h
└── src
    ├── MotionCV_capi.mm
    ├── MotionCV_core.mm
    ├── MotionCV_helper.mm
    ├── MotionCV_imgproc.mm
    ├── MotionMat.mm
    └── MotionMat_p.h
```

_OpenCVの実装ファイルは非常に細かく分かれていますが、MotionCVでは[C++リファレンス](http://opencv.jp/opencv-2svn/cpp/)の単位でファイルを分割しました。これが渡しには分かりやすかったので。_

それぞれのソースファイルについて説明します。

## MotionMat.{h,mm}、MotionMat_p.h

coreで定義されている画像のピクセルを保持するcv::MatをMotionMatというObjective-Cクラスでラップしています。

RubyMotionからは見えないが、ラッパーの実装コードからは見たいインスタンス変数やメソッドを`MotionMat_p.h`に分離しています。

`MotionMat#mat`というインスタンスメソッドが、本来の`cv::Mat`インスタンスを返すのですがRubyMotionからは見えません。逆に見えるとコンパイルエラーになります。

_ちなみにヘッダファイルの`include/opencv2/motion`という格納場所はビルドルールでフレームワークの公開ヘッダファイルとなっています。_

C++とObjective-C両方のオブジェクトを扱うので、実装ファイル名の拡張子は`.mm`です。

MotionMatクラスは内部にcv::Matインスタンスを持ち、生成とcv::Matのメソッドへのプロキシの役割をします。

_ただし、MotionMatにプロキシメソッドを追加するのは結構難しいです。例えば[copyTo](http://opencv.jp/opencv-2svn/cpp/core_basic_structures.html#cv-mat-copyto)という自分の内部状態を、引数で渡されたcv::Matにコピーするメソッドがあるのですが、今のところメモリ管理がうまくいかずMotionMatのメソッドとしては使えるようにできてません（-ω-）_

## MotionCV_core.{h,mm}

coreに定義されているcv::Matを操作する関数群をラップしています。

`cv::circle`を例にとると

```objective-c:MotionCV_core.h
+ (void)circle:(MotionMat *)mat
        center:(CGPoint)center
        radius:(int)radius
           bgr:(int *)bgr
     thickness:(int)thickness
      lineType:(int)lineType
         shift:(int)shift;
```

```objective-c:MotionCV_core.mm
+ (void)circle:(MotionMat *)mat
        center:(CGPoint)center
        radius:(int)radius
           bgr:(int *)bgr
     thickness:(int)thickness
      lineType:(int)lineType
         shift:(int)shift
{
    cv::circle([mat mat],
               cv::Point(center.x, center.y), radius,
               cv::Scalar(bgr[0], bgr[1], bgr[2]),
               thickness,
               lineType,
               shift);
}
```

このように`cv::circle`に`cv::Mat`を渡し、パラメータのつじつまを合わせているだけです。

centerのような位置指定はCGPointを使い、Ruby側で配列でも指定できるようにしてあります。

また、bgrは色の指定なのですが、Ruby側からはもう１段ラッパーを入れて、これも配列で渡せるようにしてあります。

_試行錯誤中なのでもっと良い方法があれば変えるかもしれませんが_

Cのプリミティブ型や構造体、Objective-CのオブジェクトはRubyMotionがうまくやってくれるのでそのまま使って問題ありません。

`MotionCV_imgproc.{h,mm}`については、これとほぼ同じなので詳しい説明は割愛します。

## MotionCV_helper.{h,mm}

OpenCVとRubyMotionの橋渡しをするための関数を定義しています。現在、UIImageとMotionMatを相互に変換する関数があります。

```objective-c:MotionCV_helper.h
@interface MotionCV_helper : NSObject

+ (MotionMat *)MotionMatFromUIImage:(UIImage *)image;
+ (UIImage *)UIImageFromMotionMat:(MotionMat *)mat;

@end
```

OpenCVの機能ではないがObjective-Cでの実装が必要な機能はここに定義していくつもりです。

## MotionCV_capi.{h,mm}

[@watson1978](https://twitter.com/watson1978)さんがC APIをRubyMotionから使えるようにラップしたものを入れています。まだ巷にはC APIのサンプルも多いので、こちらを拡張するのも有用かもしれません。

# Rubyによるラッパー

RubyMotionはObjective-Cのメソッドをそのまま呼び出せるのですが、もう少し使いやすく、またC++ APIを使ったOpenCVのサンプルをRubyに置き換えやすくしたいと考えRubyのラッパーレイアも作ってあります。

```
$ tree gem/lib/project/
gem/lib/project/
├── cv
│   ├── capi.rb
│   ├── core.rb
│   ├── helper.rb
│   └── imgproc.rb
├── motion_mat.rb
└── motioncv.rb
```

OpenCVのC++ APIでは、`cv::Mat`を生成して、`cv::method(srcMat, dstMat, parameters...)`という操作系メソッドを呼ぶことによっていろいろな事ができるようになっています。

_(ビルトインでない機能は自分でピクセル操作しないといけませんが…)_

できるだけ、その表記法に近づけるようにCvモジュールのクラスメソッドとしてラップしてあります。

たとえば、先ほどの`cv::circle`は

```ruby:core.rb
    def circle mat, center, radius, rgb, thickness, lineType=8, shift=0
      MotionCV.circle(mat, center:center.map(&:to_f), radius:radius.to_i,
                           bgr:rgb2bgr(rgb),
                           thickness:thickness, lineType:lineType, shift:shift)
    end
```

このようになっています。実際にこれを使うと

```
  Cv::circle(mat, [100,100], 100, [255,0,0])
```

のように書けて、Objective-Cのメソッドを直接呼び出すよりC++のサンプルを参考にし易いと思います。

# RubyMotionから使う

RubyMotionのiOSテンプレートでプロジェクトを作ります。先ほどのMotionCVとは別のディレクトリの方が良いです。

```
motion create try-motioncv
cd try-motioncv
```

frameworkへのシンボリックリンクをvendorディレクトリに作成します。

```
mkdir vendor
ln -s /path/to/opencv/gem vendor/opencv
```

Gemfileにmotioncvを追加してbundleを実行。

```
source 'https://rubygems.org'

gem "rake"
gem "motioncv", :path => "/path/to/opencv"
```

3rd Party Frameworkの設定などは[gemの中](https://github.com/iwazer/opencv/blob/motioncv/gem/lib/motioncv.rb)で自動的にされますので、これでMotionCVが呼べるようになります。

# まとめ

実際に動くサンプルを作りながら、MotionCVに足りないメソッドを追加するのもやりたかったのですが、長くなってしまったので、別のエントリーにしたいと思います。

とりあえず実際に動く例は[RubyMotionとOpenCVでマンガ風フィルタ](http://www.iwazer.com/~iwazawa/diary/2013/08/cartoonize-filter-opencv-and-rubymotion.html)を参照下さい。

最後に、現状のTODOをまとめて、来年の[もくもく会](http://connpass.com/series/130/)や[RubyMotionTokyo](http://rubymotion-tokyo.doorkeeper.jp/)でのネタを確認しときますｗ

* arch=arm64対応（本家のmasterは対応されてるっぽいけどビルドにコケるので様子見中…）
* cv::MatのメソッドをMotionMatで単にプロキシするとうまく動かない(ex. copyTo)のがあるのを何とかする
* メモリマネージメントの確認。リークしてる気がする…
* OpenCV中のマクロや定数でRubyMotionから見えないものを何とかしたい
* ビデオ系の機能もやってみたい
* |ω・)＜テストがないお
* 画像処理の経験が乏しくつらいので画像処理とOpenCV自体の勉強 orz
* OSXでもRubyMotionから使えるようにしたい気がする