react-native-vision-camera-spoof-detector 1.0.1 → 1.0.2

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.
Files changed (110) hide show
  1. package/android/.cxx/Debug/2a1it684/arm64-v8a/.cmake/api/v1/reply/{codemodel-v2-6af431df2d09cde56941.json → codemodel-v2-6c7de80461af72f456a3.json} +2 -2
  2. package/android/.cxx/Debug/2a1it684/arm64-v8a/.cmake/api/v1/reply/{index-2025-11-18T10-25-37-0358.json → index-2025-11-18T17-52-03-0810.json} +2 -2
  3. package/android/.cxx/Debug/2a1it684/arm64-v8a/.cmake/api/v1/reply/{target-yuv-Debug-d9c8b9c64fd8df9f1bcd.json → target-yuv-Debug-32bc4eddd343848e1c25.json} +3 -3
  4. package/android/.cxx/Debug/2a1it684/arm64-v8a/.cmake/api/v1/reply/{target-yuv_shared-Debug-ffb5efa9f8837691d492.json → target-yuv_shared-Debug-75ea769bf76a12914d29.json} +3 -3
  5. package/android/.cxx/Debug/2a1it684/arm64-v8a/.ninja_deps +0 -0
  6. package/android/.cxx/Debug/2a1it684/arm64-v8a/.ninja_log +63 -125
  7. package/android/.cxx/Debug/2a1it684/arm64-v8a/CMakeFiles/CMakeOutput.log +22 -22
  8. package/android/.cxx/Debug/2a1it684/arm64-v8a/configure_fingerprint.bin +15 -15
  9. package/android/.cxx/Debug/2a1it684/armeabi-v7a/.cmake/api/v1/reply/{codemodel-v2-053850119b681f93cecc.json → codemodel-v2-52ac1d8b40b69596389b.json} +2 -2
  10. package/android/.cxx/Debug/2a1it684/armeabi-v7a/.cmake/api/v1/reply/{index-2025-11-18T10-12-50-0571.json → index-2025-11-18T16-28-36-0058.json} +2 -2
  11. package/android/.cxx/Debug/2a1it684/armeabi-v7a/.cmake/api/v1/reply/{target-yuv-Debug-a12ad49ff79080871bce.json → target-yuv-Debug-97f9e822dbd84a186d44.json} +2 -2
  12. package/android/.cxx/Debug/2a1it684/armeabi-v7a/.cmake/api/v1/reply/{target-yuv_shared-Debug-2b00e64621a56b8dd632.json → target-yuv_shared-Debug-3730d20e27c3481c06b2.json} +2 -2
  13. package/android/.cxx/Debug/2a1it684/armeabi-v7a/.ninja_deps +0 -0
  14. package/android/.cxx/Debug/2a1it684/armeabi-v7a/.ninja_log +117 -119
  15. package/android/.cxx/Debug/2a1it684/armeabi-v7a/CMakeFiles/CMakeOutput.log +20 -20
  16. package/android/.cxx/Debug/2a1it684/armeabi-v7a/configure_fingerprint.bin +15 -15
  17. package/android/.cxx/Debug/2a1it684/x86/.ninja_deps +0 -0
  18. package/android/.cxx/Debug/2a1it684/x86/.ninja_log +109 -111
  19. package/android/.cxx/Debug/2a1it684/x86/CMakeFiles/CMakeOutput.log +20 -20
  20. package/android/.cxx/Debug/2a1it684/x86/configure_fingerprint.bin +15 -15
  21. package/android/.cxx/Debug/2a1it684/x86_64/.ninja_deps +0 -0
  22. package/android/.cxx/Debug/2a1it684/x86_64/.ninja_log +55 -109
  23. package/android/.cxx/Debug/2a1it684/x86_64/CMakeFiles/CMakeOutput.log +20 -20
  24. package/android/.cxx/Debug/2a1it684/x86_64/configure_fingerprint.bin +15 -15
  25. package/android/build/.transforms/4ebf3dd0dca4f83fe1776360e21ded81/transformed/classes/classes_dex/classes.dex +0 -0
  26. package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/AntiSpoofingResult.dex +0 -0
  27. package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/FaceAntiSpoofingAdvanced$Companion.dex +0 -0
  28. package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/FaceAntiSpoofingAdvanced$EnvironmentAssessment.dex +0 -0
  29. package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/FaceAntiSpoofingAdvanced$SpoofDetectionResult.dex +0 -0
  30. package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/FaceAntiSpoofingAdvanced.dex +0 -0
  31. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  32. package/android/build/intermediates/cxx/Debug/2a1it684/logs/arm64-v8a/build_stdout_targets.txt +1 -62
  33. package/android/build/intermediates/cxx/Debug/2a1it684/logs/arm64-v8a/generate_cxx_metadata_2398_timing.txt +9 -0
  34. package/android/build/intermediates/cxx/Debug/2a1it684/logs/arm64-v8a/generate_cxx_metadata_2614_timing.txt +9 -0
  35. package/android/build/intermediates/cxx/Debug/2a1it684/logs/armeabi-v7a/build_stdout_targets.txt +1 -58
  36. package/android/build/intermediates/cxx/Debug/2a1it684/logs/{x86/generate_cxx_metadata_9843_timing.txt → armeabi-v7a/generate_cxx_metadata_2399_timing.txt} +1 -1
  37. package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86/build_stdout_targets.txt +1 -54
  38. package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86/generate_cxx_metadata_2399_timing.txt +3 -0
  39. package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86/generate_cxx_metadata_2616_timing.txt +3 -0
  40. package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86_64/build_stdout_targets.txt +1 -54
  41. package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86_64/generate_cxx_metadata_2399_timing.txt +9 -0
  42. package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86_64/generate_cxx_metadata_2616_timing.txt +9 -0
  43. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -1
  44. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/AntiSpoofingResult.class +0 -0
  45. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/FaceAntiSpoofingAdvanced$Companion.class +0 -0
  46. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/FaceAntiSpoofingAdvanced$EnvironmentAssessment.class +0 -0
  47. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/FaceAntiSpoofingAdvanced$SpoofDetectionResult.class +0 -0
  48. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/FaceAntiSpoofingAdvanced.class +0 -0
  49. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  50. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
  51. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
  52. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +0 -0
  53. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
  54. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +0 -0
  55. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len +0 -0
  56. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +0 -0
  57. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
  58. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
  59. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
  60. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +0 -0
  61. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +0 -0
  62. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +0 -0
  63. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
  64. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab +0 -0
  65. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.values.at +0 -0
  66. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
  67. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
  68. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
  69. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +0 -0
  70. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
  71. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
  72. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
  73. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
  74. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +0 -0
  75. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +0 -0
  76. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
  77. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
  78. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
  79. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
  80. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab +1 -1
  81. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +0 -0
  82. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +0 -0
  83. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +0 -0
  84. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +0 -0
  85. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +0 -0
  86. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len +0 -0
  87. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +0 -0
  88. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i +0 -0
  89. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
  90. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
  91. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
  92. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
  93. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
  94. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
  95. package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
  96. package/android/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
  97. package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
  98. package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/AntiSpoofingResult.class +0 -0
  99. package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/FaceAntiSpoofingAdvanced$Companion.class +0 -0
  100. package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/FaceAntiSpoofingAdvanced$EnvironmentAssessment.class +0 -0
  101. package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/FaceAntiSpoofingAdvanced$SpoofDetectionResult.class +0 -0
  102. package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/FaceAntiSpoofingAdvanced.class +0 -0
  103. package/android/src/main/java/com/faceantispoof/FaceAntiSpoofingAdvanced.kt +298 -52
  104. package/package.json +1 -1
  105. package/CHANGELOG.md +0 -48
  106. package/android/build/intermediates/cxx/Debug/2a1it684/logs/arm64-v8a/generate_cxx_metadata_9843_timing.txt +0 -9
  107. package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86_64/generate_cxx_metadata_9843_timing.txt +0 -9
  108. /package/android/.cxx/Debug/2a1it684/x86/.cmake/api/v1/reply/{index-2025-11-18T10-13-00-0335.json → index-2025-11-18T16-28-43-0678.json} +0 -0
  109. /package/android/.cxx/Debug/2a1it684/x86_64/.cmake/api/v1/reply/{index-2025-11-18T10-26-26-0673.json → index-2025-11-18T17-52-04-0655.json} +0 -0
  110. /package/android/build/intermediates/cxx/Debug/2a1it684/logs/armeabi-v7a/{generate_cxx_metadata_9843_timing.txt → generate_cxx_metadata_2616_timing.txt} +0 -0
