Swiftで OpenCV の ArUco モジュールを使う


はじめに

OpenCV の ArUco モジュールを使ってARマーカーを生成するコードを書きました。
通常、iOSアプリへの OpenCV のインストールはPodfile等を利用することで簡単に行うことができますが、ArUcoのような OpenCV_contrib (最新の技術を中心とした拡張モジュール群) に含まれるモジュールは iOS版の OpenCV には含まれておらず、OpenCV および OpenCV_contrib のソースコードをダウンロードして自前でビルドする必要があります。

OpenCV_contrib を 含めて OpenCV をビルドする

ビルド方法についてはこちらの記事を参考にしました。
http://takesan.hatenablog.com/entry/2018/04/17/210109

  1. 以下のリンクからOpenCVとcontribそれぞれのソースコードをダウンロードします。
    OpenCV: https://github.com/opencv/opencv/releases
    contrib: https://github.com/opencv/opencv_contrib/releases
    OpenCVとcontribのバージョンは必ず合わせるようにしてください。
    私はV3.4.9を使いました。

  2. ダウンロードしたソースコードを展開し、opencv_contrib/modulesにある arucoフォルダをopencv/modules/の下にコピーします。

  3. ターミナルを開き、以下のコマンドでビルドを開始します。
    python のところは MacOS標準の python(V2.7) で実行できます。

$ cd /
$ sudo ln -s /Applications/Xcode.app/Contents/Developer Developer
$ cd <path/to/opencv&contrib>
$ python opencv/platforms/ios/build_framework.py ios

ビルドが終わるとカレントディレクトリにios/という名前のフォルダが作成されています。そこに、opencv2.framework というファイルが作られていれば成功です。

このビルドにはかなり時間がかかります。私の環境では45~60分ほどかかりました。
また、ビルドの実行には cmake が使える必要があり、私の環境ではここでエラーが発生してかなり時間を取られてしまいました。
エラーの内容はうろ覚えですが、ビルドに使われる cmake と Xcode が参照している cmake がリンクしていないという内容だった気がします。

iOSアプリで OpenCV を使う

  1. Xcodeでプロジェクトを作成します。
  2. 先ほど作成した opencv2.framework を Finder から Xcode のナビゲータエリアにドラッグ&ドロップし、「Copy items if needed」をチェックして「Finish」を選択します。

下図のように opencv2.framework がプロジェクトに追加されます。

3. Objective-C用のファイルを作成します。

OpenCV はもともと C++ で書かれたライブラリなので、Swift で使うためには以下のような少々複雑な方法をとる必要があります。

  • Objective-C++ (Objective-CとC++を混在させた言語) で OpenCV 周りの処理を書いたモジュールを用意する
  • ブリッジングヘッダー (<プロジェクト名>-Bridging-Header.h) という Swift と Objective-C 間を中継するファイルを介して Swift から Objective-C++ の処理を呼び出す

3-1. File > New > File... で新しいファイルの作成画面を開き、「Cocoa Touch Class」を選択して「Next」を押下します。

3-2. 任意のクラス名を入力し、「Subclass of:」に「NSObject」、「Language:」に「Objective-C」を選択して「Next」を押下します。

3-3. Objective-C用のブリッジングヘッダーを作成するかと聞かれるので「Create Bridging Header」を選択します。

Objective-C用の拡張子が.hと.mの2つのファイルと共に、下記のブリッジングヘッダーファイルが自動で作成され、SwiftからObjective-Cの処理を呼び出すことができるようになります。このファイルにはこれ以上手を加えることはありません。

OpencvTest-Bridging-Header.h
//
//  Use this file to import your target's public headers that you would like to expose to Swift.
//

#import "OpencvWrapper.h"

ARマーカーを生成するコードの作成 (Objective-C++)

OpencvWrapper.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface OpencvWrapper : NSObject

// 関数の定義 (- (返り値の型 *)関数名;)
- (UIImage *)createARMarker;

@end

Objective-Cは私も今回が初めてなので詳しくないですが、上記の拡張子.hのファイルはヘッダーファイルといってクラスのインスタンス変数や関数の宣言が行われます。

実際に行われる処理は、下記の拡張子.m (今回はObjective-C++なので.mm) の実装ファイルにて記述されます。

OpencvWrapper.mm
#import <opencv2/opencv.hpp>
#import <opencv2/imgcodecs/ios.h>
#import <opencv2/aruco.hpp>
#import "opencvWrapper.h"
using namespace cv;
using namespace std;

@implementation OpencvWrapper

-(UIImage *)createARMarker {
    Mat markerImage; 
    Ptr<aruco::Dictionary> dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250);
    aruco::drawMarker(dictionary, 1, 200, markerImage, 1);
    UIImage * output_img = MatToUIImage(markerImage);
    return output_img;
}

@end

createARMarkerという名前のARマーカーを生成する関数を実装しています。
処理の内容を簡単に説明すると、ArUcoモジュールの drawMaker 関数で生成したARマーカーを、Mat型のmarkerImageに格納し、最後に MatToUIImage関数で markerImage を UIImage型に変換して返しています。
OpenCVでは基本的に画像をMat型という型で扱いますが、SwiftではMat型は扱うことができないので、Swift の UIImage型に変換するため上記のような処理になっています。

getPredefinedDictionary関数は ArUco モジュール内で事前定義されたARマーカーの辞書を取得する関数です。引数でどの辞書を取得するか指定することができ、「aruco::DICT_6X6_250」は、6×6ビットの250個のマーカーが定義された辞書を意味しています。
他の辞書については以下のページに記載があります。
https://docs.opencv.org/3.2.0/d9/d6a/group__aruco.html#gac84398a9ed9dd01306592dd616c2c975

また、drawMarker関数の第二引数は生成するARマーカーのIDとなっており、ここをランダムな値が入るように変更することで、毎回異なるARマーカーを生成することが可能になります。

生成したARマーカーを表示するコードの作成 (Swift)

ViewController.swift
import UIKit

class ViewController: UIViewController {

    let openCV = OpencvWrapper()

    override func viewDidLoad() {
        super.viewDidLoad()
        let aruco_img = openCV.createARMarker()
        let imageView = UIImageView(image: aruco_img)
        imageView.center = self.view.center
        self.view.addSubview(imageView)
    }
}

画面のロード後に OpencvWrapper の createARMarker関数でARマーカーを生成して、View に追加しています。
実行結果は下図のようになります。https://github.com/atinfinity/lab/wiki/aruco%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%A7%E3%83%9E%E3%83%BC%E3%82%AB%E3%83%BC%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B

参考情報