react-native-vision-camera-spoof-detector 1.0.6 → 1.0.8
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/.cmake/api/v1/reply/{codemodel-v2-6c7de80461af72f456a3.json → codemodel-v2-501c3f4eb817726556cc.json} +2 -2
- package/android/.cxx/Debug/2a1it684/arm64-v8a/.cmake/api/v1/reply/{index-2025-11-24T17-41-56-0411.json → index-2025-11-25T16-55-22-0685.json} +2 -2
- package/android/.cxx/Debug/2a1it684/arm64-v8a/.cmake/api/v1/reply/{target-yuv-Debug-32bc4eddd343848e1c25.json → target-yuv-Debug-63fdb78450b0b16b8484.json} +2 -2
- package/android/.cxx/Debug/2a1it684/arm64-v8a/.cmake/api/v1/reply/{target-yuv_shared-Debug-75ea769bf76a12914d29.json → target-yuv_shared-Debug-5c8b5c6f50bc8769a5bd.json} +2 -2
- 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 -118
- 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 -110
- 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/AcceleratorConfig.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/FaceAntiSpoofFrameProcessor.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/ImageFormatEncoder$ImageFormat.dex +0 -0
- package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/ImageFormatEncoder.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 +51 -51
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/arm64-v8a/generate_cxx_metadata_1041_timing.txt +9 -0
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/armeabi-v7a/build_stdout_targets.txt +37 -37
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/armeabi-v7a/{generate_cxx_metadata_2687_timing.txt → generate_cxx_metadata_1042_timing.txt} +1 -1
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86/build_stdout_targets.txt +39 -39
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86/{generate_cxx_metadata_2687_timing.txt → generate_cxx_metadata_1042_timing.txt} +1 -1
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86_64/build_stdout_targets.txt +40 -40
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86_64/generate_cxx_metadata_1042_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/AcceleratorConfig.class +0 -0
- 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/FaceAntiSpoofFrameProcessor.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/ImageFormatEncoder$ImageFormat.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/ImageFormatEncoder.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/jvm/kotlin/proto.tab +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/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/AcceleratorConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/AntiSpoofingResult.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$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/ImageFormatEncoder$ImageFormat.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/ImageFormatEncoder.class +0 -0
- package/android/src/main/java/com/faceantispoof/AcceleratorConfig.kt +57 -9
- package/android/src/main/java/com/faceantispoof/FaceAntiSpoofFrameProcessor.kt +1 -0
- package/android/src/main/java/com/faceantispoof/FaceAntiSpoofingAdvanced.kt +120 -26
- package/android/src/main/java/com/faceantispoof/ImageFormatEncoder.kt +151 -46
- package/package.json +3 -3
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/arm64-v8a/generate_cxx_metadata_2688_timing.txt +0 -9
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86_64/generate_cxx_metadata_2687_timing.txt +0 -9
- /package/android/.cxx/Debug/2a1it684/armeabi-v7a/.cmake/api/v1/reply/{index-2025-11-18T18-09-57-0510.json → index-2025-11-25T16-21-43-0410.json} +0 -0
- /package/android/.cxx/Debug/2a1it684/x86/.cmake/api/v1/reply/{index-2025-11-18T18-10-04-0841.json → index-2025-11-25T16-21-51-0349.json} +0 -0
- /package/android/.cxx/Debug/2a1it684/x86_64/.cmake/api/v1/reply/{index-2025-11-24T17-42-56-0679.json → index-2025-11-25T16-55-39-0849.json} +0 -0
package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream
CHANGED
|
Binary file
|
|
Binary file
|
package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len
CHANGED
|
Binary file
|
package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/android/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/FaceAntiSpoofFrameProcessor.class
CHANGED
|
Binary file
|
|
Binary file
|
package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/FaceAntiSpoofingAdvanced.class
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -3,12 +3,14 @@ package com.faceantispoof
|
|
|
3
3
|
/**
|
|
4
4
|
* Configuration class for TensorFlow Lite accelerators
|
|
5
5
|
* Allows developers to control GPU/NNAPI delegate behavior
|
|
6
|
+
* and optimize for different device tiers (low-end, mid-range, high-end)
|
|
6
7
|
*/
|
|
7
8
|
object AcceleratorConfig {
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Enable GPU acceleration (recommended for modern devices)
|
|
11
12
|
* Accelerates model 3-10x compared to CPU
|
|
13
|
+
* GPU is automatically skipped on low-end devices even if enabled
|
|
12
14
|
*/
|
|
13
15
|
var enableGpu: Boolean = true
|
|
14
16
|
|
|
@@ -16,34 +18,80 @@ object AcceleratorConfig {
|
|
|
16
18
|
* Enable NNAPI acceleration (recommended for Pixel/Samsung devices)
|
|
17
19
|
* Accelerates model 3-10x compared to CPU
|
|
18
20
|
* Fallback if GPU is not available
|
|
21
|
+
* Works better on mid-range and high-end devices
|
|
19
22
|
*/
|
|
20
23
|
var enableNnapi: Boolean = true
|
|
21
24
|
|
|
22
25
|
/**
|
|
23
26
|
* Enable CPU fallback if GPU/NNAPI are not available
|
|
24
|
-
* Default: true
|
|
27
|
+
* Default: true (always fallback to CPU)
|
|
25
28
|
*/
|
|
26
29
|
var enableCpuFallback: Boolean = true
|
|
27
30
|
|
|
28
31
|
/**
|
|
29
32
|
* Number of threads for CPU inference
|
|
30
|
-
* Default: 4 threads
|
|
33
|
+
* Default: 4 threads (optimal for most devices)
|
|
34
|
+
* Low-end devices will use 1-2 threads regardless
|
|
31
35
|
*/
|
|
32
36
|
var cpuThreadCount: Int = 4
|
|
33
37
|
|
|
34
38
|
/**
|
|
35
|
-
*
|
|
39
|
+
* Force CPU-only mode for all devices
|
|
40
|
+
* Useful for debugging or if GPU/NNAPI causes issues
|
|
41
|
+
* Default: false
|
|
42
|
+
*/
|
|
43
|
+
var forceCpuOnly: Boolean = false
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Disable GPU delegate specifically
|
|
47
|
+
* Keeps NNAPI enabled if available
|
|
48
|
+
* Default: false
|
|
49
|
+
*/
|
|
50
|
+
var disableGpuDelegate: Boolean = false
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Disable NNAPI delegate specifically
|
|
54
|
+
* GPU will still be attempted if enabled
|
|
55
|
+
* Default: false
|
|
56
|
+
*/
|
|
57
|
+
var disableNnapiDelegate: Boolean = false
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Print debug logs for accelerator selection and performance
|
|
61
|
+
* Default: true
|
|
36
62
|
*/
|
|
37
63
|
var debugLogging: Boolean = true
|
|
38
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Enable aggressive optimization for low-end devices
|
|
67
|
+
* Reduces memory usage and processes fewer frames
|
|
68
|
+
* Default: true
|
|
69
|
+
*/
|
|
70
|
+
var optimizeForLowEndDevices: Boolean = true
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Maximum memory (in MB) to allocate for the interpreter
|
|
74
|
+
* Low-end devices will use less aggressive settings
|
|
75
|
+
* Default: 0 (use system defaults)
|
|
76
|
+
*/
|
|
77
|
+
var maxMemoryAllocation: Int = 0
|
|
78
|
+
|
|
39
79
|
override fun toString(): String {
|
|
40
80
|
return """
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
81
|
+
╔════════════════════════════════════════════════╗
|
|
82
|
+
║ TFLite Accelerator Configuration ║
|
|
83
|
+
╠════════════════════════════════════════════════╣
|
|
84
|
+
║ GPU Acceleration: $enableGpu
|
|
85
|
+
║ NNAPI Acceleration: $enableNnapi
|
|
86
|
+
║ CPU Fallback: $enableCpuFallback
|
|
87
|
+
║ CPU Threads: $cpuThreadCount
|
|
88
|
+
║ Force CPU Only: $forceCpuOnly
|
|
89
|
+
║ Disable GPU Delegate: $disableGpuDelegate
|
|
90
|
+
║ Disable NNAPI Delegate: $disableNnapiDelegate
|
|
91
|
+
║ Debug Logging: $debugLogging
|
|
92
|
+
║ Optimize Low-End: $optimizeForLowEndDevices
|
|
93
|
+
║ Max Memory (MB): $maxMemoryAllocation
|
|
94
|
+
╚════════════════════════════════════════════════╝
|
|
47
95
|
""".trimIndent()
|
|
48
96
|
}
|
|
49
97
|
}
|
|
@@ -153,6 +153,7 @@ class FaceAntiSpoofFrameProcessor : FrameProcessorPlugin() {
|
|
|
153
153
|
"combinedScore" to result.combinedScore.toDouble(),
|
|
154
154
|
"recognitionImageBase64" to result.recognitionImageBase64,
|
|
155
155
|
// Image format metadata
|
|
156
|
+
// NOTE: Image is in BGR format (OpenCV compatible), encoded as Base64 JPEG
|
|
156
157
|
"imageFormat" to result.imageFormat,
|
|
157
158
|
"imageResolution" to result.imageResolution,
|
|
158
159
|
"imageQuality" to result.imageQuality
|
|
@@ -96,22 +96,15 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
|
|
|
96
96
|
try {
|
|
97
97
|
val modelBuffer = loadModelFile(assetManager, MODEL_FILE)
|
|
98
98
|
val options = Interpreter.Options()
|
|
99
|
-
options.setNumThreads(4)
|
|
100
99
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
Log.i(TAG, "NNAPI enabled successfully")
|
|
110
|
-
} catch (e: Exception) {
|
|
111
|
-
currentAccelerator = ACCELERATOR_CPU
|
|
112
|
-
Log.i(TAG, "Running on CPU (GPU/NNAPI not available): ${e.message}")
|
|
113
|
-
}
|
|
114
|
-
}
|
|
100
|
+
// FORCE CPU-ONLY MODE FOR LOW-END DEVICE COMPATIBILITY
|
|
101
|
+
// This bypasses all GPU/NNAPI issues and uses stable CPU inference
|
|
102
|
+
val optimalThreads = getOptimalThreadCount()
|
|
103
|
+
options.setNumThreads(optimalThreads)
|
|
104
|
+
|
|
105
|
+
currentAccelerator = ACCELERATOR_CPU
|
|
106
|
+
Log.i(TAG, "✓ FORCED CPU-ONLY MODE - Bypassing GPU/NNAPI")
|
|
107
|
+
Log.i(TAG, "Using $optimalThreads threads for CPU inference")
|
|
115
108
|
|
|
116
109
|
interpreter = Interpreter(modelBuffer, options)
|
|
117
110
|
|
|
@@ -126,28 +119,46 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
|
|
|
126
119
|
outputs[outputIndex0] = clssPred
|
|
127
120
|
outputs[outputIndex1] = leafNodeMask
|
|
128
121
|
isInitialized = true
|
|
129
|
-
Log.i(TAG, "
|
|
122
|
+
Log.i(TAG, "✓ Face Anti-Spoof initialized | CPU-ONLY MODE | Threads: $optimalThreads")
|
|
130
123
|
} catch (e: Exception) {
|
|
131
|
-
Log.e(TAG, "Failed to load TFLite model", e)
|
|
124
|
+
Log.e(TAG, "✗ Failed to load TFLite model: ${e.message}", e)
|
|
132
125
|
isInitialized = false
|
|
126
|
+
currentAccelerator = ACCELERATOR_CPU
|
|
133
127
|
}
|
|
134
128
|
}
|
|
135
129
|
|
|
136
130
|
private fun tryAddGpuDelegate(options: Interpreter.Options): Boolean {
|
|
137
131
|
return try {
|
|
132
|
+
// Additional check: verify device has minimum Android version for GPU delegate
|
|
133
|
+
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
|
|
134
|
+
Log.w(TAG, "GPU delegate requires Android 5.0+, device running: ${android.os.Build.VERSION.SDK_INT}")
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
|
|
138
138
|
val compatibilityList = CompatibilityList()
|
|
139
139
|
if (compatibilityList.isDelegateSupportedOnThisDevice) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
140
|
+
try {
|
|
141
|
+
gpuDelegate = GpuDelegate(compatibilityList.bestOptionsForThisDevice)
|
|
142
|
+
options.addDelegate(gpuDelegate)
|
|
143
|
+
Log.d(TAG, "GPU delegate added successfully")
|
|
144
|
+
true
|
|
145
|
+
} catch (e: Exception) {
|
|
146
|
+
Log.w(TAG, "Failed to initialize GPU delegate after compatibility check: ${e.message}")
|
|
147
|
+
gpuDelegate?.close()
|
|
148
|
+
gpuDelegate = null
|
|
149
|
+
false
|
|
150
|
+
}
|
|
144
151
|
} else {
|
|
145
152
|
Log.d(TAG, "GPU delegate not supported on this device")
|
|
146
153
|
false
|
|
147
154
|
}
|
|
148
155
|
} catch (e: Exception) {
|
|
149
156
|
Log.w(TAG, "Failed to add GPU delegate: ${e.message}")
|
|
150
|
-
|
|
157
|
+
try {
|
|
158
|
+
gpuDelegate?.close()
|
|
159
|
+
} catch (e2: Exception) {
|
|
160
|
+
Log.w(TAG, "Error closing GPU delegate: ${e2.message}")
|
|
161
|
+
}
|
|
151
162
|
gpuDelegate = null
|
|
152
163
|
false
|
|
153
164
|
}
|
|
@@ -412,6 +423,7 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
|
|
|
412
423
|
Log.d(TAG, "YUV to RGB conversion completed in ${convertTime}ms. RGB data: ${convertedRgb.size} bytes")
|
|
413
424
|
|
|
414
425
|
// Encode RGB to target format (JPEG, PNG, or BMP) and Base64
|
|
426
|
+
// NOTE: ImageFormatEncoder.rgbToBase64() automatically converts RGB→BGR for OpenCV compatibility
|
|
415
427
|
val startEncode = System.currentTimeMillis()
|
|
416
428
|
val format = when (outputImageFormat) {
|
|
417
429
|
"PNG" -> ImageFormatEncoder.ImageFormat.PNG
|
|
@@ -430,16 +442,16 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
|
|
|
430
442
|
|
|
431
443
|
if (base64Result != null) {
|
|
432
444
|
Log.d(TAG, "$outputImageFormat encoding completed in ${encodeTime}ms. Base64 size: ${base64Result.length} bytes")
|
|
433
|
-
Log.d(TAG, "✅ Generated ${outputImageResolution}×${outputImageResolution} $outputImageFormat Base64 for recognition API (total: ${convertTime + encodeTime}ms)")
|
|
445
|
+
Log.d(TAG, "✅ Generated ${outputImageResolution}×${outputImageResolution} $outputImageFormat Base64 for recognition API in BGR format (total: ${convertTime + encodeTime}ms)")
|
|
434
446
|
val estSize = estimateImageSize()
|
|
435
|
-
Log.d(TAG, "Format: $outputImageFormat, Quality: $outputImageQuality, Estimated: ${estSize / 1024}KB")
|
|
447
|
+
Log.d(TAG, "Format: $outputImageFormat (BGR), Quality: $outputImageQuality, Estimated: ${estSize / 1024}KB")
|
|
436
448
|
} else {
|
|
437
|
-
Log.w(TAG, "⚠️ ImageFormatEncoder returned null")
|
|
449
|
+
Log.w(TAG, "⚠️ ImageFormatEncoder returned null for BGR conversion")
|
|
438
450
|
}
|
|
439
451
|
|
|
440
452
|
base64Result
|
|
441
453
|
} catch (e: Exception) {
|
|
442
|
-
Log.e(TAG, "❌ Error generating ${outputImageResolution}×${outputImageResolution} $outputImageFormat Base64: ${e.message}", e)
|
|
454
|
+
Log.e(TAG, "❌ Error generating ${outputImageResolution}×${outputImageResolution} $outputImageFormat Base64 (BGR format): ${e.message}", e)
|
|
443
455
|
e.printStackTrace()
|
|
444
456
|
null
|
|
445
457
|
}
|
|
@@ -771,6 +783,88 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
|
|
|
771
783
|
Log.e(TAG, "Error closing interpreter", e)
|
|
772
784
|
}
|
|
773
785
|
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Detects if the device is high-end based on RAM, processor, and Android version.
|
|
789
|
+
* High-end devices can use GPU/NNAPI delegates.
|
|
790
|
+
* Low-end devices default to optimized CPU inference.
|
|
791
|
+
*/
|
|
792
|
+
private fun isHighEndDevice(): Boolean {
|
|
793
|
+
try {
|
|
794
|
+
// Check Android version
|
|
795
|
+
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
|
|
796
|
+
Log.d(TAG, "Device running Android ${android.os.Build.VERSION.SDK_INT} (< 5.0) - low-end")
|
|
797
|
+
return false
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// Check available RAM
|
|
801
|
+
val runtime = Runtime.getRuntime()
|
|
802
|
+
val maxMemory = runtime.maxMemory() / (1024 * 1024) // Convert to MB
|
|
803
|
+
if (maxMemory < 256) { // Less than 256MB allocated
|
|
804
|
+
Log.d(TAG, "Low available RAM: ${maxMemory}MB - treating as low-end device")
|
|
805
|
+
return false
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// Check processor cores (devices with 2 cores or less are typically low-end)
|
|
809
|
+
val processorCount = Runtime.getRuntime().availableProcessors()
|
|
810
|
+
if (processorCount <= 2) {
|
|
811
|
+
Log.d(TAG, "Limited CPU cores: $processorCount - low-end device")
|
|
812
|
+
return false
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// Check device name for known low-end models
|
|
816
|
+
val model = android.os.Build.MODEL.lowercase()
|
|
817
|
+
val lowEndMarkers = listOf(
|
|
818
|
+
"moto g", "samsung a", "samsung j", "redmi 6", "redmi 7",
|
|
819
|
+
"honor 8a", "galaxy a01", "galaxy j2"
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
for (marker in lowEndMarkers) {
|
|
823
|
+
if (model.contains(marker)) {
|
|
824
|
+
Log.d(TAG, "Known low-end device model: ${android.os.Build.MODEL}")
|
|
825
|
+
return false
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
Log.d(TAG, "Device appears to be high-end (Android ${android.os.Build.VERSION.SDK_INT}, $processorCount cores, ${maxMemory}MB RAM)")
|
|
830
|
+
return true
|
|
831
|
+
} catch (e: Exception) {
|
|
832
|
+
Log.w(TAG, "Error detecting device tier: ${e.message}, defaulting to CPU-only")
|
|
833
|
+
return false
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* Calculates optimal thread count based on available processors and device resources.
|
|
839
|
+
* Low-end devices use 1-2 threads to avoid memory issues.
|
|
840
|
+
*/
|
|
841
|
+
private fun getOptimalThreadCount(): Int {
|
|
842
|
+
return try {
|
|
843
|
+
val availableProcessors = Runtime.getRuntime().availableProcessors()
|
|
844
|
+
|
|
845
|
+
// For low-end devices, limit thread count
|
|
846
|
+
if (!isHighEndDevice()) {
|
|
847
|
+
Log.d(TAG, "Low-end device: limiting threads to 1-2")
|
|
848
|
+
return maxOf(1, minOf(2, availableProcessors - 1))
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// For high-end devices, use configured count or optimal calculation
|
|
852
|
+
val configured = AcceleratorConfig.cpuThreadCount
|
|
853
|
+
val optimal = maxOf(1, availableProcessors - 1)
|
|
854
|
+
|
|
855
|
+
val threadCount = if (configured > 0) {
|
|
856
|
+
minOf(configured, availableProcessors)
|
|
857
|
+
} else {
|
|
858
|
+
optimal
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
Log.d(TAG, "Optimal thread count: $threadCount (available processors: $availableProcessors)")
|
|
862
|
+
threadCount
|
|
863
|
+
} catch (e: Exception) {
|
|
864
|
+
Log.w(TAG, "Error calculating optimal threads: ${e.message}, using default")
|
|
865
|
+
AcceleratorConfig.cpuThreadCount
|
|
866
|
+
}
|
|
867
|
+
}
|
|
774
868
|
}
|
|
775
869
|
|
|
776
870
|
data class AntiSpoofingResult(
|
|
@@ -8,9 +8,11 @@ import java.io.ByteArrayOutputStream
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Multi-format image encoder supporting JPEG, PNG, and BMP.
|
|
11
|
-
* Handles
|
|
11
|
+
* Handles BGR data conversion (OpenCV compatible) to various image formats for compatibility
|
|
12
12
|
* with face recognition systems (OpenCV cv2.imdecode compatible).
|
|
13
13
|
*
|
|
14
|
+
* NOTE: Converts RGB input to BGR format as required by OpenCV and FastAPI server
|
|
15
|
+
*
|
|
14
16
|
* Supported formats:
|
|
15
17
|
* - JPEG: Compressed, good for network transmission (~80-100 KB at quality 85)
|
|
16
18
|
* - PNG: Lossless, larger file size (~200-300 KB)
|
|
@@ -36,15 +38,17 @@ object ImageFormatEncoder {
|
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
/**
|
|
39
|
-
* Convert RGB data to
|
|
40
|
-
*
|
|
41
|
+
* Convert RGB data to BGR format (OpenCV compatible) and encode to specified image format.
|
|
42
|
+
* Returns Base64 string compatible with FastAPI server.
|
|
43
|
+
*
|
|
44
|
+
* IMPORTANT: This method performs RGB→BGR conversion required by OpenCV cv2.imdecode()
|
|
41
45
|
*
|
|
42
46
|
* @param rgbData RGB data as byte array (format: RGBRGBRGB..., 3 channels)
|
|
43
47
|
* @param width Image width (pixels)
|
|
44
48
|
* @param height Image height (pixels)
|
|
45
49
|
* @param format Target image format (JPEG, PNG, BMP)
|
|
46
50
|
* @param quality Compression quality for JPEG (0-100), ignored for PNG/BMP
|
|
47
|
-
* @return Base64 encoded image string, or null if encoding fails
|
|
51
|
+
* @return Base64 encoded image string in BGR color space, or null if encoding fails
|
|
48
52
|
*/
|
|
49
53
|
fun rgbToBase64(
|
|
50
54
|
rgbData: ByteArray,
|
|
@@ -57,18 +61,22 @@ object ImageFormatEncoder {
|
|
|
57
61
|
validateImageDimensions(width, height)
|
|
58
62
|
validateRgbData(rgbData, width, height)
|
|
59
63
|
|
|
60
|
-
|
|
64
|
+
// CRITICAL: Convert RGB to BGR for OpenCV compatibility
|
|
65
|
+
val bgrData = rgbToBgr(rgbData)
|
|
66
|
+
|
|
67
|
+
val imageBytes = bgrToImageBytes(bgrData, width, height, format, quality)
|
|
61
68
|
?: return null
|
|
62
69
|
|
|
63
70
|
Base64.encodeToString(imageBytes, Base64.NO_WRAP)
|
|
64
71
|
} catch (e: Exception) {
|
|
65
|
-
Log.e(TAG, "Failed to convert RGB to ${format.name} Base64: ${e.message}", e)
|
|
72
|
+
Log.e(TAG, "Failed to convert RGB to BGR ${format.name} Base64: ${e.message}", e)
|
|
66
73
|
null
|
|
67
74
|
}
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
/**
|
|
71
78
|
* Convert RGB float array (normalized 0-1) to image format and return as Base64.
|
|
79
|
+
* Performs RGB→BGR conversion for OpenCV compatibility.
|
|
72
80
|
* Each pixel is stored as 3 floats (R, G, B).
|
|
73
81
|
*
|
|
74
82
|
* @param rgbFloats RGB data as float array (normalized to 0-1)
|
|
@@ -76,7 +84,7 @@ object ImageFormatEncoder {
|
|
|
76
84
|
* @param height Image height
|
|
77
85
|
* @param format Target image format
|
|
78
86
|
* @param quality Compression quality for JPEG
|
|
79
|
-
* @return Base64 encoded image string
|
|
87
|
+
* @return Base64 encoded image string in BGR format
|
|
80
88
|
*/
|
|
81
89
|
fun rgbFloatToBase64(
|
|
82
90
|
rgbFloats: FloatArray,
|
|
@@ -113,20 +121,21 @@ object ImageFormatEncoder {
|
|
|
113
121
|
|
|
114
122
|
rgbToBase64(rgbBytes, width, height, format, quality)
|
|
115
123
|
} catch (e: Exception) {
|
|
116
|
-
Log.e(TAG, "Failed to convert RGB float to ${format.name} Base64: ${e.message}", e)
|
|
124
|
+
Log.e(TAG, "Failed to convert RGB float to BGR ${format.name} Base64: ${e.message}", e)
|
|
117
125
|
null
|
|
118
126
|
}
|
|
119
127
|
}
|
|
120
128
|
|
|
121
129
|
/**
|
|
122
130
|
* Convert RGB data to raw image bytes (not Base64).
|
|
131
|
+
* Performs RGB→BGR conversion for OpenCV compatibility.
|
|
123
132
|
*
|
|
124
133
|
* @param rgbData RGB data as byte array
|
|
125
134
|
* @param width Image width
|
|
126
135
|
* @param height Image height
|
|
127
136
|
* @param format Target image format
|
|
128
137
|
* @param quality Compression quality for JPEG
|
|
129
|
-
* @return Raw image bytes, or null if encoding fails
|
|
138
|
+
* @return Raw image bytes in BGR format, or null if encoding fails
|
|
130
139
|
*/
|
|
131
140
|
fun rgbToImageBytes(
|
|
132
141
|
rgbData: ByteArray,
|
|
@@ -139,29 +148,12 @@ object ImageFormatEncoder {
|
|
|
139
148
|
validateImageDimensions(width, height)
|
|
140
149
|
validateRgbData(rgbData, width, height)
|
|
141
150
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
when (format) {
|
|
147
|
-
ImageFormat.JPEG -> compressBitmapToBytes(bitmap, format, quality)
|
|
148
|
-
ImageFormat.PNG -> compressBitmapToBytes(bitmap, format, 100)
|
|
149
|
-
ImageFormat.BMP -> compressBitmapToBytes(bitmap, format, 100)
|
|
150
|
-
}
|
|
151
|
-
} catch (e: Exception) {
|
|
152
|
-
Log.e(TAG, "Error compressing bitmap: ${e.message}", e)
|
|
153
|
-
null
|
|
154
|
-
} finally {
|
|
155
|
-
try {
|
|
156
|
-
bitmap.recycle()
|
|
157
|
-
} catch (e: Exception) {
|
|
158
|
-
Log.w(TAG, "Error recycling bitmap: ${e.message}")
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
imageBytes
|
|
151
|
+
// CRITICAL: Convert RGB to BGR for OpenCV compatibility
|
|
152
|
+
val bgrData = rgbToBgr(rgbData)
|
|
153
|
+
|
|
154
|
+
bgrToImageBytes(bgrData, width, height, format, quality)
|
|
163
155
|
} catch (e: Exception) {
|
|
164
|
-
Log.e(TAG, "Failed to convert RGB to ${format.name} bytes: ${e.message}", e)
|
|
156
|
+
Log.e(TAG, "Failed to convert RGB to BGR ${format.name} bytes: ${e.message}", e)
|
|
165
157
|
null
|
|
166
158
|
}
|
|
167
159
|
}
|
|
@@ -226,13 +218,93 @@ object ImageFormatEncoder {
|
|
|
226
218
|
// ============ Private Helper Methods ============
|
|
227
219
|
|
|
228
220
|
/**
|
|
229
|
-
*
|
|
230
|
-
*
|
|
221
|
+
* Convert RGB byte array to BGR for OpenCV compatibility.
|
|
222
|
+
* CRITICAL: OpenCV expects BGR format, not RGB.
|
|
223
|
+
*
|
|
224
|
+
* @param rgbData RGB data in format RGBRGBRGB...
|
|
225
|
+
* @return BGR data in format BGRBGRBGR...
|
|
231
226
|
*/
|
|
232
|
-
private fun
|
|
227
|
+
private fun rgbToBgr(rgbData: ByteArray): ByteArray {
|
|
228
|
+
val bgrData = ByteArray(rgbData.size)
|
|
229
|
+
var rgbIdx = 0
|
|
230
|
+
var bgrIdx = 0
|
|
231
|
+
|
|
232
|
+
while (rgbIdx < rgbData.size && rgbIdx + 2 < rgbData.size) {
|
|
233
|
+
val r = rgbData[rgbIdx++]
|
|
234
|
+
val g = rgbData[rgbIdx++]
|
|
235
|
+
val b = rgbData[rgbIdx++]
|
|
236
|
+
|
|
237
|
+
// Swap: BGR order
|
|
238
|
+
bgrData[bgrIdx++] = b
|
|
239
|
+
bgrData[bgrIdx++] = g
|
|
240
|
+
bgrData[bgrIdx++] = r
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
Log.d(TAG, "Converted RGB to BGR: ${rgbData.size} bytes")
|
|
244
|
+
return bgrData
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Convert BGR data to raw image bytes (not Base64).
|
|
249
|
+
* BGR data is already in OpenCV format.
|
|
250
|
+
*
|
|
251
|
+
* @param bgrData BGR data as byte array (already in OpenCV format)
|
|
252
|
+
* @param width Image width
|
|
253
|
+
* @param height Image height
|
|
254
|
+
* @param format Target image format
|
|
255
|
+
* @param quality Compression quality for JPEG
|
|
256
|
+
* @return Raw image bytes, or null if encoding fails
|
|
257
|
+
*/
|
|
258
|
+
private fun bgrToImageBytes(
|
|
259
|
+
bgrData: ByteArray,
|
|
260
|
+
width: Int,
|
|
261
|
+
height: Int,
|
|
262
|
+
format: ImageFormat = ImageFormat.JPEG,
|
|
263
|
+
quality: Int = 85
|
|
264
|
+
): ByteArray? {
|
|
233
265
|
return try {
|
|
234
|
-
|
|
235
|
-
|
|
266
|
+
validateImageDimensions(width, height)
|
|
267
|
+
|
|
268
|
+
if (bgrData.size < width * height * 3) {
|
|
269
|
+
Log.e(TAG, "BGR data too small: expected ${width * height * 3}, got ${bgrData.size}")
|
|
270
|
+
return null
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
val bitmap = createBitmapFromBgr(bgrData, width, height)
|
|
274
|
+
?: return null
|
|
275
|
+
|
|
276
|
+
val imageBytes = try {
|
|
277
|
+
when (format) {
|
|
278
|
+
ImageFormat.JPEG -> compressBitmapToBytes(bitmap, format, quality)
|
|
279
|
+
ImageFormat.PNG -> compressBitmapToBytes(bitmap, format, 100)
|
|
280
|
+
ImageFormat.BMP -> compressBitmapToBytes(bitmap, format, 100)
|
|
281
|
+
}
|
|
282
|
+
} catch (e: Exception) {
|
|
283
|
+
Log.e(TAG, "Error compressing bitmap: ${e.message}", e)
|
|
284
|
+
null
|
|
285
|
+
} finally {
|
|
286
|
+
try {
|
|
287
|
+
bitmap.recycle()
|
|
288
|
+
} catch (e: Exception) {
|
|
289
|
+
Log.w(TAG, "Error recycling bitmap: ${e.message}")
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
imageBytes
|
|
294
|
+
} catch (e: Exception) {
|
|
295
|
+
Log.e(TAG, "Failed to convert BGR to ${format.name} bytes: ${e.message}", e)
|
|
296
|
+
null
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Create Bitmap from BGR byte array (OpenCV format).
|
|
302
|
+
* BGR format: BGRBGRBGR... (3 bytes per pixel, no alpha)
|
|
303
|
+
*/
|
|
304
|
+
private fun createBitmapFromBgr(bgrData: ByteArray, width: Int, height: Int): Bitmap? {
|
|
305
|
+
return try {
|
|
306
|
+
if (bgrData.isEmpty()) {
|
|
307
|
+
Log.e(TAG, "BGR data is empty")
|
|
236
308
|
return null
|
|
237
309
|
}
|
|
238
310
|
if (width <= 0 || height <= 0) {
|
|
@@ -241,31 +313,64 @@ object ImageFormatEncoder {
|
|
|
241
313
|
}
|
|
242
314
|
|
|
243
315
|
val expectedSize = width * height * 3
|
|
244
|
-
if (
|
|
245
|
-
Log.e(TAG, "
|
|
316
|
+
if (bgrData.size < expectedSize) {
|
|
317
|
+
Log.e(TAG, "BGR data too small: expected at least $expectedSize, got ${bgrData.size}")
|
|
246
318
|
return null
|
|
247
319
|
}
|
|
248
320
|
|
|
249
|
-
// Convert
|
|
321
|
+
// Convert BGR to ARGB for Bitmap (Android uses ARGB_8888)
|
|
322
|
+
// BGR order: B, G, R
|
|
250
323
|
val pixels = IntArray(width * height)
|
|
251
|
-
var
|
|
324
|
+
var bgrIdx = 0
|
|
252
325
|
for (i in pixels.indices) {
|
|
253
|
-
if (
|
|
254
|
-
Log.e(TAG, "
|
|
326
|
+
if (bgrIdx + 2 >= bgrData.size) {
|
|
327
|
+
Log.e(TAG, "BGR buffer index out of bounds at pixel $i")
|
|
255
328
|
return null
|
|
256
329
|
}
|
|
257
|
-
val
|
|
258
|
-
val g =
|
|
259
|
-
val
|
|
330
|
+
val b = bgrData[bgrIdx++].toInt() and 0xFF
|
|
331
|
+
val g = bgrData[bgrIdx++].toInt() and 0xFF
|
|
332
|
+
val r = bgrData[bgrIdx++].toInt() and 0xFF
|
|
260
333
|
pixels[i] = 0xFF000000.toInt() or (r shl 16) or (g shl 8) or b // ARGB
|
|
261
334
|
}
|
|
262
335
|
|
|
263
336
|
val bitmap = Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888)
|
|
264
337
|
if (bitmap == null) {
|
|
265
|
-
Log.e(TAG, "Failed to create bitmap")
|
|
338
|
+
Log.e(TAG, "Failed to create bitmap from BGR")
|
|
266
339
|
return null
|
|
267
340
|
}
|
|
268
341
|
bitmap
|
|
342
|
+
} catch (e: Exception) {
|
|
343
|
+
Log.e(TAG, "Error creating bitmap from BGR: ${e.message}", e)
|
|
344
|
+
null
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Create Bitmap from RGB byte array.
|
|
350
|
+
* RGB format: RGBRGBRGB... (3 bytes per pixel, no alpha)
|
|
351
|
+
* DEPRECATED: Use createBitmapFromBgr instead for OpenCV compatibility
|
|
352
|
+
*/
|
|
353
|
+
@Deprecated("Use createBitmapFromBgr for OpenCV compatibility")
|
|
354
|
+
private fun createBitmapFromRgb(rgbData: ByteArray, width: Int, height: Int): Bitmap? {
|
|
355
|
+
return try {
|
|
356
|
+
if (rgbData.isEmpty()) {
|
|
357
|
+
Log.e(TAG, "RGB data is empty")
|
|
358
|
+
return null
|
|
359
|
+
}
|
|
360
|
+
if (width <= 0 || height <= 0) {
|
|
361
|
+
Log.e(TAG, "Invalid dimensions: width=$width, height=$height")
|
|
362
|
+
return null
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
val expectedSize = width * height * 3
|
|
366
|
+
if (rgbData.size < expectedSize) {
|
|
367
|
+
Log.e(TAG, "RGB data too small: expected at least $expectedSize, got ${rgbData.size}")
|
|
368
|
+
return null
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Convert RGB to BGR first, then to ARGB for Bitmap
|
|
372
|
+
val bgrData = rgbToBgr(rgbData)
|
|
373
|
+
createBitmapFromBgr(bgrData, width, height)
|
|
269
374
|
} catch (e: Exception) {
|
|
270
375
|
Log.e(TAG, "Error creating bitmap from RGB: ${e.message}", e)
|
|
271
376
|
null
|