@@ -8,20 +8,21 @@ 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.abs
11
+ import kotlin.math.*
12
12
  import java.nio.ByteBuffer
13
13
  import java.nio.ByteOrder
14
- import java.util.HashMap
14
+ import java.util.*
15
15
  import android.util.Log
16
+ import kotlin.collections.ArrayList
16
17
 
17
18
  class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
18
19
 
19
20
  companion object {
20
21
  const val INPUT_IMAGE_SIZE = 256
21
- const val THRESHOLD = 0.2f
22
+ const val BASE_THRESHOLD = 0.1f
22
23
  const val ROUTE_INDEX = 6
23
24
  const val LAPLACE_THRESHOLD = 50
24
- const val LAPLACIAN_THRESHOLD = 1000
25
+ const val LAPLACIAN_THRESHOLD = 10000
25
26
  const val MODEL_FILE = "FaceAntiSpoofing.tflite"
26
27
  private const val TAG = "FaceAntiSpoofing"
27
28
 
@@ -29,6 +30,16 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
29
30
  const val ACCELERATOR_CPU = "CPU"
30
31
  const val ACCELERATOR_GPU = "GPU"
31
32
  const val ACCELERATOR_NNAPI = "NNAPI"
33
+
34
+ // Advanced spoof detection constants
35
+ private const val HISTORY_SIZE = 10
36
+ private const val TEMPORAL_CONSISTENCY_THRESHOLD = 0.08f
37
+ private const val SCORE_VARIANCE_THRESHOLD = 0.02f
38
+ private const val MIN_FRAMES_FOR_ANALYSIS = 5
39
+ private const val DYNAMIC_THRESHOLD_MULTIPLIER = 1.3f
40
+ private const val TEXTURE_COMPLEXITY_THRESHOLD = 800
41
+ private const val COLOR_SATURATION_THRESHOLD = 0.4f
42
+ private const val DEPTH_VARIATION_THRESHOLD = 0.1f
32
43
  }
