こんにちは、AndroidエンジニアのAndyです。これまでにZOZOSUIT、ZOZOMAT、ZOZOGLASSのアプリ機能開発に取り組んできました。
ZOZOGLASSは肌の色を計測するデバイスで、オンラインでファンデーションを購入する際の手助けをします。ZOZOGLASSのユーザーは下図のような専用の眼鏡をかけ、アプリを使用して顔の肌の色を計測します。
この技術の開発中に、私たちはクロスプラットフォームであるが故の技術的ハードルに直面しました。本記事では、そこで使用されているテクノロジーの一部と、それらの課題をどのように解決していったのかを紹介します。
クロスプラットフォームにおける技術的課題
前述の通り、開発を進めていく中でさまざまな技術的課題に直面しました。その原因はiOSとAndroidを同時にカバーするため、クロスプラットフォームである必要があったからです。それに起因し、肌の色の計測パフォーマンスの課題やフェイストラッキングの課題が発生しました。
現在では、フェイストラッキングにはARCore Augmented Facesを使用し、肌の色の計測と色補正アルゴリズムにはC++ライブラリを使用し、この課題に対応しています。本記事では、その中でも活用している、KotlinでC++ライブラリを使用する方法を簡単なサンプルを用いて説明します。
C++による計測ライブラリの作成
ユーザーの肌の色を計測するためのライブラリを、C++で開発しました。C++は、高性能アプリケーションの作成に使用できるクロスプラットフォームな言語です。これにより、開発者はシステムリソースとメモリを高度に制御することが可能になります。C#やJavaとも類似する点ですが、オブジェクト指向プログラミング言語としてプログラムに明確な構造を与えてコードを再利用できます。
計測専用の眼鏡はさまざまな配色のユニークな模様でデザインされています。そして、C++ライブラリはスマートフォンのカメラでユーザーの顔をスキャンする際に、このユニークな模様を活用して色の補正をします。検出した顔の各領域を識別し、それらの領域に対して肌の色のカラーマップを生成します。
ネイティブアプリへのC++ライブラリの組み込み
パフォーマンスとUXの向上のために、スマートフォン向けのアプリはネイティブアプリとして開発することにしました。iOSアプリはSwift、AndroidアプリはKotlinを使用して開発されています。
しかし、iOSアプリでは、SwiftがC++と直接通信できないため、手動で中間レイヤーを追加する必要があります。例えば、すべてのC++の機能をObjective-Cモジュールにラップさせる方法があります。その場合、SwiftのアプリケーションからObjective-Cフレームワークを使用するだけなので、実装は容易です。
一方、Androidアプリの場合は、iOSアプリのように容易には実装できません。Android Native Development Kit(Android NDK。以下、NDK)を使用する必要があります。このNDKは、開発者がアプリの一部をネイティブコード(C++)で記述できるようにするツールセットです。
次章では、このNDKを利用したAndroidアプリの実装方法を説明します。
NDKを用いたAndroidアプリの実装方法
NDKには、以下のデフォルトツールが含まれています。
- デバッガー
- CMake
- Java Native Interface(JNI)
JNIは、Kotlin/JavaとネイティブC++間のインタラクションの処理を司るインタフェースです。これは、Androidによって生成されたバイトコードがネイティブコードと通信する方法を定義してくれます。その結果、Kotlinのコードは、JNIを使用することでC++コードと通信が可能となります。
それらのAndroidによって生成されたバイトコードとネイティブコードは、双方で関数と変数を保持しています。JNIを使用すると、KotlinからC++で記述された関数を呼び出したり、その逆も可能になります。また、言語間で変数に格納されている値を読み取って変更することも可能です。
前述のように、C++で記述されたネイティブコードを処理する場合、ネイティブ関数を呼び出し、引数を渡し、結果を取得する必要があります。これを処理するために「プリミティブ型」が使用されます。そして、引数をネイティブコードの関数に渡したり、プリミティブ型の形式で結果を取得したりするためにJNIで定義された特別なネイティブ型が存在します。具体的にはJavaのドキュメントで記載されているように、Kotlin/Javaの各プリミティブに対応するネイティブ型が用意されています。
Android StudioでサンプルコードのNDKを動かしてみる
本章では、Android Studioを使い、簡単なサンプルを動かしながらNDKの利用方法を説明します。
環境準備とプロジェクト作成
Android StudioでNDKをサポートするには、以下のSDKを追加する必要があります。
- LLDB
- Android Studioでプロジェクトに存在するネイティブコードをデバッグするために使用
- NDK
- Androidのネイティブ言語であるC++でコーディングするために使用
- CMake
- OSでコンパイラに依存しない方法でビルドプロセスを管理するために使用
Android Studioで「Native C++」のテンプレートでプロジェクを作成すると、下図に示す構造のプロジェクトが生成されます。すべてのネイティブファイルとCMakeLists.txt
ファイルを含むcpp
ディレクトリが作成されます。なお、C++のコードはnative-lib.cpp
ファイルに含まれています。
サンプルコードによる動作確認
最も簡単な、文字列を扱うサンプルコードを見ていきます。
C++のコード、つまり native-lib.cpp
を以下のように変更します。
extern "C" JNIEXPORT jstring JNICALL Java_jp_zozo_sample_library_jni_Native_stringFromJNI( JNIEnv *env, jobject /* this */) { std::string hello = "Hello, World!"; return env->NewStringUTF(hello.c_str()); }
次に、Kotlinのコードにて、アプリケーションの起動時にSystem.loadLibrary("native-lib")
メソッドを呼び出し、先程のC++のネイティブコードをロードするようにします。
System.loadLibrary("native-lib")
続いて、ライブラリによって実装されたネイティブメソッドを、以下のようにKotlinの関数として宣言します。
external fun stringFromJNI(): String
すると、以下のようにKotlinから関数を使用できるようになります。
sample_text.text = stringFromJNI()
以上のように、ネイティブコードの関数をKotlinから簡単に使用できることが分かりました。
先程のサンプルは文字列を扱っていましたが、同様に数値を扱うこともできます。
C++は以下のように変更します。
extern "C" JNIEXPORT jint JNICALL Java_jp_zozo_sample_library_jni_Native_add( JNIEnv *env, jobject, jint x, jint y) { return x + y; }
そして、Kotlinのコードは以下のように変更します。
external fun add(x: Int, y: Int): Int val result = add(x = 1, y = 1).toString() sample_text.text = result
数値でも文字列同様、簡単に実装ができました。
ZOZOGLASSではカメラから取得された画像データをリアルタイムに処理して計測処理を行っています。そのため、最後にKotlin側で取得された画像データをC++で処理するサンプルコードを見ていきましょう。
C++を以下のように変更します。
extern "C" JNIEXPORT void JNICALL Java_jp_zozo_sample_library_jni_Native_imageProcessing( JNIEnv *env, jobject, jbyteArray byteArray) { jbyte* buffer = env->GetByteArrayElements(byteArray, nullptr); // 重い画像処理 env->ReleaseByteArrayElements(byteArray, buffer, 0); }
そして、Kotlinのコードは以下のように変更します。
external fun imageProcessing(image: ByteArray): Unit
ZOZOGLASSではこのように画像を処理しています。以上でサンプルコードを用いた解説は終了です。
ARCoreライブラリによるフェイストラッキングの実装
フェイストラッキングをするために、ARCoreライブラリを使用しました。ARCore SDKは、モーショントラッキング、環境理解、光推定などのAR機能用のAPIを提供しています。この機能を活用すると、新しいARエクスペリエンスを構築したり、既存のアプリをAR機能で強化できます。 developers.google.com
Augmented FacesはARCoreの一部です。Augmented Facesイメージトラッキング機能を使用すると、ユーザーの顔がカメラによって検出できます。そして、ARCoreは検出された顔の各領域を自動的に識別するために、拡張された顔のメッシュを生成します。その際のメッシュは顔の仮想表現であり、頂点、ユーザーの頭や顔の領域で構成されます。
そのようにARCoreで拡張された顔により、アプリはカメラから検出された顔の各領域を自動的に識別できます。
なお、拡張された顔の位置は以下のように生成されます。
- センターポーズの特定
- 鼻のうしろにあり、ユーザーの頭の物理的な中心点をセンターポーズとして特定
- 顔の各領域を識別
- 顔のメッシュとセンターポーズを使用し、ユーザーの顔の左額、右額、鼻の先の領域を識別
このように取得された要素は、Augmented Faces APIによって、肌の色のテクスチャを顔にオーバーレイするための配置ポイントおよび領域として使用されます。そして、アタッチされている顔の領域に基づいてレンダリングします。
このようにユーザーの肌の色のデータを収集することは、ユーザーの統計データの傾向を見つけるのに役立ちます。アルゴリズムを使用し、ユーザーのデータに基づいて下図のように適したファンデーションの色の推薦が可能になります。
まとめ
本記事で紹介した技術を使用することで、ユーザーの肌の色をスムーズに測定し、データを効果的に表示および使用できるユーザーフレンドリーなクロスプラットフォームなアプリを作成できました。アプリの開発前から、想定される機能を計画し、解決すべき課題に基づいた適切な技術選定をすることが重要です。
また、プロジェクトのスコープを考慮することも重要です。時間とリソースの制約があるプロジェクトでは、より迅速なサービスの提供が求められます。要件を満たす技術が既に存在する場合、必ずしも高度な技術を自ら作り出すのではなく、それらの既存の技術を活用することが重要です。
ZOZOでは、Androidエンジニアを募集しています。興味のある方はこちらからご応募ください!