iOSからOpenCVを使った話

iOSからOpenCVを使ってポッキーを認識させた話

初めての画像処理とOpenCV, 一年ぶりくらいのObjective-Cである。
xcodeのprojectの設定メニューのUIが変わっていたことに戸惑ったことから始まったのは内緒

まずはOpenCVを入れるて、xcodeから読み込めるようにするところから始めるわけだが、これは簡単

gemでcocoapodsを入れる

$  sudo gem install cocoapods

次にxcodeでプロジェクトを作成する
作成したプロジェクトトップのディレクトリなかにPodfileというファイルを作成し、下記を記述

pod 'OpenCV'

インストールコマンドを実行

$ pod install

あとは待つだけ。

次に、プロジェクトをxcodeで開く

$ open <project名>.xcworkspace #xcodeprojではないので注意

あとは読み込む。
ただし、C++を使う時はファイルの拡張しを".m"から".mm"に変えておく必要がある。

#import <opencv2/opencv.hpp>

UIImage <-> cv::Matの変換用のメソッドが用意されているので、
処理対象の画像をUIImageで読み込んで、cv::Matに変換してしまえば、
C++と全く同じようにOpenCVが扱える。
最後に出力する画像をcv::MatからUIImageに変換して出力すればよい。

それぞれの変換メソッドのシグニチャ

// UIImageからcv::Matに変換
void UIImageToMat(const UIImage* image, cv::Mat& m, bool alphaExist = false)

// cv::MatからUIImageに変換
UIImage* MatToUIImage(const cv::Mat& image)

んでポッキーをどう認識させるについて。
私は画像認識についてはド素人なので、
ネットでポッキーの画像を取得して、テンプレートマッチングさせた。

認識させる対象の画像に映るポッキーのサイズはカメラから取得される物のため、
スケールがわからない。
そのため、ポッキーの画像をスケールを変えながらスコアが閾値を超えたら矩形範囲を取得
という強引な手法をとった。処理速度はすこぶる遅い。

実際のコードは

//ポッキーの位置を探す画像
UIImage *img = [UIImage imageNamed:@"hoge.jpg"];
cv::Mat src_img;
UIImageToMat(img, src_img);

//ポッキーの画像
UIImage *pp = [UIImage imageNamed:@"pp.jpg"];
cv::Mat pp_img;
UIImageToMat(pp, pp_img);

// ポッキーの画像が小さいと問答無用でスコアが高くなるので、大きいスケールから順にスコアを計算する
for (int i = 200; i > 0; i--) {
        CGFloat scale = i * 0.01;
        CGFloat scale_x = pp.size.width * scale;
        CGFloat scale_y = pp.size.height * scale;
        if (scale_x >= img_scale.size.width || scale_y >= img_scale.size.height) continue;
        UIGraphicsBeginImageContext(CGSizeMake(scale_x, scale_y));
        [pp drawInRect:CGRectMake(0, 0, scale_x, scale_y)];
        UIImage *pp_scale = UIGraphicsGetImageFromCurrentImageContext();
    
        //cv::Matの画像
        cv::Mat pp_scale_img;
        UIImageToMat(pp_scale, pp_scale_img);
    
        //マッチング
        cv::Mat result_img;
        cv::matchTemplate(src_img, pp_scale_img, result_img, CV_TM_CCOEFF_NORMED);
    
        //スコア取得
        cv::Rect roi_rect(0, 0, pp_scale_img.cols, pp_scale_img.rows);
        cv::Point max_pt;
        double maxVal;
        cv::minMaxLoc(result_img, NULL, &maxVal, NULL, &max_pt);
        
        if (maxVal >= 0.55) { //閾値0.55はだいたい良さそうな結果が出た値
            //サイズ設定
            roi_rect.x = max_pt.x;
            roi_rect.y = max_pt.y;
    
            //ポッキーの位置を矩形表示
            cv::rectangle(src_img, roi_rect, cv::Scalar(0,0,255), 2);
            break;
        }
}

// 枠線を入れた画像をUIImageに変換
UIImage *result = MatToUIImage(src_img);

ものぐさな私はデフォルトで初期画面であるUITableViewControllerの背景に結果画像を出力させて動作確認していたので、
謎の線が入っているが、結果はこんな感じである

まだまだド素人だが、画像認識もなかなか楽しそうである。