33
44
 
34
45
  private var interpreter: Interpreter? = null
@@ -48,6 +59,21 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
48
59
  private var grayscaleBuffer: IntArray? = null
49
60
  private val yuvConverter = YuvToFloatArrayConverter()
50
61
 
62
+ // Advanced spoof detection fields
63
+ private val scoreHistory = LinkedList<Float>()
64
+ private val laplacianHistory = LinkedList<Int>()
65
+ private var frameCount = 0
66
+ private var adaptiveThreshold = BASE_THRESHOLD
67
+ private var environmentAssessment = EnvironmentAssessment()
68
+
69
+ data class EnvironmentAssessment(
70
+ var avgBrightness: Float = 0f,
71
+ var brightnessVariance: Float = 0f,
72
+ var estimatedDistance: Float = 0f,
73
+ var lightingConsistency: Float = 0f,
74
+ var lastAssessmentTime: Long = 0
75
+ )
76
+
51
77
  init {
52
78
  initializeInterpreter()
53
79
  }
@@ -58,13 +84,11 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
58
84
  val options = Interpreter.Options()
59
85
  options.setNumThreads(4)
60
86
 
61
- // Try to add GPU delegate first
62
87
  if (tryAddGpuDelegate(options)) {
63
88
  currentAccelerator = ACCELERATOR_GPU
64
89
  Log.i(TAG, "GPU delegate enabled successfully")
65
90
  }
66
91
  else {
67
- // GPU not available, enable NNAPI through interpreter options
68
92
  try {
69
93
  options.setUseNNAPI(true)
70
94
  currentAccelerator = ACCELERATOR_NNAPI
@@ -77,12 +101,10 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
77
101
 
78
102
  interpreter = Interpreter(modelBuffer, options)
79
103
 
80
- // Try to find output indices once and prepare outputs map
81
104
  try {
82
105
  outputIndex0 = interpreter!!.getOutputIndex("Identity")
83
106
  outputIndex1 = interpreter!!.getOutputIndex("Identity_1")
84
107
  } catch (e: Exception) {
85
- // Keep defaults (0 and 1) if named outputs not available
86
108
  Log.w(TAG, "Could not resolve named output indices, using defaults: ${e.message}")
87
109
  }
88
110
 
@@ -97,13 +119,8 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
97
119
  }
98
120
  }
99
121
 
