├── 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 | ![Register Face](/screenshots/ss_small_1.gif) 14 | ![Register Face](/screenshots/ss_small_2.gif) 15 | ![Register Face](/screenshots/ss_small_3.gif) 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 | 16 | 17 | 18 | 19 | 20 | 21 | 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