react-native-vision-camera-spoof-detector 1.1.0 → 1.1.11
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/.npmignore +16 -0
- package/README.md +103 -186
- package/android/CMakeLists.txt +29 -0
- package/android/build.gradle +26 -10
- package/android/fix-prefab.gradle +4 -0
- package/android/src/main/AndroidManifest.xml +1 -1
- package/android/src/main/cpp/CMakeLists.txt +18 -86
- package/android/src/main/cpp/FaceAntiSpoofJSI.cpp +80 -6
- package/android/src/main/cpp/cpp-adapter.cpp +9 -0
- package/android/src/main/java/com/faceantispoof/FaceAntiSpoofModule.kt +55 -103
- package/android/src/main/java/com/faceantispoof/FaceAntiSpoofPackage.kt +15 -8
- package/android/src/main/java/com/margelo/nitro/camera/spoofdetector/HybridSpoofDetectorFactory.kt +15 -0
- package/android/src/main/java/com/margelo/nitro/camera/spoofdetector/HybridSpoofDetectorOutput.kt +111 -0
- package/index.js +22 -43
- package/nitro.json +20 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/VisionCameraSpoofDetector+autolinking.cmake +81 -0
- package/nitrogen/generated/android/VisionCameraSpoofDetector+autolinking.gradle +27 -0
- package/nitrogen/generated/android/VisionCameraSpoofDetectorOnLoad.cpp +58 -0
- package/nitrogen/generated/android/VisionCameraSpoofDetectorOnLoad.hpp +34 -0
- package/nitrogen/generated/android/c++/JAntiSpoofingResult.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_AntiSpoofingResult.hpp +78 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__exception_ptr.hpp +76 -0
- package/nitrogen/generated/android/c++/JHybridSpoofDetectorFactorySpec.cpp +75 -0
- package/nitrogen/generated/android/c++/JHybridSpoofDetectorFactorySpec.hpp +63 -0
- package/nitrogen/generated/android/c++/JSize.hpp +61 -0
- package/nitrogen/generated/android/c++/JSpoofDetectorOutputOptions.hpp +91 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/spoofdetector/AntiSpoofingResult.kt +76 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/spoofdetector/Func_void_AntiSpoofingResult.kt +78 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/spoofdetector/Func_void_std__exception_ptr.kt +78 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/spoofdetector/HybridSpoofDetectorFactorySpec.kt +57 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/spoofdetector/Size.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/spoofdetector/SpoofDetectorOutputOptions.kt +65 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/spoofdetector/VisionCameraSpoofDetectorOnLoad.kt +35 -0
- package/nitrogen/generated/shared/c++/AntiSpoofingResult.hpp +103 -0
- package/nitrogen/generated/shared/c++/HybridSpoofDetectorFactorySpec.cpp +21 -0
- package/nitrogen/generated/shared/c++/HybridSpoofDetectorFactorySpec.hpp +67 -0
- package/nitrogen/generated/shared/c++/Size.hpp +87 -0
- package/nitrogen/generated/shared/c++/SpoofDetectorOutputOptions.hpp +98 -0
- package/package.json +10 -8
- package/src/specs/SpoofDetectorFactory.nitro.ts +23 -0
- package/android/src/main/assets/spoof_model_scale_2_7.tflite +0 -0
- package/android/src/main/assets/spoof_model_scale_4_0.tflite +0 -0
- /package/{android/src/main → lib}/assets/FaceAntiSpoofing.tflite +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#include <jni.h>
|
|
2
2
|
#include <jsi/jsi.h>
|
|
3
3
|
#include <string>
|
|
4
|
+
#include <vector>
|
|
5
|
+
#include <algorithm>
|
|
4
6
|
#include <android/log.h>
|
|
5
7
|
|
|
6
8
|
using namespace facebook;
|
|
@@ -8,18 +10,90 @@ using namespace facebook;
|
|
|
8
10
|
// Logging macro
|
|
9
11
|
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "FaceAntiSpoofJSI", __VA_ARGS__))
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
static JavaVM *javaVm = nullptr;
|
|
14
|
+
|
|
12
15
|
jsi::Value faceAntiSpoof(jsi::Runtime &runtime, const jsi::Value &frame) {
|
|
13
|
-
|
|
16
|
+
if (!frame.isObject()) {
|
|
17
|
+
throw jsi::JSError(runtime, "FaceAntiSpoof expected a Frame object");
|
|
18
|
+
}
|
|
14
19
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
auto frameObject = frame.asObject(runtime);
|
|
21
|
+
auto planesValue = frameObject.getProperty(runtime, "getPlanes").asObject(runtime)
|
|
22
|
+
.asFunction(runtime).call(runtime, frameObject);
|
|
23
|
+
auto planes = planesValue.asObject(runtime);
|
|
24
|
+
auto length = static_cast<size_t>(planes.getProperty(runtime, "length").asNumber());
|
|
25
|
+
if (length < 3) {
|
|
26
|
+
throw jsi::JSError(runtime, "FaceAntiSpoof expected Y, U and V planes");
|
|
27
|
+
}
|
|
19
28
|
|
|
29
|
+
std::vector<std::vector<uint8_t>> planeData(3);
|
|
30
|
+
jint rowStrides[3] = {};
|
|
31
|
+
jint pixelStrides[3] = {};
|
|
32
|
+
for (size_t index = 0; index < 3; index++) {
|
|
33
|
+
auto plane = planes.getProperty(runtime, jsi::Value(static_cast<double>(index))).asObject(runtime);
|
|
34
|
+
auto buffer = plane.getProperty(runtime, "getPixelBuffer").asObject(runtime)
|
|
35
|
+
.asFunction(runtime).call(runtime, plane).asObject(runtime);
|
|
36
|
+
auto arrayBuffer = std::move(buffer).getArrayBuffer(runtime);
|
|
37
|
+
auto *data = static_cast<const uint8_t *>(arrayBuffer.data(runtime));
|
|
38
|
+
planeData[index].assign(data, data + arrayBuffer.size(runtime));
|
|
39
|
+
rowStrides[index] = static_cast<jint>(plane.getProperty(runtime, "bytesPerRow").asNumber());
|
|
40
|
+
auto planeWidth = plane.getProperty(runtime, "width").asNumber();
|
|
41
|
+
pixelStrides[index] = std::max(1, static_cast<jint>(rowStrides[index] / planeWidth));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
JNIEnv *env = nullptr;
|
|
45
|
+
bool attached = false;
|
|
46
|
+
if (javaVm == nullptr) {
|
|
47
|
+
throw jsi::JSError(runtime, "FaceAntiSpoof Android runtime is unavailable");
|
|
48
|
+
}
|
|
49
|
+
if (javaVm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) == JNI_EDETACHED) {
|
|
50
|
+
if (javaVm->AttachCurrentThread(&env, nullptr) != JNI_OK) {
|
|
51
|
+
throw jsi::JSError(runtime, "FaceAntiSpoof could not attach to Android runtime");
|
|
52
|
+
}
|
|
53
|
+
attached = true;
|
|
54
|
+
}
|
|
55
|
+
auto moduleClass = env->FindClass("com/faceantispoof/FaceAntiSpoofModule");
|
|
56
|
+
auto process = env->GetStaticMethodID(moduleClass, "processFrameFromJSI", "([[BII[I[I)[D");
|
|
57
|
+
auto javaPlanes = env->NewObjectArray(3, env->FindClass("[B"), nullptr);
|
|
58
|
+
auto javaRowStrides = env->NewIntArray(3);
|
|
59
|
+
auto javaPixelStrides = env->NewIntArray(3);
|
|
60
|
+
env->SetIntArrayRegion(javaRowStrides, 0, 3, rowStrides);
|
|
61
|
+
env->SetIntArrayRegion(javaPixelStrides, 0, 3, pixelStrides);
|
|
62
|
+
for (int index = 0; index < 3; index++) {
|
|
63
|
+
auto bytes = env->NewByteArray(static_cast<jsize>(planeData[index].size()));
|
|
64
|
+
env->SetByteArrayRegion(bytes, 0, static_cast<jsize>(planeData[index].size()),
|
|
65
|
+
reinterpret_cast<const jbyte *>(planeData[index].data()));
|
|
66
|
+
env->SetObjectArrayElement(javaPlanes, index, bytes);
|
|
67
|
+
env->DeleteLocalRef(bytes);
|
|
68
|
+
}
|
|
69
|
+
auto values = static_cast<jdoubleArray>(env->CallStaticObjectMethod(
|
|
70
|
+
moduleClass, process, javaPlanes, static_cast<jint>(frameObject.getProperty(runtime, "width").asNumber()),
|
|
71
|
+
static_cast<jint>(frameObject.getProperty(runtime, "height").asNumber()),
|
|
72
|
+
javaRowStrides, javaPixelStrides));
|
|
73
|
+
jdouble output[5] = {};
|
|
74
|
+
if (values != nullptr) env->GetDoubleArrayRegion(values, 0, 5, output);
|
|
75
|
+
env->DeleteLocalRef(values);
|
|
76
|
+
env->DeleteLocalRef(javaPixelStrides);
|
|
77
|
+
env->DeleteLocalRef(javaRowStrides);
|
|
78
|
+
env->DeleteLocalRef(javaPlanes);
|
|
79
|
+
env->DeleteLocalRef(moduleClass);
|
|
80
|
+
if (attached) javaVm->DetachCurrentThread();
|
|
81
|
+
|
|
82
|
+
jsi::Object result(runtime);
|
|
83
|
+
result.setProperty(runtime, "isLive", output[0] != 0.0);
|
|
84
|
+
result.setProperty(runtime, "label", output[0] != 0.0 ? "Live Face" : "Spoof Face");
|
|
85
|
+
result.setProperty(runtime, "neuralNetworkScore", output[1]);
|
|
86
|
+
result.setProperty(runtime, "laplacianScore", output[2]);
|
|
87
|
+
result.setProperty(runtime, "combinedScore", output[3]);
|
|
88
|
+
result.setProperty(runtime, "confidence", output[4]);
|
|
20
89
|
return result;
|
|
21
90
|
}
|
|
22
91
|
|
|
92
|
+
extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *) {
|
|
93
|
+
javaVm = vm;
|
|
94
|
+
return JNI_VERSION_1_6;
|
|
95
|
+
}
|
|
96
|
+
|
|
23
97
|
// JSI registration function
|
|
24
98
|
extern "C"
|
|
25
99
|
JNIEXPORT void JNICALL
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#include "VisionCameraSpoofDetectorOnLoad.hpp"
|
|
2
|
+
#include <fbjni/fbjni.h>
|
|
3
|
+
#include <jni.h>
|
|
4
|
+
|
|
5
|
+
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
|
|
6
|
+
return facebook::jni::initialize(vm, []() {
|
|
7
|
+
margelo::nitro::camera::spoofdetector::registerAllNatives();
|
|
8
|
+
});
|
|
9
|
+
}
|
|
@@ -13,17 +13,27 @@ class FaceAntiSpoofModule(reactContext: ReactApplicationContext) : ReactContextB
|
|
|
13
13
|
|
|
14
14
|
companion object {
|
|
15
15
|
const val NAME = "FaceAntiSpoof"
|
|
16
|
+
private var activeModule: FaceAntiSpoofModule? = null
|
|
17
|
+
|
|
18
|
+
@JvmStatic
|
|
19
|
+
fun processFrameFromJSI(
|
|
20
|
+
planes: Array<ByteArray>,
|
|
21
|
+
width: Int,
|
|
22
|
+
height: Int,
|
|
23
|
+
rowStrides: IntArray,
|
|
24
|
+
pixelStrides: IntArray
|
|
25
|
+
): DoubleArray {
|
|
26
|
+
val module = activeModule
|
|
27
|
+
?: return doubleArrayOf(0.0, 0.0, 0.0, 0.0, 0.0)
|
|
28
|
+
return module.processNativeFrame(planes, width, height, rowStrides, pixelStrides)
|
|
29
|
+
}
|
|
16
30
|
}
|
|
17
31
|
|
|
18
32
|
private var faceAntiSpoofing: FaceAntiSpoofingAdvanced? = null
|
|
19
33
|
private var isModuleInitialized = false
|
|
20
34
|
|
|
21
35
|
init {
|
|
22
|
-
|
|
23
|
-
System.loadLibrary("faceantispoof")
|
|
24
|
-
} catch (e: Exception) {
|
|
25
|
-
android.util.Log.e("FaceAntiSpoof", "Error loading native library", e)
|
|
26
|
-
}
|
|
36
|
+
activeModule = this
|
|
27
37
|
|
|
28
38
|
try {
|
|
29
39
|
// Initialize face anti-spoofing with app assets
|
|
@@ -35,57 +45,6 @@ class FaceAntiSpoofModule(reactContext: ReactApplicationContext) : ReactContextB
|
|
|
35
45
|
isModuleInitialized = false
|
|
36
46
|
}
|
|
37
47
|
|
|
38
|
-
try {
|
|
39
|
-
tryRegisterJSI()
|
|
40
|
-
} catch (e: Exception) {
|
|
41
|
-
android.util.Log.e("FaceAntiSpoof", "Error starting JSI registration", e)
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
private fun tryRegisterJSI() {
|
|
46
|
-
try {
|
|
47
|
-
Thread {
|
|
48
|
-
try {
|
|
49
|
-
Thread.sleep(500)
|
|
50
|
-
val holder = reactApplicationContext.javaScriptContextHolder ?: return@Thread
|
|
51
|
-
|
|
52
|
-
// Try nativePointer
|
|
53
|
-
try {
|
|
54
|
-
val field = holder.javaClass.getDeclaredField("nativePointer")
|
|
55
|
-
field.isAccessible = true
|
|
56
|
-
val jsContextPtr = field.getLong(holder)
|
|
57
|
-
if (jsContextPtr != 0L) {
|
|
58
|
-
try {
|
|
59
|
-
registerJSIFunction(jsContextPtr)
|
|
60
|
-
} catch (e: Exception) {
|
|
61
|
-
android.util.Log.d("FaceAntiSpoof", "JSI registration not available: ${e.message}")
|
|
62
|
-
}
|
|
63
|
-
return@Thread
|
|
64
|
-
}
|
|
65
|
-
} catch (_: Exception) { }
|
|
66
|
-
|
|
67
|
-
// Try mContext
|
|
68
|
-
try {
|
|
69
|
-
val field = holder.javaClass.getDeclaredField("mContext")
|
|
70
|
-
field.isAccessible = true
|
|
71
|
-
val jsContextPtr = field.getLong(holder)
|
|
72
|
-
if (jsContextPtr != 0L) {
|
|
73
|
-
try {
|
|
74
|
-
registerJSIFunction(jsContextPtr)
|
|
75
|
-
} catch (e: Exception) {
|
|
76
|
-
android.util.Log.d("FaceAntiSpoof", "JSI registration not available: ${e.message}")
|
|
77
|
-
}
|
|
78
|
-
return@Thread
|
|
79
|
-
}
|
|
80
|
-
} catch (_: Exception) { }
|
|
81
|
-
|
|
82
|
-
} catch (e: Exception) {
|
|
83
|
-
android.util.Log.d("FaceAntiSpoof", "Error in JSI registration thread: ${e.message}")
|
|
84
|
-
}
|
|
85
|
-
}.start()
|
|
86
|
-
} catch (e: Exception) {
|
|
87
|
-
android.util.Log.d("FaceAntiSpoof", "Failed to start thread for JSI registration: ${e.message}")
|
|
88
|
-
}
|
|
89
48
|
}
|
|
90
49
|
|
|
91
50
|
override fun getName(): String = NAME
|
|
@@ -181,6 +140,41 @@ class FaceAntiSpoofModule(reactContext: ReactApplicationContext) : ReactContextB
|
|
|
181
140
|
}
|
|
182
141
|
}
|
|
183
142
|
|
|
143
|
+
@Synchronized
|
|
144
|
+
private fun processNativeFrame(
|
|
145
|
+
planes: Array<ByteArray>,
|
|
146
|
+
width: Int,
|
|
147
|
+
height: Int,
|
|
148
|
+
rowStrides: IntArray,
|
|
149
|
+
pixelStrides: IntArray
|
|
150
|
+
): DoubleArray {
|
|
151
|
+
if (!isModuleInitialized || faceAntiSpoofing == null || planes.size < 3) {
|
|
152
|
+
return doubleArrayOf(0.0, 0.0, 0.0, 0.0, 0.0)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return try {
|
|
156
|
+
val result = faceAntiSpoofing!!.advancedAntiSpoofing(
|
|
157
|
+
planes,
|
|
158
|
+
width,
|
|
159
|
+
height,
|
|
160
|
+
rowStrides,
|
|
161
|
+
pixelStrides
|
|
162
|
+
)
|
|
163
|
+
val isLive = result.neuralNetworkScore < FaceAntiSpoofingAdvanced.BASE_THRESHOLD &&
|
|
164
|
+
result.laplacianScore > FaceAntiSpoofingAdvanced.LAPLACIAN_THRESHOLD
|
|
165
|
+
doubleArrayOf(
|
|
166
|
+
if (isLive) 1.0 else 0.0,
|
|
167
|
+
result.neuralNetworkScore.toDouble(),
|
|
168
|
+
result.laplacianScore.toDouble(),
|
|
169
|
+
result.combinedScore.toDouble(),
|
|
170
|
+
result.confidence.toDouble()
|
|
171
|
+
)
|
|
172
|
+
} catch (error: Exception) {
|
|
173
|
+
android.util.Log.e("FaceAntiSpoof", "Native frame processing failed", error)
|
|
174
|
+
doubleArrayOf(0.0, 0.0, 0.0, 0.0, 0.0)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
184
178
|
@ReactMethod
|
|
185
179
|
fun isAvailable(promise: Promise) {
|
|
186
180
|
try {
|
|
@@ -218,51 +212,9 @@ class FaceAntiSpoofModule(reactContext: ReactApplicationContext) : ReactContextB
|
|
|
218
212
|
|
|
219
213
|
@ReactMethod
|
|
220
214
|
fun install(promise: Promise) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (holder != null) {
|
|
225
|
-
// Try nativePointer
|
|
226
|
-
try {
|
|
227
|
-
val f = holder.javaClass.getDeclaredField("nativePointer")
|
|
228
|
-
f.isAccessible = true
|
|
229
|
-
val ptr = f.getLong(holder)
|
|
230
|
-
if (ptr != 0L) {
|
|
231
|
-
try {
|
|
232
|
-
registerJSIFunction(ptr)
|
|
233
|
-
promise.resolve(true)
|
|
234
|
-
return
|
|
235
|
-
} catch (e: Exception) {
|
|
236
|
-
android.util.Log.d("FaceAntiSpoof", "JSI not available: ${e.message}")
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
} catch (_: Exception) {}
|
|
240
|
-
|
|
241
|
-
// Try mContext
|
|
242
|
-
try {
|
|
243
|
-
val f = holder.javaClass.getDeclaredField("mContext")
|
|
244
|
-
f.isAccessible = true
|
|
245
|
-
val ptr = f.getLong(holder)
|
|
246
|
-
if (ptr != 0L) {
|
|
247
|
-
try {
|
|
248
|
-
registerJSIFunction(ptr)
|
|
249
|
-
promise.resolve(true)
|
|
250
|
-
return
|
|
251
|
-
} catch (e: Exception) {
|
|
252
|
-
android.util.Log.d("FaceAntiSpoof", "JSI not available: ${e.message}")
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
} catch (_: Exception) {}
|
|
256
|
-
|
|
257
|
-
promise.resolve(false)
|
|
258
|
-
} else {
|
|
259
|
-
promise.resolve(false)
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
} catch (e: Exception) {
|
|
263
|
-
android.util.Log.e("FaceAntiSpoof", "install() error", e)
|
|
264
|
-
promise.reject("INSTALL_ERROR", "Failed to install JSI function", e)
|
|
265
|
-
}
|
|
215
|
+
// VisionCamera 5 uses the Nitro CameraOutput. The old reflective JSI
|
|
216
|
+
// installer targets a native library that is no longer part of this module.
|
|
217
|
+
promise.resolve(false)
|
|
266
218
|
}
|
|
267
219
|
|
|
268
220
|
@ReactMethod
|
|
@@ -279,6 +231,8 @@ class FaceAntiSpoofModule(reactContext: ReactApplicationContext) : ReactContextB
|
|
|
279
231
|
}
|
|
280
232
|
}
|
|
281
233
|
|
|
234
|
+
@Suppress("DEPRECATION")
|
|
235
|
+
@Deprecated("React Native deprecated this lifecycle callback; retained for compatibility.")
|
|
282
236
|
override fun onCatalystInstanceDestroy() {
|
|
283
237
|
// Cleanup when React Native is shutting down
|
|
284
238
|
try {
|
|
@@ -292,6 +246,4 @@ class FaceAntiSpoofModule(reactContext: ReactApplicationContext) : ReactContextB
|
|
|
292
246
|
super.onCatalystInstanceDestroy()
|
|
293
247
|
}
|
|
294
248
|
|
|
295
|
-
// Native JSI registration - optional, may not be available
|
|
296
|
-
private external fun registerJSIFunction(jsContext: Long)
|
|
297
249
|
}
|
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
package com.
|
|
1
|
+
package com.margelo.nitro.camera.spoofdetector
|
|
2
2
|
|
|
3
|
-
import com.facebook.react.
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
4
|
import com.facebook.react.bridge.NativeModule
|
|
5
5
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
-
import com.facebook.react.
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
7
|
+
import com.faceantispoof.FaceAntiSpoofModule
|
|
7
8
|
|
|
8
|
-
class FaceAntiSpoofPackage :
|
|
9
|
-
override fun
|
|
10
|
-
|
|
9
|
+
class FaceAntiSpoofPackage : BaseReactPackage() {
|
|
10
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? =
|
|
11
|
+
if (name == FaceAntiSpoofModule.NAME) FaceAntiSpoofModule(reactContext) else null
|
|
11
12
|
|
|
12
|
-
override fun
|
|
13
|
-
|
|
13
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider {
|
|
14
|
+
HashMap()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
companion object {
|
|
18
|
+
init {
|
|
19
|
+
VisionCameraSpoofDetectorOnLoad.initializeNative()
|
|
20
|
+
}
|
|
14
21
|
}
|
|
15
22
|
}
|
package/android/src/main/java/com/margelo/nitro/camera/spoofdetector/HybridSpoofDetectorFactory.kt
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
package com.margelo.nitro.camera.spoofdetector
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.Keep
|
|
4
|
+
import com.facebook.proguard.annotations.DoNotStrip
|
|
5
|
+
import com.margelo.nitro.camera.HybridCameraOutputSpec
|
|
6
|
+
|
|
7
|
+
@DoNotStrip
|
|
8
|
+
@Keep
|
|
9
|
+
class HybridSpoofDetectorFactory : HybridSpoofDetectorFactorySpec() {
|
|
10
|
+
@DoNotStrip
|
|
11
|
+
@Keep
|
|
12
|
+
override fun createSpoofDetectorOutput(options: SpoofDetectorOutputOptions): HybridCameraOutputSpec {
|
|
13
|
+
return HybridSpoofDetectorOutput(options)
|
|
14
|
+
}
|
|
15
|
+
}
|
package/android/src/main/java/com/margelo/nitro/camera/spoofdetector/HybridSpoofDetectorOutput.kt
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
package com.margelo.nitro.camera.spoofdetector
|
|
2
|
+
|
|
3
|
+
import androidx.camera.core.ImageAnalysis
|
|
4
|
+
import androidx.camera.core.ImageProxy
|
|
5
|
+
import androidx.camera.core.resolutionselector.ResolutionSelector
|
|
6
|
+
import androidx.camera.core.resolutionselector.ResolutionStrategy
|
|
7
|
+
import com.margelo.nitro.NitroModules
|
|
8
|
+
import com.margelo.nitro.camera.CameraOrientation
|
|
9
|
+
import com.margelo.nitro.camera.HybridCameraOutputSpec
|
|
10
|
+
import com.margelo.nitro.camera.MediaType
|
|
11
|
+
import com.margelo.nitro.camera.MirrorMode
|
|
12
|
+
import com.margelo.nitro.camera.extensions.setAllowDroppingLateFrames
|
|
13
|
+
import com.margelo.nitro.camera.extensions.surfaceRotation
|
|
14
|
+
import com.margelo.nitro.camera.public.NativeCameraOutput
|
|
15
|
+
import com.faceantispoof.FaceAntiSpoofingAdvanced
|
|
16
|
+
import java.util.concurrent.Executors
|
|
17
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
18
|
+
|
|
19
|
+
class HybridSpoofDetectorOutput(
|
|
20
|
+
private val options: SpoofDetectorOutputOptions,
|
|
21
|
+
) : HybridCameraOutputSpec(), ImageAnalysis.Analyzer, NativeCameraOutput {
|
|
22
|
+
override val mediaType: MediaType = MediaType.VIDEO
|
|
23
|
+
override val mirrorMode: MirrorMode = MirrorMode.AUTO
|
|
24
|
+
override var outputOrientation: CameraOrientation = CameraOrientation.UP
|
|
25
|
+
set(value) {
|
|
26
|
+
field = value
|
|
27
|
+
imageAnalysis?.targetRotation = value.surfaceRotation
|
|
28
|
+
}
|
|
29
|
+
override val currentResolution: com.margelo.nitro.camera.Size?
|
|
30
|
+
get() = null
|
|
31
|
+
|
|
32
|
+
private val executor = Executors.newSingleThreadExecutor()
|
|
33
|
+
private val busy = AtomicBoolean(false)
|
|
34
|
+
private val disposed = AtomicBoolean(false)
|
|
35
|
+
private val detector = FaceAntiSpoofingAdvanced(
|
|
36
|
+
NitroModules.applicationContext?.assets ?: error("FaceAntiSpoof: no application context")
|
|
37
|
+
)
|
|
38
|
+
private var imageAnalysis: ImageAnalysis? = null
|
|
39
|
+
|
|
40
|
+
override fun createUseCase(
|
|
41
|
+
mirrorMode: MirrorMode,
|
|
42
|
+
config: NativeCameraOutput.Config,
|
|
43
|
+
): NativeCameraOutput.PreparedUseCase {
|
|
44
|
+
val target = options.targetResolution
|
|
45
|
+
val selector = target?.let {
|
|
46
|
+
ResolutionSelector.Builder()
|
|
47
|
+
.setResolutionStrategy(
|
|
48
|
+
ResolutionStrategy(
|
|
49
|
+
android.util.Size(it.width.toInt(), it.height.toInt()),
|
|
50
|
+
ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER,
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
.build()
|
|
54
|
+
}
|
|
55
|
+
val analysis = ImageAnalysis.Builder()
|
|
56
|
+
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888)
|
|
57
|
+
.setOutputImageRotationEnabled(false)
|
|
58
|
+
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
|
59
|
+
.setAllowDroppingLateFrames(true)
|
|
60
|
+
.apply { if (selector != null) setResolutionSelector(selector) }
|
|
61
|
+
.build()
|
|
62
|
+
return NativeCameraOutput.PreparedUseCase(analysis) {
|
|
63
|
+
imageAnalysis = analysis
|
|
64
|
+
analysis.setAnalyzer(executor, this)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
override fun analyze(image: ImageProxy) {
|
|
69
|
+
if (disposed.get() || !busy.compareAndSet(false, true)) {
|
|
70
|
+
image.close()
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
val sourcePlanes = image.planes
|
|
75
|
+
val planes = Array(3) { index ->
|
|
76
|
+
val buffer = sourcePlanes[index].buffer.duplicate()
|
|
77
|
+
ByteArray(buffer.remaining()).also(buffer::get)
|
|
78
|
+
}
|
|
79
|
+
val rowStrides = IntArray(3) { sourcePlanes[it].rowStride }
|
|
80
|
+
val pixelStrides = IntArray(3) { sourcePlanes[it].pixelStride.coerceAtLeast(1) }
|
|
81
|
+
val result = detector.advancedAntiSpoofing(
|
|
82
|
+
planes, image.width, image.height, rowStrides, pixelStrides
|
|
83
|
+
)
|
|
84
|
+
val isLive = result.neuralNetworkScore < FaceAntiSpoofingAdvanced.BASE_THRESHOLD &&
|
|
85
|
+
result.laplacianScore > FaceAntiSpoofingAdvanced.LAPLACIAN_THRESHOLD
|
|
86
|
+
options.onResult.invoke(
|
|
87
|
+
AntiSpoofingResult(
|
|
88
|
+
isLive,
|
|
89
|
+
if (isLive) "Live Face" else "Spoof Face",
|
|
90
|
+
result.neuralNetworkScore.toDouble(),
|
|
91
|
+
result.laplacianScore.toDouble(),
|
|
92
|
+
result.combinedScore.toDouble(),
|
|
93
|
+
result.confidence.toDouble(),
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
} catch (error: Throwable) {
|
|
97
|
+
options.onError?.invoke(error)
|
|
98
|
+
} finally {
|
|
99
|
+
image.close()
|
|
100
|
+
busy.set(false)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
override fun dispose() {
|
|
105
|
+
disposed.set(true)
|
|
106
|
+
imageAnalysis?.clearAnalyzer()
|
|
107
|
+
detector.close()
|
|
108
|
+
executor.shutdownNow()
|
|
109
|
+
super.dispose()
|
|
110
|
+
}
|
|
111
|
+
}
|
package/index.js
CHANGED
|
@@ -1,50 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { useMemo, useRef } from 'react';
|
|
2
|
+
import { NativeModules } from 'react-native';
|
|
3
|
+
import { NitroModules } from 'react-native-nitro-modules';
|
|
4
4
|
|
|
5
5
|
const { FaceAntiSpoof } = NativeModules;
|
|
6
6
|
|
|
7
|
-
const processFrameOnRN = (frameData, width, height, onResult) => {
|
|
8
|
-
FaceAntiSpoof.processFrame(frameData.planes, width, height, frameData.rowStrides, frameData.pixelStrides)
|
|
9
|
-
.then(onResult)
|
|
10
|
-
.catch((error) => console.error('[FaceAntiSpoof] Frame processing failed:', error));
|
|
11
|
-
};
|
|
12
|
-
|
|
13
7
|
export const useFaceAntiSpoofFrameOutput = ({ onResult, ...options } = {}) => {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
return useFrameOutput({
|
|
36
|
-
...options,
|
|
37
|
-
pixelFormat: 'yuv',
|
|
38
|
-
targetResolution: { width: 256, height: 256 },
|
|
39
|
-
onFrame,
|
|
40
|
-
});
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
// Kept as a named export for source compatibility. VisionCamera 5 applications
|
|
44
|
-
// must use useFaceAntiSpoofFrameOutput because the old plugin API was removed.
|
|
45
|
-
export const faceAntiSpoofFrameProcessor = function () {
|
|
46
|
-
'worklet';
|
|
47
|
-
return null;
|
|
8
|
+
const resultRef = useRef(onResult);
|
|
9
|
+
resultRef.current = onResult;
|
|
10
|
+
const errorRef = useRef(options.onError);
|
|
11
|
+
errorRef.current = options.onError;
|
|
12
|
+
const factory = useMemo(
|
|
13
|
+
() => NitroModules.createHybridObject('SpoofDetectorFactory'),
|
|
14
|
+
[],
|
|
15
|
+
);
|
|
16
|
+
return useMemo(
|
|
17
|
+
() => factory.createSpoofDetectorOutput({
|
|
18
|
+
onResult(result) {
|
|
19
|
+
resultRef.current?.(result);
|
|
20
|
+
},
|
|
21
|
+
onError(error) {
|
|
22
|
+
errorRef.current?.(error);
|
|
23
|
+
},
|
|
24
|
+
}),
|
|
25
|
+
[factory],
|
|
26
|
+
);
|
|
48
27
|
};
|
|
49
28
|
|
|
50
29
|
export default FaceAntiSpoof;
|
package/nitro.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://nitro.margelo.com/nitro.schema.json",
|
|
3
|
+
"cxxNamespace": ["camera", "spoofdetector"],
|
|
4
|
+
"ios": {
|
|
5
|
+
"iosModuleName": "VisionCameraSpoofDetector"
|
|
6
|
+
},
|
|
7
|
+
"android": {
|
|
8
|
+
"androidNamespace": ["camera", "spoofdetector"],
|
|
9
|
+
"androidCxxLibName": "VisionCameraSpoofDetector"
|
|
10
|
+
},
|
|
11
|
+
"autolinking": {
|
|
12
|
+
"SpoofDetectorFactory": {
|
|
13
|
+
"android": {
|
|
14
|
+
"language": "kotlin",
|
|
15
|
+
"implementationClassName": "HybridSpoofDetectorFactory"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"ignorePaths": ["**/node_modules"]
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
** linguist-generated=true
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#
|
|
2
|
+
# VisionCameraSpoofDetector+autolinking.cmake
|
|
3
|
+
# This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
# https://github.com/mrousavy/nitro
|
|
5
|
+
# Copyright © Marc Rousavy @ Margelo
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
# This is a CMake file that adds all files generated by Nitrogen
|
|
9
|
+
# to the current CMake project.
|
|
10
|
+
#
|
|
11
|
+
# To use it, add this to your CMakeLists.txt:
|
|
12
|
+
# ```cmake
|
|
13
|
+
# include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/VisionCameraSpoofDetector+autolinking.cmake)
|
|
14
|
+
# ```
|
|
15
|
+
|
|
16
|
+
# Define a flag to check if we are building properly
|
|
17
|
+
add_definitions(-DBUILDING_VISIONCAMERASPOOFDETECTOR_WITH_GENERATED_CMAKE_PROJECT)
|
|
18
|
+
|
|
19
|
+
# Enable Raw Props parsing in react-native (for Nitro Views)
|
|
20
|
+
add_definitions(-DRN_SERIALIZABLE_STATE)
|
|
21
|
+
|
|
22
|
+
# Add all headers that were generated by Nitrogen
|
|
23
|
+
include_directories(
|
|
24
|
+
"../nitrogen/generated/shared/c++"
|
|
25
|
+
"../nitrogen/generated/android/c++"
|
|
26
|
+
"../nitrogen/generated/android/"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Add all .cpp sources that were generated by Nitrogen
|
|
30
|
+
target_sources(
|
|
31
|
+
# CMake project name (Android C++ library name)
|
|
32
|
+
VisionCameraSpoofDetector PRIVATE
|
|
33
|
+
# Autolinking Setup
|
|
34
|
+
../nitrogen/generated/android/VisionCameraSpoofDetectorOnLoad.cpp
|
|
35
|
+
# Shared Nitrogen C++ sources
|
|
36
|
+
../nitrogen/generated/shared/c++/HybridSpoofDetectorFactorySpec.cpp
|
|
37
|
+
# Android-specific Nitrogen C++ sources
|
|
38
|
+
../nitrogen/generated/android/c++/JHybridSpoofDetectorFactorySpec.cpp
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# From node_modules/react-native/ReactAndroid/cmake-utils/folly-flags.cmake
|
|
42
|
+
# Used in node_modules/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake
|
|
43
|
+
target_compile_definitions(
|
|
44
|
+
VisionCameraSpoofDetector PRIVATE
|
|
45
|
+
-DFOLLY_NO_CONFIG=1
|
|
46
|
+
-DFOLLY_HAVE_CLOCK_GETTIME=1
|
|
47
|
+
-DFOLLY_USE_LIBCPP=1
|
|
48
|
+
-DFOLLY_CFG_NO_COROUTINES=1
|
|
49
|
+
-DFOLLY_MOBILE=1
|
|
50
|
+
-DFOLLY_HAVE_RECVMMSG=1
|
|
51
|
+
-DFOLLY_HAVE_PTHREAD=1
|
|
52
|
+
# Once we target android-23 above, we can comment
|
|
53
|
+
# the following line. NDK uses GNU style stderror_r() after API 23.
|
|
54
|
+
-DFOLLY_HAVE_XSI_STRERROR_R=1
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Add all libraries required by the generated specs
|
|
58
|
+
find_package(fbjni REQUIRED) # <-- Used for communication between Java <-> C++
|
|
59
|
+
find_package(ReactAndroid REQUIRED) # <-- Used to set up React Native bindings (e.g. CallInvoker/TurboModule)
|
|
60
|
+
find_package(react-native-nitro-modules REQUIRED) # <-- Used to create all HybridObjects and use the Nitro core library
|
|
61
|
+
|
|
62
|
+
# Link all libraries together
|
|
63
|
+
target_link_libraries(
|
|
64
|
+
VisionCameraSpoofDetector
|
|
65
|
+
fbjni::fbjni # <-- Facebook C++ JNI helpers
|
|
66
|
+
ReactAndroid::jsi # <-- RN: JSI
|
|
67
|
+
react-native-nitro-modules::NitroModules # <-- NitroModules Core :)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Link react-native (different prefab between RN 0.75 and RN 0.76)
|
|
71
|
+
if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 76)
|
|
72
|
+
target_link_libraries(
|
|
73
|
+
VisionCameraSpoofDetector
|
|
74
|
+
ReactAndroid::reactnative # <-- RN: Native Modules umbrella prefab
|
|
75
|
+
)
|
|
76
|
+
else()
|
|
77
|
+
target_link_libraries(
|
|
78
|
+
VisionCameraSpoofDetector
|
|
79
|
+
ReactAndroid::react_nativemodule_core # <-- RN: TurboModules Core
|
|
80
|
+
)
|
|
81
|
+
endif()
|