100
- /**
101
- * Try to enable GPU delegate for modern devices
102
- * Returns true if GPU delegate was successfully added
103
- */
104
122
  private fun tryAddGpuDelegate(options: Interpreter.Options): Boolean {
105
123
  return try {
106
- // Check if device supports GPU delegate
107
124
  val compatibilityList = CompatibilityList()
108
125
  if (compatibilityList.isDelegateSupportedOnThisDevice) {
109
126
  gpuDelegate = GpuDelegate(compatibilityList.bestOptionsForThisDevice)
@@ -122,9 +139,6 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
122
139
  }
123
140
  }
124
141
 
125
- /**
126
- * Get the currently active accelerator type
127
- */
128
142
  fun getAccelerator(): String = currentAccelerator
129
143
 
130
144
  fun advancedAntiSpoofing(nv21: ByteArray, width: Int, height: Int): AntiSpoofingResult {
@@ -132,58 +146,85 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
132
146
  return AntiSpoofingResult(
133
147
  neuralNetworkScore = 0f,
134
148
  laplacianScore = 0,
149
+ textureComplexity = 0,
150
+ temporalConsistency = 0f,
151
+ colorSaturation = 0f,
135
152
  combinedScore = 0f,
136
153
  isLive = false,
137
- label = "Not Initialized"
154
+ confidence = 0f,
155
+ label = "Not Initialized",
156
+ spoofType = "UNKNOWN"
138
157
  )
139
158
  }
140
159
 
141
- // Validate input parameters
142
- if (nv21 == null || nv21.isEmpty()) {
143
- Log.e(TAG, "Invalid NV21 buffer: null or empty")
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")
160
+ // Enhanced input validation
161
+ if (nv21.isEmpty()) {
162
+ return AntiSpoofingResult(0f, 0, 0, 0f, 0f, 0f, false, 0f, "Error: Invalid NV21 buffer", "INVALID_INPUT")
150
163
  }
151
164
 
152
165
  val expectedSize = width * height + (width * height) / 2
153
166
  if (nv21.size < expectedSize) {
154
- Log.e(TAG, "NV21 buffer too small: expected >= $expectedSize, got ${nv21.size}")
155
- return AntiSpoofingResult(0f, 0, 0f, false, "Error: Buffer size mismatch")
167
+ return AntiSpoofingResult(0f, 0, 0, 0f, 0f, 0f, false, 0f, "Error: Buffer size mismatch", "INVALID_INPUT")
156
168
  }
157
169
 
158
170
  return try {
171
+ // Multiple detection techniques
159
172
  val nnScore = neuralNetworkAntiSpoofing(nv21, width, height)
160
173
  val laplacianScore = laplacian(nv21, width, height)
161
-
162
- val isLive = nnScore < THRESHOLD && laplacianScore > LAPLACIAN_THRESHOLD
163
- val combinedScore = calculateCombinedScore(nnScore, laplacianScore)
174
+ val textureScore = calculateTextureComplexity(nv21, width, height)
175
+ val colorSat = calculateColorSaturation(nv21, width, height)
176
+ val temporalConsistency = calculateTemporalConsistency(nnScore)
177
+
178
+ // Update adaptive threshold based on environment
179
+ updateAdaptiveThreshold(nv21, width, height)
180
+
181
+ // Multi-factor spoof detection
182
+ val spoofDetection = detectSpoofType(
183
+ nnScore,
184
+ laplacianScore,
185
+ textureScore,
186
+ colorSat,
187
+ temporalConsistency
188
+ )
189
+
190
+ val confidence = calculateConfidence(nnScore, laplacianScore, textureScore, temporalConsistency)
191
+ val combinedScore = calculateCombinedScore(nnScore, laplacianScore, textureScore, temporalConsistency)
192
+
193
+ // Final decision with multiple safeguards
194
+ val isLive = isLiveFace(
195
+ nnScore,
196
+ laplacianScore,
197
+ textureScore,
198
+ colorSat,
199
+ temporalConsistency,
200
+ spoofDetection
201
+ )
164
202
 
165
203
  AntiSpoofingResult(
166
204
  neuralNetworkScore = nnScore,
167
205
  laplacianScore = laplacianScore,
206
+ textureComplexity = textureScore,
207
+ temporalConsistency = temporalConsistency,
208
+ colorSaturation = colorSat,
168
209
  combinedScore = combinedScore,
169
210
  isLive = isLive,
170
- label = if (isLive) "Live Face" else "Spoof Face"
211
+ confidence = confidence,
212
+ label = if (isLive) "Live Face" else "Spoof Face: ${spoofDetection.type}",
213
+ spoofType = spoofDetection.type
171
214
  )
172
215
  } catch (e: Exception) {
173
216
  Log.e(TAG, "Error in advancedAntiSpoofing", e)
174
- AntiSpoofingResult(0f, 0, 0f, false, "Error: ${e.message}")
217
+ AntiSpoofingResult(0f, 0, 0, 0f, 0f, 0f, false, 0f, "Error: ${e.message}", "PROCESSING_ERROR")
175
218
  }
176
219
  }
177
220
 
178
221
  private fun neuralNetworkAntiSpoofing(nv21: ByteArray, width: Int, height: Int): Float {
179
222
  return try {
180
- // Validate NV21 buffer before processing
181
223
  val expectedSize = width * height + (width * height) / 2
182
224
  if (nv21.size < expectedSize) {
183
225
  throw IllegalArgumentException("NV21 buffer size mismatch: expected $expectedSize, got ${nv21.size}")
184
226
  }
185
227
 
186
- // Convert NV21 YUV to RGB float array, resized to model input size
187
228
  if (rgbFloatBuffer == null || rgbFloatBuffer!!.size < INPUT_IMAGE_SIZE * INPUT_IMAGE_SIZE * 3) {
188
229
  rgbFloatBuffer = FloatArray(INPUT_IMAGE_SIZE * INPUT_IMAGE_SIZE * 3)
189
230
  }
@@ -197,32 +238,40 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
197
238
  throw Exception("RGB conversion returned empty array")
198
239
  }
199
240
 
200
- // Fill reusable inputBuffer with normalized floats (row-major RGB)
201
241
  inputBuffer.rewind()
202
242
  for (i in 0 until (INPUT_IMAGE_SIZE * INPUT_IMAGE_SIZE * 3)) {
203
243
  inputBuffer.putFloat(rgbFloats[i])
204
244
  }
205
245
  inputBuffer.rewind()
206
246
 
207
- // Validate interpreter
208
247
  if (interpreter == null) {
209
248
  throw Exception("Interpreter is null")
210
249
  }
211
250
 
212
- // Reuse preallocated outputs map and arrays
213
251
  outputs[outputIndex0] = clssPred
214
252
  outputs[outputIndex1] = leafNodeMask
215
253
 
216
- // Run model with ByteBuffer input (single input)
217
254
  interpreter!!.runForMultipleInputsOutputs(arrayOf<Any>(inputBuffer), outputs)
218
255
 
219
- leafScore1(clssPred, leafNodeMask)
256
+ val rawScore = leafScore1(clssPred, leafNodeMask)
257
+
258
+ // Apply non-linear scaling to make high scores more sensitive
259
+ applyNonLinearScaling(rawScore)
220
260
  } catch (e: Exception) {
221
261
  Log.e(TAG, "Error in neuralNetworkAntiSpoofing: ${e.message}", e)
222
262
  throw e
223
263
  }
224
264
  }
