OpenCV for iOS でウォーリーを探すカメラアプリをつくる

[こちら](http://qiita.com/makietan/items/76b01cc86848c5f33f65) の記事を読んで、これは楽しそうだ、ということで習作として **カメラをかざすとウォーリーを見つけてくれるアプリ** を OpenCV で実装してみました。

OpenCV を iOS アプリに導入する方法は下記記事をご参照ください（バージョン 3.0.0 rc1 で試して更新してあります）

- [OpenCV for iOSの使い方](http://qiita.com/shu223/items/3ff48a8bd6edd910e780)

##テンプレートマッチング

まずは参考記事と同様のテンプレートマッチング処理を iOS で実装してみました。

マッチング処理の実装はこんな感じ。`cv::matchTemplate` を利用します。

```
+ (UIImage *)match :(UIImage *)srcImage templateImage:(UIImage *)templateImage {
    
    cv::Mat srcMat = [OpenCVHelper cvMatFromUIImage:srcImage];
    cv::Mat tmpMat = [OpenCVHelper cvMatFromUIImage:templateImage];
    
    // 入力画像をコピー
    cv::Mat dst = srcMat.clone();
    
    // マッチング
    cv::matchTemplate(srcMat, tmpMat, dst, cv::TM_CCOEFF);
    
    double min_val, max_val;
    cv::Point min_loc, max_loc;
    cv::minMaxLoc(dst, &min_val, &max_val, &min_loc, &max_loc);
    
    // 結果の描画
    cv::rectangle(srcMat, max_loc, cv::Point(max_loc.x + tmpMat.cols, max_loc.y + tmpMat.rows), CV_RGB(0, 255, 0), 2);
    
    return [OpenCVHelper UIImageFromCVMat:srcMat];
}
```

（※OpenCVHelper は名前の通りただのヘルパークラスです。UIImage ↔ cv::Mat の相互変換メソッドを実装してあります）

###利用画像

ちなみにテンプレート画像と入力画像はこんな感じです。冒頭に挙げた[参考記事](http://qiita.com/makietan/items/76b01cc86848c5f33f65)のリンクにあったリポジトリよりお借りしました。

- テンプレート画像

![template.png](https://qiita-image-store.s3.amazonaws.com/0/3180/79a27a27-bbe0-8f2c-08ea-4fba160d47cb.png)

- 入力画像

![wally.jpg](https://qiita-image-store.s3.amazonaws.com/0/3180/c510d930-5ec5-3b87-9019-d8d9fad6e769.jpeg)


###実行結果

うまく見つけてくれました！（左下あたり）

![wally1.jpg](https://qiita-image-store.s3.amazonaws.com/0/3180/056cf494-7e31-c2e9-2f43-35adf3d63e3b.jpeg)


##動画のリアルタイム処理

テンプレートマッチングがうまくいったところで、「カメラをかざすとウォーリーを見つける」ということを実現するために、カメラ入力に対してリアルタイム処理を行うよう実装してみます。

OpenCV は CvVideoCamera という **AVFoundation による動画撮影処理をラップしつつ各フレームに対して OpenCV での画像処理を行いやすくしたようなクラス** があるので、それを利用します。

```
#import <opencv2/videoio/cap_ios.h>
```

```
@interface ViewController () <CvVideoCameraDelegate>
```

```
@property (nonatomic, strong) CvVideoCamera *videoCamera;
```

```
self.videoCamera = [[CvVideoCamera alloc] initWithParentView:self.imageView];

self.videoCamera.defaultAVCaptureDevicePosition = AVCaptureDevicePositionBack;
self.videoCamera.defaultFPS = 30;
self.videoCamera.grayscaleMode = NO;

self.videoCamera.rotateVideo = YES;
self.videoCamera.delegate = self;

[self.videoCamera start];
```

```
#pragma mark - CvVideoCameraDelegate

#ifdef __cplusplus
- (void)processImage:(Mat&)image;
{
    // マッチング処理
}
#endif
```

参考：[OpenCV iOS - Video Processing](http://docs.opencv.org/doc/tutorials/ios/video_processing/video_processing.html)

###実行結果

**全然うまくいきません**でした。まったくウォーリーではない部分を検出してきます。


##テンプレートマッチングのロバスト性

うまくいったケースではテンプレートとして**入力画像の当該部分とビットマップレベルでまったく同じもの**を与えていたわけですが、カメラから入力するとなると、入力画像のサイズ（解像度）が変わったり、方向が変わったり、歪みが加わったりします。

試しに、

- 2倍に拡大した画像
- 半分に縮小した画像
- 180°回転させた画像

<img src="https://qiita-image-store.s3.amazonaws.com/0/3180/f740710e-daad-52fd-60f1-38a6f6d06888.jpeg" width="560">

- 歪ませた画像

<img src="https://qiita-image-store.s3.amazonaws.com/0/3180/639ef379-da13-e96f-52b5-69d307f070b2.jpeg" width="560">

をつくってみて、それぞれテンプレートマッチングにかけてみると**結果は全てNG**でした。

テンプレートマッチングは拡大縮小や回転や歪みに弱いのか、もしくはテンプレート画像が小さすぎたのか。。


##`matchTemplate` で指定できる比較手法の種類

`matchTemplate` のリファレンスページみると他にも比較手法が指定できたりもするようです。

- http://opencv.jp/opencv-2svn/cpp/object_detection.html

ただ、上のページでは計算式しか示されていないので、各手法の違いを説明しているページを探してみました。

- http://homepage3.nifty.com/ishidate/opencv_12/opencv_12.htm

> 　　TM_SQDIFF = 0 ------------- 差の二乗の合計、小さいほど良くマッチしている
　　TM_SQDIFF_NORMED = 1 ---- 同上の正規化
　　TM_CCORR = 2 ------------- 乗算したものの合計、大きいほど良くマッチしている
　　TM_CCORR_NORMED = 3 ---- 同上の正規化
　　TM_CCOEFF = 4 ------------ 相関係数であり、正に大きいほど良くマッチしている
　　TM_CCOEFF_NORMED = 5 --- 同上の正規化

- http://imagingsolution.blog107.fc2.com/blog-entry-186.html

> SSD(Sum of Squared Difference)
 ＳＳＤはテンプレートをラスタスキャンし、同じ位置の画素の輝度値の差の２乗の合計が用いられます。

> SAD(Sum of Absolute Difference)
ＳＡＤはテンプレートをラスタスキャンし、同じ位置の画素の輝度値の差の絶対値の合計が用いられます。
ＳＡＤの値が小さいほど、似ている位置となります。

> 正規化相互相関【NCC:Normalized Cross-Correlation】
テンプレートと画像との類似度として、以下の正規化相互相関を用いられる場合もあります。
類似度が１に近いほど、似ている位置となります。

> 正規化相互相関【ZNCC:Zero-mean Normalized Cross-Correlation】
上記ＮＣＣの相互相関係数ではテンプレートや画像の明るさが変動すると、ＮＣＣの値もふらついてしまうので、テンプレートおよび画像の輝度値の平均値をそれぞれの値から引いて計算することで、明るさの変動があっても安定的に類似度を計算することができます。

> ここで説明しておきながら、最近の工業用途では、この正規化相関の方法よりもパターンの輪郭に基づき、回転やスケール変動などにも対応したマッチング（輪郭サーチ）が一般的になってきています。
最近では、さらにカメラが動いてもマッチングする三次元マッチングなども登場してきています。
正規化相関では明るさの変動にも強く優れたアルゴリズムのように聞こえるかもしれませんが、パターンが重なった場合や欠けた場合に意外と弱かったりもします。

- http://zin.co.jp/blog/opencv-matchtemplate%E3%81%AE%E7%A8%AE%E9%A1%9E%E3%81%BE%E3%81%A8%E3%82%81/


意外と奥が深い・・・また試してみて更新します。「この問題には手法が向いてるんじゃないか」等々アドバイスなどあれば是非！


##こちらもどうぞ

- [OpenCV for iOS で画像内の不要部分を取り除き、修復する - Over&Out その後](http://d.hatena.ne.jp/shu223/20150512/1431341853)
- [iOS / OpenCV 3.0 で画像の特徴点を検出する（AKAZE, SIFT, SURF, ORB） - Over&Out その後](http://d.hatena.ne.jp/shu223/20150511/1431327125)
- [iOS - 「顔以外」のものを画像認識する - Qiita](http://qiita.com/shu223/items/ffd2202eaf92d342f83d)
- [OpenCV 3.0 の拡張モジュール群 opencv_contrib を iOS で利用する - Qiita](http://qiita.com/shu223/items/5a517bc10503278d1ff1)