Xamarin.iOS から OpenCV を使って QR コードを認識してみる

はじめに

こんにちは。ソリューション開発部 農業開発チームの梅田です。
アンケート記事以来、約1ヶ月ぶりの再登板です。

tech-blog.optim.co.jp

今回は Xamarin.iOS から OpenCV を利用する方法をご紹介します。

Xamarin とは

Xamarin とは、.NET Framework の UNIX 互換実装である Mono をベースとしたアプリケーションを作成するための開発ツールであり、その開発元の企業名です。
企業としての Xamarin は2016年に Microsoft に買収され、ツールとしての Xamarin も OSS 化され、無料で使えるようになりました。

で、何ができるかというと、共通の C# のコードで、iOS / Android / Mac アプリの開発が行えます。(昔は Windows Phone もできたんだけどどこに行ったんだろう…)
同じロジックを Swift や Kotlin でそれぞれ実装する手間が省けるということですね。
クロスプラットフォーム開発の言語・フレームワークでは、最近では Kotlin や React Native など新しいものも続々と出てきており、大変アツい界隈です。

なぜ Xamarin?

実はオプティムの農業開発チーム、社内で唯一プロダクトに Xamarin を利用しているチームだったりします。
その中で、開発した Xamarin の iOS アプリで OpenCV を使って動画を処理する必要が出てきました。

そこで OpenCVSharpOpenCV.NETEmgu.CV などを検討したのですが、前者2つはモバイル非対応、Emgu.CV は有償ライセンス。
ということで、まずは本家のライブラリを使ってできないか、ということで調査を行いました。
その調査内容が、今回の記事のベースとなっています。

今回のサンプルアプリについて

今回はせっかくなので、最近リリースされた OpenCV 4 から追加された QR コード検出機能を例として、OpenCV を利用して QR コードの内容を読み取って表示する Xamarin.iOS アプリを作成してみます。

ちなみに、Xamarin.iOS / Android で QR コードを扱いたい場合は ZXing.Net.Mobile という素晴らしいライブラリが nuget にあるので、素直にそちらを使いましょう。
Xamarin.Forms 版もありますよ!

github.com

やってみる

0. OpenCV をダウンロード

まずは OpenCV のパッケージを公式サイトからダウンロードします。
今回は v4.1.0 の iOS Pack を使用します。

1. [Xcode] ライブラリを作成

https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20190724/20190724100144.png

https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20190724/20190724100148.png

まずは Xcode でスタティックライブラリを作成します。
"Cocoa Touch Static Library" を選択し、言語を "Objective-C" に設定。

Objective-C++ での開発なので、.m ファイルを .mm にリネームします。

プロジェクトに先ほどダウンロードした OpenCV のライブラリを追加します。 opencv2.framework という名前で追加されます。

.mm ファイルに実装を書いていきます。
QR コードの画像を UIImage で受け取って、検出結果を NSString で返すことにします。
iOS Pack は UIImage と cv::Mat の相互変換が一発でできて便利ですね。

一方、QR コードの認識は、cv::QRCodeDetector::detectAndDecode に Mat を渡すだけでできてしまいます。

#ifdef __cplusplus

#import <opencv2/opencv.hpp>
#import <opencv2/objdetect.hpp>
#import <opencv2/imgcodecs/ios.h>
#import <opencv2/highgui.hpp>
#import <opencv2/imgproc.hpp>
#import "OpenCV.h"

#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

#endif

@implementation OpenCV