225
265
 
266
+ private fun applyNonLinearScaling(score: Float): Float {
267
+ // Exponential scaling to make scores > 0.1 much more sensitive
268
+ return if (score > 0.1f) {
269
+ score * (1 + (score - 0.1f) * 2.0f) // Amplify higher scores
270
+ } else {
271
+ score
272
+ }
273
+ }
274
+
226
275
  private fun leafScore1(clssPred: Array<FloatArray>, leafNodeMask: Array<FloatArray>): Float {
227
276
  var score = 0f
228
277
  for (i in 0 until 8) {
@@ -235,19 +284,17 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
235
284
  return try {
236
285
  calculateLaplacianScore(nv21, width, height)
237
286
  } catch (e: Exception) {
238
- android.util.Log.e(TAG, "Error in laplacian calculation", e)
287
+ Log.e(TAG, "Error in laplacian calculation", e)
239
288
  throw e
240
289
  }
241
290
  }
242
291
 
243
292
  private fun calculateLaplacianScore(nv21: ByteArray, width: Int, height: Int): Int {
244
- // Validate NV21 buffer
245
293
  val expectedSize = width * height + (width * height) / 2
246
294
  if (nv21.size < expectedSize) {
247
295
  throw IllegalArgumentException("NV21 buffer size mismatch for Laplacian: expected $expectedSize, got ${nv21.size}")
248
296
  }
249
297
 
250
- // Convert to grayscale intensity array (resized to model input size)
251
298
  if (grayscaleBuffer == null || grayscaleBuffer!!.size < INPUT_IMAGE_SIZE * INPUT_IMAGE_SIZE) {
252
299
  grayscaleBuffer = IntArray(INPUT_IMAGE_SIZE * INPUT_IMAGE_SIZE)
253
300
  }
@@ -263,7 +310,6 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
263
310
  }
264
311
 
265
312
  var score = 0
266
- // Apply 3x3 laplacian kernel without extra allocation
267
313
  for (y in 1 until INPUT_IMAGE_SIZE - 1) {
268
314
  val rowOffset = y * INPUT_IMAGE_SIZE
269
315
  for (x in 1 until INPUT_IMAGE_SIZE - 1) {
@@ -280,9 +326,199 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
280
326
  return score
281
327
  }
282
328
 
283
- private fun calculateCombinedScore(neuralScore: Float, laplacianScore: Int): Float {
329
+ private fun calculateTextureComplexity(nv21: ByteArray, width: Int, height: Int): Int {
330
+ // Enhanced texture analysis using multiple filters
331
+ if (grayscaleBuffer == null || grayscaleBuffer!!.size < INPUT_IMAGE_SIZE * INPUT_IMAGE_SIZE) {
332
+ grayscaleBuffer = IntArray(INPUT_IMAGE_SIZE * INPUT_IMAGE_SIZE)
333
+ }
334
+
335
+ val grayscale = yuvConverter.nv21ToGrayscaleIntArray(
336
+ nv21, width, height,
337
+ INPUT_IMAGE_SIZE, INPUT_IMAGE_SIZE,
338
+ grayscaleBuffer
339
+ ) ?: return 0
340
+
341
+ var complexity = 0
342
+
343
+ // Sobel edge detection for texture analysis
344
+ for (y in 1 until INPUT_IMAGE_SIZE - 1) {
345
+ val rowOffset = y * INPUT_IMAGE_SIZE
346
+ for (x in 1 until INPUT_IMAGE_SIZE - 1) {
347
+ val gx = (-1 * grayscale[rowOffset - INPUT_IMAGE_SIZE + x - 1] +
348
+ 1 * grayscale[rowOffset - INPUT_IMAGE_SIZE + x + 1] +
349
+ -2 * grayscale[rowOffset + x - 1] +
350
+ 2 * grayscale[rowOffset + x + 1] +
351
+ -1 * grayscale[rowOffset + INPUT_IMAGE_SIZE + x - 1] +
352
+ 1 * grayscale[rowOffset + INPUT_IMAGE_SIZE + x + 1])
353
+
354
+ val gy = (-1 * grayscale[rowOffset - INPUT_IMAGE_SIZE + x - 1] +
355
+ -2 * grayscale[rowOffset - INPUT_IMAGE_SIZE + x] +
356
+ -1 * grayscale[rowOffset - INPUT_IMAGE_SIZE + x + 1] +
357
+ 1 * grayscale[rowOffset + INPUT_IMAGE_SIZE + x - 1] +
358
+ 2 * grayscale[rowOffset + INPUT_IMAGE_SIZE + x] +
359
+ 1 * grayscale[rowOffset + INPUT_IMAGE_SIZE + x + 1])
360
+
361
+ val gradient = sqrt((gx * gx + gy * gy).toDouble()).toInt()
362
+ if (gradient > 50) complexity++
363
+ }
364
+ }
365
+
366
+ return complexity
367
+ }
368
+
369
+ private fun calculateColorSaturation(nv21: ByteArray, width: Int, height: Int): Float {
370
+ // Calculate average color saturation to detect printed attacks
371
+ val uvStart = width * height
372
+ var totalSaturation = 0f
373
+ var sampleCount = 0
374
+
375
+ for (i in uvStart until nv21.size step 2) {
376
+ val u = (nv21[i].toInt() and 0xFF) - 128
377
+ val v = (nv21[i + 1].toInt() and 0xFF) - 128
378
+ val saturation = sqrt((u * u + v * v).toDouble()).toFloat()
379
+ totalSaturation += saturation
380
+ sampleCount++
381
+ }
382
+
383
+ return if (sampleCount > 0) totalSaturation / sampleCount / 128.0f else 0f
384
+ }
385
+
386
+ private fun calculateTemporalConsistency(currentScore: Float): Float {
387
+ // Analyze score consistency over time
388
+ scoreHistory.addFirst(currentScore)
389
+ if (scoreHistory.size > HISTORY_SIZE) {
390
+ scoreHistory.removeLast()
391
+ }
392
+
393
+ if (scoreHistory.size < MIN_FRAMES_FOR_ANALYSIS) {
394
+ return 1.0f // Maximum consistency initially
395
+ }
396
+
397
+ // Calculate variance of recent scores
398
+ val mean = scoreHistory.average().toFloat()
399
+ val variance = scoreHistory.map { (it - mean) * (it - mean) }.average().toFloat()
400
+
401
+ // Convert variance to consistency score (lower variance = higher consistency)
402
+ return max(0f, 1.0f - (variance / SCORE_VARIANCE_THRESHOLD))
403
+ }
404
+
405
+ private fun updateAdaptiveThreshold(nv21: ByteArray, width: Int, height: Int) {
406
+ // Adjust threshold based on environmental factors
407
+ val brightness = calculateBrightness(nv21, width, height)
408
+ val currentTime = System.currentTimeMillis()
409
+
410
+ if (currentTime - environmentAssessment.lastAssessmentTime > 1000) { // Assess every second
411
+ environmentAssessment.avgBrightness = brightness
412
+ environmentAssessment.lastAssessmentTime = currentTime
413
+
414
+ // Make threshold more strict in good lighting conditions
415
+ adaptiveThreshold = if (brightness > 0.6f) {
416
+ BASE_THRESHOLD * 0.8f // Stricter in good light
417
+ } else if (brightness < 0.3f) {
418
+ BASE_THRESHOLD * 1.2f // Slightly more lenient in poor light
419
+ } else {
420
+ BASE_THRESHOLD
421
+ }
422
+ }
423
+ }
424
+
425
+ private fun calculateBrightness(nv21: ByteArray, width: Int, height: Int): Float {
426
+ // Calculate average brightness from Y channel
427
+ var totalLuma = 0f
428
+ for (i in 0 until width * height) {
429
+ totalLuma += (nv21[i].toInt() and 0xFF) / 255.0f
430
+ }
431
+ return totalLuma / (width * height)
432
+ }
433
+
434
+ data class SpoofDetectionResult(
435
+ val type: String,
436
+ val confidence: Float
437
+ )
438
+
439
+ private fun detectSpoofType(
440
+ nnScore: Float,
441
+ laplacianScore: Int,
442
+ textureScore: Int,
443
+ colorSat: Float,
444
+ temporalConsistency: Float
445
+ ): SpoofDetectionResult {
446
+
447
+ // Detect specific spoof types based on feature patterns
448
+ return when {
449
+ // Printed photo detection (low texture, low laplacian)
450
+ textureScore < TEXTURE_COMPLEXITY_THRESHOLD * 0.6 &&
451
+ laplacianScore < LAPLACIAN_THRESHOLD * 0.7 ->
452
+ SpoofDetectionResult("PRINTED_PHOTO", 0.8f)
453
+
454
+ // Screen replay detection (high color saturation, medium texture)
455
+ colorSat > COLOR_SATURATION_THRESHOLD * 1.2 &&
456
+ textureScore > TEXTURE_COMPLEXITY_THRESHOLD * 0.8 ->
457
+ SpoofDetectionResult("SCREEN_REPLAY", 0.7f)
458
+
459
+ // 3D mask detection (medium scores across all features)
460
+ nnScore > BASE_THRESHOLD * 1.5 &&
461
+ temporalConsistency < 0.7f ->
462
+ SpoofDetectionResult("3D_MASK", 0.6f)
463
+
464
+ // General spoof
465
+ nnScore > BASE_THRESHOLD ->
466
+ SpoofDetectionResult("GENERAL_SPOOF", 0.9f)
467
+
468
+ else -> SpoofDetectionResult("UNKNOWN", 0.5f)
469
+ }
470
+ }
471
+
472
+ private fun calculateConfidence(
473
+ nnScore: Float,
474
+ laplacianScore: Int,
475
+ textureScore: Int,
476
+ temporalConsistency: Float
477
+ ): Float {
478
+ val nnConfidence = 1.0f - (nnScore / BASE_THRESHOLD).coerceIn(0f, 1f)
479
+ val textureConfidence = (textureScore.toFloat() / TEXTURE_COMPLEXITY_THRESHOLD).coerceIn(0f, 1f)
480
+ val laplacianConfidence = (laplacianScore.toFloat() / LAPLACIAN_THRESHOLD).coerceIn(0f, 1f)
481
+
482
+ return (nnConfidence * 0.4f + textureConfidence * 0.3f +
483
+ laplacianConfidence * 0.2f + temporalConsistency * 0.1f)
484
+ }
485
+
486
+ private fun calculateCombinedScore(
487
+ neuralScore: Float,
488
+ laplacianScore: Int,
489
+ textureScore: Int,
490
+ temporalConsistency: Float
491
+ ): Float {
284
492
  val normalizedLaplacian = laplacianScore.toFloat() / LAPLACIAN_THRESHOLD
285
- return (1 - neuralScore) * 0.7f + (normalizedLaplacian * 0.3f)
493
+ val normalizedTexture = textureScore.toFloat() / TEXTURE_COMPLEXITY_THRESHOLD
494
+
495
+ return (1 - neuralScore) * 0.5f +
496
+ normalizedLaplacian * 0.25f +
497
+ normalizedTexture * 0.15f +
498
+ temporalConsistency * 0.1f
499
+ }
500
+
501
+ private fun isLiveFace(
502
+ nnScore: Float,
503
+ laplacianScore: Int,
504
+ textureScore: Int,
505
+ colorSat: Float,
506
+ temporalConsistency: Float,
507
+ spoofDetection: SpoofDetectionResult
508
+ ): Boolean {
509
+
510
+ // Multi-layered spoof detection
511
+ val layer1 = nnScore < adaptiveThreshold
512
+ val layer2 = laplacianScore > LAPLACIAN_THRESHOLD
513
+ val layer3 = textureScore > TEXTURE_COMPLEXITY_THRESHOLD
514
+ val layer4 = temporalConsistency > 0.7f
515
+ val layer5 = colorSat < COLOR_SATURATION_THRESHOLD * 1.5f
516
+
517
+ // Require majority of layers to pass, with neural network score being most important
518
+ val passedLayers = listOf(layer1, layer2, layer3, layer4, layer5).count { it }
519
+
520
+ // Stricter requirement: must pass at least 4 out of 5 layers, including neural network
521
+ return passedLayers >= 4 && layer1
286
522
  }
287
523
 
288
524
  @Throws(IOException::class)
@@ -297,23 +533,28 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
297
533
  fileDescriptor.declaredLength
298
534
  )
299
535
  } catch (e: Exception) {
300
- android.util.Log.e(TAG, "Error loading model file", e)
536
+ Log.e(TAG, "Error loading model file", e)
301
537
  throw e
302
538
  }
303
539
  }
