react-native-vision-camera-spoof-detector 1.1.21 → 1.1.23

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.
@@ -260,6 +260,50 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
260
260
  }
261
261
  }
262
262
 
263
+ fun advancedAntiSpoofingRGBA(buffer: ByteBuffer, width: Int, height: Int, rowStride: Int, pixelStride: Int): AntiSpoofingResult {
264
+ if (!isInitialized) {
265
+ return AntiSpoofingResult(
266
+ neuralNetworkScore = 0f,
267
+ laplacianScore = 0,
268
+ textureComplexity = 0,
269
+ temporalConsistency = 0f,
270
+ colorSaturation = 0f,
271
+ combinedScore = 0f,
272
+ confidence = 0f
273
+ )
274
+ }
275
+
276
+ return try {
277
+ val sampled = sampleRgbaBufferToModelAndGray(buffer, width, height, rowStride, pixelStride)
278
+ val nnResult = neuralNetworkAntiSpoofingSampled(sampled.rgbBuffer)
279
+ val nnScore = nnResult.getOrNull() ?: throw IllegalStateException(nnResult.exceptionOrNull()?.message ?: "NN inference failed")
280
+ val laplacianScore = calculateLaplacianFromGray(sampled.grayBuffer)
281
+ val combinedScore = calculateCombinedScore(nnScore, laplacianScore)
282
+ val confidence = calculateConfidence(nnScore, laplacianScore)
283
+
284
+ AntiSpoofingResult(
285
+ neuralNetworkScore = nnScore,
286
+ laplacianScore = laplacianScore,
287
+ textureComplexity = 0,
288
+ temporalConsistency = 0f,
289
+ colorSaturation = 0f,
290
+ combinedScore = combinedScore,
291
+ confidence = confidence
292
+ )
293
+ } catch (e: Exception) {
294
+ Log.e(TAG, "Error in advancedAntiSpoofingRGBA buffer path", e)
295
+ AntiSpoofingResult(
296
+ neuralNetworkScore = 0f,
297
+ laplacianScore = 0,
298
+ textureComplexity = 0,
299
+ temporalConsistency = 0f,
300
+ colorSaturation = 0f,
301
+ combinedScore = 0f,
302
+ confidence = 0f
303
+ )
304
+ }
305
+ }
306
+
263
307
  // FROM WORKING VERSION: neuralNetworkAntiSpoofing with Bitmap
264
308
  private fun neuralNetworkAntiSpoofing(bitmap: Bitmap): Result<Float> {
265
309
  return try {
@@ -292,29 +336,6 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
292
336
  }
293
337
  }
294
338
 