-(NSString *)detectQr:(UIImage *)image {
    
    Mat input;
    UIImageToMat(image, input);
    
    QRCodeDetector qrDecoder = QRCodeDetector();
    NSString *result = @"Not detected";
    vector<cv::Point> points;
    
    if(!input.empty())
    {
        string data = qrDecoder.detectAndDecode(input, points);
        if(data.length() > 0)
        {
            result = [NSString stringWithUTF8String:data.c_str()];
        }
    
    return result;
}

@end

ここまで完了したら、ビルドターゲットを "Generic iOS Device" に設定してビルドすると、lib[プロジェクト名].a ファイルが出力されます。

2. [Visual Studio] Xamarin.iOS プロジェクトを作成

次に Visual Studio でソリューションを作成し、Xamarin.iOS プロジェクトを追加します。
今回は Xamarin 公式のレシピからカメラロールから画像・動画を選択して表示するサンプルを拝借しました。
やや古いですが、iOS 12 でも問題なく動きます。

今回はシミュレータ用のライブラリをビルドしていないので、実機でのデバッグを行います。
このままだと Provisioning Profile がないとエラーが出てしまうので、この Xamarin.iOS プロジェクトと同じ Bundle Identifier を持つ iOS アプリを Xcode で作り、デプロイしておきましょう。
こちらなどが参考になります。

3. [Visual Studio] バインディングライブラリを追加

https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20190724/20190724100151.png

最後に、2. で作成したソリューションに バインディングライブラリを追加します。
"iOS > ライブラリ > バインディングライブラリ (C#)" を選択します。

https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20190724/20190724100201.png

https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20190724/20190724100157.png

"ネイティブ参照" を右クリックし、"ネイティブ参照の追加" から、0. でダウンロードした OpenCV のパックと 1. で作成したスタティックライブラリを追加します。
追加した参照を右クリックし、"プロパティ" を開きます。
OpenCV のパックは "Kind" に "Framework" を選択し、"Is C++" にチェックを入れます。
スタティックライブラリは "Kind" は "Static" のまま、同じく "Is C++" にチェック。

ApiDefinition.cs に定義を追加します。

using Foundation;
using UIKit;

[BaseType(typeof(NSObject))]
interface OpenCV
{
    [Export("detectQr:")]
    NSString DetectQr(UIImage image);
}

Export のメソッド名は スタティックライブラリのメソッド名と合わせる必要があります。
また、引数がないメソッドの場合はコロンなし、引数がある場合は引数の数だけコロンが必要なので、うっかり忘れないように気をつけてください。

最後に、Xamarin.iOS プロジェクトの参照に、バインディングライブラリを追加します。
あとはこんな感じで呼び出すだけです。

private OpenCV openCV = new OpenCV();

resultTextView.Text = openCV.DetectQr(originalImage);

今回の場合は UIImage と NSString なので特に気にせず受け渡していますが、戻り値がアンマネージドな場合は、都度マーシャリングを行ってください。

試してみる

https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20190724/20190724100206.png

こちらのサイトで作成した QR コードを端末に保存して、カメラロールから呼び出してみます。
正常に認識できていれば「OPTiM Tech Blog」という文字が表示されるはずです。

https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20190724/20190724100208.png

無事、Xamarin.iOS から呼び出した OpenCV で QR コードを検出することができました!

おわりに

今回は、Objective-C++ で作成した OpenCV のライブラリを Xamarin.iOS で読み込み、利用する方法を紹介しました。
動画像処理のライブラリは、ネイティブで用意されている API やサードパーティ・個人作成のものだけでも十分揃っていると思いますが、どうしても OpenCV を使いたい、という需要は一定数あるのではないでしょうか。
それをわざわざ Xamarin で実装する必要があるかどうかは検討の余地がありますが、Xamarin ではこんなこともできるんだよ、ということが少しでも伝われば嬉しいです。

また、今回は Xamarin.Android での実装をご紹介できませんでしたので、次回は Android 側の実装や、Xamarin の真骨頂である "Bait and Switch" についてもご紹介したいと思います。


OPTiM ではクロスプラットフォーム技術に限らず、様々な新しい技術に関心のあるエンジニアを募集しています。

www.optim.co.jp

夏のインターンシップも募集中です。

www.optim.co.jp

参考

chamoda.com

qiita.com

m.qrqrq.com

github.com