├── .gitignore ├── .idea ├── .gitignore ├── compiler.xml ├── gradle.xml ├── kotlinc.xml └── misc.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── libs │ └── miniai_face.aar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── miniai │ │ └── liveness │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── license.skm │ ├── java │ │ └── com │ │ │ └── miniai │ │ │ └── liveness │ │ │ ├── BitmapUtils.kt │ │ │ ├── BoundingBoxOverlay.kt │ │ │ ├── CameraActivity.kt │ │ │ ├── FrameAnalyser.kt │ │ │ ├── FrameInferface.kt │ │ │ ├── ImageRotator.java │ │ │ ├── SplashScreenActivity.java │ │ │ ├── UserActivity.kt │ │ │ └── Utils.java │ └── res │ │ ├── drawable │ │ ├── ic_add_48.xml │ │ ├── ic_camera_48.xml │ │ ├── ic_menu_48.xml │ │ └── splashlogo.png │ │ ├── layout │ │ ├── activity_camera.xml │ │ └── activity_splash.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_miniai.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-night │ │ └── themes.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── com │ └── miniai │ └── liveness │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.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 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

Face Liveness Detection Android SDK

3 | MiniAiLive Logo 5 |
6 | 7 | ## Welcome to the [MiniAiLive](https://www.miniai.live/)! 8 | Upgrade your Android app with MiniAiLive's 3D Passive Face Liveness Detection! With our advanced computer vision techniques, you can now enhance security and accuracy on your Android platform. Check out our latest repository containing a demonstration of 2D & 3D passive face liveness detection (face anti-spoofing) capabilities. Try it out today! 9 | 10 | 11 | > **Note** 12 | > 13 | > SDK is fully on-premise, processing all happens on hosting server and no data leaves server. 14 | 15 | 16 | ## Face LivenessDetection APK 17 | 18 | 19 | 20 | 21 | 22 | ## Liveness Test Results 23 | Test4 24 | Test5 25 | Test2 26 | Test3 27 | Test1 28 | 29 |

30 | 31 | https://github.com/MiniAiLive/MiniAI-Face-LivenessDetection-AndroidSDK/assets/153516004/99ae5b73-cb36-47d9-ae38-d91c031c2953 32 | 33 | ## Request license 34 | Feel free to [Contact US](https://www.miniai.live/contact/) to get a trial License. We are 24/7 online on [WhatsApp](https://wa.me/+19162702374). 35 | 36 | 37 | ## Face & IDSDK Online Demo, Resources 38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
51 |
52 | 53 | ## Our Products 54 | 55 | ### Face Recognition SDK 56 | | No | Project | Features | 57 | |----|---------|-----------| 58 | | 1 | [FaceRecognition-SDK-Docker](https://github.com/MiniAiLive/FaceRecognition-SDK-Docker) | 1:1 & 1:N Face Matching SDK | 59 | | 2 | [FaceRecognition-SDK-Windows](https://github.com/MiniAiLive/FaceRecognition-SDK-Windows) | 1:1 & 1:N Face Matching SDK | 60 | | 3 | [FaceRecognition-SDK-Linux](https://github.com/MiniAiLive/FaceRecognition-SDK-Linux) | 1:1 & 1:N Face Matching SDK | 61 | | 4 | [FaceRecognition-LivenessDetection-SDK-Android](https://github.com/MiniAiLive/FaceRecognition-LivenessDetection-SDK-Android) | 1:1 & 1:N Face Matching, 2D & 3D Face Passive Liveness Detection SDK | 62 | | 5 | [FaceRecognition-LivenessDetection-SDK-iOS](https://github.com/MiniAiLive/FaceRecognition-LivenessDetection-SDK-iOS) | 1:1 & 1:N Face Matching, 2D & 3D Face Passive Liveness Detection SDK | 63 | | 6 | [FaceRecognition-LivenessDetection-SDK-CPP](https://github.com/MiniAiLive/FaceRecognition-LivenessDetection-SDK-CPP) | 1:1 & 1:N Face Matching, 2D & 3D Face Passive Liveness Detection SDK | 64 | | 7 | [FaceMatching-SDK-Android](https://github.com/MiniAiLive/FaceMatching-SDK-Android) | 1:1 Face Matching SDK | 65 | | 8 | [FaceAttributes-SDK-Android](https://github.com/MiniAiLive/FaceAttributes-SDK-Android) | Face Attributes, Age & Gender Estimation SDK | 66 | 67 | ### Face Liveness Detection SDK 68 | | No | Project | Features | 69 | |----|---------|-----------| 70 | | 1 | [FaceLivenessDetection-SDK-Docker](https://github.com/MiniAiLive/FaceLivenessDetection-SDK-Docker) | 2D & 3D Face Passive Liveness Detection SDK | 71 | | 2 | [FaceLivenessDetection-SDK-Windows](https://github.com/MiniAiLive/FaceLivenessDetection-SDK-Windows) | 2D & 3D Face Passive Liveness Detection SDK | 72 | | 3 | [FaceLivenessDetection-SDK-Linux](https://github.com/MiniAiLive/FaceLivenessDetection-SDK-Linux) | 2D & 3D Face Passive Liveness Detection SDK | 73 | | 4 | [FaceLivenessDetection-SDK-Android](https://github.com/MiniAiLive/FaceLivenessDetection-SDK-Android) | 2D & 3D Face Passive Liveness Detection SDK | 74 | | 5 | [FaceLivenessDetection-SDK-iOS](https://github.com/MiniAiLive/FaceLivenessDetection-SDK-iOS) | 2D & 3D Face Passive Liveness Detection SDK | 75 | 76 | ### ID Document Recognition SDK 77 | | No | Project | Features | 78 | |----|---------|-----------| 79 | | 1 | [ID-DocumentRecognition-SDK-Docker](https://github.com/MiniAiLive/ID-DocumentRecognition-SDK-Docker) | ID Document, Passport, Driver License, Credit Card, MRZ Recognition SDK | 80 | | 2 | [ID-DocumentRecognition-SDK-Windows](https://github.com/MiniAiLive/ID-DocumentRecognition-SDK-Windows) | ID Document, Passport, Driver License, Credit Card, MRZ Recognition SDK | 81 | | 3 | [ID-DocumentRecognition-SDK-Linux](https://github.com/MiniAiLive/ID-DocumentRecognition-SDK-Linux) | ID Document, Passport, Driver License, Credit Card, MRZ Recognition SDK | 82 | | 4 | [ID-DocumentRecognition-SDK-Android](https://github.com/MiniAiLive/ID-DocumentRecognition-SDK-Android) | ID Document, Passport, Driver License, Credit Card, MRZ Recognition SDK | 83 | 84 | ### ID Document Liveness Detection SDK 85 | | No | Project | Features | 86 | |----|---------|-----------| 87 | | 1 | [ID-DocumentLivenessDetection-SDK-Docker](https://github.com/MiniAiLive/ID-DocumentLivenessDetection-SDK-Docker) | ID Document Liveness Detection SDK | 88 | | 2 | [ID-DocumentLivenessDetection-SDK-Windows](https://github.com/MiniAiLive/ID-DocumentLivenessDetection-SDK-Windows) | ID Document Liveness Detection SDK | 89 | | 3 | [ID-DocumentLivenessDetection-SDK-Linux](https://github.com/MiniAiLive/ID-DocumentLivenessDetection-SDK-Linux) | ID Document Liveness Detection SDK | 90 | 91 | ### Web & Desktop Demo 92 | | No | Project | Features | 93 | |----|---------|-----------| 94 | | 1 | [FaceRecognition-IDRecognition-Playground-Next.JS](https://github.com/MiniAiLive/FaceRecognition-IDRecognition-Playground-Next.JS) | FaceSDK & IDSDK Playground | 95 | | 2 | [FaceCapture-LivenessDetection-Next.JS](https://github.com/MiniAiLive/FaceCapture-LivenessDetection-Next.JS) | Face Capture, Face LivenessDetection, Face Attributes | 96 | | 3 | [FaceMatching-Windows-App](https://github.com/MiniAiLive/FaceMatching-Windows-App) | 1:1 Face Matching Windows Demo Application | 97 | 98 | ## About MiniAiLive 99 | [MiniAiLive](https://www.miniai.live/) is a leading AI solutions company specializing in computer vision and machine learning technologies. We provide cutting-edge solutions for various industries, leveraging the power of AI to drive innovation and efficiency. 100 | 101 | ## Contact US 102 | For any inquiries or questions, please contact us on [WhatsApp](https://wa.me/+19162702374). 103 | 104 |