295
- private fun neuralNetworkAntiSpoofingRGBA(rgba: ByteArray, width: Int, height: Int): Result<Float> {
296
- return try {
297
- val img = normalizeRgbaImage(rgba, width, height, INPUT_IMAGE_SIZE, INPUT_IMAGE_SIZE)
298
- val input = arrayOf(img)
299
-
300
- clssPred[0].fill(0f)
301
- leafNodeMask[0].fill(0f)
302
-
303
- outputs[outputIndex0] = clssPred
304
- outputs[outputIndex1] = leafNodeMask
305
-
306
- interpreter!!.runForMultipleInputsOutputs(arrayOf(input), outputs)
307
-
308
- Log.i(TAG, "clss_pred: [${clssPred[0].joinToString()}]")
309
- Log.i(TAG, "leaf_node_mask: [${leafNodeMask[0].joinToString()}]")
310
-
311
- Result.success(leafScore1(clssPred, leafNodeMask))
312
- } catch (e: Exception) {
313
- Log.e(TAG, "Error in neuralNetworkAntiSpoofingRGBA", e)
314
- Result.failure(e)
315
- }
316
- }
317
-
318
339
  private fun neuralNetworkAntiSpoofingSampled(rgbBuffer: Array<Array<FloatArray>>): Result<Float> {
319
340
  return try {
320
341
  val input = arrayOf(rgbBuffer)
@@ -363,14 +384,20 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
363
384
  return floatValues
364
385
  }
365
386
 
366
- private fun normalizeRgbaImage(
387
+ private data class SampledFrame(
388
+ val rgbBuffer: Array<Array<FloatArray>>,
389
+ val grayBuffer: Array<IntArray>
390
+ )
391
+
392
+ // Single-pass resize: one source pixel sample writes both the model RGB tensor
393
+ // and the grayscale buffer used for Laplacian scoring.
394
+ private fun sampleRgbaToModelAndGray(
367
395
  rgba: ByteArray,
368
396
  srcWidth: Int,
369
- srcHeight: Int,
370
- targetWidth: Int,
371
- targetHeight: Int
372
- ): Array<Array<FloatArray>> {
373
- val floatValues = Array(targetHeight) { Array(targetWidth) { FloatArray(3) } }
397
+ srcHeight: Int
398
+ ): SampledFrame {
399
+ val targetWidth = INPUT_IMAGE_SIZE
400
+ val targetHeight = INPUT_IMAGE_SIZE
374
401
  val scaleX = srcWidth.toFloat() / targetWidth
375
402
  val scaleY = srcHeight.toFloat() / targetHeight
376
403
 
@@ -383,45 +410,53 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
383
410
  val r = (rgba[srcIndex].toInt() and 0xFF) / 255f
384
411
  val g = (rgba[srcIndex + 1].toInt() and 0xFF) / 255f
385
412
  val b = (rgba[srcIndex + 2].toInt() and 0xFF) / 255f
386
- floatValues[targetY][targetX] = floatArrayOf(r, g, b)
413
+
414
+ modelInputBuffer[targetY][targetX][0] = r
415
+ modelInputBuffer[targetY][targetX][1] = g
416
+ modelInputBuffer[targetY][targetX][2] = b
417
+ grayBuffer[targetY][targetX] = (
418
+ 0.299f * rgba[srcIndex].toInt() +
419
+ 0.587f * rgba[srcIndex + 1].toInt() +
420
+ 0.114f * rgba[srcIndex + 2].toInt()
421
+ ).toInt()
387
422
  }
388
423
  }
389
424
 
390
- return floatValues
425
+ return SampledFrame(modelInputBuffer, grayBuffer)
391
426
  }
392
427
 