304
540
 
305
541
  fun isInitialized(): Boolean = isInitialized
306
542
 
543
+ fun resetHistory() {
544
+ scoreHistory.clear()
545
+ laplacianHistory.clear()
546
+ frameCount = 0
547
+ adaptiveThreshold = BASE_THRESHOLD
548
+ }
549
+
307
550
  fun close() {
308
551
  try {
309
552
  interpreter?.close()
310
553
  interpreter = null
311
-
312
- // Clean up GPU delegate
313
554
  gpuDelegate?.close()
314
555
  gpuDelegate = null
315
-
316
556
  currentAccelerator = ACCELERATOR_CPU
557
+ resetHistory()
317
558
  Log.i(TAG, "Interpreter and delegates closed successfully")
318
559
  } catch (e: Exception) {
319
560
  Log.e(TAG, "Error closing interpreter", e)
@@ -324,7 +565,12 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
324
565
  data class AntiSpoofingResult(
325
566
  val neuralNetworkScore: Float,
326
567
  val laplacianScore: Int,
568
+ val textureComplexity: Int,
569
+ val temporalConsistency: Float,
570
+ val colorSaturation: Float,
327
571
  val combinedScore: Float,
328
572
  val isLive: Boolean,
329
- val label: String
330
- )
573
+ val confidence: Float,
574
+ val label: String,
575
+ val spoofType: String
576
+ )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-vision-camera-spoof-detector",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "High-performance face anti-spoofing and liveness detection module for React Native Vision Camera. Uses TensorFlow Lite with GPU acceleration and optimized YUV processing.",
5
5
  "homepage": "https://github.com/jescon-tech/react-native-vision-camera-spoof-detector",
6
6
  "repository": {
package/CHANGELOG.md DELETED
@@ -1,48 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [1.0.0] - 2025-11-18
9
-
10
- ### Added
11
- - Multi-threading support with `Executors.newSingleThreadExecutor()`
12
- - Proper npm package structure with reduced bundle size
13
- - TypeScript type definitions (`index.d.ts`)
14
- - `.npmignore` and `.gitignore` files for proper package management
15
- - Support for GPU acceleration via TensorFlow Lite GPU delegate
16
- - Support for NNAPI acceleration as fallback
17
- - Daemon thread configuration for proper app lifecycle management
18
- - Comprehensive error handling and logging
19
-
20
- ### Changed
21
- - Updated package.json with proper metadata and peer dependencies
22
- - Improved module initialization and cleanup procedures
23
- - Enhanced frame processor to prevent blocking VisionCamera callback thread
24
- - Optimized YUV to RGB conversion with reusable buffers
25
- - Increased version from 0.0.26 to 1.0.0 for stable release
26
-
27
- ### Fixed
28
- - Frame drops caused by blocking processing in callback thread
29
- - Memory leaks by implementing proper resource cleanup
30
- - Executor thread lifecycle management
31
-
32
- ### Removed
33
- - Build artifacts from `.cxx/` directory (49.44 MB)
34
- - Build output from `build/` directory (56.99 MB)
35
- - Unnecessary Gradle wrapper binaries
36
-
37
- ### Performance Improvements
38
- - Reduced package size from 127.35 MB to 20.92 MB (83.6% reduction)
39
- - Non-blocking callback returns immediately
40
- - Single-frame processing prevents queue overflow
41
- - Optimized memory usage with daemon threads
42
-
43
- ## [0.0.26] - Previous
44
-
45
- ### Initial Release
46
- - Basic face anti-spoofing detection
47
- - React Native Vision Camera integration
48
- - TensorFlow Lite model support
@@ -1,9 +0,0 @@
1
- # C/C++ build system timings
2
- generate_cxx_metadata
3
- execute-generate-process
4
- exec-configure 758ms
5
- [gap of 23ms]
6
- execute-generate-process completed in 782ms
7
- [gap of 14ms]
8
- generate_cxx_metadata completed in 803ms
9
-
@@ -1,9 +0,0 @@
1
- # C/C++ build system timings
2
- generate_cxx_metadata
3
- execute-generate-process
4
- exec-configure 746ms
5
- [gap of 23ms]
6
- execute-generate-process completed in 770ms
7
- [gap of 16ms]
8
- generate_cxx_metadata completed in 794ms
9
-