Xcode8.21のSwiftでOpenCV2を使う(CocoaPodsで組み込む)

エンジニアの德光です。 先日、書棚の整理が行われたのですが、そこでOpenCV2の書籍が廃棄扱いとなっていたの…

エンジニアの德光です。
先日、書棚の整理が行われたのですが、そこでOpenCV2の書籍が廃棄扱いとなっていたので、保護してちょっと遊んでみることにしました。
以前に試した時とは違い、今ではPodで簡単に導入ができるようになっているんですねっ!

◆プロジェクト準備

・Xcodeで「Single View Application」を選び、〔次へ〕ボタンを押す
・言語で「Swift」を選び、名前を「blogOpenCV01」としてプロジェクトを生成させ、Xcodeを終了させる
・「ターミナル」を開いてプロジェクトのフォルダに移動し、「pod init」を実行して【Podfile】を生成させる
・【Podfile】を開き「pod ‘OpenCV2’」を追記する
・「pod install」を実行して、【blogOpenCV01.xcworkspace】を作成させる
・作成された【blogOpenCV01.xcworkspace】を開く

◆OpenCVの利用

残念なことに、OpenCV は Objective-C++ で記述する必要があるので、ラッパークラスを書いて利用することになります。
また、SwiftとObjectiveC++を併用することになるので、ブリッジファイルが必要となります。
とりあえず、ラッパークラスとして「OpenCVWrapper」を作成します。
・プロジェクトにファイルを追加します。「CocoaTouchClass」を選びます
・サブクラスに「NSObject」を選び、言語を「Objective-C」に変更して、クラス「OpenCVWrapper」を作成させます
・生成場所を選んで次へを押すと「Would you like to configure an Objective-C bridging header?」と尋ねてくれるので、〔Create Bridging Header〕ボタンを押します
・これで、ラッパークラスが生成されるとともに、【blogOpenCV01-Bridging-Header】というブリッジファイルまで生成してくれるのです!
・生成された【OpenCVWrapper.m】の拡張子を.mmに変更して、Objective-C++対応にしておきます
(さらに【OpenCVWrapper.h】の拡張子も.hppに変更しておいた方が良いのかも知れません…)
・生成されたブリッジファイルに「#import “OpenCVWrapper.h”」を追記して、Swift側からも利用できるようにします

◆画面の準備

画面を適当に作成し、動作確認する準備をしておきます。
動作したのか確認しやすいように、変換前と変換後の画像を表示できるようにしておき、ボタンを押すと変換されるようにしておきましょう。
・StoryboardでUIImageViewを2つ用意して、元画像と加工画像を表示させることにする
・配置を考えるの面倒なので、StackViewを全画面に貼って「Equal Spacing」を選んでおく
・StackViewの中にImageView2つとButtonを入れて、2つのImageViewの高さを「Equal Height」、Buttonの高さを40pxに設定しておく
・ImageView2つのOutletを「ivOrig」「ivConv」として接続し、ButtonからのActionを「actConv」として繋いでおく
・ivOrigに適当な画像を表示させておき、とりあえずactConvでその画像をivConvにも表示させるようにして動作確認しておく
・適当な画像をアセットなどに登録しておく。(ここでは【SampleImage.jpg】を登録している)

class ViewController: UIViewController {
    @IBOutlet weak var ivOrig: UIImageView!
    @IBOutlet weak var ivConv: UIImageView!
    @IBAction func actConv(_ sender: Any) {
        if let img = self.ivOrig.image {
            self.ivConv.image = img
        }
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        self.ivOrig.image = UIImage(named: "SampleImage")
    }
}

・とりあえずこの状態で実行すると、ivOrig側に画像が表示され、ボタンを押すとivConv側にも同じ画像が表示されればオッケーです

◆画像処理フィルタを作成する

ここからが、やっと本番。OpenCVを用いて、カラー画像をモロクロ化させてみましょう。
モノクロ画像化させるフィルタ関数「convMonochrome()」を作成することにします。
UIImage形式な画像を渡すと、モノクロ化した画像をUIImage形式で返却させることにします。
・【OpenCVWrapper.h】に「-(UIImage *)convMonochrome:(UIImage *)imgSrc;」として定義しておく
・【OpenCVWrapper.mm】に先ほどの関数の実装を記述する
 ・OpenCV側では画像をcv::Mat形式で扱う
 ・cv::Matでは、最初はBGR形式になっているので、形式変換時に気をつける(BGR、RGBなど色の並びの他に、Alpheの有無とかにも)

#import <opencv2/opencv.hpp> //先頭追加必須
#import <opencv2/imgcodecs/ios.h>
#import "OpenCVWrapper.h"

@implementation OpenCVWrapper
//UIImageを渡すと、OpenCVで画像処理したものを、UIImageにして戻してくれる
-(UIImage *)convGrayscale:(UIImage *)imgSrc {
    cv::Mat matOrig; //OpenCVでは画像をcv:Matで扱うので宣言しておく
    cv::Mat matGray; //変換後の画像も宣言しておく
    UIImageToMat(imgSrc, matOrig); //UIImageをcv::Matに変換(RGB->BGR)
    cv::cvtColor(matOrig, matGray, CV_BGR2GRAY); //cv:MatはBGR並び。それをグレイスケールに変換
    return MatToUIImage(matGray);    // cv::MatをUIImageに変換(グレイスケールなのでBGR->RGBは端折る)
}
@end

◆組み込んで、動作確認して終了

さきほど、単純に画像の横流しで動作確認をしていたところに、作成したモノクロ化フィルタを組み込みます。

    @IBAction func actConv(_ sender: Any) {
        if let img = self.ivOrig.image {
            let openCV = OpenCVWrapper()
            let imgConv = openCV.convGrayscale(img)
            self.ivConv.image = imgConv
        }
    }

これで、ボタンを押すとモノクロ化された画像が表示させるようになったはずです。
さくっとOpenCVを使う準備ができたので、次回からは、このOpenCVWrapperに用意するフィルタを増やしていったりしたいと思います。