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 |
24 |
25 |
26 |
27 |
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 |
106 |
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 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/test/java/com/miniai/liveness/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.miniai.liveness;
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 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | id 'com.android.application' version '8.1.4' apply false
4 | id 'com.android.library' version '8.1.4' apply false
5 | id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
6 | }
7 |
8 | task clean(type: Delete) {
9 | delete rootProject.buildDir
10 | }
--------------------------------------------------------------------------------
/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 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiniAiLive/FaceLivenessDetection-SDK-Android/a89dab192d1edd567aea0f193fc4369863fa26fc/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Dec 05 11:31:01 HKT 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 |
16 | rootProject.name = "MiniAI-Liveness"
17 | include ':app'
18 |
--------------------------------------------------------------------------------