├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── assets
│ │ │ └── mobile_face_net.tflite
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── drawable-hdpi
│ │ │ │ └── outline_add_black_48.png
│ │ │ ├── drawable-mdpi
│ │ │ │ └── outline_add_black_48.png
│ │ │ ├── drawable-xhdpi
│ │ │ │ └── outline_add_black_48.png
│ │ │ ├── drawable-xxhdpi
│ │ │ │ └── outline_add_black_48.png
│ │ │ ├── drawable-xxxhdpi
│ │ │ │ └── outline_add_black_48.png
│ │ │ ├── drawable
│ │ │ │ ├── white_roundedcorner.xml
│ │ │ │ ├── roundedcorner.xml
│ │ │ │ ├── outline_add_24.xml
│ │ │ │ ├── outline_image_24.xml
│ │ │ │ ├── outline_flip_camera_android_24.xml
│ │ │ │ └── ic_launcher_background.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── themes.xml
│ │ │ ├── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ └── layout
│ │ │ │ └── activity_main.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── irhammuch
│ │ │ └── android
│ │ │ └── facerecognition
│ │ │ ├── SimilarityClassifier.java
│ │ │ ├── GraphicOverlay.java
│ │ │ └── MainActivity.java
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── irhammuch
│ │ │ └── android
│ │ │ └── facerecognition
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── irhammuch
│ │ └── android
│ │ └── facerecognition
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── .idea
├── .name
├── .gitignore
├── compiler.xml
├── vcs.xml
├── gradle.xml
└── misc.xml
├── screenshots
├── ss_small_1.gif
├── ss_small_2.gif
├── ss_small_3.gif
├── screenshots1.gif
├── screenshots2.gif
└── screenshots3.gif
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── settings.gradle
├── README.md
├── gradle.properties
├── gradlew.bat
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | Face Recognition
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/screenshots/ss_small_1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/irhammuch/android-face-recognition/HEAD/screenshots/ss_small_1.gif
--------------------------------------------------------------------------------
/screenshots/ss_small_2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/irhammuch/android-face-recognition/HEAD/screenshots/ss_small_2.gif
--------------------------------------------------------------------------------
/screenshots/ss_small_3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/irhammuch/android-face-recognition/HEAD/screenshots/ss_small_3.gif
--------------------------------------------------------------------------------
/screenshots/screenshots1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/irhammuch/android-face-recognition/HEAD/screenshots/screenshots1.gif
--------------------------------------------------------------------------------
/screenshots/screenshots2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/irhammuch/android-face-recognition/HEAD/screenshots/screenshots2.gif
--------------------------------------------------------------------------------
/screenshots/screenshots3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/irhammuch/android-face-recognition/HEAD/screenshots/screenshots3.gif
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/irhammuch/android-face-recognition/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/assets/mobile_face_net.tflite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/irhammuch/android-face-recognition/HEAD/app/src/main/assets/mobile_face_net.tflite
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/irhammuch/android-face-recognition/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/irhammuch/android-face-recognition/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/irhammuch/android-face-recognition/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/irhammuch/android-face-recognition/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/irhammuch/android-face-recognition/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/irhammuch/android-face-recognition/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/irhammuch/android-face-recognition/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/irhammuch/android-face-recognition/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/irhammuch/android-face-recognition/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/irhammuch/android-face-recognition/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/outline_add_black_48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/irhammuch/android-face-recognition/HEAD/app/src/main/res/drawable-hdpi/outline_add_black_48.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/outline_add_black_48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/irhammuch/android-face-recognition/HEAD/app/src/main/res/drawable-mdpi/outline_add_black_48.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/outline_add_black_48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/irhammuch/android-face-recognition/HEAD/app/src/main/res/drawable-xhdpi/outline_add_black_48.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/outline_add_black_48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/irhammuch/android-face-recognition/HEAD/app/src/main/res/drawable-xxhdpi/outline_add_black_48.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/outline_add_black_48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/irhammuch/android-face-recognition/HEAD/app/src/main/res/drawable-xxxhdpi/outline_add_black_48.png
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/white_roundedcorner.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/roundedcorner.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Feb 26 14:43:10 ICT 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | dependencyResolutionManagement {
2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
3 | repositories {
4 | google()
5 | mavenCentral()
6 | jcenter() // Warning: this repository is going to shut down soon
7 | }
8 | }
9 | rootProject.name = "Face Recognition"
10 | include ':app'
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Face Recognition
3 | Preview Photo
4 | register face
5 | switch camera
6 | Face Detected
7 | No Face Detected!
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/outline_add_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Android Face Recognition
2 |
3 | Real Time Face Recognition App using Google MLKit, Tensorflow Lite, & MobileFaceNet.
4 |
5 | ### Key features
6 |
7 | - Detect and Recognize faces in **Real Time**.
8 | - Works **offline** without using API connection.
9 | - **Fast** and **Accurate**.
10 |
11 | ### Demo
12 |
13 | 
14 | 
15 | 
16 |
--------------------------------------------------------------------------------
/app/src/test/java/com/irhammuch/android/facerecognition/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.irhammuch.android.facerecognition;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/outline_image_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/irhammuch/android/facerecognition/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.irhammuch.android.facerecognition;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 | assertEquals("com.irhammuch.android.facerecognition", appContext.getPackageName());
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/outline_flip_camera_android_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
13 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | }
4 |
5 | android {
6 | compileSdk 31
7 |
8 | defaultConfig {
9 | applicationId "com.irhammuch.android.facerecognition"
10 | minSdk 21
11 | targetSdk 31
12 | versionCode 1
13 | versionName "1.0"
14 |
15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 | compileOptions {
25 | sourceCompatibility JavaVersion.VERSION_1_8
26 | targetCompatibility JavaVersion.VERSION_1_8
27 | }
28 | }
29 |
30 | dependencies {
31 |
32 | implementation 'androidx.appcompat:appcompat:1.4.1'
33 | implementation 'com.google.android.material:material:1.5.0'
34 | implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
35 |
36 | // camera x
37 | def camerax_version = "1.0.2"
38 | implementation "androidx.camera:camera-core:${camerax_version}"
39 | implementation "androidx.camera:camera-lifecycle:${camerax_version}"
40 | implementation "androidx.camera:camera-camera2:${camerax_version}"
41 | implementation "androidx.camera:camera-view:1.0.0-alpha32"
42 |
43 | // mlkit face detection & gson
44 | implementation "com.google.mlkit:face-detection:16.1.5"
45 | implementation "com.google.code.gson:gson:2.8.6"
46 |
47 | // tflite
48 | implementation("org.tensorflow:tensorflow-lite:2.4.0") { changing = true }
49 | implementation("org.tensorflow:tensorflow-lite-gpu:0.0.0-nightly") { changing = true }
50 | implementation("org.tensorflow:tensorflow-lite-support:0.0.0-nightly") { changing = true }
51 |
52 | testImplementation 'junit:junit:4.+'
53 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
54 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
55 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/irhammuch/android/facerecognition/SimilarityClassifier.java:
--------------------------------------------------------------------------------
1 | /* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 |
16 | package com.irhammuch.android.facerecognition;
17 |
18 | public interface SimilarityClassifier {
19 | /** An immutable result returned by a Classifier describing what was recognized. */
20 | class Recognition {
21 | /**
22 | * A unique identifier for what has been recognized. Specific to the class, not the instance of
23 | * the object.
24 | */
25 | private final String id;
26 | /** Display name for the recognition. */
27 | private final String title;
28 |
29 |
30 | private final Float distance;
31 | private Object extra;
32 |
33 | public Recognition(
34 | final String id, final String title, final Float distance) {
35 | this.id = id;
36 | this.title = title;
37 | this.distance = distance;
38 | this.extra = null;
39 |
40 | }
41 |
42 | public void setExtra(Object extra) {
43 | this.extra = extra;
44 | }
45 | public Object getExtra() {
46 | return this.extra;
47 | }
48 |
49 | @Override
50 | public String toString() {
51 | String resultString = "";
52 | if (id != null) {
53 | resultString += "[" + id + "] ";
54 | }
55 |
56 | if (title != null) {
57 | resultString += title + " ";
58 | }
59 |
60 | if (distance != null) {
61 | resultString += String.format("(%.1f%%) ", distance * 100.0f);
62 | }
63 |
64 | return resultString.trim();
65 | }
66 |
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/app/src/main/java/com/irhammuch/android/facerecognition/GraphicOverlay.java:
--------------------------------------------------------------------------------
1 | package com.irhammuch.android.facerecognition;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.graphics.Rect;
8 | import android.graphics.RectF;
9 | import android.graphics.Typeface;
10 | import android.util.AttributeSet;
11 | import android.util.Log;
12 | import android.view.View;
13 |
14 | import androidx.annotation.Nullable;
15 |
16 |
17 | public class GraphicOverlay extends View {
18 | private static final String TAG = "MainActivity";
19 | private final Paint rectPaint = new Paint();
20 | private float scaleX = 1.0f;
21 | private float scaleY = 1.0f;
22 | private final Paint textPaint = new Paint();
23 | private String name = null;
24 | private RectF rectF = null;
25 | private final Paint labelPaint = new Paint();
26 |
27 | public GraphicOverlay(Context context, @Nullable AttributeSet attrs) {
28 | super(context, attrs);
29 | }
30 |
31 | @Override
32 | public void onDraw(Canvas canvas) {
33 | super.onDraw(canvas);
34 |
35 | if(name != null && !name.trim().isEmpty() && rectF != null && !name.equals("unknown")) {
36 | labelPaint.setColor(Color.BLUE);
37 | labelPaint.setStyle(Paint.Style.FILL);
38 | canvas.drawRect(
39 | rectF.left,
40 | rectF.bottom - 40.0f,
41 | (float) (rectF.left + (rectF.right - rectF.left) * 0.75),
42 | rectF.bottom,
43 | labelPaint
44 | );
45 | textPaint.setColor(Color.WHITE);
46 | textPaint.setTextSize(30.0f);
47 | textPaint.setTypeface(Typeface.DEFAULT_BOLD);
48 | canvas.drawText(name, rectF.left + 15.0f , rectF.bottom - 15.0f, textPaint);
49 | rectPaint.setColor(Color.BLUE);
50 | } else {
51 | rectPaint.setColor(Color.RED);
52 | }
53 |
54 | if(rectF != null) {
55 | rectPaint.setStrokeWidth(8.0f);
56 | rectPaint.setStyle(Paint.Style.STROKE);
57 | float cornerRadius = 10.0f;
58 | canvas.drawRoundRect(rectF, cornerRadius, cornerRadius, rectPaint);
59 | }
60 | }
61 |
62 | public void draw(Rect rect, float scaleX, float scaleY, String name) {
63 | RectF adjustedRect = adjustBoundingRect(rect);
64 | this.rectF = adjustedRect;
65 | this.scaleX = scaleX;
66 | this.scaleY = scaleY;
67 | this.name = name;
68 | postInvalidate();
69 | requestLayout();
70 | logInfo(rect, scaleX, scaleY, adjustedRect);
71 | }
72 |
73 | private float translateX(float x){
74 | return x * scaleX;
75 | }
76 |
77 | private float translateY(float y) {
78 | return y * scaleY;
79 | }
80 |
81 | private RectF adjustBoundingRect(Rect rect) {
82 | if(rect != null) {
83 | float padding = 10.0f;
84 | return new RectF(
85 | translateX((float) rect.left) - padding,
86 | translateY((float) rect.top) - padding,
87 | translateX((float) rect.right) + padding,
88 | translateY((float)rect.bottom) + padding
89 | );
90 | }
91 | return null;
92 | }
93 |
94 | private void logInfo(Rect rect, float scaleX, float scaleY, RectF rectF) {
95 | Log.i(TAG, "Boundingbox : " + rect);
96 | Log.i(TAG, "RectF : " + rectF);
97 | Log.i(TAG, "scaleX : " + scaleX);
98 | Log.i(TAG, "scaleY : " + scaleY);
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
24 |
25 |
37 |
38 |
49 |
50 |
63 |
64 |
65 |
66 |
78 |
79 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/app/src/main/java/com/irhammuch/android/facerecognition/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.irhammuch.android.facerecognition;
2 |
3 | import android.Manifest;
4 | import android.annotation.SuppressLint;
5 | import android.app.Activity;
6 | import android.content.pm.PackageManager;
7 | import android.content.res.AssetFileDescriptor;
8 | import android.graphics.Bitmap;
9 | import android.graphics.BitmapFactory;
10 | import android.graphics.Canvas;
11 | import android.graphics.Color;
12 | import android.graphics.ImageFormat;
13 | import android.graphics.Matrix;
14 | import android.graphics.Paint;
15 | import android.graphics.Rect;
16 | import android.graphics.RectF;
17 | import android.graphics.YuvImage;
18 | import android.media.Image;
19 | import android.os.Bundle;
20 | import android.text.InputType;
21 | import android.util.Log;
22 | import android.util.Pair;
23 | import android.widget.EditText;
24 | import android.widget.ImageButton;
25 | import android.widget.ImageView;
26 | import android.widget.TextView;
27 | import android.widget.Toast;
28 |
29 | import androidx.annotation.NonNull;
30 | import androidx.appcompat.app.AlertDialog;
31 | import androidx.appcompat.app.AppCompatActivity;
32 | import androidx.camera.core.AspectRatio;
33 | import androidx.camera.core.CameraSelector;
34 | import androidx.camera.core.ImageAnalysis;
35 | import androidx.camera.core.ImageProxy;
36 | import androidx.camera.core.Preview;
37 | import androidx.camera.lifecycle.ProcessCameraProvider;
38 | import androidx.camera.view.PreviewView;
39 | import androidx.core.app.ActivityCompat;
40 | import androidx.core.content.ContextCompat;
41 |
42 | import com.google.common.util.concurrent.ListenableFuture;
43 | import com.google.mlkit.vision.common.InputImage;
44 | import com.google.mlkit.vision.face.Face;
45 | import com.google.mlkit.vision.face.FaceDetection;
46 | import com.google.mlkit.vision.face.FaceDetector;
47 |
48 | import org.tensorflow.lite.Interpreter;
49 |
50 | import java.io.ByteArrayOutputStream;
51 | import java.io.FileInputStream;
52 | import java.io.IOException;
53 | import java.nio.ByteBuffer;
54 | import java.nio.ByteOrder;
55 | import java.nio.MappedByteBuffer;
56 | import java.nio.ReadOnlyBufferException;
57 | import java.nio.channels.FileChannel;
58 | import java.util.HashMap;
59 | import java.util.List;
60 | import java.util.Map;
61 | import java.util.concurrent.ExecutionException;
62 | import java.util.concurrent.Executor;
63 | import java.util.concurrent.Executors;
64 |
65 | public class MainActivity extends AppCompatActivity {
66 | private static final String TAG = "MainActivity";
67 | private static final int PERMISSION_CODE = 1001;
68 | private static final String CAMERA_PERMISSION = Manifest.permission.CAMERA;
69 | private PreviewView previewView;
70 | private CameraSelector cameraSelector;
71 | private ProcessCameraProvider cameraProvider;
72 | private int lensFacing = CameraSelector.LENS_FACING_BACK;
73 | private Preview previewUseCase;
74 | private ImageAnalysis analysisUseCase;
75 | private GraphicOverlay graphicOverlay;
76 | private ImageView previewImg;
77 | private TextView detectionTextView;
78 |
79 | private final HashMap registered = new HashMap<>(); //saved Faces
80 | private Interpreter tfLite;
81 | private boolean flipX = false;
82 | private boolean start = true;
83 | private float[][] embeddings;
84 |
85 | private static final float IMAGE_MEAN = 128.0f;
86 | private static final float IMAGE_STD = 128.0f;
87 | private static final int INPUT_SIZE = 112;
88 | private static final int OUTPUT_SIZE=192;
89 |
90 | @Override
91 | protected void onCreate(Bundle savedInstanceState) {
92 | super.onCreate(savedInstanceState);
93 | setContentView(R.layout.activity_main);
94 | previewView = findViewById(R.id.previewView);
95 | previewView.setScaleType(PreviewView.ScaleType.FIT_CENTER);
96 | graphicOverlay = findViewById(R.id.graphic_overlay);
97 | previewImg = findViewById(R.id.preview_img);
98 | detectionTextView = findViewById(R.id.detection_text);
99 |
100 | ImageButton addBtn = findViewById(R.id.add_btn);
101 | addBtn.setOnClickListener((v -> addFace()));
102 |
103 | ImageButton switchCamBtn = findViewById(R.id.switch_camera);
104 | switchCamBtn.setOnClickListener((view -> switchCamera()));
105 |
106 | loadModel();
107 | }
108 |
109 | @Override
110 | protected void onResume() {
111 | super.onResume();
112 | startCamera();
113 | }
114 |
115 | /** Permissions Handler */
116 | private void getPermissions() {
117 | ActivityCompat.requestPermissions(this, new String[]{CAMERA_PERMISSION}, PERMISSION_CODE);
118 | }
119 |
120 | @Override
121 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, int[] grantResults) {
122 | for (int r : grantResults) {
123 | if (r == PackageManager.PERMISSION_DENIED) {
124 | Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show();
125 | return;
126 | }
127 | }
128 |
129 | if (requestCode == PERMISSION_CODE) {
130 | setupCamera();
131 | }
132 |
133 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
134 | }
135 |
136 | /** Setup camera & use cases */
137 | private void startCamera() {
138 | if(ContextCompat.checkSelfPermission(this, CAMERA_PERMISSION) == PackageManager.PERMISSION_GRANTED) {
139 | setupCamera();
140 | } else {
141 | getPermissions();
142 | }
143 | }
144 |
145 | private void setupCamera() {
146 | final ListenableFuture cameraProviderFuture =
147 | ProcessCameraProvider.getInstance(this);
148 |
149 | cameraSelector = new CameraSelector.Builder().requireLensFacing(lensFacing).build();
150 |
151 | cameraProviderFuture.addListener(() -> {
152 | try {
153 | cameraProvider = cameraProviderFuture.get();
154 | bindAllCameraUseCases();
155 | } catch (ExecutionException | InterruptedException e) {
156 | Log.e(TAG, "cameraProviderFuture.addListener Error", e);
157 | }
158 | }, ContextCompat.getMainExecutor(this));
159 | }
160 |
161 | private void bindAllCameraUseCases() {
162 | if (cameraProvider != null) {
163 | cameraProvider.unbindAll();
164 | bindPreviewUseCase();
165 | bindAnalysisUseCase();
166 | }
167 | }
168 |
169 | private void bindPreviewUseCase() {
170 | if (cameraProvider == null) {
171 | return;
172 | }
173 |
174 | if (previewUseCase != null) {
175 | cameraProvider.unbind(previewUseCase);
176 | }
177 |
178 | Preview.Builder builder = new Preview.Builder();
179 | builder.setTargetAspectRatio(AspectRatio.RATIO_4_3);
180 | builder.setTargetRotation(getRotation());
181 |
182 | previewUseCase = builder.build();
183 | previewUseCase.setSurfaceProvider(previewView.getSurfaceProvider());
184 |
185 | try {
186 | cameraProvider
187 | .bindToLifecycle(this, cameraSelector, previewUseCase);
188 | } catch (Exception e) {
189 | Log.e(TAG, "Error when bind preview", e);
190 | }
191 | }
192 |
193 | private void bindAnalysisUseCase() {
194 | if (cameraProvider == null) {
195 | return;
196 | }
197 |
198 | if (analysisUseCase != null) {
199 | cameraProvider.unbind(analysisUseCase);
200 | }
201 |
202 | Executor cameraExecutor = Executors.newSingleThreadExecutor();
203 |
204 | ImageAnalysis.Builder builder = new ImageAnalysis.Builder();
205 | builder.setTargetAspectRatio(AspectRatio.RATIO_4_3);
206 | builder.setTargetRotation(getRotation());
207 |
208 | analysisUseCase = builder.build();
209 | analysisUseCase.setAnalyzer(cameraExecutor, this::analyze);
210 |
211 | try {
212 | cameraProvider
213 | .bindToLifecycle(this, cameraSelector, analysisUseCase);
214 | } catch (Exception e) {
215 | Log.e(TAG, "Error when bind analysis", e);
216 | }
217 | }
218 |
219 | protected int getRotation() throws NullPointerException {
220 | return previewView.getDisplay().getRotation();
221 | }
222 |
223 | private void switchCamera() {
224 | if (lensFacing == CameraSelector.LENS_FACING_BACK) {
225 | lensFacing = CameraSelector.LENS_FACING_FRONT;
226 | flipX = true;
227 | } else {
228 | lensFacing = CameraSelector.LENS_FACING_BACK;
229 | flipX = false;
230 | }
231 |
232 | if(cameraProvider != null) cameraProvider.unbindAll();
233 | startCamera();
234 | }
235 |
236 | /** Face detection processor */
237 | @SuppressLint("UnsafeOptInUsageError")
238 | private void analyze(@NonNull ImageProxy image) {
239 | if (image.getImage() == null) return;
240 |
241 | InputImage inputImage = InputImage.fromMediaImage(
242 | image.getImage(),
243 | image.getImageInfo().getRotationDegrees()
244 | );
245 |
246 | FaceDetector faceDetector = FaceDetection.getClient();
247 |
248 | faceDetector.process(inputImage)
249 | .addOnSuccessListener(faces -> onSuccessListener(faces, inputImage))
250 | .addOnFailureListener(e -> Log.e(TAG, "Barcode process failure", e))
251 | .addOnCompleteListener(task -> image.close());
252 | }
253 |
254 | private void onSuccessListener(List faces, InputImage inputImage) {
255 | Rect boundingBox = null;
256 | String name = null;
257 | float scaleX = (float) previewView.getWidth() / (float) inputImage.getHeight();
258 | float scaleY = (float) previewView.getHeight() / (float) inputImage.getWidth();
259 |
260 | if(faces.size() > 0) {
261 | detectionTextView.setText(R.string.face_detected);
262 | // get first face detected
263 | Face face = faces.get(0);
264 |
265 | // get bounding box of face;
266 | boundingBox = face.getBoundingBox();
267 |
268 | // convert img to bitmap & crop img
269 | Bitmap bitmap = mediaImgToBmp(
270 | inputImage.getMediaImage(),
271 | inputImage.getRotationDegrees(),
272 | boundingBox);
273 |
274 | if(start) name = recognizeImage(bitmap);
275 | if(name != null) detectionTextView.setText(name);
276 | }
277 | else {
278 | detectionTextView.setText(R.string.no_face_detected);
279 | }
280 |
281 | graphicOverlay.draw(boundingBox, scaleX, scaleY, name);
282 | }
283 |
284 | /** Recognize Processor */
285 | private void addFace() {
286 | start=false;
287 | AlertDialog.Builder builder = new AlertDialog.Builder(this);
288 | builder.setTitle("Enter Name");
289 |
290 | // Set up the input
291 | final EditText input = new EditText(this);
292 |
293 | input.setInputType(InputType.TYPE_CLASS_TEXT );
294 | input.setMaxWidth(200);
295 | builder.setView(input);
296 |
297 | // Set up the buttons
298 | builder.setPositiveButton("ADD", (dialog, which) -> {
299 | //Toast.makeText(context, input.getText().toString(), Toast.LENGTH_SHORT).show();
300 |
301 | //Create and Initialize new object with Face embeddings and Name.
302 | SimilarityClassifier.Recognition result = new SimilarityClassifier.Recognition(
303 | "0", "", -1f);
304 | result.setExtra(embeddings);
305 |
306 | registered.put( input.getText().toString(),result);
307 | start = true;
308 |
309 | });
310 | builder.setNegativeButton("Cancel", (dialog, which) -> {
311 | start = true;
312 | dialog.cancel();
313 | });
314 |
315 | builder.show();
316 | }
317 |
318 | public String recognizeImage(final Bitmap bitmap) {
319 | // set image to preview
320 | previewImg.setImageBitmap(bitmap);
321 |
322 | //Create ByteBuffer to store normalized image
323 |
324 | ByteBuffer imgData = ByteBuffer.allocateDirect(INPUT_SIZE * INPUT_SIZE * 3 * 4);
325 |
326 | imgData.order(ByteOrder.nativeOrder());
327 |
328 | int[] intValues = new int[INPUT_SIZE * INPUT_SIZE];
329 |
330 | //get pixel values from Bitmap to normalize
331 | bitmap.getPixels(intValues, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
332 |
333 | imgData.rewind();
334 |
335 | for (int i = 0; i < INPUT_SIZE; ++i) {
336 | for (int j = 0; j < INPUT_SIZE; ++j) {
337 | int pixelValue = intValues[i * INPUT_SIZE + j];
338 | imgData.putFloat((((pixelValue >> 16) & 0xFF) - IMAGE_MEAN) / IMAGE_STD);
339 | imgData.putFloat((((pixelValue >> 8) & 0xFF) - IMAGE_MEAN) / IMAGE_STD);
340 | imgData.putFloat(((pixelValue & 0xFF) - IMAGE_MEAN) / IMAGE_STD);
341 | }
342 | }
343 | //imgData is input to our model
344 | Object[] inputArray = {imgData};
345 |
346 | Map outputMap = new HashMap<>();
347 |
348 |
349 | embeddings = new float[1][OUTPUT_SIZE]; //output of model will be stored in this variable
350 |
351 | outputMap.put(0, embeddings);
352 |
353 | tfLite.runForMultipleInputsOutputs(inputArray, outputMap); //Run model
354 |
355 |
356 |
357 | float distance;
358 |
359 | //Compare new face with saved Faces.
360 | if (registered.size() > 0) {
361 |
362 | final Pair nearest = findNearest(embeddings[0]);//Find closest matching face
363 |
364 | if (nearest != null) {
365 |
366 | final String name = nearest.first;
367 | distance = nearest.second;
368 | if(distance<1.000f) //If distance between Closest found face is more than 1.000 ,then output UNKNOWN face.
369 | return name;
370 | else
371 | return "unknown";
372 | }
373 | }
374 |
375 | return null;
376 | }
377 |
378 | //Compare Faces by distance between face embeddings
379 | private Pair findNearest(float[] emb) {
380 |
381 | Pair ret = null;
382 | for (Map.Entry entry : registered.entrySet()) {
383 |
384 | final String name = entry.getKey();
385 | final float[] knownEmb = ((float[][]) entry.getValue().getExtra())[0];
386 |
387 | float distance = 0;
388 | for (int i = 0; i < emb.length; i++) {
389 | float diff = emb[i] - knownEmb[i];
390 | distance += diff*diff;
391 | }
392 | distance = (float) Math.sqrt(distance);
393 | if (ret == null || distance < ret.second) {
394 | ret = new Pair<>(name, distance);
395 | }
396 | }
397 |
398 | return ret;
399 |
400 | }
401 |
402 | /** Bitmap Converter */
403 | private Bitmap mediaImgToBmp(Image image, int rotation, Rect boundingBox) {
404 | //Convert media image to Bitmap
405 | Bitmap frame_bmp = toBitmap(image);
406 |
407 | //Adjust orientation of Face
408 | Bitmap frame_bmp1 = rotateBitmap(frame_bmp, rotation, flipX);
409 |
410 | //Crop out bounding box from whole Bitmap(image)
411 | float padding = 0.0f;
412 | RectF adjustedBoundingBox = new RectF(
413 | boundingBox.left - padding,
414 | boundingBox.top - padding,
415 | boundingBox.right + padding,
416 | boundingBox.bottom + padding);
417 | Bitmap cropped_face = getCropBitmapByCPU(frame_bmp1, adjustedBoundingBox);
418 |
419 | //Resize bitmap to 112,112
420 | return getResizedBitmap(cropped_face);
421 | }
422 |
423 | private Bitmap getResizedBitmap(Bitmap bm) {
424 | int width = bm.getWidth();
425 | int height = bm.getHeight();
426 | float scaleWidth = ((float) 112) / width;
427 | float scaleHeight = ((float) 112) / height;
428 | // CREATE A MATRIX FOR THE MANIPULATION
429 | Matrix matrix = new Matrix();
430 | // RESIZE THE BIT MAP
431 | matrix.postScale(scaleWidth, scaleHeight);
432 |
433 | // "RECREATE" THE NEW BITMAP
434 | Bitmap resizedBitmap = Bitmap.createBitmap(
435 | bm, 0, 0, width, height, matrix, false);
436 | bm.recycle();
437 | return resizedBitmap;
438 | }
439 |
440 | private static Bitmap getCropBitmapByCPU(Bitmap source, RectF cropRectF) {
441 | Bitmap resultBitmap = Bitmap.createBitmap((int) cropRectF.width(),
442 | (int) cropRectF.height(), Bitmap.Config.ARGB_8888);
443 | Canvas canvas = new Canvas(resultBitmap);
444 |
445 | // draw background
446 | Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG);
447 | paint.setColor(Color.WHITE);
448 | canvas.drawRect(//from w w w. ja v a 2s. c om
449 | new RectF(0, 0, cropRectF.width(), cropRectF.height()),
450 | paint);
451 |
452 | Matrix matrix = new Matrix();
453 | matrix.postTranslate(-cropRectF.left, -cropRectF.top);
454 |
455 | canvas.drawBitmap(source, matrix, paint);
456 |
457 | if (source != null && !source.isRecycled()) {
458 | source.recycle();
459 | }
460 |
461 | return resultBitmap;
462 | }
463 |
464 | private static Bitmap rotateBitmap(
465 | Bitmap bitmap, int rotationDegrees, boolean flipX) {
466 | Matrix matrix = new Matrix();
467 |
468 | // Rotate the image back to straight.
469 | matrix.postRotate(rotationDegrees);
470 |
471 | // Mirror the image along the X or Y axis.
472 | matrix.postScale(flipX ? -1.0f : 1.0f, 1.0f);
473 | Bitmap rotatedBitmap =
474 | Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
475 |
476 | // Recycle the old bitmap if it has changed.
477 | if (rotatedBitmap != bitmap) {
478 | bitmap.recycle();
479 | }
480 | return rotatedBitmap;
481 | }
482 |
483 | private static byte[] YUV_420_888toNV21(Image image) {
484 |
485 | int width = image.getWidth();
486 | int height = image.getHeight();
487 | int ySize = width*height;
488 | int uvSize = width*height/4;
489 |
490 | byte[] nv21 = new byte[ySize + uvSize*2];
491 |
492 | ByteBuffer yBuffer = image.getPlanes()[0].getBuffer(); // Y
493 | ByteBuffer uBuffer = image.getPlanes()[1].getBuffer(); // U
494 | ByteBuffer vBuffer = image.getPlanes()[2].getBuffer(); // V
495 |
496 | int rowStride = image.getPlanes()[0].getRowStride();
497 | assert(image.getPlanes()[0].getPixelStride() == 1);
498 |
499 | int pos = 0;
500 |
501 | if (rowStride == width) { // likely
502 | yBuffer.get(nv21, 0, ySize);
503 | pos += ySize;
504 | }
505 | else {
506 | long yBufferPos = -rowStride; // not an actual position
507 | for (; pos