iOS(swift)でOpenCVを使うシンプルなサンプル

# iOS(swift)でOpenCVを使うシンプルなサンプル

ふとiOSでOpenCVを使いたくなったのですが、iOSのプログラミング経験がなかったので、０から調べつつサンプルを作ってみました。
他にも同様のことをやっている記事がいくつかありましたが、情報を整理する意味でも自分なりにまとめることにしました。
swiftからOpenCVを使って入力画像をグレースケール変換するだけのシンプルなサンプルです。(Xcodeの使い方が多め)

## CocoaPodsのセットアップ

CocoaPodsはiOSのライブラリ管理ツールです。OpenCVはCocoaPods経由でインストールします。
CocoaPodsのインストールにはRubyGemsを用います。

以下のコマンドを実行してセットアップします。

```sh
$ sudo gem install cocoapods
$ pod setup
```

## プロジェクトの作成

次にXcodeを立ち上げます。バージョンは`Version 9.2`を使いました。
Xcodeを起動すると以下のウインドウが表示されるので、Create a new Xcode projectをクリックします。

<img width="490" alt="Screen Shot 2018-04-06 at 13.25.06.png" src="https://qiita-image-store.s3.amazonaws.com/0/71629/bb9e6524-1811-3d40-15f8-74276422f316.png">

<img width="734" alt="Screen Shot 2018-04-06 at 13.24.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/71629/36e0e6c8-a01d-f883-9d99-55eb8af9d9fe.png">

Single View Applicationは何もないまっさらなアプリで一番シンプルなので、それを選択し次へをクリック。

続いて、プロジェクトの詳細設定です。
今回はシミュレータでのみの実行なので、Teamは必要ありません。(実機で動作させたい場合に必要になります)
ProductNameはOpenCVSwiftSample、Languageはswiftとしました。その他は初期設定のままです。適当なディレクトリを指定して作成をしましょう。

<img width="732" alt="Screen Shot 2018-04-06 at 13.27.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/71629/f69007d9-8662-bedb-a159-35ab0824a8a4.png">

## CocoaPodsを用いてOpenCVをインストール
ここで一度Xcodeを閉じます。
ターミナルから先ほどXcodeプロジェクトを作成したディレクトリ(.xcodeprojがあるディレクトリ)に移動して以下を実行します。

```sh
$ pod init
```

するとPodfileというファイルが作成されるので、これを以下のように編集します。
(OpenCV3系だとO Linker Errorなどの不具合があるため、今回は2系を入れます)

```Podfile
target 'OpenCVSwiftSample' do
  use_frameworks!

  # Pods for opencvSwiftTest
  platform :ios, "9.0"
  pod 'OpenCV', '2.4.9'
end
```

下記のコマンドを実行すればOpenCVのインストールが始まります。

```sh
$ pod install
```

## Objective-CからOpenCVを呼び出す
Swiftではc言語で記述されたOpenCVを実行することができないので、Objective-Cの中にOpenCVの処理を記述し、それをSwiftから呼び出します。

まずFinderから`OpenCVSwiftSample.xcworkspace` を開き、Xcodeを起動します。(.xcodeprojでないので注意)

OpenCVSwiftSampleのディレクトリを右クリックしてNew Fileから`Cocoa Touch Class`を選択し、nextと進みます。

<img width="729" alt="Screen Shot 2018-04-06 at 13.42.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/71629/14b2edcd-b06b-99d8-1180-12c1dc3cdbbf.png">

Classは`OpenCVSample`、Subclass of は`NSObject`、Languageは `Objective-C` を選択してください。
<img width="731" alt="Screen Shot 2018-04-06 at 13.43.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/71629/0e150aed-7e57-7c7d-aa72-9084cae7dceb.png">

nextをクリックしcreateを押すと、以下のようなダイアログが立ち上がります。
<img width="561" alt="Screen Shot 2018-04-06 at 13.44.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/71629/757394f1-7f8d-63de-f16d-6d5cc1c1d724.png">

SwiftからObjective-Cを実行するにはbridging headerなるものが必要で、それをXcodeが自動で生成してくれます。
ファイル名は`{project-name}-Bridging-Header.h`といったものになります。
詳しくはこちらに記載されています。
https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html

