こちら の記事を読んで、これは楽しそうだ、ということで習作として カメラをかざすとウォーリーを見つけてくれるアプリ を OpenCV で実装してみました。
OpenCV を iOS アプリに導入する方法は下記記事をご参照ください(バージョン 3.0.0 rc1 で試して更新してあります)
##テンプレートマッチング
まずは参考記事と同様のテンプレートマッチング処理を 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 の相互変換メソッドを実装してあります)
###利用画像
ちなみにテンプレート画像と入力画像はこんな感じです。冒頭に挙げた参考記事のリンクにあったリポジトリよりお借りしました。
- テンプレート画像
- 入力画像
###実行結果
うまく見つけてくれました!(左下あたり)
##動画のリアルタイム処理
テンプレートマッチングがうまくいったところで、「カメラをかざすとウォーリーを見つける」ということを実現するために、カメラ入力に対してリアルタイム処理を行うよう実装してみます。
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
###実行結果
全然うまくいきませんでした。まったくウォーリーではない部分を検出してきます。
##テンプレートマッチングのロバスト性
うまくいったケースではテンプレートとして入力画像の当該部分とビットマップレベルでまったく同じものを与えていたわけですが、カメラから入力するとなると、入力画像のサイズ(解像度)が変わったり、方向が変わったり、歪みが加わったりします。
試しに、
- 2倍に拡大した画像
- 半分に縮小した画像
- 180°回転させた画像
- 歪ませた画像
をつくってみて、それぞれテンプレートマッチングにかけてみると結果は全てNGでした。
テンプレートマッチングは拡大縮小や回転や歪みに弱いのか、もしくはテンプレート画像が小さすぎたのか。。
##matchTemplate
で指定できる比較手法の種類
matchTemplate
のリファレンスページみると他にも比較手法が指定できたりもするようです。
ただ、上のページでは計算式しか示されていないので、各手法の違いを説明しているページを探してみました。
TM_SQDIFF = 0 ------------- 差の二乗の合計、小さいほど良くマッチしている
TM_SQDIFF_NORMED = 1 ---- 同上の正規化
TM_CCORR = 2 ------------- 乗算したものの合計、大きいほど良くマッチしている
TM_CCORR_NORMED = 3 ---- 同上の正規化
TM_CCOEFF = 4 ------------ 相関係数であり、正に大きいほど良くマッチしている
TM_CCOEFF_NORMED = 5 --- 同上の正規化
SSD(Sum of Squared Difference)
SSDはテンプレートをラスタスキャンし、同じ位置の画素の輝度値の差の2乗の合計が用いられます。
SAD(Sum of Absolute Difference)
SADはテンプレートをラスタスキャンし、同じ位置の画素の輝度値の差の絶対値の合計が用いられます。
SADの値が小さいほど、似ている位置となります。
正規化相互相関【NCC:Normalized Cross-Correlation】
テンプレートと画像との類似度として、以下の正規化相互相関を用いられる場合もあります。
類似度が1に近いほど、似ている位置となります。
正規化相互相関【ZNCC:Zero-mean Normalized Cross-Correlation】
上記NCCの相互相関係数ではテンプレートや画像の明るさが変動すると、NCCの値もふらついてしまうので、テンプレートおよび画像の輝度値の平均値をそれぞれの値から引いて計算することで、明るさの変動があっても安定的に類似度を計算することができます。
ここで説明しておきながら、最近の工業用途では、この正規化相関の方法よりもパターンの輪郭に基づき、回転やスケール変動などにも対応したマッチング(輪郭サーチ)が一般的になってきています。
最近では、さらにカメラが動いてもマッチングする三次元マッチングなども登場してきています。
正規化相関では明るさの変動にも強く優れたアルゴリズムのように聞こえるかもしれませんが、パターンが重なった場合や欠けた場合に意外と弱かったりもします。
意外と奥が深い・・・また試してみて更新します。「この問題には手法が向いてるんじゃないか」等々アドバイスなどあれば是非!
##こちらもどうぞ