105 | www.miniai.live  106 | www.miniai.live  107 |

108 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | } 5 | 6 | android { 7 | namespace 'com.miniai.liveness' 8 | compileSdk 33 9 | 10 | defaultConfig { 11 | applicationId "com.miniai.liveness" 12 | minSdk 21 13 | targetSdk 32 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | kotlinOptions { 31 | jvmTarget = '1.8' 32 | } 33 | } 34 | 35 | dependencies { 36 | 37 | implementation 'androidx.core:core-ktx:1.7.0' 38 | implementation 'androidx.appcompat:appcompat:1.6.0' 39 | implementation 'com.google.android.material:material:1.7.0' 40 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 41 | 42 | // CameraX dependencies 43 | implementation "androidx.camera:camera-camera2:1.1.0" 44 | implementation "androidx.camera:camera-lifecycle:1.1.0" 45 | implementation "androidx.camera:camera-view:1.1.0" 46 | 47 | implementation files('libs/miniai_face.aar') 48 | testImplementation 'junit:junit:4.13.2' 49 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 50 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 51 | 52 | } -------------------------------------------------------------------------------- /app/libs/miniai_face.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniAiLive/FaceLivenessDetection-SDK-Android/a89dab192d1edd567aea0f193fc4369863fa26fc/app/libs/miniai_face.aar -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/src/androidTest/java/com/miniai/liveness/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.miniai.liveness; 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.miniai.liveness", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 11 | 12 | 22 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/assets/license.skm: -------------------------------------------------------------------------------- 1 | { 2 | "licenseKey": "eyJQcm9kdWN0SWQiOjE2OTYyLCJJRCI6MTEsIktleSI6IktMQ0NDLUlTVUlKLUdUTllJLUFaRUZSIiwiQ3JlYXRlZCI6MTcwMTgzNjkyMiwiRXhwaXJlcyI6MTc2MjMxNjkyMiwiUGVyaW9kIjo3MDAsIkYxIjp0cnVlLCJGMiI6ZmFsc2UsIkYzIjpmYWxzZSwiRjQiOmZhbHNlLCJGNSI6ZmFsc2UsIkY2IjpmYWxzZSwiRjciOmZhbHNlLCJGOCI6ZmFsc2UsIk5vdGVzIjoicHNoIDIgeWVhcnMiLCJCbG9jayI6ZmFsc2UsIkdsb2JhbElkIjo0MDYwODYsIkN1c3RvbWVyIjpudWxsLCJBY3RpdmF0ZWRNYWNoaW5lcyI6W3siTWlkIjoiY29tLm1pbmlhaS5saXZlbmVzcyIsIklQIjoiMzguNzUuMTM3LjkyIiwiVGltZSI6MTcwMTgzNjkzNX0seyJNaWQiOiJjb20ubWluaWFpLmZhY2VyZWNvZ25pdGlvbiIsIklQIjoiMzguNzUuMTM3LjkyIiwiVGltZSI6MTcwMTg3NDM2Mn0seyJNaWQiOiJjb20ubWluaWFpLmZhY2VtYXRjaCIsIklQIjoiMzguNzUuMTM3LjkyIiwiVGltZSI6MTcwMTg3NDM2OH1dLCJUcmlhbEFjdGl2YXRpb24iOmZhbHNlLCJNYXhOb09mTWFjaGluZXMiOjQsIkFsbG93ZWRNYWNoaW5lcyI6IiIsIkRhdGFPYmplY3RzIjpbXSwiU2lnbkRhdGUiOjE3MDE4NzQzNjh9", 3 | "signature": "FteWASCQBE01tM5p5Lx4njbC2w7Y4LszwgyswJaYjxVb+3XMHM3bBPVqBliZtoUT0GZePSuqdxahaCof0eYcgyA+MMmsrx2291v/bmGFiD5b0UqjNaNmacjvsk/lp+Uipxw5iHoXR+hpLUF01tELj4S+vsDnFbaqA7CIDijLo6OaUQZ8DP/hBY5BBFr1yY+zDhp447VUME7PFlbVjmdFhtybVb41ls8Doi9LDw0lrl+lweT5gw0W9YPC0Lvmy5Vtd9/7JXrQZBXP6cma/V6eqaAAvneoIC7Aw4nNKlemYg9VZXM2wTGsQlq+0PWF/gaIDbJX2GVaa1InEH+dDXbLTQ==", 4 | "result": 0, 5 | "message": "" 6 | } -------------------------------------------------------------------------------- /app/src/main/java/com/miniai/liveness/BitmapUtils.kt: -------------------------------------------------------------------------------- 1 | package com.miniai.liveness 2 | 3 | import android.content.ContentResolver 4 | import android.content.Context 5 | import android.graphics.* 6 | import android.media.Image 7 | import android.net.Uri 8 | import android.os.ParcelFileDescriptor 9 | import android.util.Log 10 | import kotlinx.coroutines.Dispatchers 11 | import kotlinx.coroutines.withContext 12 | import java.io.ByteArrayOutputStream 13 | import java.io.File 14 | import java.io.FileDescriptor 15 | import java.io.FileOutputStream 16 | 17 | // Helper class for operations on Bitmaps 18 | class BitmapUtils { 19 | companion object { 20 | 21 | // Crop the given bitmap with the given rect. 22 | fun cropRectFromBitmap(source: Bitmap, rect: Rect ): Bitmap { 23 | var width = rect.width() 24 | var height = rect.height() 25 | if ( (rect.left + width) > source.width ){ 26 | width = source.width - rect.left 27 | } 28 | if ( (rect.top + height ) > source.height ){ 29 | height = source.height - rect.top 30 | } 31 | val croppedBitmap = Bitmap.createBitmap( source , rect.left , rect.top , width , height ) 32 | // Uncomment the below line if you want to save the input image. 33 | // BitmapUtils.saveBitmap( context , croppedBitmap , "source" ) 34 | return croppedBitmap 35 | } 36 | 37 | 38 | // Get the image as a Bitmap from given Uri 39 | // Source -> https://developer.android.com/training/data-storage/shared/documents-files#bitmap 40 | fun getBitmapFromUri( contentResolver : ContentResolver , uri: Uri): Bitmap { 41 | val parcelFileDescriptor: ParcelFileDescriptor? = contentResolver.openFileDescriptor(uri, "r") 42 | val fileDescriptor: FileDescriptor = parcelFileDescriptor!!.fileDescriptor 43 | val image: Bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor) 44 | parcelFileDescriptor.close() 45 | return image 46 | } 47 | 48 | 49 | // Rotate the given `source` by `degrees`. 50 | // See this SO answer -> https://stackoverflow.com/a/16219591/10878733 51 | fun rotateBitmap( source: Bitmap , degrees : Float ): Bitmap { 52 | val matrix = Matrix() 53 | matrix.postRotate( degrees ) 54 | return Bitmap.createBitmap(source, 0, 0, source.width, source.height, matrix , false ) 55 | } 56 | 57 | 58 | // Flip the given `Bitmap` horizontally. 59 | // See this SO answer -> https://stackoverflow.com/a/36494192/10878733 60 | fun flipBitmap( source: Bitmap ): Bitmap { 61 | val matrix = Matrix() 62 | matrix.postScale(-1f, 1f, source.width / 2f, source.height / 2f) 63 | return Bitmap.createBitmap(source, 0, 0, source.width, source.height, matrix, true) 64 | } 65 | 66 | 67 | // Use this method to save a Bitmap to the internal storage ( app-specific storage ) of your device. 68 | // To see the image, go to "Device File Explorer" -> "data" -> "data" -> "com.ml.quaterion.facenetdetection" -> "files" 69 | fun saveBitmap(context: Context, image: Bitmap, name: String) { 70 | val fileOutputStream = FileOutputStream(File( context.filesDir.absolutePath + "/$name.png")) 71 | image.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream) 72 | } 73 | 74 | 75 | // Convert android.media.Image to android.graphics.Bitmap 76 | // See the SO answer -> https://stackoverflow.com/a/44486294/10878733 77 | fun imageToBitmap( image : Image , rotationDegrees : Int ): Bitmap { 78 | val yBuffer = image.planes[0].buffer 79 | val uBuffer = image.planes[1].buffer 80 | val vBuffer = image.planes[2].buffer 81 | val ySize = yBuffer.remaining() 82 | val uSize = uBuffer.remaining() 83 | val vSize = vBuffer.remaining() 84 | val nv21 = ByteArray(ySize + uSize + vSize) 85 | yBuffer.get(nv21, 0, ySize) 86 | vBuffer.get(nv21, ySize, vSize) 87 | uBuffer.get(nv21, ySize + vSize, uSize) 88 | val yuvImage = YuvImage(nv21, ImageFormat.NV21, image.width, image.height, null) 89 | val out = ByteArrayOutputStream() 90 | yuvImage.compressToJpeg(Rect(0, 0, yuvImage.width, yuvImage.height), 100, out) 91 | val yuv = out.toByteArray() 92 | var output = BitmapFactory.decodeByteArray(yuv, 0, yuv.size) 93 | output = rotateBitmap( output , rotationDegrees.toFloat() ) 94 | return flipBitmap( output ) 95 | } 96 | 97 | 98 | // Convert the given Bitmap to NV21 ByteArray 99 | // See this comment -> https://github.com/firebase/quickstart-android/issues/932#issuecomment-531204396 100 | suspend fun bitmapToNV21ByteArray(bitmap: Bitmap): ByteArray = withContext(Dispatchers.Default) { 101 | val argb = IntArray(bitmap.width * bitmap.height ) 102 | bitmap.getPixels(argb, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height) 103 | val yuv = ByteArray(bitmap.height * bitmap.width + 2 * Math.ceil(bitmap.height / 2.0).toInt() 104 | * Math.ceil(bitmap.width / 2.0).toInt()) 105 | encodeYUV420SP( yuv, argb, bitmap.width, bitmap.height) 106 | return@withContext yuv 107 | } 108 | 109 | private fun encodeYUV420SP(yuv420sp: ByteArray, argb: IntArray, width: Int, height: Int) { 110 | val frameSize = width * height 111 | var yIndex = 0 112 | var uvIndex = frameSize 113 | var R: Int 114 | var G: Int 115 | var B: Int 116 | var Y: Int 117 | var U: Int 118 | var V: Int 119 | var index = 0 120 | for (j in 0 until height) { 121 | for (i in 0 until width) { 122 | R = argb[index] and 0xff0000 shr 16 123 | G = argb[index] and 0xff00 shr 8 124 | B = argb[index] and 0xff shr 0 125 | Y = (66 * R + 129 * G + 25 * B + 128 shr 8) + 16 126 | U = (-38 * R - 74 * G + 112 * B + 128 shr 8) + 128 127 | V = (112 * R - 94 * G - 18 * B + 128 shr 8) + 128 128 | yuv420sp[yIndex++] = (if (Y < 0) 0 else if (Y > 255) 255 else Y).toByte() 129 | if (j % 2 == 0 && index % 2 == 0) { 130 | yuv420sp[uvIndex++] = (if (V < 0) 0 else if (V > 255) 255 else V).toByte() 131 | yuv420sp[uvIndex++] = (if (U < 0) 0 else if (U > 255) 255 else U).toByte() 132 | } 133 | index++ 134 | } 135 | } 136 | } 137 | 138 | } 139 | 140 | } -------------------------------------------------------------------------------- /app/src/main/java/com/miniai/liveness/BoundingBoxOverlay.kt: -------------------------------------------------------------------------------- 1 | package com.miniai.liveness 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Color 6 | import android.graphics.Matrix 7 | import android.graphics.Paint 8 | import android.graphics.RectF 9 | import android.util.AttributeSet 10 | import android.util.Log 11 | import android.view.SurfaceHolder 12 | import android.view.SurfaceView 13 | import androidx.core.graphics.toRectF 14 | import com.fm.face.* 15 | 16 | // Defines an overlay on which the boxes and text will be drawn. 17 | class BoundingBoxOverlay( context: Context , attributeSet: AttributeSet ) 18 | : SurfaceView( context , attributeSet ) , SurfaceHolder.Callback { 19 | 20 | companion object { 21 | private val TAG = BoundingBoxOverlay::class.simpleName 22 | } 23 | // Variables used to compute output2overlay transformation matrix 24 | // These are assigned in FrameAnalyser.kt 25 | var areDimsInit = false 26 | var frameHeight = 0 27 | var frameWidth = 0 28 | 29 | // This var is assigned in FrameAnalyser.kt 30 | var faceBoundingBoxes: List? = null 31 | var livenessScore = 0.0f 32 | var livenessResult: Int = 0 33 | 34 | private var output2OverlayTransform: Matrix = Matrix() 35 | 36 | // Paint for boxes and text 37 | private val realBoxPaint = Paint().apply { 38 | strokeWidth = 5.0f 39 | color = Color.parseColor("#FF00FF00") 40 | style = Paint.Style.STROKE 41 | textSize = 64f 42 | } 43 | 44 | private val spoofBoxPaint = Paint().apply { 45 | strokeWidth = 5.0f 46 | color = Color.parseColor("#FFFF0000") 47 | style = Paint.Style.STROKE 48 | textSize = 64f 49 | } 50 | 51 | private val mulitpleBoxPaint = Paint().apply { 52 | strokeWidth = 5.0f 53 | color = Color.parseColor("#FFFFFF00") 54 | style = Paint.Style.STROKE 55 | textSize = 64f 56 | } 57 | 58 | private val realTextPaint = Paint().apply { 59 | strokeWidth = 2.0f 60 | color = Color.parseColor("#FF00FF00") 61 | textSize = 64f 62 | } 63 | 64 | private val spoofTextPaint = Paint().apply { 65 | strokeWidth = 2.0f 66 | color = Color.parseColor("#FFFF0000") 67 | textSize = 64f 68 | } 69 | override fun surfaceCreated(holder: SurfaceHolder) { 70 | TODO("Not yet implemented") 71 | } 72 | 73 | override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { 74 | TODO("Not yet implemented") 75 | } 76 | 77 | override fun surfaceDestroyed(holder: SurfaceHolder) { 78 | TODO("Not yet implemented") 79 | } 80 | 81 | override fun onDraw(canvas: Canvas?) { 82 | if (faceBoundingBoxes != null) { 83 | if (!areDimsInit) { 84 | val viewWidth = canvas!!.width.toFloat() 85 | val viewHeight = canvas.height.toFloat() 86 | val xFactor: Float = viewWidth / frameWidth.toFloat() 87 | val yFactor: Float = viewHeight / frameHeight.toFloat() 88 | // Scale and mirror the coordinates ( required for front lens ) 89 | output2OverlayTransform.preScale(xFactor, yFactor) 90 | output2OverlayTransform.postScale(1f, 1f, viewWidth / 2f, viewHeight / 2f) 91 | areDimsInit = true 92 | } 93 | else { 94 | for (face in faceBoundingBoxes!!) { 95 | val boundingBox = RectF(face.left.toFloat(), face.top.toFloat(), face.right.toFloat(), face.bottom.toFloat()) 96 | output2OverlayTransform.mapRect(boundingBox) 97 | val formattedScore = "%.4f".format(livenessScore) 98 | if(livenessResult == 0) { 99 | canvas?.drawText( 100 | "SPOOF $formattedScore", 101 | boundingBox.left + 20, 102 | boundingBox.top - 30, 103 | spoofTextPaint 104 | ) 105 | 106 | canvas?.drawRoundRect(boundingBox, 16f, 16f, spoofBoxPaint) 107 | } else if(livenessResult == 1) { 108 | canvas?.drawText( 109 | "REAL $formattedScore", 110 | boundingBox.left + 20, 111 | boundingBox.top - 30, 112 | realTextPaint 113 | ) 114 | 115 | canvas?.drawRoundRect(boundingBox, 16f, 16f, realBoxPaint) 116 | } else if(livenessResult == 2) { 117 | canvas?.drawRoundRect(boundingBox, 16f, 16f, mulitpleBoxPaint) 118 | } 119 | } 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /app/src/main/java/com/miniai/liveness/CameraActivity.kt: -------------------------------------------------------------------------------- 1 | package com.miniai.liveness 2 | 3 | import android.Manifest 4 | import android.content.Intent 5 | import android.content.pm.PackageManager 6 | import android.graphics.Bitmap 7 | import android.os.Build 8 | import androidx.appcompat.app.AppCompatActivity 9 | import android.os.Bundle 10 | import android.text.TextUtils 11 | import android.util.Size 12 | import android.view.LayoutInflater 13 | import android.view.View 14 | import android.view.WindowInsets 15 | import android.widget.* 16 | import androidx.appcompat.app.AlertDialog 17 | import androidx.camera.core.CameraSelector 18 | import androidx.camera.core.ImageAnalysis 19 | import androidx.camera.core.Preview 20 | import androidx.camera.lifecycle.ProcessCameraProvider 21 | import androidx.camera.view.PreviewView 22 | import androidx.core.app.ActivityCompat 23 | import androidx.core.content.ContextCompat 24 | import androidx.lifecycle.LifecycleOwner 25 | import com.fm.face.FaceSDK 26 | import com.google.android.material.floatingactionbutton.FloatingActionButton 27 | import com.google.common.util.concurrent.ListenableFuture 28 | import java.util.concurrent.Executors 29 | 30 | class CameraActivity : AppCompatActivity(), FrameInferface { 31 | 32 | companion object { 33 | private const val FRAME_WIDTH = 720 34 | private const val FRAME_HEIGHT = 1280 35 | private const val CAMERA_PERMISSION_REQUEST_CODE = 1 36 | private val TAG = CameraActivity::class.simpleName 37 | } 38 | 39 | private lateinit var previewView: PreviewView 40 | private lateinit var cameraProviderFuture: ListenableFuture 41 | private lateinit var frameAnalyser: FrameAnalyser 42 | private lateinit var boundingBoxOverlay: BoundingBoxOverlay 43 | 44 | private lateinit var viewBackgroundOfMessage: View 45 | private lateinit var textViewMessage: TextView 46 | 47 | 48 | override fun onCreate(savedInstanceState: Bundle?) { 49 | super.onCreate(savedInstanceState) 50 | setContentView(R.layout.activity_camera) 51 | 52 | previewView = findViewById(R.id.preview_view) 53 | viewBackgroundOfMessage = findViewById(R.id.viewBackgroundOfMessage) 54 | textViewMessage = findViewById(R.id.textViewMessage) 55 | 56 | // userDb = UserDB(this) 57 | 58 | boundingBoxOverlay = findViewById(R.id.bbox_overlay) 59 | boundingBoxOverlay.setWillNotDraw(false) 60 | boundingBoxOverlay.setZOrderOnTop(true) 61 | frameAnalyser = 62 | FrameAnalyser(this, boundingBoxOverlay, viewBackgroundOfMessage, textViewMessage) 63 | cameraProviderFuture = ProcessCameraProvider.getInstance(this) 64 | 65 | // Remove the status bar to have a full screen experience 66 | // See this answer on SO -> https://stackoverflow.com/a/68152688/10878733 67 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { 68 | window.decorView.windowInsetsController!! 69 | .hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars()) 70 | } else { 71 | window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN 72 | } 73 | 74 | frameAnalyser.addOnFrameListener(this) 75 | } 76 | 77 | override fun onResume() { 78 | super.onResume() 79 | 80 | frameAnalyser.setRunning(true) 81 | // We'll only require the CAMERA permission from the user. 82 | // For scoped storage, particularly for accessing documents, we won't require WRITE_EXTERNAL_STORAGE or 83 | // READ_EXTERNAL_STORAGE permissions. See https://developer.android.com/training/data-storage 84 | if (ActivityCompat.checkSelfPermission( 85 | this, 86 | Manifest.permission.CAMERA 87 | ) != PackageManager.PERMISSION_GRANTED 88 | ) { 89 | requestCameraPermission() 90 | } else { 91 | startCameraPreview() 92 | } 93 | } 94 | 95 | override fun onPause() { 96 | super.onPause() 97 | 98 | frameAnalyser.setRunning(false) 99 | stopCameraPreview() 100 | } 101 | 102 | private fun requestCameraPermission() { 103 | ActivityCompat.requestPermissions( 104 | this, 105 | arrayOf(Manifest.permission.CAMERA), 106 | CAMERA_PERMISSION_REQUEST_CODE 107 | ); 108 | } 109 | 110 | private fun startCameraPreview() { 111 | cameraProviderFuture.addListener( 112 | { 113 | val cameraProvider = cameraProviderFuture.get() 114 | bindPreview(cameraProvider) 115 | }, 116 | ContextCompat.getMainExecutor(this) 117 | ) 118 | } 119 | 120 | private fun stopCameraPreview() { 121 | cameraProviderFuture.get().unbindAll() 122 | } 123 | 124 | private fun bindPreview(cameraProvider: ProcessCameraProvider) { 125 | val preview: Preview = Preview.Builder().build() 126 | val cameraSelector: CameraSelector = CameraSelector.Builder() 127 | .requireLensFacing(CameraSelector.LENS_FACING_FRONT) 128 | .build() 129 | preview.setSurfaceProvider(previewView.surfaceProvider) 130 | val imageFrameAnalysis = ImageAnalysis.Builder() 131 | .setTargetResolution(Size(FRAME_WIDTH, FRAME_HEIGHT)) 132 | .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) 133 | .build() 134 | imageFrameAnalysis.setAnalyzer(Executors.newSingleThreadExecutor(), frameAnalyser) 135 | cameraProvider.bindToLifecycle( 136 | this as LifecycleOwner, 137 | cameraSelector, 138 | preview, 139 | imageFrameAnalysis 140 | ) 141 | } 142 | 143 | override fun onRegister(faceImage: Bitmap, featData: ByteArray?) { 144 | 145 | } 146 | 147 | override fun onVerify(msg: String) { 148 | 149 | } 150 | } -------------------------------------------------------------------------------- /app/src/main/java/com/miniai/liveness/FrameAnalyser.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Shubham Panchal 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 | package com.miniai.liveness 16 | 17 | import android.annotation.SuppressLint 18 | import android.content.Context 19 | import android.graphics.* 20 | import android.util.Log 21 | import android.view.View 22 | import android.widget.TextView 23 | import androidx.camera.core.ImageAnalysis 24 | import androidx.camera.core.ImageProxy 25 | import com.fm.face.FaceBox 26 | import com.fm.face.FaceSDK 27 | import kotlinx.coroutines.CoroutineScope 28 | import kotlinx.coroutines.Dispatchers 29 | import kotlinx.coroutines.launch 30 | import kotlinx.coroutines.withContext 31 | 32 | 33 | // Analyser class to process frames and produce detections. 34 | class FrameAnalyser( private var context: Context , 35 | private var boundingBoxOverlay: BoundingBoxOverlay, 36 | private var viewBackgroundOfMessage: View, 37 | private var textViewMessage: TextView 38 | ) : ImageAnalysis.Analyzer { 39 | 40 | companion object { 41 | private val TAG = FrameAnalyser::class.simpleName 42 | const val LIVENESS_THRESHOLD = 0.5f 43 | } 44 | 45 | enum class PROC_MODE { 46 | VERIFY, REGISTER 47 | } 48 | 49 | var mode = PROC_MODE.VERIFY 50 | var startVerifyTime: Long = 0 51 | 52 | private var isRunning = false 53 | private var isProcessing = false 54 | private var isRegistering = false 55 | private var frameInterface: FrameInferface? = null 56 | fun cancelRegister() { 57 | mode = PROC_MODE.VERIFY 58 | isRegistering = false 59 | } 60 | 61 | fun setRunning(running: Boolean) { 62 | isRunning = running 63 | 64 | viewBackgroundOfMessage.alpha = 0f 65 | textViewMessage.alpha = 0f 66 | boundingBoxOverlay.faceBoundingBoxes = null 67 | boundingBoxOverlay.invalidate() 68 | } 69 | fun addOnFrameListener(frameInterface: FrameInferface) { 70 | this.frameInterface = frameInterface 71 | } 72 | @SuppressLint("UnsafeOptInUsageError") 73 | override fun analyze(image: ImageProxy) { 74 | 75 | if(!isRunning) { 76 | boundingBoxOverlay.faceBoundingBoxes = null 77 | boundingBoxOverlay.invalidate() 78 | image.close() 79 | return 80 | } 81 | 82 | if (isProcessing) { 83 | image.close() 84 | return 85 | } 86 | else { 87 | isProcessing = true 88 | 89 | // Rotated bitmap for the FaceNet model 90 | val frameBitmap = BitmapUtils.imageToBitmap( image.image!! , image.imageInfo.rotationDegrees ) 91 | 92 | // Configure frameHeight and frameWidth for output2overlay transformation matrix. 93 | if ( !boundingBoxOverlay.areDimsInit ) { 94 | boundingBoxOverlay.frameHeight = frameBitmap.height 95 | boundingBoxOverlay.frameWidth = frameBitmap.width 96 | } 97 | 98 | var livenessScore = 0.0f; 99 | var faceResult: List? = FaceSDK.getInstance().detectFace(frameBitmap) 100 | if(!faceResult.isNullOrEmpty()) { 101 | if (faceResult!!.size == 1) { 102 | hideMessage() 103 | livenessScore = 104 | FaceSDK.getInstance().checkLiveness(frameBitmap, faceResult!!.get(0)) 105 | Log.i("liveness score : ", livenessScore.toString()) 106 | 107 | if (livenessScore > LIVENESS_THRESHOLD) { 108 | boundingBoxOverlay.livenessResult = 1 109 | } else { 110 | boundingBoxOverlay.livenessResult = 0 111 | hideMessage() 112 | } 113 | } else { 114 | boundingBoxOverlay.livenessResult = 2 115 | showMessage(context.getString(R.string.multiple_face_detected)) 116 | } 117 | } 118 | 119 | CoroutineScope( Dispatchers.Default ).launch { 120 | withContext( Dispatchers.Main ) { 121 | // Clear the BoundingBoxOverlay and set the new results ( boxes ) to be displayed. 122 | boundingBoxOverlay.faceBoundingBoxes = faceResult 123 | boundingBoxOverlay.livenessScore = livenessScore 124 | boundingBoxOverlay.invalidate() 125 | } 126 | } 127 | 128 | isProcessing = false 129 | image.close() 130 | } 131 | } 132 | 133 | private fun showMessage(msg: String) { 134 | CoroutineScope( Dispatchers.Default ).launch { 135 | withContext( Dispatchers.Main ) { 136 | textViewMessage.text = msg 137 | viewBackgroundOfMessage.alpha = 1.0f 138 | textViewMessage.alpha = 1.0f 139 | } 140 | } 141 | } 142 | 143 | private fun hideMessage() { 144 | CoroutineScope( Dispatchers.Default ).launch { 145 | withContext( Dispatchers.Main ) { 146 | viewBackgroundOfMessage.alpha = 0.0f 147 | textViewMessage.alpha = 0.0f 148 | 149 | } 150 | } 151 | } 152 | } -------------------------------------------------------------------------------- /app/src/main/java/com/miniai/liveness/FrameInferface.kt: -------------------------------------------------------------------------------- 1 | package com.miniai.liveness 2 | 3 | import android.graphics.Bitmap 4 | 5 | interface FrameInferface { 6 | fun onRegister(faceImage: Bitmap, featData: ByteArray?) 7 | fun onVerify(msg: String) 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/miniai/liveness/ImageRotator.java: -------------------------------------------------------------------------------- 1 | package com.miniai.liveness; 2 | 3 | import android.content.Context; 4 | import android.database.Cursor; 5 | import android.graphics.Bitmap; 6 | import android.graphics.BitmapFactory; 7 | import android.graphics.Matrix; 8 | import android.net.Uri; 9 | import android.provider.MediaStore; 10 | 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | 14 | public class ImageRotator { 15 | 16 | public static int getOrientation(Context context, Uri photoUri) { 17 | /* it's on the external media. */ 18 | Cursor cursor = context.getContentResolver().query(photoUri, 19 | new String[] { MediaStore.Images.ImageColumns.ORIENTATION }, null, null, null); 20 | 21 | if (cursor.getCount() != 1) { 22 | return -1; 23 | } 24 | 25 | cursor.moveToFirst(); 26 | return cursor.getInt(0); 27 | } 28 | 29 | public static Bitmap getCorrectlyOrientedImage(Context context, Uri photoUri) throws IOException { 30 | InputStream is = context.getContentResolver().openInputStream(photoUri); 31 | BitmapFactory.Options dbo = new BitmapFactory.Options(); 32 | dbo.inJustDecodeBounds = true; 33 | BitmapFactory.decodeStream(is, null, dbo); 34 | is.close(); 35 | 36 | int rotatedWidth, rotatedHeight; 37 | int orientation = getOrientation(context, photoUri); 38 | 39 | if (orientation == 90 || orientation == 270) { 40 | rotatedWidth = dbo.outHeight; 41 | rotatedHeight = dbo.outWidth; 42 | } else { 43 | rotatedWidth = dbo.outWidth; 44 | rotatedHeight = dbo.outHeight; 45 | } 46 | 47 | Bitmap srcBitmap; 48 | is = context.getContentResolver().openInputStream(photoUri); 49 | srcBitmap = BitmapFactory.decodeStream(is); 50 | is.close(); 51 | 52 | /* 53 | * if the orientation is not 0 (or -1, which means we don't know), we 54 | * have to do a rotation. 55 | */ 56 | if (orientation > 0) { 57 | Matrix matrix = new Matrix(); 58 | matrix.postRotate(orientation); 59 | 60 | srcBitmap = Bitmap.createBitmap(srcBitmap, 0, 0, srcBitmap.getWidth(), 61 | srcBitmap.getHeight(), matrix, true); 62 | } 63 | 64 | return srcBitmap; 65 | } 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/com/miniai/liveness/SplashScreenActivity.java: -------------------------------------------------------------------------------- 1 | package com.miniai.liveness; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.os.Handler; 6 | 7 | import androidx.appcompat.app.AppCompatActivity; 8 | 9 | public class SplashScreenActivity extends AppCompatActivity { 10 | private static final long SPLASH_DISPLAY_TIME = 3000; 11 | @Override 12 | protected void onCreate(Bundle savedInstanceState) { 13 | super.onCreate(savedInstanceState); 14 | setContentView(R.layout.activity_splash); 15 | new Handler().postDelayed(new Runnable() { 16 | @Override 17 | public void run() { 18 | Intent mainIntent = new Intent(SplashScreenActivity.this, UserActivity.class); 19 | startActivity(mainIntent); 20 | finish(); 21 | } 22 | }, SPLASH_DISPLAY_TIME); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/miniai/liveness/UserActivity.kt: -------------------------------------------------------------------------------- 1 | package com.miniai.liveness 2 | 3 | import android.content.DialogInterface 4 | import android.content.Intent 5 | import android.graphics.Bitmap 6 | import android.graphics.Rect 7 | import androidx.appcompat.app.AppCompatActivity 8 | import android.os.Bundle 9 | import android.text.TextUtils 10 | import android.util.Log 11 | import android.view.LayoutInflater 12 | import android.view.View 13 | import android.widget.* 14 | import androidx.appcompat.app.AlertDialog 15 | import com.fm.face.* 16 | import com.google.android.material.floatingactionbutton.FloatingActionButton 17 | 18 | class UserActivity : AppCompatActivity() { 19 | 20 | companion object { 21 | private val TAG = UserActivity::class.simpleName 22 | } 23 | 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | 27 | startActivity(Intent(this, CameraActivity::class.java)) 28 | 29 | FaceSDK.createInstance(this) 30 | val ret = FaceSDK.getInstance().init(assets) 31 | if(ret != FaceSDK.SDK_SUCCESS) { 32 | Log.i("SYSTEM Log : ", ret.toString()) 33 | } 34 | } 35 | override fun onResume() { 36 | super.onResume() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/miniai/liveness/Utils.java: -------------------------------------------------------------------------------- 1 | package com.miniai.liveness; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Matrix; 5 | import android.graphics.Rect; 6 | 7 | public class Utils { 8 | 9 | public static Rect getBestRect(int width, int height, Rect srcRect) { 10 | if (srcRect == null) { 11 | return null; 12 | } 13 | Rect rect = new Rect(srcRect); 14 | 15 | int maxOverFlow = Math.max(-rect.left, Math.max(-rect.top, Math.max(rect.right - width, rect.bottom - height))); 16 | if (maxOverFlow >= 0) { 17 | rect.inset(maxOverFlow, maxOverFlow); 18 | return rect; 19 | } 20 | 21 | int padding = rect.height() / 2; 22 | 23 | if (!(rect.left - padding > 0 && rect.right + padding < width && rect.top - padding > 0 && rect.bottom + padding < height)) { 24 | padding = Math.min(Math.min(Math.min(rect.left, width - rect.right), height - rect.bottom), rect.top); 25 | } 26 | rect.inset(-padding, -padding); 27 | return rect; 28 | } 29 | 30 | public static Bitmap crop(final Bitmap src, final int srcX, int srcY, int srcCroppedW, int srcCroppedH, int newWidth, int newHeight) { 31 | final int srcWidth = src.getWidth(); 32 | final int srcHeight = src.getHeight(); 33 | float scaleWidth = ((float) newWidth) / srcCroppedW; 34 | float scaleHeight = ((float) newHeight) / srcCroppedH; 35 | 36 | final Matrix m = new Matrix(); 37 | 38 | m.setScale(1.0f, 1.0f); 39 | m.postScale(scaleWidth, scaleHeight); 40 | final Bitmap cropped = Bitmap.createBitmap(src, srcX, srcY, srcCroppedW, srcCroppedH, m, 41 | true /* filter */); 42 | return cropped; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add_48.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_camera_48.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_48.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/splashlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniAiLive/FaceLivenessDetection-SDK-Android/a89dab192d1edd567aea0f193fc4369863fa26fc/app/src/main/res/drawable/splashlogo.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_camera.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 19 | 28 | 29 | 41 | 42 | 57 | 58 | 69 | 70 | 71 | 85 | 86 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniAiLive/FaceLivenessDetection-SDK-Android/a89dab192d1edd567aea0f193fc4369863fa26fc/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_miniai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniAiLive/FaceLivenessDetection-SDK-Android/a89dab192d1edd567aea0f193fc4369863fa26fc/app/src/main/res/mipmap-hdpi/ic_miniai.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniAiLive/FaceLivenessDetection-SDK-Android/a89dab192d1edd567aea0f193fc4369863fa26fc/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniAiLive/FaceLivenessDetection-SDK-Android/a89dab192d1edd567aea0f193fc4369863fa26fc/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniAiLive/FaceLivenessDetection-SDK-Android/a89dab192d1edd567aea0f193fc4369863fa26fc/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniAiLive/FaceLivenessDetection-SDK-Android/a89dab192d1edd567aea0f193fc4369863fa26fc/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | 11 | #FF9800 12 | #212121 13 | #00BAF2 14 | #dddddd 15 | 16 | #121212 17 | #333333 18 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 80dp 4 | 20dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MiniAI-Liveness 3 | 4 | Please input name. 5 | Duplicated name! 6 | 7 | Multiple face detected! 8 | No face detected! 9 | AppID Error! 10 | Invalid License! 11 | License Expired! 12 | No Activated! 13 | Init Error! 14 | OK 15 | Cancel 16 | Register succeed! 17 | Delete User 18 | Delete 19 | Delete All 20 | Select Picture 21 | Liveness check failed! 22 | Users 23 | Verify Succeed!: 24 | Verify Timeout! 25 | Verify Failed! 26 | Liveness Check Failed! 27 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 |