react-native-vision-camera-spoof-detector 1.0.26 → 1.0.28
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 +50 -24
- package/android/.gradle/9.2.0/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +2 -2
- package/ios/FaceAntiSpoofFrameProcessor.swift +29 -11
- package/package.json +1 -1
- package/react-native-vision-camera-spoof-detector.podspec +1 -1
package/README.md
CHANGED
|
@@ -20,9 +20,10 @@ 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-core
|
|
23
|
+
- React Native Vision Camera `^4.6.4` or `^5.0.0`
|
|
24
|
+
- `react-native-reanimated` `^3.0.0`
|
|
25
|
+
- `react-native-worklets-core` `^1.0.0`
|
|
26
|
+
- iOS 11.0 or later
|
|
26
27
|
- react-native-vision-camera-face-detector (optional, for enhanced features)
|
|
27
28
|
|
|
28
29
|
## 📦 Installation
|
|
@@ -43,7 +44,26 @@ npm install react-native-vision-camera react-native-reanimated react-native-work
|
|
|
43
44
|
yarn add react-native-vision-camera react-native-reanimated react-native-worklets-core
|
|
44
45
|
```
|
|
45
46
|
|
|
46
|
-
### Step 3: Configure
|
|
47
|
+
### Step 3: Configure iOS
|
|
48
|
+
|
|
49
|
+
Install the CocoaPods dependencies from the iOS directory:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
cd ios
|
|
53
|
+
pod install
|
|
54
|
+
cd ..
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Add a camera usage description to `ios/<YourApp>/Info.plist`:
|
|
58
|
+
|
|
59
|
+
```xml
|
|
60
|
+
<key>NSCameraUsageDescription</key>
|
|
61
|
+
<string>This app uses the camera for face liveness verification.</string>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
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.
|
|
65
|
+
|
|
66
|
+
### Step 4: Configure Android (if not auto-linked)
|
|
47
67
|
|
|
48
68
|
Add to `android/app/build.gradle`:
|
|
49
69
|
|
|
@@ -53,7 +73,7 @@ dependencies {
|
|
|
53
73
|
}
|
|
54
74
|
```
|
|
55
75
|
|
|
56
|
-
### Step
|
|
76
|
+
### Step 5: Link native module (for React Native < 0.60)
|
|
57
77
|
|
|
58
78
|
```bash
|
|
59
79
|
react-native link react-native-vision-camera-spoof-detector
|
|
@@ -61,24 +81,31 @@ react-native link react-native-vision-camera-spoof-detector
|
|
|
61
81
|
|
|
62
82
|
## 🚀 Quick Start
|
|
63
83
|
|
|
84
|
+
> **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.
|
|
85
|
+
|
|
64
86
|
### Simple Usage
|
|
65
87
|
|
|
66
88
|
```javascript
|
|
67
89
|
import React, { useEffect, useState } from 'react';
|
|
68
90
|
import { StyleSheet, Text, View } from 'react-native';
|
|
69
|
-
import { Camera, useCameraDevices, useFrameProcessor } from 'react-native-vision-camera';
|
|
91
|
+
import { Camera, useCameraDevices, useCameraPermission, useFrameProcessor } from 'react-native-vision-camera';
|
|
70
92
|
import { faceAntiSpoofFrameProcessor, initializeFaceAntiSpoof } from 'react-native-vision-camera-spoof-detector';
|
|
71
93
|
import { runOnJS } from 'react-native-reanimated';
|
|
72
94
|
|
|
73
95
|
export default function App() {
|
|
74
96
|
const devices = useCameraDevices();
|
|
75
97
|
const device = devices.front;
|
|
98
|
+
const { hasPermission, requestPermission } = useCameraPermission();
|
|
76
99
|
const [spoofResult, setSpoofResult] = useState(null);
|
|
77
100
|
|
|
78
101
|
useEffect(() => {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
102
|
+
if (!hasPermission) requestPermission();
|
|
103
|
+
}, [hasPermission, requestPermission]);
|
|
104
|
+
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
initializeFaceAntiSpoof()
|
|
107
|
+
.then((success) => console.log('FaceAntiSpoof initialized:', success))
|
|
108
|
+
.catch((error) => console.error('FaceAntiSpoof initialization failed:', error));
|
|
82
109
|
}, []);
|
|
83
110
|
|
|
84
111
|
const frameProcessor = useFrameProcessor((frame) => {
|
|
@@ -89,7 +116,7 @@ export default function App() {
|
|
|
89
116
|
}
|
|
90
117
|
}, []);
|
|
91
118
|
|
|
92
|
-
if (device == null) return <Text>
|
|
119
|
+
if (!hasPermission || device == null) return <Text>Waiting for camera permission...</Text>;
|
|
93
120
|
|
|
94
121
|
return (
|
|
95
122
|
<View style={styles.container}>
|
|
@@ -292,14 +319,14 @@ const result = faceAntiSpoofFrameProcessor(frame);
|
|
|
292
319
|
|
|
293
320
|
**Parameters**: `frame` (Vision Camera Frame)
|
|
294
321
|
|
|
295
|
-
**Returns**: `FaceAntiSpoofingResult | null
|
|
322
|
+
**Returns**: `FaceAntiSpoofingResult | null`. On iOS, inference runs off the frame callback and the returned object can be the latest completed result.
|
|
296
323
|
|
|
297
324
|
### `FaceAntiSpoofingResult`
|
|
298
325
|
|
|
299
326
|
```typescript
|
|
300
327
|
interface FaceAntiSpoofingResult {
|
|
301
328
|
isLive: boolean; // Real face (true) or spoof (false)
|
|
302
|
-
label: string; // "
|
|
329
|
+
label: string; // Usually "live", "spoof", or "error"
|
|
303
330
|
neuralNetworkScore: number; // 0.0-1.0 confidence
|
|
304
331
|
laplacianScore: number; // Image quality score
|
|
305
332
|
combinedScore: number; // Weighted average
|
|
@@ -331,11 +358,7 @@ const FACE_CENTER_THRESHOLD_Y = 0.15; // Y-axis tolerance
|
|
|
331
358
|
|
|
332
359
|
## 🎮 Complete Examples
|
|
333
360
|
|
|
334
|
-
|
|
335
|
-
- Basic anti-spoofing detection
|
|
336
|
-
- Face detection with liveness
|
|
337
|
-
- Complete capture flow
|
|
338
|
-
- UI components and feedback
|
|
361
|
+
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
362
|
|
|
340
363
|
## 🔍 Attack Detection Capabilities
|
|
341
364
|
|
|
@@ -363,8 +386,8 @@ Performance depends on:
|
|
|
363
386
|
|
|
364
387
|
| Platform | Status | GPU | Notes |
|
|
365
388
|
|----------|--------|-----|-------|
|
|
366
|
-
| Android | ✅ Supported | Yes |
|
|
367
|
-
| iOS |
|
|
389
|
+
| Android | ✅ Supported | Yes | TensorFlow Lite model bundled in the AAR |
|
|
390
|
+
| iOS | ✅ Supported | CPU | Requires CocoaPods and iOS 11+ |
|
|
368
391
|
| Web | ❌ No | N/A | Not applicable |
|
|
369
392
|
|
|
370
393
|
## 🐛 Troubleshooting
|
|
@@ -377,11 +400,15 @@ if (!available) {
|
|
|
377
400
|
}
|
|
378
401
|
```
|
|
379
402
|
|
|
403
|
+
- iOS: run `pod install` from the `ios` directory and rebuild the app from the generated workspace.
|
|
404
|
+
- iOS: verify `NSCameraUsageDescription` exists in the app's `Info.plist`.
|
|
405
|
+
- Check the initialization promise result and inspect the native logs for model-loading errors.
|
|
406
|
+
|
|
380
407
|
**Low accuracy**
|
|
381
408
|
- Check lighting conditions
|
|
382
409
|
- Ensure face is centered
|
|
383
410
|
- Adjust `antispooflevel` parameter
|
|
384
|
-
- Verify
|
|
411
|
+
- Verify the `FaceAntiSpoofing.tflite` model is bundled (the package does this automatically on iOS)
|
|
385
412
|
|
|
386
413
|
**Performance issues**
|
|
387
414
|
- Reduce frame processing frequency
|
|
@@ -397,10 +424,9 @@ if (!available) {
|
|
|
397
424
|
|
|
398
425
|
## 📖 Documentation
|
|
399
426
|
|
|
400
|
-
- [
|
|
401
|
-
- [
|
|
402
|
-
- [
|
|
403
|
-
- [Comprehensive Example](./examples/CompleteExampleApp.tsx)
|
|
427
|
+
- [Project Wiki](https://github.com/dpraful/react-native-vision-camera-spoof-detector/wiki)
|
|
428
|
+
- [Changelog](./CHANGELOG.md)
|
|
429
|
+
- [Contributing Guide](./CONTRIBUTING.md)
|
|
404
430
|
|
|
405
431
|
## 🤝 Contributing
|
|
406
432
|
|
|
Binary file
|
|
Binary file
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#Thu Sep 03
|
|
2
|
-
gradle.version=9
|
|
1
|
+
#Thu Sep 03 17:06:38 IST 2026
|
|
2
|
+
gradle.version=8.9
|
|
@@ -11,9 +11,9 @@ import Accelerate
|
|
|
11
11
|
import TensorFlowLite
|
|
12
12
|
#endif
|
|
13
13
|
|
|
14
|
-
typealias FaceAntiSpoofFrameType = Frame
|
|
15
|
-
typealias FaceAntiSpoofProxyType = VisionCameraProxyHolder
|
|
16
|
-
typealias FaceAntiSpoofPluginBase = FrameProcessorPlugin
|
|
14
|
+
typealias FaceAntiSpoofFrameType = VisionCamera.Frame
|
|
15
|
+
typealias FaceAntiSpoofProxyType = VisionCamera.VisionCameraProxyHolder
|
|
16
|
+
typealias FaceAntiSpoofPluginBase = VisionCamera.FrameProcessorPlugin
|
|
17
17
|
|
|
18
18
|
@objc(FaceAntiSpoofFrameProcessor)
|
|
19
19
|
class FaceAntiSpoofFrameProcessor: FaceAntiSpoofPluginBase {
|
|
@@ -24,6 +24,9 @@ class FaceAntiSpoofFrameProcessor: FaceAntiSpoofPluginBase {
|
|
|
24
24
|
private var isProcessing: Bool = false
|
|
25
25
|
private var latestResult: [String: Any] = ["error": "Not initialized", "isLive": false, "label": "Not Initialized"]
|
|
26
26
|
private static let INPUT_IMAGE_SIZE: Int = 256
|
|
27
|
+
private static let BASE_THRESHOLD: Float = 0.2
|
|
28
|
+
private static let LAPLACE_THRESHOLD: Int = 50
|
|
29
|
+
private static let LAPLACIAN_THRESHOLD: Int = 4000
|
|
27
30
|
|
|
28
31
|
#if canImport(TensorFlowLite)
|
|
29
32
|
// Interpreter is provided by FaceAntiSpoofManager.sharedInterpreter
|
|
@@ -90,7 +93,7 @@ class FaceAntiSpoofFrameProcessor: FaceAntiSpoofPluginBase {
|
|
|
90
93
|
|
|
91
94
|
// Capture a small, resized RGB float snapshot synchronously while the pixel buffer is valid
|
|
92
95
|
let targetSize = CGSize(width: Self.INPUT_IMAGE_SIZE, height: Self.INPUT_IMAGE_SIZE)
|
|
93
|
-
guard let rgbInput = Self.rgbFloatData(from: pixelBuffer, size: targetSize) else {
|
|
96
|
+
guard let rgbInput = Self.rgbFloatData(from: pixelBuffer, orientation: frame.orientation, size: targetSize) else {
|
|
94
97
|
objc_sync_enter(self)
|
|
95
98
|
isProcessing = false
|
|
96
99
|
objc_sync_exit(self)
|
|
@@ -145,14 +148,15 @@ class FaceAntiSpoofFrameProcessor: FaceAntiSpoofPluginBase {
|
|
|
145
148
|
|
|
146
149
|
let combined = Self.calculateCombinedScore(neuralScore: nnScore, laplacianScore: lapScore)
|
|
147
150
|
let confidence = Self.calculateConfidence(neuralScore: nnScore, laplacianScore: lapScore)
|
|
151
|
+
let isLive = nnScore < Self.BASE_THRESHOLD && lapScore > Self.LAPLACIAN_THRESHOLD
|
|
148
152
|
|
|
149
153
|
self.latestResult = [
|
|
150
154
|
"neuralNetworkScore": Double(nnScore),
|
|
151
155
|
"laplacianScore": lapScore,
|
|
152
156
|
"combinedScore": Double(combined),
|
|
153
157
|
"confidence": Double(confidence),
|
|
154
|
-
"isLive":
|
|
155
|
-
"label":
|
|
158
|
+
"isLive": isLive,
|
|
159
|
+
"label": isLive ? "live" : "spoof",
|
|
156
160
|
"width": width,
|
|
157
161
|
"height": height
|
|
158
162
|
]
|
|
@@ -180,11 +184,25 @@ class FaceAntiSpoofFrameProcessor: FaceAntiSpoofPluginBase {
|
|
|
180
184
|
|
|
181
185
|
private static let sharedCIContext: CIContext = CIContext(options: nil)
|
|
182
186
|
|
|
183
|
-
private static func rgbFloatData(from pixelBuffer: CVPixelBuffer, size: CGSize) -> [Float]? {
|
|
187
|
+
private static func rgbFloatData(from pixelBuffer: CVPixelBuffer, orientation: UIImage.Orientation, size: CGSize) -> [Float]? {
|
|
184
188
|
CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags.readOnly)
|
|
185
189
|
defer { CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags.readOnly) }
|
|
186
190
|
|
|
187
|
-
let
|
|
191
|
+
let image = CIImage(cvPixelBuffer: pixelBuffer)
|
|
192
|
+
let exifOrientation: CGImagePropertyOrientation
|
|
193
|
+
switch orientation {
|
|
194
|
+
case .up, .upMirrored:
|
|
195
|
+
exifOrientation = .up
|
|
196
|
+
case .right, .rightMirrored:
|
|
197
|
+
exifOrientation = .right
|
|
198
|
+
case .down, .downMirrored:
|
|
199
|
+
exifOrientation = .down
|
|
200
|
+
case .left, .leftMirrored:
|
|
201
|
+
exifOrientation = .left
|
|
202
|
+
@unknown default:
|
|
203
|
+
exifOrientation = .up
|
|
204
|
+
}
|
|
205
|
+
let ciImage = image.oriented(forExifOrientation: Int32(exifOrientation.rawValue))
|
|
188
206
|
let context = Self.sharedCIContext
|
|
189
207
|
guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else { return nil }
|
|
190
208
|
|
|
@@ -243,7 +261,7 @@ class FaceAntiSpoofFrameProcessor: FaceAntiSpoofPluginBase {
|
|
|
243
261
|
let laplace = [[0,1,0],[1,-4,1],[0,1,0]]
|
|
244
262
|
let size = 3
|
|
245
263
|
var score = 0
|
|
246
|
-
let threshold =
|
|
264
|
+
let threshold = Self.LAPLACE_THRESHOLD
|
|
247
265
|
for y in 0..<(height - size + 1) {
|
|
248
266
|
for x in 0..<(width - size + 1) {
|
|
249
267
|
var result = 0
|
|
@@ -262,7 +280,7 @@ class FaceAntiSpoofFrameProcessor: FaceAntiSpoofPluginBase {
|
|
|
262
280
|
|
|
263
281
|
private static func calculateCombinedScore(neuralScore: Float, laplacianScore: Int) -> Float {
|
|
264
282
|
let normalizedNeural = min(max(neuralScore, 0), 1)
|
|
265
|
-
let normalizedLaplacian = min(max(Float(laplacianScore) /
|
|
283
|
+
let normalizedLaplacian = min(max(Float(laplacianScore) / Float(Self.LAPLACIAN_THRESHOLD), 0), 1)
|
|
266
284
|
let neuralWeight: Float = 0.6
|
|
267
285
|
let laplacianWeight: Float = 0.4
|
|
268
286
|
let invertedNeural = 1.0 - normalizedNeural
|
|
@@ -271,7 +289,7 @@ class FaceAntiSpoofFrameProcessor: FaceAntiSpoofPluginBase {
|
|
|
271
289
|
|
|
272
290
|
private static func calculateConfidence(neuralScore: Float, laplacianScore: Int) -> Float {
|
|
273
291
|
let neuralConfidence = min(max(neuralScore, 0), 1)
|
|
274
|
-
let laplacianConfidence = min(max(Float(laplacianScore) /
|
|
292
|
+
let laplacianConfidence = min(max(Float(laplacianScore) / Float(Self.LAPLACIAN_THRESHOLD), 0), 1)
|
|
275
293
|
let invertedNeural = 1.0 - neuralConfidence
|
|
276
294
|
return (invertedNeural * 0.6 + laplacianConfidence * 0.4)
|
|
277
295
|
}
|
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.0.28",
|
|
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": {
|
|
@@ -18,5 +18,5 @@ Pod::Spec.new do |s|
|
|
|
18
18
|
s.dependency 'VisionCamera'
|
|
19
19
|
|
|
20
20
|
# Keep default deployment target and static linkage consistent with RN
|
|
21
|
-
|
|
21
|
+
s.pod_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' }
|
|
22
22
|
end
|