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 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
- - react-native-vision-camera >= 4.6.4
24
- - react-native-reanimated >= 3.0.0
25
- - react-native-worklets-core >= 1.0.0
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 Android (if not auto-linked)
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 4: Link native module (for React Native < 0.60)
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
- initializeFaceAntiSpoof().then((success) => {
80
- console.log('FaceAntiSpoof initialized:', success);
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>Loading...</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; // "Live Face" or "Spoof Face"
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
- Check the [examples](./examples) folder for:
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 | Fully optimized |
367
- | iOS | In Progress | Yes | Coming soon |
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 TensorFlow Lite models are bundled
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
- - [Complete API Documentation](./.docs/API.md)
401
- - [Migration Guide](./.docs/MIGRATION.md)
402
- - [Best Practices](./.docs/BEST_PRACTICES.md)
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
 
@@ -1,2 +1,2 @@
1
- #Thu Sep 03 10:53:58 IST 2026
2
- gradle.version=9.2.0
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": combined > 0.5,
155
- "label": combined > 0.5 ? "live" : "spoof",
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 ciImage = CIImage(cvPixelBuffer: pixelBuffer)
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 = 50
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) / 4000.0, 0), 1)
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) / 4000.0, 0), 1)
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.26",
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
- s.pod_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' }
21
+ s.pod_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' }
22
22
  end