393
- private data class SampledFrame(
394
- val rgbBuffer: Array<Array<FloatArray>>,
395
- val grayBuffer: Array<IntArray>
396
- )
397
-
398
- private fun sampleRgbaToModelAndGray(
399
- rgba: ByteArray,
428
+ private fun sampleRgbaBufferToModelAndGray(
429
+ buffer: ByteBuffer,
400
430
  srcWidth: Int,
401
- srcHeight: Int
431
+ srcHeight: Int,
432
+ rowStride: Int,
433
+ pixelStride: Int
402
434
  ): SampledFrame {
403
435
  val targetWidth = INPUT_IMAGE_SIZE
404
436
  val targetHeight = INPUT_IMAGE_SIZE
405
437
  val scaleX = srcWidth.toFloat() / targetWidth
406
438
  val scaleY = srcHeight.toFloat() / targetHeight
439
+ val duplicated = buffer.duplicate()
440
+ duplicated.order(ByteOrder.nativeOrder())
407
441
 
408
442
  for (targetY in 0 until targetHeight) {
409
443
  val srcY = (targetY * scaleY).toInt().coerceIn(0, srcHeight - 1)
444
+ val rowOffset = srcY * rowStride
410
445
  for (targetX in 0 until targetWidth) {
411
446
  val srcX = (targetX * scaleX).toInt().coerceIn(0, srcWidth - 1)
412
- val srcIndex = (srcY * srcWidth + srcX) * 4
447
+ val pixelIndex = rowOffset + srcX * pixelStride
413
448
 
414
- val r = (rgba[srcIndex].toInt() and 0xFF) / 255f
415
- val g = (rgba[srcIndex + 1].toInt() and 0xFF) / 255f
416
- val b = (rgba[srcIndex + 2].toInt() and 0xFF) / 255f
449
+ val r = duplicated.get(pixelIndex).toInt() and 0xFF
450
+ val g = duplicated.get(pixelIndex + 1).toInt() and 0xFF
451
+ val b = duplicated.get(pixelIndex + 2).toInt() and 0xFF
417
452
 
418
- modelInputBuffer[targetY][targetX][0] = r
419
- modelInputBuffer[targetY][targetX][1] = g
420
- modelInputBuffer[targetY][targetX][2] = b
453
+ modelInputBuffer[targetY][targetX][0] = r / 255f
454
+ modelInputBuffer[targetY][targetX][1] = g / 255f
455
+ modelInputBuffer[targetY][targetX][2] = b / 255f
421
456
  grayBuffer[targetY][targetX] = (
422
- 0.299f * rgba[srcIndex].toInt() +
423
- 0.587f * rgba[srcIndex + 1].toInt() +
424
- 0.114f * rgba[srcIndex + 2].toInt()
457
+ 0.299f * r +
458
+ 0.587f * g +
459
+ 0.114f * b
425
460
  ).toInt()
426
461
  }
427
462
  }
@@ -446,17 +481,6 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
446
481
  }
447
482
  }
448
483
 
449
- private fun laplacianFromRgba(rgba: ByteArray, width: Int, height: Int): Result<Int> {
450
- return try {
451
- val gray = rgbaToGrayscale(rgba, width, height, INPUT_IMAGE_SIZE, INPUT_IMAGE_SIZE)
452
- val score = calculateLaplacianFromGray(gray)
453
- Result.success(score)
454
- } catch (e: Exception) {
455
- Log.e(TAG, "Error in laplacianFromRgba", e)
456
- Result.failure(e)
457
- }
458
- }
459
-
460
484
  // FROM WORKING VERSION: calculateLaplacianScore
