passive_liveness 0.0.3
passive_liveness: ^0.0.3 copied to clipboard
Ultra-lightweight passive face liveness detection using LiteRT (TensorFlow Lite) for Flutter on iOS and Android.
passive_liveness #
An ultra-lightweight, high-performance passive face anti-spoofing (liveness) detection package for Flutter powered by LiteRT (TensorFlow Lite) edge inference on Android and iOS.
Features #
- ⚡ LiteRT Next Hardware Acceleration: Powered by
flutter_litertCompiledModelwith zero-copy hardware acceleration (GPU / NPU / CPU fallback). - 🔓 100% Standalone (No ML Kit Required): Works directly out-of-the-box on raw images or camera streams without needing Google ML Kit or third-party face detectors.
- 📷 Zero-Copy Camera Stream Processing: Preprocesses raw Flutter
CameraImagebyte buffers (NV21/YUV420on Android,BGRA8888on iOS) directly to TFLite tensors without main-thread image decoding. - 👓 Glasses Glare Resistance (Asymmetric EMA): Uses Asymmetric Exponential Moving Average filtering ($\alpha=0.1$ for score drops, $\alpha=0.4$ for recovery) to resist momentary specular reflections on glasses.
- 🏃 Motion Gate & Stability Heuristic: Tracks bounding box delta shifts to bypass inference on motion-blurred or out-of-focus frames during user movement.
- 🖼️ Static Photo & File Detection: Uses Flutter's built-in C++ Skia engine codecs (
dart:ui) to evaluate liveness from static images (FileorUint8List) with zero external image package dependencies. - 📱 Android Rotated Bounding Box Mapping: Built-in
isRotatedBoundingBoxauto-detection andFaceBoundingBox.toRawBufferSpace()transformation for portrait ML Kit face detection bounding boxes on Android (0°,90°,180°,270°). - 💡 Adaptive Contrast Stretching & Edge Clamping: Automatically normalizes dark backlit faces and uses Edge Pixel Replication (
BORDER_REPLICATE) to eliminate black border artifacts.
Do I Need Google ML Kit or a Face Detector? #
Short Answer: NO! #
The boundingBox parameter is 100% OPTIONAL.
If you don't pass a boundingBox, passive_liveness automatically uses the entire image frame as the face region. You can run liveness detection in just one line of code!
How Bounding Box Works: #
-
Without Face Detector (Default - Super Easy): Simply pass your camera buffer, photo file, or bytes.
passive_livenessevaluates the entire image automatically:// Works out of the box without ML Kit! final result = await detector.detectLivenessFromImageBytes(imageBytes); -
With Face Detector (Optional - Advanced Precision): If your app already uses a face detector (like
google_mlkit_face_detection), passingFaceBoundingBox.fromRect(face.boundingBox)crops directly onto the detected face for pinpoint accuracy:// Optional: pass boundingBox if you have ML Kit final result = await detector.detectLivenessFromImageBytes( imageBytes, boundingBox: FaceBoundingBox.fromRect(faceRect), );
Installation #
Add passive_liveness to your project:
flutter pub add passive_liveness
Usage #
1. Initialize Detector Engine #
Initialize PassiveLivenessDetector. The bundled MiniFAS model is loaded automatically:
import 'package:passive_liveness/passive_liveness.dart';
final detector = PassiveLivenessDetector();
await detector.initialize();
2. Real-Time Camera Stream Detection (CameraImage) #
Option A: Simple (Without ML Kit - Full Frame)
import 'package:camera/camera.dart';
import 'package:passive_liveness/passive_liveness.dart';
void processCameraFrame(CameraImage cameraImage, int sensorRotation) async {
final buffer = LivenessImageBuffer(
width: cameraImage.width,
height: cameraImage.height,
format: cameraImage.format.group == ImageFormatGroup.bgra8888
? LivenessImageFormat.bgra8888
: (cameraImage.planes.length == 1
? LivenessImageFormat.nv21
: LivenessImageFormat.yuv420),
planes: cameraImage.planes
.map((p) => LivenessImagePlane(
bytes: p.bytes,
bytesPerRow: p.bytesPerRow,
bytesPerPixel: p.bytesPerPixel,
))
.toList(),
);
// No boundingBox passed - evaluates full frame!
final LivenessResult result = await detector.detectLivenessFromBuffer(
buffer,
rotation: sensorRotation,
);
if (result.isReal) {
print('Real human face!');
} else {
print('Spoof face detected!');
}
}
Option B: Advanced (With ML Kit Face Detector)
// Pass faceRect from ML Kit
final boundingBox = faceRect != null ? FaceBoundingBox.fromRect(faceRect) : null;
final LivenessResult result = await detector.detectLivenessFromBuffer(
buffer,
boundingBox: boundingBox,
rotation: sensorRotation,
isRotatedBoundingBox: Platform.isAndroid,
);
3. Using LivenessFrameProcessor for Smooth Streaming #
For stream processing with automatic motion gating and frame throttling:
final processor = LivenessFrameProcessor(
detector: detector,
throttleInterval: const Duration(milliseconds: 150),
);
// In your camera stream listener:
final LivenessResult? result = await processor.processBufferFrame(
buffer,
rotation: sensorRotation,
);
4. Detect Liveness from Static Photo File (File) #
Evaluate a photo file picked via image_picker or taken with takePicture():
import 'dart:io';
import 'package:passive_liveness/passive_liveness.dart';
Future<void> checkPhotoLiveness(File imageFile) async {
// Works directly on the file - boundingBox is optional!
final LivenessResult result = await detector.detectLivenessFromImageFile(
imageFile,
);
print('Is Real: ${result.isReal}');
print('Real Score: ${result.realScore}');
print('Inference Time: ${result.inferenceTime.inMilliseconds}ms');
}
5. Detect Liveness from Image Bytes (Uint8List) #
Evaluate liveness directly from in-memory image bytes:
import 'dart:typed_data';
import 'package:passive_liveness/passive_liveness.dart';
Future<void> checkBytesLiveness(Uint8List imageBytes) async {
// Works directly on raw bytes - boundingBox is optional!
final LivenessResult result = await detector.detectLivenessFromImageBytes(
imageBytes,
);
print('Is Real: ${result.isReal}');
print('Real Score: ${result.realScore}');
}
Clean Up #
When you are done using the detector (e.g. in a State's dispose method):
@override
void dispose() {
detector.dispose();
super.dispose();
}
Core Classes Reference #
| Class | Description |
|---|---|
PassiveLivenessDetector |
Main engine class for initializing the LiteRT model and running inferences. |
LivenessFrameProcessor |
Stream processor with motion-gating heuristic and frame throttling. |
LivenessImageBuffer |
Lightweight container for camera raw byte planes (NV21, YUV420, BGRA8888). |
FaceBoundingBox |
Coordinates (x, y, width, height) defining the face area. Optional. Supports .fromRect(Rect). |
LivenessResult |
Detection result containing isReal, realScore, spoofScore, logitDiff, and inferenceTime. |
Recent Improvements & Changelog #
🚀 Performance & Accuracy Upgrades #
- LiteRT Next
CompiledModelIntegration: Upgraded engine toflutter_litertCompiledModelwith zero-copy hardware acceleration (GPU / NPU / CPU fallback). - Standalone Support (Optional BoundingBox): Bounding box parameter is optional; default fallback automatically processes the full image frame.
- Asymmetric EMA Glare Resistance: Implemented Asymmetric Exponential Moving Average ($\alpha=0.1$ for score drops, $\alpha=0.4$ for recovery) to prevent specular lens reflections on glasses from causing false spoof drops.
- Bounding Box Motion Stability Gate: Added motion stability heuristic ($5%$ position/size shift threshold) in
LivenessFrameProcessorto bypass TFLite inference during user movement. - Android Sensor Coordinate Rotation: Auto-transforms ML Kit portrait face bounding boxes to match raw landscape sensor buffers (
0°,90°,180°,270°). - Edge Pixel Replication (
BORDER_REPLICATE): Switched from zero-padding to coordinate clamping (rawX.round().clamp(0, rawW - 1)), eliminating pitch-black border artifacts on edge face crops. - Adaptive Contrast Stretching: Added
enableContrastStretchoption to automatically brighten dark, backlit face crops without distorting skin moiré signals. - Visual Tensor Dumper: Added
ImagePreprocessor.saveTensorToDisk(...)to export 128x128 Float32List tensors to PNG/PPM images for debugging.
Model Attribution & Credits #
This package utilizes the MiniFASNet v2 SE passive face anti-spoofing model architecture, derived and converted from facenox/face-antispoof-onnx (MiniFASNetV2 SE trained with Fourier Transform frequency spectrum loss).
Author #
Andika Tri Prasetya
- GitHub: github.com/andikatp
License #
This project is licensed under the OSI-approved MIT License - see the LICENSE file for details.