顔識別技術としてEigenfacesを使ってみる。
顔1=基準顔1×変数A+基準顔2×変数B+基準顔3×変数C+・・・のような感じで、すべての入力した顔写真を変数の組み合わせで作れるようにして、変数の近さで顔を識別する。
Microsoft Visual StudioでEigenfacesのサンプルプログラムをビルドする。
顔画像は27人28枚を使用した。
./faces/000_1.jpg;0
./faces/001_1.jpg;1
./faces/002_1.jpg;2
./faces/003_1.jpg;3
./faces/004_1.jpg;4
./faces/005_1.jpg;5
./faces/006_1.jpg;6
./faces/007_1.jpg;7
./faces/008_1.jpg;8
./faces/009_1.jpg;9
./faces/010_1.jpg;10
./faces/011_1.jpg;11
./faces/012_1.jpg;12
./faces/013_1.jpg;13
./faces/014_1.jpg;14
./faces/015_1.jpg;15
./faces/016_1.jpg;16
./faces/017_1.jpg;17
./faces/018_1.jpg;18
./faces/019_1.jpg;19
./faces/020_1.jpg;20
./faces/021_1.jpg;21
./faces/022_1.jpg;22
./faces/023_1.jpg;23
./faces/024_1.jpg;24
./faces/025_1.jpg;25
./faces/026_1.jpg;26
./faces/026_2.jpg;26
実行コマンドは以下。outputは出力ファイルフォルダ。
D:¥ドキュメント¥Visual Studio 2015¥Projects¥eigenfaces¥x64¥Debug>eigenfaces.exe facelist.txt output
Predicted class = 26 / Actual class = 26.
Eigenvalue #0 = 11450386.23945
Eigenvalue #1 = 5581722.32239
Eigenvalue #2 = 3671017.20937
Eigenvalue #3 = 2693774.57220
Eigenvalue #4 = 2088823.91652
Eigenvalue #5 = 1465460.40608
Eigenvalue #6 = 1345912.99003
Eigenvalue #7 = 1168820.11379
Eigenvalue #8 = 1029193.83296
Eigenvalue #9 = 897022.50187
トレーニングで使用していない26番の写真026_2.jpgが、26番として認識成功している。
WindowsでEigenfacesの動作を確認できたが、OpenCVのAndroidパッケージはExtra moduleを含まないので、Androidでfaceモジュールを使うためには、ソースからAndroid用にビルド(Windows OS上で、Android OS用にクロスコンパイル)する必要がある。
JDK: C:¥java-se-8u41-ri¥jre¥lib¥security¥cacerts
JRE: C:¥Program Files (x86)¥Java¥jre1.8.0_45¥lib¥security¥cacerts
Name | 説明 | 例 |
GRADLE_USER_HOME | Gradleのホームディレクトリを明確にしておく。 | D:¥android¥gradle |
JAVA_HOME | JAVAのホームディレクトリをダウンロードしたJDKを置いたディレクトリにする。 | C:¥java-se-8u41-ri |
Name | 説明 | 例 |
OPENCV_EXTRA_MODULES_PATH (PATH) | Extra moduleディレクトリ。 | D:¥opencv4android¥opencv_contrib-4.3.0¥modules |
ARM64_V8A (BOOL) | ターゲットCPU。SO-02J用。 | TRUE |
ANDROID_TOOLCHAIN_NAME (STRING) | 使用するコンパイラ。 | aarch64-linux-android-4.9 |
ANDROID_ABI (STRING) | ターゲットCPU。SO-02J用。 | arm64-v8a |
ANDROID_NDK (PATH) | NDKディレクトリ。Android Studioでダウンロード可能。最新NDKには対応されていないのでr17を使う。 | D:¥android¥SDK¥ndk¥17.2.4988734 |
ANDROID_NDK_HOST_X64 (BOOL) | 64bitフラグ。 | TRUE |
ANDROID_SDK_ROOT (PATH) | Android Studioをインストールすると入っているAndroid SDKのディレクトリ。 | D:¥android¥SDK |
ANDROID_HOME (PATH) | ANDROID_SDK_ROOTと同じ。 | D:¥android¥SDK |
ANDROID_PLATFORM (STRING) | サポートされる最小の API レベル。 | 21 |
ANDROID_NATIVE_API_LEVEL (STRING) | ANDROID_PLATFORMと同じ。 | 21 |
ANDROID_MIN_SDK_VERSION (STRING) | ANDROID_PLATFORMと同じ。 | 21 |
// エラー内容 'to_string' is not a member of 'std'.
// 対象
// modules¥core¥src¥utils¥logtagconfigparser.cpp
// modules¥gapi¥src¥compiler¥gcompiler.cpp
// modules¥gapi¥src¥backends¥fluid¥gfluidbackend.cpp
// modules¥gapi¥test¥gapi_fluid_parallel_rois_test.cpp
// modules¥gapi¥test¥internal¥gapi_int_gmodel_builder_test.cpp
以下を追加。
#include <string>
#include <sstream>
namespace patch
{
template < typename T > std::string to_string( const T& n )
{
std::ostringstream stm ;
stm << n ;
return stm.str() ;
}
}
std::to_stringをpatch::to_stringに変更。
// エラー内容 'rint' is not a member of 'std'.
// 対象
// modules¥gapi¥test¥gapi_fluid_test_kernels.cpp
以下を追加。
#include <math.h>
std::rintをrintに変更。
// エラー内容 'round' is not a member of 'std'.
// 対象
// modules¥gapi¥test¥gapi_gcomputation_tests.cpp
以下を追加。
#include <math.h>
std::roundをroundに変更。
Name | 説明 | 例 |
BUILD_ANDROID_EXAMPLES (BOOL) | samplesディレクトリのビルドはしない。 | FALSE |
PYTHON3_EXECUTABLE | Pythonの実行ファイルパス。 | C:/python/python.exe |
sdk.dir=D:/android/SDK
ndk.dir=D:/android/SDK/ndk/17.2.4988734
ソースからビルドしたライブラリに差し替える。
まずトレーニング画像とラベルを定義する。
private static final int IMAGENUM = 26;
List<Mat> sourceimages = new ArrayList<Mat>(IMAGENUM);
static String[] labelstr={"省略",・・・};
static int[] labelint = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25};
onCreate()に以下追加。drawableリソースから顔画像を取得し、グレー画像に変更する。ここで、BitmapFactory.decodeResourceで読み込むと画像サイズが元ファイルとは変わることがあるので、resizeする必要がある。さらに、normalizeが必要。normalizeしないと画像どうしの明るさの差に影響されて正常にトレーニングできない。また、トレーニング画像は事前にカスケード分類器で顔認識される枠サイズにトリミングしておく。
Mat srcmat = new Mat();
Mat srcmatgray = new Mat();
Mat srcmatgray100 = new Mat();
int[] imageid={R.drawable.p000_1,R.drawable.p003_1,R.drawable.p004_1,R.drawable.p005_1,
R.drawable.p006_1,R.drawable.p007_1,R.drawable.p008_1,R.drawable.p010_1,R.drawable.p011_1,
R.drawable.p012_1,R.drawable.p013_1,R.drawable.p014_1,R.drawable.p015_1,R.drawable.p016_1,R.drawable.p017_1,
R.drawable.p018_1,R.drawable.p019_1,R.drawable.p020_1,R.drawable.p021_1,R.drawable.p022_1,R.drawable.p023_1,
R.drawable.p024_1,R.drawable.p025_1,R.drawable.p026_1,R.drawable.p027_1,R.drawable.p028_1};
Mat labels = new Mat(1,IMAGENUM,CvType.CV_32SC1);
org.opencv.core.Size sz = new org.opencv.core.Size(EIGENFACEX,EIGENFACEY);
int count=0;
for (int id:imageid) {
Utils.bitmapToMat(BitmapFactory.decodeResource(getResources(), id), srcmat);
Imgproc.cvtColor(srcmat, srcmatgray, Imgproc.COLOR_BGR2GRAY);
Imgproc.resize(srcmatgray, srcmatgray100, sz);
Core.normalize(srcmatgray100, srcmatgray100, 0,255, Core.NORM_MINMAX);
sourceimages.add(new Mat());
srcmatgray100.copyTo(sourceimages.get(count));
count=count+1;
}
labels.put(0,0,labelint);
model = EigenFaceRecognizer.create();
model.train(sourceimages, labels);
カスケード分類器で顔の枠を取得後、以下のように枠を抜き出してFaceRecognizerでテストする。ここでもnormalizeする。これでlabel[0]にEigenfacesによる顔の識別結果が入る。
int fx,fy,fw,fh;
fx=(int)facesArray[i].tl().x;
fy=(int)facesArray[i].tl().y;
fw=(int)(facesArray[i].br().x-facesArray[i].tl().x);
fh=(int)(facesArray[i].br().y-facesArray[i].tl().y);
Mat face0 = new Mat(rgbamat, new Rect(fx,fy,fw,fh));
Mat face100 = new Mat(EIGENFACEX,EIGENFACEY,CvType.CV_8UC4);
Mat face100gray = new Mat(EIGENFACEX,EIGENFACEY,CvType.CV_8UC1);
org.opencv.core.Size sz = new org.opencv.core.Size(EIGENFACEX,EIGENFACEY);
Imgproc.resize(face0, face100, sz);
Imgproc.cvtColor(face100,face100gray,Imgproc.COLOR_RGB2GRAY);
Core.normalize(face100gray, face100gray, 0,255, Core.NORM_MINMAX);
int[] label={99,0};
double[] confidence={0.0,0.0};
model.predict(face100gray, label, confidence);
有名人の写真26枚をトレーニング画像に使い、PCに適当なフリー素材写真を表示して、ビルドしたアプリでリアルタイム認識している結果が下のキャプチャ。パーセント値は適当。confidence[0]の値は該当人物の写真までのdistanceで、完全一致でゼロだが、完全間違いの数値は難しくて不明。フリー素材の人物はトレーニングに入れてないので、一番近い有名人が表示される。取り込んだ有名人の写真そのものをPCに表示して認識させると、26人ほぼ常時正しく認識する。どちらかというと、識別を間違える瞬間はカスケード分類器の顔認識枠がずれるのが原因。