Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@
- [Native Setup](#flutterSetupNativeSetup)
- [iOS](#flutterSetupNativeiOS)
- [Android](#flutterSetupNativeAndroid)
- [Privacy & compliance](#privacy)
- [Contributors](#contributors)

<a name="whatIsLivelyness"></a>

## What is the Livelyness Detection?

Liveness detection for face recognition in biometrics is a technique where an algorithm detects if the person in front of the camera is alive and real. The algorithm is able to recognize a live person from presentation attacks - where a bad actor, or fraud perpetrator, uses someone else's physical characteristics or biometric data (known as "spoofs") for impersonation.
This package runs a short sequence of **active liveness challenges** — blink, turn left/right, smile — detected **on-device** via Google ML Kit face detection, and captures a selfie once the challenge steps pass. All processing stays on the device; no images or biometric data are sent anywhere by this package.

`Spoofing` attempts using printed photos, recordings, deep fake pictures, and 3D masks poses a serious threat. `Facial Liveness Detection` incorporates specialized features to identify biometric spoofing attacks, which could be an imitation emulating a person’s unique biometrics scanned through the biometric detector to deceive or bypass the identification and authentication steps provided by the system. Even though face recognition can reliably answer the question, “Is this the right person?” but not the question, “Is this a live person?” This is where liveness detection technology plays a significant role in fraud detection and mitigation. Face biometric matching must be able to detect spoofs in order to be trusted, and to maintain the integrity of biometric data.
> **⚠️ This is active challenge-response, NOT certified anti-spoofing.** The package does **not** implement [ISO/IEC 30107](https://www.iso.org/standard/79520.html) presentation-attack detection (PAD). It performs no texture, depth, motion, or reflection analysis, so a **printed photo, screen replay, recorded video, or 3D mask can defeat the gesture challenge.** Do **not** use it as the sole control for KYC, identity verification, payments, or any security-critical authentication. Pair it with a dedicated PAD/anti-spoofing solution and your own server-side risk checks, and obtain the user's consent before capture (see [Privacy & compliance](#privacy)).

<!-- <iframe src="https://embed.lottiefiles.com/animation/16432" width="100%" aspect-ratio="auto"></iframe> -->

Expand Down Expand Up @@ -80,10 +81,10 @@ Next comes the native setup on both android and iOS
```xml
<key>NSCameraUsageDescription</key>
<string>Camera Access for Scanning</string>
<key>NSMicrophoneUsageDescription</key>
<string>Microphone for playing instructions audio.</string>
```

> Only the camera permission is required — this package neither records nor plays audio (`enableAudio: false`), so do **not** add `NSMicrophoneUsageDescription`. Declaring an unused microphone purpose string can cause App Store review rejection (Guideline 5.1.1).

4. Open the `ios/Runner/Podfile` and uncomment the second line.

```yaml
Expand Down Expand Up @@ -143,6 +144,25 @@ A call to a single line function will return a temporary path to the captured im



<a name="privacy"></a>

## Privacy & compliance

This package captures **biometric data** (a face selfie and derived facial landmarks) — a special/sensitive data category under most privacy laws. Read this before shipping to production.

**What this package does**

- **On-device only.** Face detection runs locally via Google ML Kit; the package makes **no network calls** and transmits no images or biometric data.
- Uses [Google ML Kit Face Detection](https://developers.google.com/ml-kit/vision/face-detection) — your app must comply with Google's ML Kit terms and disclose its use.
- The captured selfie is written **unencrypted** to the OS temporary directory and returned as a file path. **The package never deletes it** — the file persists until you remove it or the OS evicts the cache.

**What you (the consuming app) are responsible for**

- **Obtain consent before capture.** This package does not collect consent and its info screen is *instructional only* — **not** lawful-basis consent. You must establish a lawful basis: GDPR Art. 9(2)(a) *explicit* consent, Illinois BIPA §15(b) informed *written* release, China PIPL *separate* consent, India DPDP notice+consent, etc.
- **Delete the captured image** as soon as it is no longer needed, and define a retention/destruction schedule (BIPA §15(a); GDPR storage limitation, Art. 5(1)(e)).
- **Do not treat this as anti-spoofing/PAD.** A capture where `didCaptureAutomatically == false` (the manual button shown after a timeout) has passed **no** liveness check — do not accept it as verified.
- If you deploy in the EU for identity purposes, review your EU AI Act obligations (transparency, robustness, human oversight).

## Contributors

<a href="https://github.com/GhagSagar23/livelyness_detection/graphs/contributors"><img src="https://contrib.rocks/image?repo=GhagSagar23/livelyness_detection" width="128" height="128" /></a>
2 changes: 0 additions & 2 deletions lib/index.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,3 @@ export 'package:rxdart/rxdart.dart';
export 'package:uuid/uuid.dart';

export './src/index.dart';
export 'livelyness_detection_method_channel.dart';
export 'livelyness_detection_platform_interface.dart';
18 changes: 0 additions & 18 deletions lib/livelyness_detection_method_channel.dart

This file was deleted.

30 changes: 0 additions & 30 deletions lib/livelyness_detection_platform_interface.dart

This file was deleted.

29 changes: 5 additions & 24 deletions lib/src/screens/m7_livelyness_detection_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -364,28 +364,6 @@ class _MLivelyness7DetectionScreenState
if (_isProcessingStep) {
return;
}
final faceWidth = face.boundingBox.width;
final Point<int>? leftEyePosition = face
.getContour(
FaceContourType.leftEye,
)
?.points
.elementAt(8);
final Point<int>? rightEyePosition = face
.getContour(
FaceContourType.rightEye,
)
?.points
.elementAt(0);
if (leftEyePosition != null && rightEyePosition != null) {
final goldenRatio = (faceWidth /
leftEyePosition.distanceTo(
rightEyePosition,
));
if (kDebugMode) {
print("Golden Ratio: $goldenRatio");
}
}
switch (step) {
case LivelynessStep.blink:
final BlinkDetectionThreshold? blinkThreshold =
Expand Down Expand Up @@ -420,8 +398,11 @@ class _MLivelyness7DetectionScreenState
LivelynessDetection.instance.thresholdConfig.firstWhereOrNull(
(p0) => p0 is HeadTurnDetectionThreshold,
) as HeadTurnDetectionThreshold?;
if ((face.headEulerAngleY ?? 0) >
(headTurnThreshold?.rotationAngle ?? -50)) {
// turnRight: yaw is negative when the head turns right, so require the
// angle to be MORE negative than the (magnitude of the) threshold.
// Using `>` here made a forward-facing head (yaw ~0) pass instantly.
if ((face.headEulerAngleY ?? 0) <
-((headTurnThreshold?.rotationAngle ?? 50).abs())) {
_startProcessing();
await _completeStep(step: step);
}
Expand Down
67 changes: 3 additions & 64 deletions lib/src/screens/m7_livelyness_detection_screen_v2.dart
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,9 @@ class _LivelynessDetectionScreenAndroidState
() => _isProcessing = false,
);
}
debugPrint("...sending image resulted error $error");
if (kDebugMode) {
debugPrint("...sending image resulted error $error");
}
}
}

Expand All @@ -147,56 +149,6 @@ class _LivelynessDetectionScreenAndroidState
return;
}
final Face firstFace = faces.first;
final landmarks = firstFace.landmarks;
// Get landmark positions for relevant facial features
final Point<int>? leftEye = landmarks[FaceLandmarkType.leftEye]?.position;
final Point<int>? rightEye =
landmarks[FaceLandmarkType.rightEye]?.position;
final Point<int>? leftCheek =
landmarks[FaceLandmarkType.leftCheek]?.position;
final Point<int>? rightCheek =
landmarks[FaceLandmarkType.rightCheek]?.position;
final Point<int>? leftEar = landmarks[FaceLandmarkType.leftEar]?.position;
final Point<int>? rightEar =
landmarks[FaceLandmarkType.rightEar]?.position;
final Point<int>? leftMouth =
landmarks[FaceLandmarkType.leftMouth]?.position;
final Point<int>? rightMouth =
landmarks[FaceLandmarkType.rightMouth]?.position;

// Calculate symmetry values based on corresponding landmark positions
final Map<String, double> symmetry = {};
final eyeSymmetry = calculateSymmetry(
leftEye,
rightEye,
);
symmetry['eyeSymmetry'] = eyeSymmetry;

final cheekSymmetry = calculateSymmetry(
leftCheek,
rightCheek,
);
symmetry['cheekSymmetry'] = cheekSymmetry;

final earSymmetry = calculateSymmetry(
leftEar,
rightEar,
);
symmetry['earSymmetry'] = earSymmetry;

final mouthSymmetry = calculateSymmetry(
leftMouth,
rightMouth,
);
symmetry['mouthSymmetry'] = mouthSymmetry;
double total = 0.0;
symmetry.forEach((key, value) {
total += value;
});
final double average = total / symmetry.length;
if (kDebugMode) {
print("Face Symmetry: $average");
}
if (_isProcessingStep &&
_steps[_stepsKey.currentState?.currentIndex ?? 0].step ==
LivelynessStep.blink) {
Expand Down Expand Up @@ -521,17 +473,4 @@ class _LivelynessDetectionScreenAndroidState
],
);
}

double calculateSymmetry(
Point<int>? leftPosition, Point<int>? rightPosition) {
if (leftPosition != null && rightPosition != null) {
final double dx = (rightPosition.x - leftPosition.x).abs().toDouble();
final double dy = (rightPosition.y - leftPosition.y).abs().toDouble();
final distance = Offset(dx, dy).distance;

return distance;
}

return 0.0;
}
}
2 changes: 0 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@ dependencies:
flutter:
sdk: flutter
google_mlkit_face_detection: ^0.11.0
image: ^4.0.15
lottie: ^3.1.2
path_provider: ^2.0.14
plugin_platform_interface: ^2.1.3
rxdart: ^0.28.0
uuid: ^4.4.2

Expand Down