※ここで`Cocoa Touch Class`ではなく`Objective-C`を選んでしまうとBridge-headerは自動で生成されないようです。

また今回はOpenCVのC++ライブラリを用いるため、`Objective-C++` というものを用います。
こちらは作成された`OpenCVSample.m`のファイル拡張子を`.mm`に変更するだけでObjective-C++として使用できます。

bridging headerファイルで先ほど作成したOpenCVSample.hファイルをインポートします。

```OpenCVSwiftSample-Bridging-Header.h
#import "OpenCVSample.h"
```

次にヘッダーファイルにメソッドを宣言します。

```OpenCVSample.h
#import <UIKit/UIKit.h>

@interface OpenCVSample : NSObject
+(UIImage *)GrayScale:(UIImage *)image;
@end
```

続いて実装ファイルです。
処理としてはUIImagをmatrix(cv::Mat)に変換して画像処理を行なった後にUIImageに再変換しています。

```OpenCVSample.mm
#import <opencv2/opencv.hpp>
#import <opencv2/highgui/ios.h>

#import "OpenCVSample.h"

@implementation OpenCVSample

+(UIImage *)GrayScale:(UIImage *)image{
    // convert image to mat
    cv::Mat mat;
    UIImageToMat(image, mat);
    
    // convert mat to gray scale
    cv::Mat gray;
    cv::cvtColor(mat, gray, CV_BGR2GRAY);
    
    // convert to image
    UIImage * grayImg = MatToUIImage(gray);
    
    return grayImg;
}

@end
```

## swiftからObjective-Cを実行
プロジェクト作成時に自動で生成されているViewControllerで先ほど実装したGrayScaleメソッドを呼び出します。

```ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var myImageView: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        let image = myImageView.image;
        let gray = OpenCVSample.grayScale(image)
        myImageView.image = gray;
        
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}
```

UIImageViewに紐づけた画像をグレースケールに変換して設定し直しています。

このとき、Objective-Cで設定したメソッド名が変わるので注意してください。今回は小文字になっているだけですが、メソッド名が長いと結構変更されてしまうようです。
どうやらSwiftからObjective-Cのメソッドを呼び出す場合、Xcodeがメソッド名を最適化してしまうようです。
（めちゃくちゃハマりました。）

続いてストーリーボードに画像を配置しましょう。
Main.storyboardを開きます。最初は空のViewControllerがあるのみなので、右側のユーティリティ領域のペインからImageViewを選択してストーリーボードのViewControllerにドラッグ&ドロップします。
<img width="886" alt="Screen_Shot_2018-04-06_at_17_14_33.png" src="https://qiita-image-store.s3.amazonaws.com/0/71629/e4852b9a-5ee9-65d1-d966-e036930e0478.png">

ストーリーボードのImageViewとViewControllerのmyImageView変数を紐づけます。
`@IBOutlet` と記述された変数はストーリーボードと紐づけることができます。
ViewController.swiftを開きます。この時にoptionキーを押しながら開くとウインドウが左右分割して開くので紐付けが便利になります。
`@IBOutlet` が記述されている行の左端に白丸が表示されているのでそれをドラッグアンドドロップし、UIImageViewに紐付けます。

<img width="772" alt="Screen Shot 2018-04-06 at 18.21.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/71629/0c5f1489-56ff-cbae-d894-cb65099d01bc.png">

処理を行う画像をXcodeプロジェクトに追加します。これはXcodeにドラッグアンドドロップでいけます。

ストーリーボードのImageViewを選択し、右側のユーティリティ領域のペインから先ほど追加した画像を選択します。(今回だとcat.jpg)
<img width="537" alt="Screen Shot 2018-04-06 at 18.39.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/71629/fbdf67d8-d2cd-fa02-75af-467c5db333d5.png">

するとストーリーボードに画像が表示されます。

<img width="388" alt="Screen Shot 2018-04-06 at 18.43.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/71629/f8faf25b-0ac4-cabc-bad7-cca070841a70.png">

ここでXcodeの実行ボタンを押しましょう。
シミュレータが立ち上がって、先ほどの画像がグレースケール変換されたものが無事表示されました。

<img width="375" alt="Screen Shot 2018-04-06 at 19.00.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/71629/a1b319bc-fdf0-15cd-bacf-c876f06cb6dd.png">