react-native-vision-camera-spoof-detector 1.0.27 → 1.1.0
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/README.md +54 -27
- package/android/build.gradle +3 -5
- package/android/src/main/java/com/faceantispoof/FaceAntiSpoofModule.kt +55 -0
- package/android/src/main/java/com/faceantispoof/FaceAntiSpoofPackage.kt +2 -24
- package/android/src/main/java/com/faceantispoof/FaceAntiSpoofingAdvanced.kt +58 -1
- package/index.d.ts +14 -1
- package/index.js +39 -51
- package/package.json +4 -7
- package/react-native-vision-camera-spoof-detector.podspec +4 -4
- package/.gitattributes +0 -2
- package/.npmignore +0 -99
- package/CONTRIBUTING.md +0 -280
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/checksums/sha1-checksums.bin +0 -0
- package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.9/gc.properties +0 -0
- package/android/.gradle/9.2.0/checksums/checksums.lock +0 -0
- package/android/.gradle/9.2.0/checksums/md5-checksums.bin +0 -0
- package/android/.gradle/9.2.0/checksums/sha1-checksums.bin +0 -0
- package/android/.gradle/9.2.0/fileChanges/last-build.bin +0 -0
- package/android/.gradle/9.2.0/fileHashes/fileHashes.bin +0 -0
- package/android/.gradle/9.2.0/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/9.2.0/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/build/reports/problems/problems-report.html +0 -659
- package/android/src/main/java/com/faceantispoof/FaceAntiSpoofFrameProcessor.kt +0 -198
package/README.md
CHANGED
|
@@ -20,9 +20,11 @@ High-performance face anti-spoofing and liveness detection module for React Nati
|
|
|
20
20
|
## 📋 Requirements
|
|
21
21
|
|
|
22
22
|
- React Native >= 0.60.0
|
|
23
|
-
-
|
|
24
|
-
- react-native-reanimated
|
|
25
|
-
- react-native-worklets
|
|
23
|
+
- React Native Vision Camera `^4.6.4` or `^5.0.0`
|
|
24
|
+
- `react-native-reanimated` `^3.0.0`
|
|
25
|
+
- `react-native-worklets` `^0.12.0`
|
|
26
|
+
- `react-native-vision-camera-worklets` `^5.0.0`
|
|
27
|
+
- iOS 11.0 or later
|
|
26
28
|
- react-native-vision-camera-face-detector (optional, for enhanced features)
|
|
27
29
|
|
|
28
30
|
## 📦 Installation
|
|
@@ -38,12 +40,31 @@ yarn add react-native-vision-camera-spoof-detector
|
|
|
38
40
|
### Step 2: Install peer dependencies
|
|
39
41
|
|
|
40
42
|
```bash
|
|
41
|
-
npm install react-native-vision-camera react-native-reanimated react-native-worklets-
|
|
43
|
+
npm install react-native-vision-camera react-native-reanimated react-native-worklets react-native-vision-camera-worklets
|
|
42
44
|
# or
|
|
43
|
-
yarn add react-native-vision-camera react-native-reanimated react-native-worklets-
|
|
45
|
+
yarn add react-native-vision-camera react-native-reanimated react-native-worklets react-native-vision-camera-worklets
|
|
44
46
|
```
|
|
45
47
|
|
|
46
|
-
### Step 3: Configure
|
|
48
|
+
### Step 3: Configure iOS
|
|
49
|
+
|
|
50
|
+
Install the CocoaPods dependencies from the iOS directory:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
cd ios
|
|
54
|
+
pod install
|
|
55
|
+
cd ..
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Add a camera usage description to `ios/<YourApp>/Info.plist`:
|
|
59
|
+
|
|
60
|
+
```xml
|
|
61
|
+
<key>NSCameraUsageDescription</key>
|
|
62
|
+
<string>This app uses the camera for face liveness verification.</string>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Open the generated `.xcworkspace` in Xcode, or run the app with the React Native CLI. The package pod bundles `FaceAntiSpoofing.tflite` and declares its `TensorFlowLiteSwift` and `VisionCamera` dependencies automatically. No manual model copy is required.
|
|
66
|
+
|
|
67
|
+
### Step 4: Configure Android (if not auto-linked)
|
|
47
68
|
|
|
48
69
|
Add to `android/app/build.gradle`:
|
|
49
70
|
|
|
@@ -53,7 +74,7 @@ dependencies {
|
|
|
53
74
|
}
|
|
54
75
|
```
|
|
55
76
|
|
|
56
|
-
### Step
|
|
77
|
+
### Step 5: Link native module (for React Native < 0.60)
|
|
57
78
|
|
|
58
79
|
```bash
|
|
59
80
|
react-native link react-native-vision-camera-spoof-detector
|
|
@@ -61,24 +82,31 @@ react-native link react-native-vision-camera-spoof-detector
|
|
|
61
82
|
|
|
62
83
|
## 🚀 Quick Start
|
|
63
84
|
|
|
85
|
+
> **Important:** Request camera permission before rendering `Camera`, and call `initializeFaceAntiSpoof()` once before the frame processor starts. The frame processor returns `null` until native initialization or a result is available. Native inference is throttled and may return the most recent result while another frame is being processed.
|
|
86
|
+
|
|
64
87
|
### Simple Usage
|
|
65
88
|
|
|
66
89
|
```javascript
|
|
67
90
|
import React, { useEffect, useState } from 'react';
|
|
68
91
|
import { StyleSheet, Text, View } from 'react-native';
|
|
69
|
-
import { Camera, useCameraDevices, useFrameProcessor } from 'react-native-vision-camera';
|
|
92
|
+
import { Camera, useCameraDevices, useCameraPermission, useFrameProcessor } from 'react-native-vision-camera';
|
|
70
93
|
import { faceAntiSpoofFrameProcessor, initializeFaceAntiSpoof } from 'react-native-vision-camera-spoof-detector';
|
|
71
94
|
import { runOnJS } from 'react-native-reanimated';
|
|
72
95
|
|
|
73
96
|
export default function App() {
|
|
74
97
|
const devices = useCameraDevices();
|
|
75
98
|
const device = devices.front;
|
|
99
|
+
const { hasPermission, requestPermission } = useCameraPermission();
|
|
76
100
|
const [spoofResult, setSpoofResult] = useState(null);
|
|
77
101
|
|
|
78
102
|
useEffect(() => {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
103
|
+
if (!hasPermission) requestPermission();
|
|
104
|
+
}, [hasPermission, requestPermission]);
|
|
105
|
+
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
initializeFaceAntiSpoof()
|
|
108
|
+
.then((success) => console.log('FaceAntiSpoof initialized:', success))
|
|
109
|
+
.catch((error) => console.error('FaceAntiSpoof initialization failed:', error));
|
|
82
110
|
}, []);
|
|
83
111
|
|
|
84
112
|
const frameProcessor = useFrameProcessor((frame) => {
|
|
@@ -89,7 +117,7 @@ export default function App() {
|
|
|
89
117
|
}
|
|
90
118
|
}, []);
|
|
91
119
|
|
|
92
|
-
if (device == null) return <Text>
|
|
120
|
+
if (!hasPermission || device == null) return <Text>Waiting for camera permission...</Text>;
|
|
93
121
|
|
|
94
122
|
return (
|
|
95
123
|
<View style={styles.container}>
|
|
@@ -140,7 +168,7 @@ const styles = StyleSheet.create({
|
|
|
140
168
|
|
|
141
169
|
```javascript
|
|
142
170
|
import { useCallback, useMemo, useEffect, useRef } from 'react';
|
|
143
|
-
import {
|
|
171
|
+
import { scheduleOnRN } from 'react-native-worklets';
|
|
144
172
|
import { useFrameProcessor } from 'react-native-vision-camera';
|
|
145
173
|
import { useFaceDetector } from 'react-native-vision-camera-face-detector';
|
|
146
174
|
import {
|
|
@@ -292,14 +320,14 @@ const result = faceAntiSpoofFrameProcessor(frame);
|
|
|
292
320
|
|
|
293
321
|
**Parameters**: `frame` (Vision Camera Frame)
|
|
294
322
|
|
|
295
|
-
**Returns**: `FaceAntiSpoofingResult | null
|
|
323
|
+
**Returns**: `FaceAntiSpoofingResult | null`. On iOS, inference runs off the frame callback and the returned object can be the latest completed result.
|
|
296
324
|
|
|
297
325
|
### `FaceAntiSpoofingResult`
|
|
298
326
|
|
|
299
327
|
```typescript
|
|
300
328
|
interface FaceAntiSpoofingResult {
|
|
301
329
|
isLive: boolean; // Real face (true) or spoof (false)
|
|
302
|
-
label: string; // "
|
|
330
|
+
label: string; // Usually "live", "spoof", or "error"
|
|
303
331
|
neuralNetworkScore: number; // 0.0-1.0 confidence
|
|
304
332
|
laplacianScore: number; // Image quality score
|
|
305
333
|
combinedScore: number; // Weighted average
|
|
@@ -331,11 +359,7 @@ const FACE_CENTER_THRESHOLD_Y = 0.15; // Y-axis tolerance
|
|
|
331
359
|
|
|
332
360
|
## 🎮 Complete Examples
|
|
333
361
|
|
|
334
|
-
|
|
335
|
-
- Basic anti-spoofing detection
|
|
336
|
-
- Face detection with liveness
|
|
337
|
-
- Complete capture flow
|
|
338
|
-
- UI components and feedback
|
|
362
|
+
The [Quick Start](#-quick-start) above shows the basic frame-processor integration. For the complete capture flow, face detection, liveness, and UI patterns, see the [project wiki](https://github.com/dpraful/react-native-vision-camera-spoof-detector/wiki).
|
|
339
363
|
|
|
340
364
|
## 🔍 Attack Detection Capabilities
|
|
341
365
|
|
|
@@ -363,8 +387,8 @@ Performance depends on:
|
|
|
363
387
|
|
|
364
388
|
| Platform | Status | GPU | Notes |
|
|
365
389
|
|----------|--------|-----|-------|
|
|
366
|
-
| Android | ✅ Supported | Yes |
|
|
367
|
-
| iOS |
|
|
390
|
+
| Android | ✅ Supported | Yes | TensorFlow Lite model bundled in the AAR |
|
|
391
|
+
| iOS | ✅ Supported | CPU | Requires CocoaPods and iOS 11+ |
|
|
368
392
|
| Web | ❌ No | N/A | Not applicable |
|
|
369
393
|
|
|
370
394
|
## 🐛 Troubleshooting
|
|
@@ -377,11 +401,15 @@ if (!available) {
|
|
|
377
401
|
}
|
|
378
402
|
```
|
|
379
403
|
|
|
404
|
+
- iOS: run `pod install` from the `ios` directory and rebuild the app from the generated workspace.
|
|
405
|
+
- iOS: verify `NSCameraUsageDescription` exists in the app's `Info.plist`.
|
|
406
|
+
- Check the initialization promise result and inspect the native logs for model-loading errors.
|
|
407
|
+
|
|
380
408
|
**Low accuracy**
|
|
381
409
|
- Check lighting conditions
|
|
382
410
|
- Ensure face is centered
|
|
383
411
|
- Adjust `antispooflevel` parameter
|
|
384
|
-
- Verify
|
|
412
|
+
- Verify the `FaceAntiSpoofing.tflite` model is bundled (the package does this automatically on iOS)
|
|
385
413
|
|
|
386
414
|
**Performance issues**
|
|
387
415
|
- Reduce frame processing frequency
|
|
@@ -397,10 +425,9 @@ if (!available) {
|
|
|
397
425
|
|
|
398
426
|
## 📖 Documentation
|
|
399
427
|
|
|
400
|
-
- [
|
|
401
|
-
- [
|
|
402
|
-
- [
|
|
403
|
-
- [Comprehensive Example](./examples/CompleteExampleApp.tsx)
|
|
428
|
+
- [Project Wiki](https://github.com/dpraful/react-native-vision-camera-spoof-detector/wiki)
|
|
429
|
+
- [Changelog](./CHANGELOG.md)
|
|
430
|
+
- [Contributing Guide](./CONTRIBUTING.md)
|
|
404
431
|
|
|
405
432
|
## 🤝 Contributing
|
|
406
433
|
|
package/android/build.gradle
CHANGED
|
@@ -130,10 +130,8 @@ dependencies {
|
|
|
130
130
|
// AndroidX - EXIF Support for image orientation
|
|
131
131
|
implementation "androidx.exifinterface:exifinterface:1.3.6"
|
|
132
132
|
|
|
133
|
-
//
|
|
133
|
+
// VisionCamera 5 uses Nitro frame outputs backed by react-native-worklets.
|
|
134
134
|
implementation project(':react-native-vision-camera')
|
|
135
|
-
implementation project(':react-native-worklets
|
|
136
|
-
|
|
137
|
-
// Optional: Face Detector if needed
|
|
138
|
-
implementation project(':react-native-vision-camera-face-detector')
|
|
135
|
+
implementation project(':react-native-vision-camera-worklets')
|
|
136
|
+
implementation project(':react-native-worklets')
|
|
139
137
|
}
|
|
@@ -4,6 +4,8 @@ import com.facebook.react.bridge.ReactApplicationContext
|
|
|
4
4
|
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
5
5
|
import com.facebook.react.bridge.ReactMethod
|
|
6
6
|
import com.facebook.react.bridge.Promise
|
|
7
|
+
import com.facebook.react.bridge.Arguments
|
|
8
|
+
import com.facebook.react.bridge.ReadableArray
|
|
7
9
|
import com.facebook.react.module.annotations.ReactModule
|
|
8
10
|
|
|
9
11
|
@ReactModule(name = FaceAntiSpoofModule.NAME)
|
|
@@ -126,6 +128,59 @@ class FaceAntiSpoofModule(reactContext: ReactApplicationContext) : ReactContextB
|
|
|
126
128
|
}
|
|
127
129
|
}
|
|
128
130
|
|
|
131
|
+
@ReactMethod
|
|
132
|
+
fun processFrame(
|
|
133
|
+
frameData: ReadableArray,
|
|
134
|
+
width: Int,
|
|
135
|
+
height: Int,
|
|
136
|
+
rowStrides: ReadableArray,
|
|
137
|
+
pixelStrides: ReadableArray,
|
|
138
|
+
promise: Promise
|
|
139
|
+
) {
|
|
140
|
+
try {
|
|
141
|
+
if (frameData.size() < 3 || rowStrides.size() < 3 || pixelStrides.size() < 3) {
|
|
142
|
+
promise.reject("INVALID_FRAME", "Expected Y, U and V camera planes")
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!isModuleInitialized || faceAntiSpoofing == null) {
|
|
147
|
+
promise.reject("NOT_INITIALIZED", "Face anti-spoofing is not initialized")
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
val planes = Array(3) { planeIndex ->
|
|
152
|
+
val plane = frameData.getArray(planeIndex)
|
|
153
|
+
?: throw IllegalArgumentException("Camera plane $planeIndex is null")
|
|
154
|
+
ByteArray(plane.size()) { index -> plane.getInt(index).toByte() }
|
|
155
|
+
}
|
|
156
|
+
val yuvRowStrides = IntArray(3) { rowStrides.getDouble(it).toInt() }
|
|
157
|
+
val yuvPixelStrides = IntArray(3) { pixelStrides.getDouble(it).toInt().coerceAtLeast(1) }
|
|
158
|
+
val result = faceAntiSpoofing!!.advancedAntiSpoofing(
|
|
159
|
+
planes,
|
|
160
|
+
width,
|
|
161
|
+
height,
|
|
162
|
+
yuvRowStrides,
|
|
163
|
+
yuvPixelStrides
|
|
164
|
+
)
|
|
165
|
+
val isLive = result.neuralNetworkScore < FaceAntiSpoofingAdvanced.BASE_THRESHOLD &&
|
|
166
|
+
result.laplacianScore > FaceAntiSpoofingAdvanced.LAPLACIAN_THRESHOLD
|
|
167
|
+
promise.resolve(
|
|
168
|
+
Arguments.makeNativeMap(
|
|
169
|
+
mapOf(
|
|
170
|
+
"isLive" to isLive,
|
|
171
|
+
"label" to if (isLive) "Live Face" else "Spoof Face",
|
|
172
|
+
"neuralNetworkScore" to result.neuralNetworkScore.toDouble(),
|
|
173
|
+
"laplacianScore" to result.laplacianScore,
|
|
174
|
+
"combinedScore" to result.combinedScore.toDouble(),
|
|
175
|
+
"confidence" to result.confidence.toDouble()
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
} catch (e: Exception) {
|
|
180
|
+
promise.reject("PROCESS_ERROR", "Failed to process camera frame", e)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
129
184
|
@ReactMethod
|
|
130
185
|
fun isAvailable(promise: Promise) {
|
|
131
186
|
try {
|
|
@@ -4,32 +4,10 @@ import com.facebook.react.ReactPackage
|
|
|
4
4
|
import com.facebook.react.bridge.NativeModule
|
|
5
5
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
6
|
import com.facebook.react.uimanager.ViewManager
|
|
7
|
-
import com.mrousavy.camera.frameprocessors.FrameProcessorPluginRegistry
|
|
8
7
|
|
|
9
8
|
class FaceAntiSpoofPackage : ReactPackage {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
init {
|
|
13
|
-
try {
|
|
14
|
-
FrameProcessorPluginRegistry.addFrameProcessorPlugin("faceAntiSpoof") { proxy, options ->
|
|
15
|
-
val plugin = FaceAntiSpoofFrameProcessor()
|
|
16
|
-
try {
|
|
17
|
-
plugin.initialize(proxy.context.assets)
|
|
18
|
-
} catch (e: Exception) {
|
|
19
|
-
android.util.Log.e("FaceAntiSpoof", "Failed to initialize plugin with assets", e)
|
|
20
|
-
}
|
|
21
|
-
plugin
|
|
22
|
-
}
|
|
23
|
-
} catch (e: Exception) {
|
|
24
|
-
android.util.Log.e("FaceAntiSpoof", "Plugin already registered or failed: ${e.message}", e)
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
30
|
-
// Only return the module — DO NOT register plugin here.
|
|
31
|
-
return listOf(FaceAntiSpoofModule(reactContext))
|
|
32
|
-
}
|
|
9
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
|
|
10
|
+
listOf(FaceAntiSpoofModule(reactContext))
|
|
33
11
|
|
|
34
12
|
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
35
13
|
return emptyList()
|
|
@@ -24,7 +24,7 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
|
|
|
24
24
|
const val BASE_THRESHOLD = 0.2f
|
|
25
25
|
const val ROUTE_INDEX = 6
|
|
26
26
|
const val LAPLACE_THRESHOLD = 50
|
|
27
|
-
const val LAPLACIAN_THRESHOLD =
|
|
27
|
+
const val LAPLACIAN_THRESHOLD = 1000
|
|
28
28
|
const val MODEL_FILE = "FaceAntiSpoofing.tflite"
|
|
29
29
|
private const val TAG = "FaceAntiSpoofing"
|
|
30
30
|
|
|
@@ -165,6 +165,25 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
fun advancedAntiSpoofing(
|
|
169
|
+
planes: Array<ByteArray>,
|
|
170
|
+
width: Int,
|
|
171
|
+
height: Int,
|
|
172
|
+
rowStrides: IntArray,
|
|
173
|
+
pixelStrides: IntArray
|
|
174
|
+
): AntiSpoofingResult {
|
|
175
|
+
if (planes.size < 3) {
|
|
176
|
+
throw IllegalArgumentException("Expected Y, U and V planes")
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
val bitmap = yuv420ToBitmap(planes, width, height, rowStrides, pixelStrides)
|
|
180
|
+
return try {
|
|
181
|
+
runBlocking { advancedAntiSpoofing(bitmap).getOrThrow() }
|
|
182
|
+
} finally {
|
|
183
|
+
bitmap.recycle()
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
168
187
|
// FROM WORKING VERSION: neuralNetworkAntiSpoofing with Bitmap
|
|
169
188
|
private fun neuralNetworkAntiSpoofing(bitmap: Bitmap): Result<Float> {
|
|
170
189
|
return try {
|
|
@@ -365,6 +384,44 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
|
|
|
365
384
|
return bitmap
|
|
366
385
|
}
|
|
367
386
|
|
|
387
|
+
private fun yuv420ToBitmap(
|
|
388
|
+
planes: Array<ByteArray>,
|
|
389
|
+
width: Int,
|
|
390
|
+
height: Int,
|
|
391
|
+
rowStrides: IntArray,
|
|
392
|
+
pixelStrides: IntArray
|
|
393
|
+
): Bitmap {
|
|
394
|
+
val pixels = IntArray(width * height)
|
|
395
|
+
val yPlane = planes[0]
|
|
396
|
+
val uPlane = planes[1]
|
|
397
|
+
val vPlane = planes[2]
|
|
398
|
+
|
|
399
|
+
fun sample(plane: ByteArray, row: Int, column: Int, rowStride: Int, pixelStride: Int): Int {
|
|
400
|
+
val index = row * rowStride + column * pixelStride
|
|
401
|
+
return if (index in plane.indices) plane[index].toInt() and 0xFF else 128
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
for (y in 0 until height) {
|
|
405
|
+
for (x in 0 until width) {
|
|
406
|
+
val yValue = sample(yPlane, y, x, rowStrides[0], pixelStrides[0])
|
|
407
|
+
val chromaRow = y / 2
|
|
408
|
+
val chromaColumn = x / 2
|
|
409
|
+
val uValue = sample(uPlane, chromaRow, chromaColumn, rowStrides[1], pixelStrides[1]) - 128
|
|
410
|
+
val vValue = sample(vPlane, chromaRow, chromaColumn, rowStrides[2], pixelStrides[2]) - 128
|
|
411
|
+
|
|
412
|
+
val red = (yValue + 1.402f * vValue).coerceIn(0f, 255f).toInt()
|
|
413
|
+
val green = (yValue - 0.344f * uValue - 0.714f * vValue).coerceIn(0f, 255f).toInt()
|
|
414
|
+
val blue = (yValue + 1.772f * uValue).coerceIn(0f, 255f).toInt()
|
|
415
|
+
pixels[y * width + x] = (0xFF shl 24) or
|
|
416
|
+
(red shl 16) or
|
|
417
|
+
(green shl 8) or
|
|
418
|
+
blue
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888)
|
|
423
|
+
}
|
|
424
|
+
|
|
368
425
|
@Throws(IOException::class)
|
|
369
426
|
private fun loadModelFile(assetManager: AssetManager, modelName: String): MappedByteBuffer {
|
|
370
427
|
return try {
|
package/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* TypeScript type definitions
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { Frame, FrameProcessor } from 'react-native-vision-camera';
|
|
7
|
+
import type { CameraFrameOutput, Frame, FrameOutputOptions, FrameProcessor } from 'react-native-vision-camera';
|
|
8
8
|
import type { SharedValue } from 'react-native-reanimated';
|
|
9
9
|
|
|
10
10
|
// ============================================================================
|
|
@@ -281,6 +281,19 @@ export interface UseFaceDetectionFrameProcessorReturn {
|
|
|
281
281
|
antiSpoofState: AntiSpoofState;
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
+
/**
|
|
285
|
+
* VisionCamera 5 frame-output options for spoof detection.
|
|
286
|
+
* The callback runs on the React Native runtime after native inference.
|
|
287
|
+
*/
|
|
288
|
+
export interface UseFaceAntiSpoofFrameOutputOptions extends Partial<FrameOutputOptions> {
|
|
289
|
+
onResult?: (result: FaceAntiSpoofingResult) => void;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** Create a VisionCamera 5 frame output backed by the native detector. */
|
|
293
|
+
export function useFaceAntiSpoofFrameOutput(
|
|
294
|
+
options?: UseFaceAntiSpoofFrameOutputOptions,
|
|
295
|
+
): CameraFrameOutput;
|
|
296
|
+
|
|
284
297
|
// ============================================================================
|
|
285
298
|
// Module Functions
|
|
286
299
|
// ============================================================================
|
package/index.js
CHANGED
|
@@ -1,60 +1,50 @@
|
|
|
1
1
|
import { NativeModules } from 'react-native';
|
|
2
|
-
import {
|
|
2
|
+
import { useFrameOutput } from 'react-native-vision-camera';
|
|
3
|
+
import { scheduleOnRN } from 'react-native-worklets';
|
|
3
4
|
|
|
4
5
|
const { FaceAntiSpoof } = NativeModules;
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} catch (e) {
|
|
12
|
-
console.warn('[FaceAntiSpoof] Failed to init frame processor plugin:', e);
|
|
13
|
-
}
|
|
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
|
+
};
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
export const useFaceAntiSpoofFrameOutput = ({ onResult, ...options } = {}) => {
|
|
14
|
+
const onFrame = (frame) => {
|
|
15
|
+
'worklet';
|
|
17
16
|
try {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
const planes = frame.getPlanes();
|
|
18
|
+
if (planes.length < 3) return;
|
|
19
|
+
|
|
20
|
+
const planeData = planes.map((plane) => Array.from(new Uint8Array(plane.getPixelBuffer())));
|
|
21
|
+
const rowStrides = planes.map((plane) => plane.bytesPerRow);
|
|
22
|
+
const pixelStrides = planes.map((plane) => plane.bytesPerRow / plane.width);
|
|
23
|
+
scheduleOnRN(
|
|
24
|
+
processFrameOnRN,
|
|
25
|
+
{ planes: planeData, rowStrides, pixelStrides },
|
|
26
|
+
frame.width,
|
|
27
|
+
frame.height,
|
|
28
|
+
onResult,
|
|
29
|
+
);
|
|
30
|
+
} finally {
|
|
31
|
+
frame.dispose();
|
|
22
32
|
}
|
|
23
|
-
}
|
|
24
|
-
return !!_faceAntiSpoofPlugin;
|
|
25
|
-
}
|
|
33
|
+
};
|
|
26
34
|
|
|
27
|
-
|
|
28
|
-
|
|
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 () {
|
|
29
46
|
'worklet';
|
|
30
|
-
|
|
31
|
-
if (!_faceAntiSpoofPlugin) {
|
|
32
|
-
console.warn('[FaceAntiSpoof] Plugin not initialized');
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (typeof _faceAntiSpoofPlugin.call !== 'function') {
|
|
37
|
-
console.warn('[FaceAntiSpoof] Plugin call method not available');
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (!frame) {
|
|
42
|
-
console.warn('[FaceAntiSpoof] Frame is null or undefined');
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const result = _faceAntiSpoofPlugin.call(frame);
|
|
47
|
-
|
|
48
|
-
// Validate result structure
|
|
49
|
-
if (result && typeof result === 'object') {
|
|
50
|
-
return result;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return null;
|
|
54
|
-
} catch (err) {
|
|
55
|
-
console.error('[FaceAntiSpoof] Error in frame processor:', err);
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
47
|
+
return null;
|
|
58
48
|
};
|
|
59
49
|
|
|
60
50
|
export default FaceAntiSpoof;
|
|
@@ -86,9 +76,7 @@ export const initializeFaceAntiSpoof = async () => {
|
|
|
86
76
|
}
|
|
87
77
|
|
|
88
78
|
const isSuccess = result && status.pluginAvailable;
|
|
89
|
-
|
|
90
|
-
const pluginInitialized = isSuccess ? ensurePluginInitialized() : false;
|
|
91
|
-
const initialized = isSuccess && pluginInitialized;
|
|
79
|
+
const initialized = isSuccess;
|
|
92
80
|
console.log('[FaceAntiSpoof] Initialization result:', initialized);
|
|
93
81
|
return initialized;
|
|
94
82
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-vision-camera-spoof-detector",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
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/dpraful/react-native-vision-camera-spoof-detector",
|
|
6
6
|
"repository": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"bugs": {
|
|
11
11
|
"url": "https://github.com/dpraful/react-native-vision-camera-spoof-detector/issues"
|
|
12
12
|
},
|
|
13
|
-
"license": "
|
|
13
|
+
"license": "MIT",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "PRAFULDAS M M",
|
|
16
16
|
"company": "JESCON TECHNOLOGIES PVT LTD",
|
|
@@ -29,11 +29,7 @@
|
|
|
29
29
|
"files": [
|
|
30
30
|
"android/",
|
|
31
31
|
"ios/",
|
|
32
|
-
".gitattributes",
|
|
33
|
-
".gitignore",
|
|
34
|
-
".npmignore",
|
|
35
32
|
"CHANGELOG.md",
|
|
36
|
-
"CONTRIBUTING.md",
|
|
37
33
|
"index.js",
|
|
38
34
|
"index.d.ts",
|
|
39
35
|
"README.md",
|
|
@@ -62,7 +58,8 @@
|
|
|
62
58
|
"react-native": ">=0.60.0",
|
|
63
59
|
"react-native-vision-camera": "^4.6.4 || ^5.0.0",
|
|
64
60
|
"react-native-reanimated": "^3.0.0",
|
|
65
|
-
"react-native-worklets
|
|
61
|
+
"react-native-vision-camera-worklets": "^5.0.0",
|
|
62
|
+
"react-native-worklets": "^0.12.0"
|
|
66
63
|
},
|
|
67
64
|
"peerDependenciesMeta": {
|
|
68
65
|
"react-native-vision-camera-face-detector": {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Pod::Spec.new do |s|
|
|
2
2
|
s.name = 'react-native-vision-camera-spoof-detector'
|
|
3
|
-
s.version = '
|
|
3
|
+
s.version = '1.1.0'
|
|
4
4
|
s.summary = 'Face anti-spoof detector for VisionCamera (iOS)'
|
|
5
|
-
s.homepage = 'https://
|
|
6
|
-
s.license = { :type => 'MIT' }
|
|
7
|
-
s.authors = { '
|
|
5
|
+
s.homepage = 'https://github.com/dpraful/react-native-vision-camera-spoof-detector'
|
|
6
|
+
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
|
7
|
+
s.authors = { 'JESCON TECHNOLOGIES PVT LTD' => 'jescontechnologies@gmail.com' }
|
|
8
8
|
s.platform = :ios, '11.0'
|
|
9
9
|
s.source = { :path => '.' }
|
|
10
10
|
|
package/.gitattributes
DELETED
package/.npmignore
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
# npm ignore patterns - files that should not be published to npm
|
|
2
|
-
|
|
3
|
-
# Development files
|
|
4
|
-
.git
|
|
5
|
-
.gitignore
|
|
6
|
-
.gitattributes
|
|
7
|
-
|
|
8
|
-
# Development dependencies
|
|
9
|
-
node_modules
|
|
10
|
-
npm-debug.log*
|
|
11
|
-
yarn-debug.log*
|
|
12
|
-
yarn-error.log*
|
|
13
|
-
|
|
14
|
-
# IDE / Editor files
|
|
15
|
-
.vscode
|
|
16
|
-
.idea
|
|
17
|
-
.DS_Store
|
|
18
|
-
*.swp
|
|
19
|
-
*.swo
|
|
20
|
-
*~
|
|
21
|
-
.env
|
|
22
|
-
.env.local
|
|
23
|
-
|
|
24
|
-
# Build artifacts and temporary files
|
|
25
|
-
build/
|
|
26
|
-
dist/
|
|
27
|
-
.rollup.cache/
|
|
28
|
-
.eslintcache
|
|
29
|
-
*.tsbuildinfo
|
|
30
|
-
|
|
31
|
-
# Android build artifacts and caches
|
|
32
|
-
android/build/
|
|
33
|
-
android/.gradle/
|
|
34
|
-
android/.kotlin/
|
|
35
|
-
android/*.log
|
|
36
|
-
*.apk
|
|
37
|
-
*.aar
|
|
38
|
-
*.so
|
|
39
|
-
|
|
40
|
-
# Testing
|
|
41
|
-
__tests__
|
|
42
|
-
__mocks__
|
|
43
|
-
*.test.ts
|
|
44
|
-
*.test.tsx
|
|
45
|
-
*.spec.ts
|
|
46
|
-
*.spec.tsx
|
|
47
|
-
coverage
|
|
48
|
-
.jest
|
|
49
|
-
|
|
50
|
-
# Documentation (optional - remove if you want to publish docs)
|
|
51
|
-
docs/*.md
|
|
52
|
-
.docs/
|
|
53
|
-
|
|
54
|
-
# Example files (optional - remove if you want to include examples)
|
|
55
|
-
example/
|
|
56
|
-
examples/
|
|
57
|
-
demo/
|
|
58
|
-
|
|
59
|
-
# CI/CD
|
|
60
|
-
.github/workflows
|
|
61
|
-
.gitlab-ci.yml
|
|
62
|
-
.travis.yml
|
|
63
|
-
azure-pipelines.yml
|
|
64
|
-
|
|
65
|
-
# Root level files
|
|
66
|
-
.prettierrc
|
|
67
|
-
.prettierignore
|
|
68
|
-
.eslintrc
|
|
69
|
-
.eslintignore
|
|
70
|
-
.babelrc
|
|
71
|
-
tsconfig.json
|
|
72
|
-
jest.config.js
|
|
73
|
-
babel.config.js
|
|
74
|
-
webpack.config.js
|
|
75
|
-
Makefile
|
|
76
|
-
|
|
77
|
-
# Contribution files
|
|
78
|
-
CONTRIBUTING.md
|
|
79
|
-
CODE_OF_CONDUCT.md
|
|
80
|
-
PULL_REQUEST_TEMPLATE.md
|
|
81
|
-
|
|
82
|
-
# Misc
|
|
83
|
-
*.map
|
|
84
|
-
.npmrc
|
|
85
|
-
.yarnrc
|
|
86
|
-
.DS_Store
|
|
87
|
-
Thumbs.db
|
|
88
|
-
*.lock
|
|
89
|
-
package-lock.json
|
|
90
|
-
yarn.lock
|
|
91
|
-
|
|
92
|
-
# Keep essential files
|
|
93
|
-
!android/
|
|
94
|
-
!index.js
|
|
95
|
-
!index.d.ts
|
|
96
|
-
!README.md
|
|
97
|
-
!CHANGELOG.md
|
|
98
|
-
!LICENSE
|
|
99
|
-
!package.json
|