461
485
  private fun calculateLaplacianScore(bitmap: Bitmap): Int {
462
486
  val laplace = arrayOf(
@@ -508,32 +532,6 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
508
532
  return greyImg
509
533
  }
510
534
 
511
- private fun rgbaToGrayscale(
512
- rgba: ByteArray,
513
- srcWidth: Int,
514
- srcHeight: Int,
515
- targetWidth: Int,
516
- targetHeight: Int
517
- ): Array<IntArray> {
518
- val gray = Array(targetHeight) { IntArray(targetWidth) }
519
- val scaleX = srcWidth.toFloat() / targetWidth
520
- val scaleY = srcHeight.toFloat() / targetHeight
521
-
522
- for (targetY in 0 until targetHeight) {
523
- val srcY = (targetY * scaleY).toInt().coerceIn(0, srcHeight - 1)
524
- for (targetX in 0 until targetWidth) {
525
- val srcX = (targetX * scaleX).toInt().coerceIn(0, srcWidth - 1)
526
- val srcIndex = (srcY * srcWidth + srcX) * 4
527
- val r = rgba[srcIndex].toInt() and 0xFF
528
- val g = rgba[srcIndex + 1].toInt() and 0xFF
529
- val b = rgba[srcIndex + 2].toInt() and 0xFF
530
- gray[targetY][targetX] = (0.299 * r + 0.587 * g + 0.114 * b).toInt()
531
- }
532
- }
533
-
534
- return gray
535
- }
536
-
537
535
  private fun calculateLaplacianFromGray(img: Array<IntArray>): Int {
538
536
  val laplace = arrayOf(
539
537
  intArrayOf(0, 1, 0),
@@ -83,12 +83,21 @@ class HybridSpoofDetectorOutput(
83
83
  return
84
84
  }
85
85
  try {
86
- val rgba = extractRgbaBytes(image)
87
- if (!shouldProcessFrame(rgba, image.width, image.height)) {
86
+ val plane = image.planes.firstOrNull() ?: throw IllegalStateException("RGBA image has no planes")
87
+ val rgba = plane.buffer.duplicate()
88
+ rgba.order(java.nio.ByteOrder.nativeOrder())
89
+
90
+ if (!shouldProcessFrameFromPlane(rgba, image.width, image.height, plane.rowStride, plane.pixelStride)) {
88
91
  return
89
92
  }
90
93
 
91
- val result = detector.advancedAntiSpoofingRGBA(rgba, image.width, image.height)
94
+ val result = detector.advancedAntiSpoofingRGBA(
95
+ rgba,
96
+ image.width,
97
+ image.height,
98
+ plane.rowStride,
99
+ plane.pixelStride,
100
+ )
92
101
  val isLive = result.neuralNetworkScore < FaceAntiSpoofingAdvanced.BASE_THRESHOLD &&
93
102
  result.laplacianScore > FaceAntiSpoofingAdvanced.LAPLACIAN_THRESHOLD
94
103
  options.onResult.invoke(
@@ -109,7 +118,13 @@ class HybridSpoofDetectorOutput(
109
118
  }
110
119
  }
111
120
 
112
- private fun shouldProcessFrame(rgba: ByteArray, width: Int, height: Int): Boolean {
121
+ private fun shouldProcessFrameFromPlane(
122
+ buffer: java.nio.ByteBuffer,
123
+ width: Int,
124
+ height: Int,
125
+ rowStride: Int,
126
+ pixelStride: Int,
127
+ ): Boolean {
113
128
  val frameIndex = frameCounter.incrementAndGet()
114
129
  if (frameIndex % FRAME_EVALUATION_INTERVAL != 0) {
115
130
  return false
@@ -121,14 +136,14 @@ class HybridSpoofDetectorOutput(
121
136
  var sampleCount = 0
122
137
 
123
138
  for (y in 0 until height step sampleStep) {
124
- val rowOffset = y * width * 4
139
+ val rowOffset = y * rowStride
125
140
  for (x in 0 until width step sampleStep) {
126
- val index = rowOffset + x * 4
127
- if (index + 2 >= rgba.size) continue
141
+ val index = rowOffset + x * pixelStride
142
+ if (index + 2 >= buffer.capacity()) continue
128
143
 
129
- val r = rgba[index].toInt() and 0xFF
130
- val g = rgba[index + 1].toInt() and 0xFF
131
- val b = rgba[index + 2].toInt() and 0xFF
144
+ val r = buffer.get(index).toInt() and 0xFF
145
+ val g = buffer.get(index + 1).toInt() and 0xFF
146
+ val b = buffer.get(index + 2).toInt() and 0xFF
132
147
  val luma = (0.299 * r + 0.587 * g + 0.114 * b).toInt()
133
148
 
134
149
  brightnessSum += luma.toLong()
@@ -143,7 +158,7 @@ class HybridSpoofDetectorOutput(
143
158
 
144
159
  val avgBrightness = brightnessSum.toFloat() / sampleCount
145
160
  val variance = (brightnessSquaredSum.toFloat() / sampleCount) - (avgBrightness * avgBrightness)
146
- val brightnessDelta = kotlin.math.abs(avgBrightness - lastFrameBrightness)
161
+ val brightnessDelta = abs(avgBrightness - lastFrameBrightness)
147
162
  lastFrameBrightness = avgBrightness
148
163
 
149
164
  val brightnessOk = avgBrightness in MIN_FRAME_BRIGHTNESS..MAX_FRAME_BRIGHTNESS
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-vision-camera-spoof-detector",
3
- "version": "1.1.21",
3
+ "version": "1.1.23",
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": {