Skip to content

Commit 7c92777

Browse files
committed
update
1 parent 7fce946 commit 7c92777

File tree

12 files changed

+74
-50
lines changed

12 files changed

+74
-50
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 1.1.0
2+
3+
- Can config use GPU
4+
- Can config CPU threads
5+
- Small improvements
6+
17
## 1.0.0
28

39
- Update Sigmoid score (probability of class=1)

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ void dispose() {
7575
```txt
7676
Live: true
7777
Score (P(spoof)): 0.023
78-
Clarity (laplacian): 5939.0
78+
Clarity (laplacian): 10239.0
7979
Inference time: 66 ms
8080
```
8181

@@ -87,15 +87,15 @@ You can tune thresholds:
8787
final liveness = await FlutterLiveness.create(
8888
options: LivenessOptions(
8989
threshold: 0.5, // sigmoid cutoff
90-
laplacianThreshold: 1000, // blur block
90+
laplacianThreshold: 4000, // blur block
9191
),
9292
);
9393
```
9494

9595
## ❗ Usage Notes
9696

9797
- Feed cropped face images (not full camera frames)
98-
- Calls are fast (~20–35ms on modern devices)
98+
- Calls are fast (~60–70ms on modern devices)
9999
- Throttle analysis: ~3–5 FPS recommended
100100
- Do not consider this as the only security mechanism for KYC/financial apps — use it as one signal in a trust pipeline
101101

example/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,5 @@ app.*.map.json
4343
/android/app/debug
4444
/android/app/profile
4545
/android/app/release
46+
47+
/assets/sample_face_*.jpg

example/assets/sample_face_1.jpg

-5.18 KB
Binary file not shown.

example/assets/sample_face_2.jpg

-1.36 MB
Binary file not shown.

example/assets/sample_face_3.jpg

-862 KB
Binary file not shown.

lib/src/flutter_liveness.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ class FlutterLiveness {
1616
static Future<FlutterLiveness> create({
1717
LivenessOptions options = const LivenessOptions(),
1818
}) async {
19-
final runner = await TFLiteRunner.create(useGpu: options.useGpu);
19+
final runner = await TFLiteRunner.create(
20+
useGpu: options.useGpu,
21+
threads: options.threads,
22+
);
2023
final engine = LivenessEngine(runner, options);
2124
return FlutterLiveness._(engine, runner);
2225
}

lib/src/image_to_nhwc.dart

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@ List<List<List<List<double>>>> toNHWC(img.Image resized) {
77
List.generate(
88
224,
99
(_) => List.generate(224, (_) {
10-
final r = bytes[index++].toInt() / 255.0;
11-
final g = bytes[index++].toInt() / 255.0;
12-
final b = bytes[index++].toInt() / 255.0;
13-
10+
final r = bytes[index++] / 255.0;
11+
final g = bytes[index++] / 255.0;
12+
final b = bytes[index++] / 255.0;
1413
return [r, g, b];
1514
}),
1615
),

lib/src/laplacian.dart

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,27 @@ int laplacianScore(
55
required int laplacePixelThreshold,
66
}) {
77
final gray = img.grayscale(resized);
8-
const kernel = [
9-
[0, 1, 0],
10-
[1, -4, 1],
11-
[0, 1, 0],
12-
];
13-
var score = 0;
8+
final bytes = gray.getBytes(order: img.ChannelOrder.red);
149

15-
for (var r = 0; r <= gray.height - 3; r++) {
16-
for (var c = 0; c <= gray.width - 3; c++) {
17-
var conv = 0;
18-
for (var kr = 0; kr < 3; kr++) {
19-
for (var kc = 0; kc < 3; kc++) {
20-
final px = gray.getPixel(c + kc, r + kr);
21-
conv += px.r.toInt() * kernel[kr][kc];
22-
}
10+
int score = 0;
11+
12+
for (int x = 1; x < 224 - 2; x++) {
13+
for (int y = 1; y < 224 - 2; y++) {
14+
final int index = x * 224 + y;
15+
16+
final int center = bytes[index];
17+
18+
final int north = bytes[index - 224];
19+
final int south = bytes[index + 224];
20+
final int east = bytes[index + 1];
21+
final int west = bytes[index - 1];
22+
23+
// Conv = N + S + E + W - 4*Center
24+
final int conv = (north + south + east + west) - (4 * center);
25+
26+
if (conv.abs() > laplacePixelThreshold) {
27+
score++;
2328
}
24-
if (conv.abs() > laplacePixelThreshold) score++;
2529
}
2630
}
2731
return score;

lib/src/liveness_engine.dart

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,40 @@ import 'package:flutter_liveness/src/laplacian.dart';
33
import 'package:flutter_liveness/src/liveness_options.dart';
44
import 'package:flutter_liveness/src/liveness_result.dart';
55
import 'package:flutter_liveness/src/tflite_runner.dart';
6-
import 'package:image/image.dart' as imglib;
6+
import 'package:image/image.dart' as img;
77

88
class LivenessEngine {
99
final TFLiteRunner _runner;
10-
final LivenessOptions _opt;
10+
final LivenessOptions _options;
1111

1212
const LivenessEngine(
1313
this._runner,
14-
this._opt,
14+
this._options,
1515
);
1616

17-
Future<LivenessResult> analyze(imglib.Image face) async {
18-
final resized = imglib.copyResize(
19-
face,
20-
width: 224,
21-
height: 224,
22-
);
17+
Future<LivenessResult> analyze(img.Image face) async {
18+
final t0 = DateTime.now();
19+
20+
final img.Image resized;
21+
if (face.width == 224 && face.height == 224) {
22+
resized = face;
23+
} else {
24+
resized = img.copyResize(face, width: 224, height: 224);
25+
}
2326

2427
final int laplacian;
25-
if (_opt.applyLaplacianGate) {
28+
if (_options.applyLaplacianGate) {
2629
laplacian = laplacianScore(
2730
resized,
28-
laplacePixelThreshold: _opt.laplacePixelThreshold,
31+
laplacePixelThreshold: _options.laplacePixelThreshold,
2932
);
3033
} else {
3134
/// Bypass laplacian calculation
3235
laplacian = 999999;
3336
}
3437

35-
if (_opt.applyLaplacianGate && laplacian < _opt.laplacianThreshold) {
38+
if (_options.applyLaplacianGate &&
39+
laplacian < _options.laplacianThreshold) {
3640
return LivenessResult(
3741
isLive: false,
3842
score: 1,
@@ -41,21 +45,21 @@ class LivenessEngine {
4145
);
4246
}
4347

44-
final input = toNHWC(face);
45-
final t0 = DateTime.now();
48+
final input = toNHWC(resized);
4649

4750
/// Probability of class=1 (spoof)
4851
final prob1 = await _runner.inferProb(input);
4952

50-
final dt = DateTime.now().difference(t0);
5153
final score = (1.0 - prob1);
52-
final isLive = score >= _opt.threshold;
54+
final isLive = score >= _options.threshold;
55+
56+
final duration = DateTime.now().difference(t0);
5357

5458
return LivenessResult(
5559
isLive: isLive,
5660
score: prob1,
5761
laplacian: laplacian,
58-
duration: dt,
62+
duration: duration,
5963
);
6064
}
6165
}

0 commit comments

Comments
 (0)