react-native-vision-camera-spoof-detector 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/.cxx/Debug/2a1it684/arm64-v8a/.ninja_deps +0 -0
- package/android/.cxx/Debug/2a1it684/arm64-v8a/.ninja_log +125 -125
- package/android/.cxx/Debug/2a1it684/arm64-v8a/CMakeFiles/CMakeOutput.log +22 -22
- package/android/.cxx/Debug/2a1it684/arm64-v8a/configure_fingerprint.bin +15 -15
- package/android/.cxx/Debug/2a1it684/armeabi-v7a/.ninja_deps +0 -0
- package/android/.cxx/Debug/2a1it684/armeabi-v7a/.ninja_log +176 -119
- package/android/.cxx/Debug/2a1it684/armeabi-v7a/CMakeFiles/CMakeOutput.log +20 -20
- package/android/.cxx/Debug/2a1it684/armeabi-v7a/configure_fingerprint.bin +15 -15
- package/android/.cxx/Debug/2a1it684/x86/.ninja_deps +0 -0
- package/android/.cxx/Debug/2a1it684/x86/.ninja_log +164 -111
- package/android/.cxx/Debug/2a1it684/x86/CMakeFiles/CMakeOutput.log +20 -20
- package/android/.cxx/Debug/2a1it684/x86/configure_fingerprint.bin +15 -15
- package/android/.cxx/Debug/2a1it684/x86_64/.ninja_deps +0 -0
- package/android/.cxx/Debug/2a1it684/x86_64/.ninja_log +109 -109
- package/android/.cxx/Debug/2a1it684/x86_64/CMakeFiles/CMakeOutput.log +20 -20
- package/android/.cxx/Debug/2a1it684/x86_64/configure_fingerprint.bin +15 -15
- package/android/build/.transforms/4ebf3dd0dca4f83fe1776360e21ded81/transformed/classes/classes_dex/classes.dex +0 -0
- package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/AntiSpoofingResult.dex +0 -0
- package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/ByteBufferPool$Companion.dex +0 -0
- package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/ByteBufferPool.dex +0 -0
- package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/FaceAntiSpoofFrameProcessor.dex +0 -0
- package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/FaceAntiSpoofingAdvanced$Companion.dex +0 -0
- package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/FaceAntiSpoofingAdvanced$EnvironmentAssessment.dex +0 -0
- package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/FaceAntiSpoofingAdvanced$SpoofDetectionResult.dex +0 -0
- package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/FaceAntiSpoofingAdvanced.dex +0 -0
- package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/JpegEncoder$Companion.dex +0 -0
- package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/JpegEncoder.dex +0 -0
- package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/YuvToFloatArrayConverter.dex +0 -0
- package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/arm64-v8a/build_stdout_targets.txt +45 -45
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/arm64-v8a/generate_cxx_metadata_715_timing.txt +9 -0
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/armeabi-v7a/build_stdout_targets.txt +46 -46
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/armeabi-v7a/{generate_cxx_metadata_9843_timing.txt → generate_cxx_metadata_716_timing.txt} +1 -1
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86/build_stdout_targets.txt +43 -43
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86/{generate_cxx_metadata_9843_timing.txt → generate_cxx_metadata_716_timing.txt} +1 -1
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86_64/build_stdout_targets.txt +37 -37
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86_64/generate_cxx_metadata_714_timing.txt +9 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -1
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/AntiSpoofingResult.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/ByteBufferPool$Companion.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/ByteBufferPool.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/FaceAntiSpoofFrameProcessor.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/FaceAntiSpoofingAdvanced$Companion.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/FaceAntiSpoofingAdvanced$EnvironmentAssessment.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/FaceAntiSpoofingAdvanced$SpoofDetectionResult.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/FaceAntiSpoofingAdvanced.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/JpegEncoder$Companion.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/JpegEncoder.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/YuvToFloatArrayConverter.class +0 -0
- package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab +1 -1
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
- package/android/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
- package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/AntiSpoofingResult.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/ByteBufferPool$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/ByteBufferPool.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/FaceAntiSpoofFrameProcessor.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/FaceAntiSpoofingAdvanced$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/FaceAntiSpoofingAdvanced$EnvironmentAssessment.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/FaceAntiSpoofingAdvanced$SpoofDetectionResult.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/FaceAntiSpoofingAdvanced.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/JpegEncoder$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/JpegEncoder.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/YuvToFloatArrayConverter.class +0 -0
- package/android/src/main/java/com/faceantispoof/ByteBufferPool.kt +68 -0
- package/android/src/main/java/com/faceantispoof/FaceAntiSpoofFrameProcessor.kt +2 -1
- package/android/src/main/java/com/faceantispoof/FaceAntiSpoofingAdvanced.kt +367 -50
- package/android/src/main/java/com/faceantispoof/JpegEncoder.kt +204 -0
- package/android/src/main/java/com/faceantispoof/YuvToFloatArrayConverter.kt +62 -0
- package/package.json +3 -3
- package/CHANGELOG.md +0 -48
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/arm64-v8a/generate_cxx_metadata_9843_timing.txt +0 -9
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86_64/generate_cxx_metadata_9843_timing.txt +0 -9
- /package/android/.cxx/Debug/2a1it684/arm64-v8a/.cmake/api/v1/reply/{index-2025-11-18T10-25-37-0358.json → index-2025-11-24T09-46-02-0941.json} +0 -0
- /package/android/.cxx/Debug/2a1it684/armeabi-v7a/.cmake/api/v1/reply/{index-2025-11-18T10-12-50-0571.json → index-2025-11-18T18-09-57-0510.json} +0 -0
- /package/android/.cxx/Debug/2a1it684/x86/.cmake/api/v1/reply/{index-2025-11-18T10-13-00-0335.json → index-2025-11-18T18-10-04-0841.json} +0 -0
- /package/android/.cxx/Debug/2a1it684/x86_64/.cmake/api/v1/reply/{index-2025-11-18T10-26-26-0673.json → index-2025-11-24T09-46-22-0202.json} +0 -0
|
@@ -8,17 +8,19 @@ import java.io.IOException
|
|
|
8
8
|
import java.nio.MappedByteBuffer
|
|
9
9
|
import java.nio.channels.FileChannel
|
|
10
10
|
import java.io.FileInputStream
|
|
11
|
-
import kotlin.math
|
|
11
|
+
import kotlin.math.*
|
|
12
12
|
import java.nio.ByteBuffer
|
|
13
13
|
import java.nio.ByteOrder
|
|
14
|
-
import java.util
|
|
14
|
+
import java.util.*
|
|
15
15
|
import android.util.Log
|
|
16
|
+
import kotlin.collections.ArrayList
|
|
17
|
+
import android.util.Base64
|
|
16
18
|
|
|
17
19
|
class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
|
|
18
20
|
|
|
19
21
|
companion object {
|
|
20
22
|
const val INPUT_IMAGE_SIZE = 256
|
|
21
|
-
const val
|
|
23
|
+
const val BASE_THRESHOLD = 0.2f
|
|
22
24
|
const val ROUTE_INDEX = 6
|
|
23
25
|
const val LAPLACE_THRESHOLD = 50
|
|
24
26
|
const val LAPLACIAN_THRESHOLD = 1000
|
|
@@ -29,6 +31,16 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
|
|
|
29
31
|
const val ACCELERATOR_CPU = "CPU"
|
|
30
32
|
const val ACCELERATOR_GPU = "GPU"
|
|
31
33
|
const val ACCELERATOR_NNAPI = "NNAPI"
|
|
34
|
+
|
|
35
|
+
// Advanced spoof detection constants
|
|
36
|
+
private const val HISTORY_SIZE = 10
|
|
37
|
+
private const val TEMPORAL_CONSISTENCY_THRESHOLD = 0.08f
|
|
38
|
+
private const val SCORE_VARIANCE_THRESHOLD = 0.02f
|
|
39
|
+
private const val MIN_FRAMES_FOR_ANALYSIS = 5
|
|
40
|
+
private const val DYNAMIC_THRESHOLD_MULTIPLIER = 1.3f
|
|
41
|
+
private const val TEXTURE_COMPLEXITY_THRESHOLD = 800
|
|
42
|
+
private const val COLOR_SATURATION_THRESHOLD = 0.4f
|
|
43
|
+
private const val DEPTH_VARIATION_THRESHOLD = 0.1f
|
|
32
44
|
}
|
|
33
45
|
|
|
34
46
|
private var interpreter: Interpreter? = null
|
|
@@ -48,6 +60,21 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
|
|
|
48
60
|
private var grayscaleBuffer: IntArray? = null
|
|
49
61
|
private val yuvConverter = YuvToFloatArrayConverter()
|
|
50
62
|
|
|
63
|
+
// Advanced spoof detection fields
|
|
64
|
+
private val scoreHistory = LinkedList<Float>()
|
|
65
|
+
private val laplacianHistory = LinkedList<Int>()
|
|
66
|
+
private var frameCount = 0
|
|
67
|
+
private var adaptiveThreshold = BASE_THRESHOLD
|
|
68
|
+
private var environmentAssessment = EnvironmentAssessment()
|
|
69
|
+
|
|
70
|
+
data class EnvironmentAssessment(
|
|
71
|
+
var avgBrightness: Float = 0f,
|
|
72
|
+
var brightnessVariance: Float = 0f,
|
|
73
|
+
var estimatedDistance: Float = 0f,
|
|
74
|
+
var lightingConsistency: Float = 0f,
|
|
75
|
+
var lastAssessmentTime: Long = 0
|
|
76
|
+
)
|
|
77
|
+
|
|
51
78
|
init {
|
|
52
79
|
initializeInterpreter()
|
|
53
80
|
}
|
|
@@ -58,13 +85,11 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
|
|
|
58
85
|
val options = Interpreter.Options()
|
|
59
86
|
options.setNumThreads(4)
|
|
60
87
|
|
|
61
|
-
// Try to add GPU delegate first
|
|
62
88
|
if (tryAddGpuDelegate(options)) {
|
|
63
89
|
currentAccelerator = ACCELERATOR_GPU
|
|
64
90
|
Log.i(TAG, "GPU delegate enabled successfully")
|
|
65
91
|
}
|
|
66
92
|
else {
|
|
67
|
-
// GPU not available, enable NNAPI through interpreter options
|
|
68
93
|
try {
|
|
69
94
|
options.setUseNNAPI(true)
|
|
70
95
|
currentAccelerator = ACCELERATOR_NNAPI
|
|
@@ -77,12 +102,10 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
|
|
|
77
102
|
|
|
78
103
|
interpreter = Interpreter(modelBuffer, options)
|
|
79
104
|
|
|
80
|
-
// Try to find output indices once and prepare outputs map
|
|
81
105
|
try {
|
|
82
106
|
outputIndex0 = interpreter!!.getOutputIndex("Identity")
|
|
83
107
|
outputIndex1 = interpreter!!.getOutputIndex("Identity_1")
|
|
84
108
|
} catch (e: Exception) {
|
|
85
|
-
// Keep defaults (0 and 1) if named outputs not available
|
|
86
109
|
Log.w(TAG, "Could not resolve named output indices, using defaults: ${e.message}")
|
|
87
110
|
}
|
|
88
111
|
|
|
@@ -97,13 +120,8 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
|
|
|
97
120
|
}
|
|
98
121
|
}
|
|
99
122
|
|
|
100
|
-
/**
|
|
101
|
-
* Try to enable GPU delegate for modern devices
|
|
102
|
-
* Returns true if GPU delegate was successfully added
|
|
103
|
-
*/
|
|
104
123
|
private fun tryAddGpuDelegate(options: Interpreter.Options): Boolean {
|
|
105
124
|
return try {
|
|
106
|
-
// Check if device supports GPU delegate
|
|
107
125
|
val compatibilityList = CompatibilityList()
|
|
108
126
|
if (compatibilityList.isDelegateSupportedOnThisDevice) {
|
|
109
127
|
gpuDelegate = GpuDelegate(compatibilityList.bestOptionsForThisDevice)
|
|
@@ -122,9 +140,6 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
|
|
|
122
140
|
}
|
|
123
141
|
}
|
|
124
142
|
|
|
125
|
-
/**
|
|
126
|
-
* Get the currently active accelerator type
|
|
127
|
-
*/
|
|
128
143
|
fun getAccelerator(): String = currentAccelerator
|
|
129
144
|
|
|
130
145
|
fun advancedAntiSpoofing(nv21: ByteArray, width: Int, height: Int): AntiSpoofingResult {
|
|
@@ -132,58 +147,107 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
|
|
|
132
147
|
return AntiSpoofingResult(
|
|
133
148
|
neuralNetworkScore = 0f,
|
|
134
149
|
laplacianScore = 0,
|
|
150
|
+
textureComplexity = 0,
|
|
151
|
+
temporalConsistency = 0f,
|
|
152
|
+
colorSaturation = 0f,
|
|
135
153
|
combinedScore = 0f,
|
|
136
154
|
isLive = false,
|
|
137
|
-
|
|
155
|
+
confidence = 0f,
|
|
156
|
+
label = "Not Initialized",
|
|
157
|
+
spoofType = "UNKNOWN"
|
|
138
158
|
)
|
|
139
159
|
}
|
|
140
160
|
|
|
141
|
-
//
|
|
142
|
-
if (nv21
|
|
143
|
-
|
|
144
|
-
return AntiSpoofingResult(0f, 0, 0f, false, "Error: Invalid NV21 buffer")
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (width <= 0 || height <= 0) {
|
|
148
|
-
Log.e(TAG, "Invalid frame dimensions: width=$width, height=$height")
|
|
149
|
-
return AntiSpoofingResult(0f, 0, 0f, false, "Error: Invalid dimensions")
|
|
161
|
+
// Enhanced input validation
|
|
162
|
+
if (nv21.isEmpty()) {
|
|
163
|
+
return AntiSpoofingResult(0f, 0, 0, 0f, 0f, 0f, false, 0f, "Error: Invalid NV21 buffer", "INVALID_INPUT")
|
|
150
164
|
}
|
|
151
165
|
|
|
152
166
|
val expectedSize = width * height + (width * height) / 2
|
|
153
167
|
if (nv21.size < expectedSize) {
|
|
154
|
-
|
|
155
|
-
return AntiSpoofingResult(0f, 0, 0f, false, "Error: Buffer size mismatch")
|
|
168
|
+
return AntiSpoofingResult(0f, 0, 0, 0f, 0f, 0f, false, 0f, "Error: Buffer size mismatch", "INVALID_INPUT")
|
|
156
169
|
}
|
|
157
170
|
|
|
158
171
|
return try {
|
|
172
|
+
// Multiple detection techniques
|
|
159
173
|
val nnScore = neuralNetworkAntiSpoofing(nv21, width, height)
|
|
160
174
|
val laplacianScore = laplacian(nv21, width, height)
|
|
175
|
+
val textureScore = calculateTextureComplexity(nv21, width, height)
|
|
176
|
+
val colorSat = calculateColorSaturation(nv21, width, height)
|
|
177
|
+
val temporalConsistency = calculateTemporalConsistency(nnScore)
|
|
178
|
+
|
|
179
|
+
// Update adaptive threshold based on environment
|
|
180
|
+
updateAdaptiveThreshold(nv21, width, height)
|
|
181
|
+
|
|
182
|
+
// Multi-factor spoof detection
|
|
183
|
+
val spoofDetection = detectSpoofType(
|
|
184
|
+
nnScore,
|
|
185
|
+
laplacianScore,
|
|
186
|
+
textureScore,
|
|
187
|
+
colorSat,
|
|
188
|
+
temporalConsistency
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
val confidence = calculateConfidence(nnScore, laplacianScore, textureScore, temporalConsistency)
|
|
192
|
+
val combinedScore = calculateCombinedScore(nnScore, laplacianScore, textureScore, temporalConsistency)
|
|
193
|
+
|
|
194
|
+
// Final decision with multiple safeguards
|
|
195
|
+
val isLive = isLiveFace(
|
|
196
|
+
nnScore,
|
|
197
|
+
laplacianScore,
|
|
198
|
+
textureScore,
|
|
199
|
+
colorSat,
|
|
200
|
+
temporalConsistency,
|
|
201
|
+
spoofDetection
|
|
202
|
+
)
|
|
161
203
|
|
|
162
|
-
|
|
163
|
-
|
|
204
|
+
// Generate 640×640 JPEG Base64 for Recognition API when face is live
|
|
205
|
+
var recognitionImageBase64: String? = null
|
|
206
|
+
val DEBUG_FORCE_BASE64 = false // Set to true to force Base64 generation for testing
|
|
207
|
+
|
|
208
|
+
if (isLive || DEBUG_FORCE_BASE64) {
|
|
209
|
+
Log.d(TAG, "🔍 isLive=$isLive (DEBUG_FORCE_BASE64=$DEBUG_FORCE_BASE64) - generating 640x640 JPEG Base64...")
|
|
210
|
+
try {
|
|
211
|
+
recognitionImageBase64 = generate640x640JpegBase64(nv21, width, height)
|
|
212
|
+
if (recognitionImageBase64 != null) {
|
|
213
|
+
Log.d(TAG, " Generated 640x640 JPEG Base64 for recognition API (size: ${recognitionImageBase64.length} bytes)")
|
|
214
|
+
} else {
|
|
215
|
+
Log.w(TAG, " generate640x640JpegBase64 returned null")
|
|
216
|
+
}
|
|
217
|
+
} catch (e: Exception) {
|
|
218
|
+
Log.w(TAG, " Failed to generate 640x640 JPEG Base64: ${e.message}", e)
|
|
219
|
+
// Continue without the image - it's not critical
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
Log.d(TAG, " isLive is FALSE - skipping Base64 generation")
|
|
223
|
+
}
|
|
164
224
|
|
|
165
225
|
AntiSpoofingResult(
|
|
166
226
|
neuralNetworkScore = nnScore,
|
|
167
227
|
laplacianScore = laplacianScore,
|
|
228
|
+
textureComplexity = textureScore,
|
|
229
|
+
temporalConsistency = temporalConsistency,
|
|
230
|
+
colorSaturation = colorSat,
|
|
168
231
|
combinedScore = combinedScore,
|
|
169
232
|
isLive = isLive,
|
|
170
|
-
|
|
233
|
+
confidence = confidence,
|
|
234
|
+
label = if (isLive) "Live Face" else "Spoof Face: ${spoofDetection.type}",
|
|
235
|
+
spoofType = spoofDetection.type,
|
|
236
|
+
recognitionImageBase64 = recognitionImageBase64
|
|
171
237
|
)
|
|
172
238
|
} catch (e: Exception) {
|
|
173
239
|
Log.e(TAG, "Error in advancedAntiSpoofing", e)
|
|
174
|
-
AntiSpoofingResult(0f, 0, 0f, false, "Error: ${e.message}")
|
|
240
|
+
AntiSpoofingResult(0f, 0, 0, 0f, 0f, 0f, false, 0f, "Error: ${e.message}", "PROCESSING_ERROR")
|
|
175
241
|
}
|
|
176
242
|
}
|
|
177
243
|
|
|
178
244
|
private fun neuralNetworkAntiSpoofing(nv21: ByteArray, width: Int, height: Int): Float {
|
|
179
245
|
return try {
|
|
180
|
-
// Validate NV21 buffer before processing
|
|
181
246
|
val expectedSize = width * height + (width * height) / 2
|
|
182
247
|
if (nv21.size < expectedSize) {
|
|
183
248
|
throw IllegalArgumentException("NV21 buffer size mismatch: expected $expectedSize, got ${nv21.size}")
|
|
184
249
|
}
|
|
185
250
|
|
|
186
|
-
// Convert NV21 YUV to RGB float array, resized to model input size
|
|
187
251
|
if (rgbFloatBuffer == null || rgbFloatBuffer!!.size < INPUT_IMAGE_SIZE * INPUT_IMAGE_SIZE * 3) {
|
|
188
252
|
rgbFloatBuffer = FloatArray(INPUT_IMAGE_SIZE * INPUT_IMAGE_SIZE * 3)
|
|
189
253
|
}
|
|
@@ -197,32 +261,87 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
|
|
|
197
261
|
throw Exception("RGB conversion returned empty array")
|
|
198
262
|
}
|
|
199
263
|
|
|
200
|
-
// Fill reusable inputBuffer with normalized floats (row-major RGB)
|
|
201
264
|
inputBuffer.rewind()
|
|
202
265
|
for (i in 0 until (INPUT_IMAGE_SIZE * INPUT_IMAGE_SIZE * 3)) {
|
|
203
266
|
inputBuffer.putFloat(rgbFloats[i])
|
|
204
267
|
}
|
|
205
268
|
inputBuffer.rewind()
|
|
206
269
|
|
|
207
|
-
// Validate interpreter
|
|
208
270
|
if (interpreter == null) {
|
|
209
271
|
throw Exception("Interpreter is null")
|
|
210
272
|
}
|
|
211
273
|
|
|
212
|
-
// Reuse preallocated outputs map and arrays
|
|
213
274
|
outputs[outputIndex0] = clssPred
|
|
214
275
|
outputs[outputIndex1] = leafNodeMask
|
|
215
276
|
|
|
216
|
-
// Run model with ByteBuffer input (single input)
|
|
217
277
|
interpreter!!.runForMultipleInputsOutputs(arrayOf<Any>(inputBuffer), outputs)
|
|
218
278
|
|
|
219
|
-
leafScore1(clssPred, leafNodeMask)
|
|
279
|
+
val rawScore = leafScore1(clssPred, leafNodeMask)
|
|
280
|
+
|
|
281
|
+
// Apply non-linear scaling to make high scores more sensitive
|
|
282
|
+
applyNonLinearScaling(rawScore)
|
|
220
283
|
} catch (e: Exception) {
|
|
221
284
|
Log.e(TAG, "Error in neuralNetworkAntiSpoofing: ${e.message}", e)
|
|
222
285
|
throw e
|
|
223
286
|
}
|
|
224
287
|
}
|
|
225
288
|
|
|
289
|
+
private fun applyNonLinearScaling(score: Float): Float {
|
|
290
|
+
// Exponential scaling to make scores > 0.1 much more sensitive
|
|
291
|
+
return if (score > 0.1f) {
|
|
292
|
+
score * (1 + (score - 0.1f) * 2.0f) // Amplify higher scores
|
|
293
|
+
} else {
|
|
294
|
+
score
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Generate 640×640 JPEG image as Base64 string for Recognition API.
|
|
300
|
+
* Optimized for high-speed encoding without intermediate allocations.
|
|
301
|
+
*
|
|
302
|
+
* @param nv21 NV21 encoded YUV frame
|
|
303
|
+
* @param width Original frame width
|
|
304
|
+
* @param height Original frame height
|
|
305
|
+
* @return Base64 encoded JPEG string (640×640), or null if generation fails
|
|
306
|
+
*/
|
|
307
|
+
private fun generate640x640JpegBase64(nv21: ByteArray, width: Int, height: Int): String? {
|
|
308
|
+
return try {
|
|
309
|
+
val TARGET_SIZE = 640
|
|
310
|
+
Log.d(TAG, "Starting 640x640 JPEG generation. Input: ${width}x${height}, NV21 size: ${nv21.size}")
|
|
311
|
+
|
|
312
|
+
// Convert NV21 to RGB byte array directly (640×640)
|
|
313
|
+
val rgbBytes = ByteArray(TARGET_SIZE * TARGET_SIZE * 3)
|
|
314
|
+
Log.d(TAG, "Allocated RGB buffer: ${rgbBytes.size} bytes")
|
|
315
|
+
|
|
316
|
+
val startConvert = System.currentTimeMillis()
|
|
317
|
+
val convertedRgb = yuvConverter.nv21ToRgbByteArray(
|
|
318
|
+
nv21, width, height,
|
|
319
|
+
TARGET_SIZE, TARGET_SIZE,
|
|
320
|
+
rgbBytes
|
|
321
|
+
)
|
|
322
|
+
val convertTime = System.currentTimeMillis() - startConvert
|
|
323
|
+
Log.d(TAG, "YUV to RGB conversion completed in ${convertTime}ms. RGB data: ${convertedRgb.size} bytes")
|
|
324
|
+
|
|
325
|
+
// Encode RGB to JPEG Base64 with quality 85
|
|
326
|
+
val startJpeg = System.currentTimeMillis()
|
|
327
|
+
val base64Result = JpegEncoder.rgbToJpegBase64(convertedRgb, TARGET_SIZE, TARGET_SIZE, quality = 85)
|
|
328
|
+
val jpegTime = System.currentTimeMillis() - startJpeg
|
|
329
|
+
|
|
330
|
+
if (base64Result != null) {
|
|
331
|
+
Log.d(TAG, "JPEG encoding completed in ${jpegTime}ms. Base64 size: ${base64Result.length} bytes")
|
|
332
|
+
Log.d(TAG, " Generated 640x640 JPEG Base64 for recognition API (total: ${convertTime + jpegTime}ms)")
|
|
333
|
+
} else {
|
|
334
|
+
Log.w(TAG, " JpegEncoder returned null")
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
base64Result
|
|
338
|
+
} catch (e: Exception) {
|
|
339
|
+
Log.e(TAG, " Error generating 640x640 JPEG Base64: ${e.message}", e)
|
|
340
|
+
e.printStackTrace()
|
|
341
|
+
null
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
226
345
|
private fun leafScore1(clssPred: Array<FloatArray>, leafNodeMask: Array<FloatArray>): Float {
|
|
227
346
|
var score = 0f
|
|
228
347
|
for (i in 0 until 8) {
|
|
@@ -235,19 +354,17 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
|
|
|
235
354
|
return try {
|
|
236
355
|
calculateLaplacianScore(nv21, width, height)
|
|
237
356
|
} catch (e: Exception) {
|
|
238
|
-
|
|
357
|
+
Log.e(TAG, "Error in laplacian calculation", e)
|
|
239
358
|
throw e
|
|
240
359
|
}
|
|
241
360
|
}
|
|
242
361
|
|
|
243
362
|
private fun calculateLaplacianScore(nv21: ByteArray, width: Int, height: Int): Int {
|
|
244
|
-
// Validate NV21 buffer
|
|
245
363
|
val expectedSize = width * height + (width * height) / 2
|
|
246
364
|
if (nv21.size < expectedSize) {
|
|
247
365
|
throw IllegalArgumentException("NV21 buffer size mismatch for Laplacian: expected $expectedSize, got ${nv21.size}")
|
|
248
366
|
}
|
|
249
367
|
|
|
250
|
-
// Convert to grayscale intensity array (resized to model input size)
|
|
251
368
|
if (grayscaleBuffer == null || grayscaleBuffer!!.size < INPUT_IMAGE_SIZE * INPUT_IMAGE_SIZE) {
|
|
252
369
|
grayscaleBuffer = IntArray(INPUT_IMAGE_SIZE * INPUT_IMAGE_SIZE)
|
|
253
370
|
}
|
|
@@ -263,7 +380,6 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
|
|
|
263
380
|
}
|
|
264
381
|
|
|
265
382
|
var score = 0
|
|
266
|
-
// Apply 3x3 laplacian kernel without extra allocation
|
|
267
383
|
for (y in 1 until INPUT_IMAGE_SIZE - 1) {
|
|
268
384
|
val rowOffset = y * INPUT_IMAGE_SIZE
|
|
269
385
|
for (x in 1 until INPUT_IMAGE_SIZE - 1) {
|
|
@@ -280,9 +396,199 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
|
|
|
280
396
|
return score
|
|
281
397
|
}
|
|
282
398
|
|
|
283
|
-
private fun
|
|
399
|
+
private fun calculateTextureComplexity(nv21: ByteArray, width: Int, height: Int): Int {
|
|
400
|
+
// Enhanced texture analysis using multiple filters
|
|
401
|
+
if (grayscaleBuffer == null || grayscaleBuffer!!.size < INPUT_IMAGE_SIZE * INPUT_IMAGE_SIZE) {
|
|
402
|
+
grayscaleBuffer = IntArray(INPUT_IMAGE_SIZE * INPUT_IMAGE_SIZE)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
val grayscale = yuvConverter.nv21ToGrayscaleIntArray(
|
|
406
|
+
nv21, width, height,
|
|
407
|
+
INPUT_IMAGE_SIZE, INPUT_IMAGE_SIZE,
|
|
408
|
+
grayscaleBuffer
|
|
409
|
+
) ?: return 0
|
|
410
|
+
|
|
411
|
+
var complexity = 0
|
|
412
|
+
|
|
413
|
+
// Sobel edge detection for texture analysis
|
|
414
|
+
for (y in 1 until INPUT_IMAGE_SIZE - 1) {
|
|
415
|
+
val rowOffset = y * INPUT_IMAGE_SIZE
|
|
416
|
+
for (x in 1 until INPUT_IMAGE_SIZE - 1) {
|
|
417
|
+
val gx = (-1 * grayscale[rowOffset - INPUT_IMAGE_SIZE + x - 1] +
|
|
418
|
+
1 * grayscale[rowOffset - INPUT_IMAGE_SIZE + x + 1] +
|
|
419
|
+
-2 * grayscale[rowOffset + x - 1] +
|
|
420
|
+
2 * grayscale[rowOffset + x + 1] +
|
|
421
|
+
-1 * grayscale[rowOffset + INPUT_IMAGE_SIZE + x - 1] +
|
|
422
|
+
1 * grayscale[rowOffset + INPUT_IMAGE_SIZE + x + 1])
|
|
423
|
+
|
|
424
|
+
val gy = (-1 * grayscale[rowOffset - INPUT_IMAGE_SIZE + x - 1] +
|
|
425
|
+
-2 * grayscale[rowOffset - INPUT_IMAGE_SIZE + x] +
|
|
426
|
+
-1 * grayscale[rowOffset - INPUT_IMAGE_SIZE + x + 1] +
|
|
427
|
+
1 * grayscale[rowOffset + INPUT_IMAGE_SIZE + x - 1] +
|
|
428
|
+
2 * grayscale[rowOffset + INPUT_IMAGE_SIZE + x] +
|
|
429
|
+
1 * grayscale[rowOffset + INPUT_IMAGE_SIZE + x + 1])
|
|
430
|
+
|
|
431
|
+
val gradient = sqrt((gx * gx + gy * gy).toDouble()).toInt()
|
|
432
|
+
if (gradient > 50) complexity++
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return complexity
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
private fun calculateColorSaturation(nv21: ByteArray, width: Int, height: Int): Float {
|
|
440
|
+
// Calculate average color saturation to detect printed attacks
|
|
441
|
+
val uvStart = width * height
|
|
442
|
+
var totalSaturation = 0f
|
|
443
|
+
var sampleCount = 0
|
|
444
|
+
|
|
445
|
+
for (i in uvStart until nv21.size step 2) {
|
|
446
|
+
val u = (nv21[i].toInt() and 0xFF) - 128
|
|
447
|
+
val v = (nv21[i + 1].toInt() and 0xFF) - 128
|
|
448
|
+
val saturation = sqrt((u * u + v * v).toDouble()).toFloat()
|
|
449
|
+
totalSaturation += saturation
|
|
450
|
+
sampleCount++
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return if (sampleCount > 0) totalSaturation / sampleCount / 128.0f else 0f
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
private fun calculateTemporalConsistency(currentScore: Float): Float {
|
|
457
|
+
// Analyze score consistency over time
|
|
458
|
+
scoreHistory.addFirst(currentScore)
|
|
459
|
+
if (scoreHistory.size > HISTORY_SIZE) {
|
|
460
|
+
scoreHistory.removeLast()
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (scoreHistory.size < MIN_FRAMES_FOR_ANALYSIS) {
|
|
464
|
+
return 1.0f // Maximum consistency initially
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Calculate variance of recent scores
|
|
468
|
+
val mean = scoreHistory.average().toFloat()
|
|
469
|
+
val variance = scoreHistory.map { (it - mean) * (it - mean) }.average().toFloat()
|
|
470
|
+
|
|
471
|
+
// Convert variance to consistency score (lower variance = higher consistency)
|
|
472
|
+
return max(0f, 1.0f - (variance / SCORE_VARIANCE_THRESHOLD))
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
private fun updateAdaptiveThreshold(nv21: ByteArray, width: Int, height: Int) {
|
|
476
|
+
// Adjust threshold based on environmental factors
|
|
477
|
+
val brightness = calculateBrightness(nv21, width, height)
|
|
478
|
+
val currentTime = System.currentTimeMillis()
|
|
479
|
+
|
|
480
|
+
if (currentTime - environmentAssessment.lastAssessmentTime > 1000) { // Assess every second
|
|
481
|
+
environmentAssessment.avgBrightness = brightness
|
|
482
|
+
environmentAssessment.lastAssessmentTime = currentTime
|
|
483
|
+
|
|
484
|
+
// Make threshold more strict in good lighting conditions
|
|
485
|
+
adaptiveThreshold = if (brightness > 0.6f) {
|
|
486
|
+
BASE_THRESHOLD * 0.8f // Stricter in good light
|
|
487
|
+
} else if (brightness < 0.3f) {
|
|
488
|
+
BASE_THRESHOLD * 1.2f // Slightly more lenient in poor light
|
|
489
|
+
} else {
|
|
490
|
+
BASE_THRESHOLD
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
private fun calculateBrightness(nv21: ByteArray, width: Int, height: Int): Float {
|
|
496
|
+
// Calculate average brightness from Y channel
|
|
497
|
+
var totalLuma = 0f
|
|
498
|
+
for (i in 0 until width * height) {
|
|
499
|
+
totalLuma += (nv21[i].toInt() and 0xFF) / 255.0f
|
|
500
|
+
}
|
|
501
|
+
return totalLuma / (width * height)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
data class SpoofDetectionResult(
|
|
505
|
+
val type: String,
|
|
506
|
+
val confidence: Float
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
private fun detectSpoofType(
|
|
510
|
+
nnScore: Float,
|
|
511
|
+
laplacianScore: Int,
|
|
512
|
+
textureScore: Int,
|
|
513
|
+
colorSat: Float,
|
|
514
|
+
temporalConsistency: Float
|
|
515
|
+
): SpoofDetectionResult {
|
|
516
|
+
|
|
517
|
+
// Detect specific spoof types based on feature patterns
|
|
518
|
+
return when {
|
|
519
|
+
// Printed photo detection (low texture, low laplacian)
|
|
520
|
+
textureScore < TEXTURE_COMPLEXITY_THRESHOLD * 0.6 &&
|
|
521
|
+
laplacianScore < LAPLACIAN_THRESHOLD * 0.7 ->
|
|
522
|
+
SpoofDetectionResult("PRINTED_PHOTO", 0.8f)
|
|
523
|
+
|
|
524
|
+
// Screen replay detection (high color saturation, medium texture)
|
|
525
|
+
colorSat > COLOR_SATURATION_THRESHOLD * 1.2 &&
|
|
526
|
+
textureScore > TEXTURE_COMPLEXITY_THRESHOLD * 0.8 ->
|
|
527
|
+
SpoofDetectionResult("SCREEN_REPLAY", 0.7f)
|
|
528
|
+
|
|
529
|
+
// 3D mask detection (medium scores across all features)
|
|
530
|
+
nnScore > BASE_THRESHOLD * 1.5 &&
|
|
531
|
+
temporalConsistency < 0.7f ->
|
|
532
|
+
SpoofDetectionResult("3D_MASK", 0.6f)
|
|
533
|
+
|
|
534
|
+
// General spoof
|
|
535
|
+
nnScore > BASE_THRESHOLD ->
|
|
536
|
+
SpoofDetectionResult("GENERAL_SPOOF", 0.9f)
|
|
537
|
+
|
|
538
|
+
else -> SpoofDetectionResult("UNKNOWN", 0.5f)
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
private fun calculateConfidence(
|
|
543
|
+
nnScore: Float,
|
|
544
|
+
laplacianScore: Int,
|
|
545
|
+
textureScore: Int,
|
|
546
|
+
temporalConsistency: Float
|
|
547
|
+
): Float {
|
|
548
|
+
val nnConfidence = 1.0f - (nnScore / BASE_THRESHOLD).coerceIn(0f, 1f)
|
|
549
|
+
val textureConfidence = (textureScore.toFloat() / TEXTURE_COMPLEXITY_THRESHOLD).coerceIn(0f, 1f)
|
|
550
|
+
val laplacianConfidence = (laplacianScore.toFloat() / LAPLACIAN_THRESHOLD).coerceIn(0f, 1f)
|
|
551
|
+
|
|
552
|
+
return (nnConfidence * 0.4f + textureConfidence * 0.3f +
|
|
553
|
+
laplacianConfidence * 0.2f + temporalConsistency * 0.1f)
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
private fun calculateCombinedScore(
|
|
557
|
+
neuralScore: Float,
|
|
558
|
+
laplacianScore: Int,
|
|
559
|
+
textureScore: Int,
|
|
560
|
+
temporalConsistency: Float
|
|
561
|
+
): Float {
|
|
284
562
|
val normalizedLaplacian = laplacianScore.toFloat() / LAPLACIAN_THRESHOLD
|
|
285
|
-
|
|
563
|
+
val normalizedTexture = textureScore.toFloat() / TEXTURE_COMPLEXITY_THRESHOLD
|
|
564
|
+
|
|
565
|
+
return (1 - neuralScore) * 0.5f +
|
|
566
|
+
normalizedLaplacian * 0.25f +
|
|
567
|
+
normalizedTexture * 0.15f +
|
|
568
|
+
temporalConsistency * 0.1f
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
private fun isLiveFace(
|
|
572
|
+
nnScore: Float,
|
|
573
|
+
laplacianScore: Int,
|
|
574
|
+
textureScore: Int,
|
|
575
|
+
colorSat: Float,
|
|
576
|
+
temporalConsistency: Float,
|
|
577
|
+
spoofDetection: SpoofDetectionResult
|
|
578
|
+
): Boolean {
|
|
579
|
+
|
|
580
|
+
// Multi-layered spoof detection
|
|
581
|
+
val layer1 = nnScore < adaptiveThreshold
|
|
582
|
+
val layer2 = laplacianScore > LAPLACIAN_THRESHOLD
|
|
583
|
+
val layer3 = textureScore > TEXTURE_COMPLEXITY_THRESHOLD
|
|
584
|
+
val layer4 = temporalConsistency > 0.7f
|
|
585
|
+
val layer5 = colorSat < COLOR_SATURATION_THRESHOLD * 1.5f
|
|
586
|
+
|
|
587
|
+
// Require majority of layers to pass, with neural network score being most important
|
|
588
|
+
val passedLayers = listOf(layer1, layer2, layer3, layer4, layer5).count { it }
|
|
589
|
+
|
|
590
|
+
// Stricter requirement: must pass at least 4 out of 5 layers, including neural network
|
|
591
|
+
return passedLayers >= 4 && layer1
|
|
286
592
|
}
|
|
287
593
|
|
|
288
594
|
@Throws(IOException::class)
|
|
@@ -297,23 +603,28 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
|
|
|
297
603
|
fileDescriptor.declaredLength
|
|
298
604
|
)
|
|
299
605
|
} catch (e: Exception) {
|
|
300
|
-
|
|
606
|
+
Log.e(TAG, "Error loading model file", e)
|
|
301
607
|
throw e
|
|
302
608
|
}
|
|
303
609
|
}
|
|
304
610
|
|
|
305
611
|
fun isInitialized(): Boolean = isInitialized
|
|
306
612
|
|
|
613
|
+
fun resetHistory() {
|
|
614
|
+
scoreHistory.clear()
|
|
615
|
+
laplacianHistory.clear()
|
|
616
|
+
frameCount = 0
|
|
617
|
+
adaptiveThreshold = BASE_THRESHOLD
|
|
618
|
+
}
|
|
619
|
+
|
|
307
620
|
fun close() {
|
|
308
621
|
try {
|
|
309
622
|
interpreter?.close()
|
|
310
623
|
interpreter = null
|
|
311
|
-
|
|
312
|
-
// Clean up GPU delegate
|
|
313
624
|
gpuDelegate?.close()
|
|
314
625
|
gpuDelegate = null
|
|
315
|
-
|
|
316
626
|
currentAccelerator = ACCELERATOR_CPU
|
|
627
|
+
resetHistory()
|
|
317
628
|
Log.i(TAG, "Interpreter and delegates closed successfully")
|
|
318
629
|
} catch (e: Exception) {
|
|
319
630
|
Log.e(TAG, "Error closing interpreter", e)
|
|
@@ -324,7 +635,13 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
|
|
|
324
635
|
data class AntiSpoofingResult(
|
|
325
636
|
val neuralNetworkScore: Float,
|
|
326
637
|
val laplacianScore: Int,
|
|
638
|
+
val textureComplexity: Int,
|
|
639
|
+
val temporalConsistency: Float,
|
|
640
|
+
val colorSaturation: Float,
|
|
327
641
|
val combinedScore: Float,
|
|
328
642
|
val isLive: Boolean,
|
|
329
|
-
val
|
|
330
|
-
|
|
643
|
+
val confidence: Float,
|
|
644
|
+
val label: String,
|
|
645
|
+
val spoofType: String,
|
|
646
|
+
val recognitionImageBase64: String? = null
|
|
647
|
+
)
|