capacitor-plugin-faceantispoofing 0.0.1
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/CapacitorPluginFaceantispoofing.podspec +20 -0
- package/Package.swift +28 -0
- package/README.md +175 -0
- package/android/build.gradle +64 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/assets/FaceAntiSpoofing.tflite +0 -0
- package/android/src/main/assets/onet.tflite +0 -0
- package/android/src/main/assets/pnet.tflite +0 -0
- package/android/src/main/assets/rnet.tflite +0 -0
- package/android/src/main/java/io/github/asephermann/plugins/faceantispoofing/FaceAntiSpoofing.java +112 -0
- package/android/src/main/java/io/github/asephermann/plugins/faceantispoofing/FaceAntiSpoofingPlugin.java +178 -0
- package/android/src/main/java/io/github/asephermann/plugins/faceantispoofing/MyUtil.java +174 -0
- package/android/src/main/java/io/github/asephermann/plugins/faceantispoofing/mtcnn/Align.java +28 -0
- package/android/src/main/java/io/github/asephermann/plugins/faceantispoofing/mtcnn/Box.java +73 -0
- package/android/src/main/java/io/github/asephermann/plugins/faceantispoofing/mtcnn/MTCNN.java +268 -0
- package/android/src/main/java/io/github/asephermann/plugins/faceantispoofing/mtcnn/Utils.java +25 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +104 -0
- package/dist/esm/definitions.d.ts +22 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +5 -0
- package/dist/esm/web.js +14 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +28 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +31 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/FaceAntiSpoofingPlugin/Align.swift +41 -0
- package/ios/Sources/FaceAntiSpoofingPlugin/Box.swift +70 -0
- package/ios/Sources/FaceAntiSpoofingPlugin/FaceAntiSpoofing.swift +105 -0
- package/ios/Sources/FaceAntiSpoofingPlugin/FaceAntiSpoofing.tflite +0 -0
- package/ios/Sources/FaceAntiSpoofingPlugin/FaceAntiSpoofingPlugin.swift +166 -0
- package/ios/Sources/FaceAntiSpoofingPlugin/MTCNN.swift +407 -0
- package/ios/Sources/FaceAntiSpoofingPlugin/Tools.swift +103 -0
- package/ios/Sources/FaceAntiSpoofingPlugin/onet.tflite +0 -0
- package/ios/Sources/FaceAntiSpoofingPlugin/pnet.tflite +0 -0
- package/ios/Sources/FaceAntiSpoofingPlugin/rnet.tflite +0 -0
- package/ios/Tests/FaceAntiSpoofingPluginTests/FaceAntiSpoofingTests.swift +15 -0
- package/package.json +80 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
package io.github.asephermann.plugins.faceantispoofing;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
import android.content.res.AssetFileDescriptor;
|
|
5
|
+
import android.content.res.AssetManager;
|
|
6
|
+
import android.graphics.Bitmap;
|
|
7
|
+
import android.graphics.BitmapFactory;
|
|
8
|
+
import android.graphics.Matrix;
|
|
9
|
+
import android.graphics.Rect;
|
|
10
|
+
|
|
11
|
+
import java.io.FileInputStream;
|
|
12
|
+
import java.io.IOException;
|
|
13
|
+
import java.io.InputStream;
|
|
14
|
+
import java.nio.MappedByteBuffer;
|
|
15
|
+
import java.nio.channels.FileChannel;
|
|
16
|
+
|
|
17
|
+
import io.github.asephermann.plugins.faceantispoofing.mtcnn.Box;
|
|
18
|
+
|
|
19
|
+
public class MyUtil {
|
|
20
|
+
|
|
21
|
+
public static Bitmap readFromAssets(Context context, String filename){
|
|
22
|
+
Bitmap bitmap;
|
|
23
|
+
AssetManager asm = context.getAssets();
|
|
24
|
+
try {
|
|
25
|
+
InputStream is = asm.open(filename);
|
|
26
|
+
bitmap = BitmapFactory.decodeStream(is);
|
|
27
|
+
is.close();
|
|
28
|
+
} catch (IOException e) {
|
|
29
|
+
e.printStackTrace();
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return bitmap;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public static void rectExtend(Bitmap bitmap, Rect rect, int marginX, int marginY) {
|
|
36
|
+
rect.left = Math.max(0, rect.left - marginX / 2);
|
|
37
|
+
rect.right = Math.min(bitmap.getWidth() - 1, rect.right + marginX / 2);
|
|
38
|
+
rect.top = Math.max(0, rect.top - marginY / 2);
|
|
39
|
+
rect.bottom = Math.min(bitmap.getHeight() - 1, rect.bottom + marginY / 2);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public static void rectExtend(Bitmap bitmap, Rect rect) {
|
|
43
|
+
int width = rect.right - rect.left;
|
|
44
|
+
int height = rect.bottom - rect.top;
|
|
45
|
+
int margin = (height - width) / 2;
|
|
46
|
+
rect.left = Math.max(0, rect.left - margin);
|
|
47
|
+
rect.right = Math.min(bitmap.getWidth() - 1, rect.right + margin);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public static MappedByteBuffer loadModelFile(AssetManager assetManager, String modelPath) throws IOException {
|
|
51
|
+
AssetFileDescriptor fileDescriptor = assetManager.openFd(modelPath);
|
|
52
|
+
FileInputStream inputStream = new FileInputStream(fileDescriptor.getFileDescriptor());
|
|
53
|
+
FileChannel fileChannel = inputStream.getChannel();
|
|
54
|
+
long startOffset = fileDescriptor.getStartOffset();
|
|
55
|
+
long declaredLength = fileDescriptor.getDeclaredLength();
|
|
56
|
+
return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public static float[][][] normalizeImage(Bitmap bitmap) {
|
|
60
|
+
int h = bitmap.getHeight();
|
|
61
|
+
int w = bitmap.getWidth();
|
|
62
|
+
float[][][] floatValues = new float[h][w][3];
|
|
63
|
+
|
|
64
|
+
float imageMean = 127.5f;
|
|
65
|
+
float imageStd = 128;
|
|
66
|
+
|
|
67
|
+
int[] pixels = new int[h * w];
|
|
68
|
+
bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, w, h);
|
|
69
|
+
for (int i = 0; i < h; i++) {
|
|
70
|
+
for (int j = 0; j < w; j++) {
|
|
71
|
+
final int val = pixels[i * w + j];
|
|
72
|
+
float r = (((val >> 16) & 0xFF) - imageMean) / imageStd;
|
|
73
|
+
float g = (((val >> 8) & 0xFF) - imageMean) / imageStd;
|
|
74
|
+
float b = ((val & 0xFF) - imageMean) / imageStd;
|
|
75
|
+
float[] arr = {r, g, b};
|
|
76
|
+
floatValues[i][j] = arr;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return floatValues;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public static Bitmap bitmapResize(Bitmap bitmap, float scale) {
|
|
83
|
+
int width = bitmap.getWidth();
|
|
84
|
+
int height = bitmap.getHeight();
|
|
85
|
+
Matrix matrix = new Matrix();
|
|
86
|
+
matrix.postScale(scale, scale);
|
|
87
|
+
return Bitmap.createBitmap(
|
|
88
|
+
bitmap, 0, 0, width, height, matrix, true);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public static float[][][] transposeImage(float[][][] in) {
|
|
92
|
+
int h = in.length;
|
|
93
|
+
int w = in[0].length;
|
|
94
|
+
int channel = in[0][0].length;
|
|
95
|
+
float[][][] out = new float[w][h][channel];
|
|
96
|
+
for (int i = 0; i < h; i++) {
|
|
97
|
+
for (int j = 0; j < w; j++) {
|
|
98
|
+
out[j][i] = in[i][j] ;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public static float[][][][] transposeBatch(float[][][][] in) {
|
|
105
|
+
int batch = in.length;
|
|
106
|
+
int h = in[0].length;
|
|
107
|
+
int w = in[0][0].length;
|
|
108
|
+
int channel = in[0][0][0].length;
|
|
109
|
+
float[][][][] out = new float[batch][w][h][channel];
|
|
110
|
+
for (int i = 0; i < batch; i++) {
|
|
111
|
+
for (int j = 0; j < h; j++) {
|
|
112
|
+
for (int k = 0; k < w; k++) {
|
|
113
|
+
out[i][k][j] = in[i][j][k] ;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
public static float[][][] cropAndResize(Bitmap bitmap, Box box, int size) {
|
|
121
|
+
Matrix matrix = new Matrix();
|
|
122
|
+
float scaleW = 1.0f * size / box.width();
|
|
123
|
+
float scaleH = 1.0f * size / box.height();
|
|
124
|
+
matrix.postScale(scaleW, scaleH);
|
|
125
|
+
Rect rect = box.transform2Rect();
|
|
126
|
+
Bitmap croped = Bitmap.createBitmap(
|
|
127
|
+
bitmap, rect.left, rect.top, box.width(), box.height(), matrix, true);
|
|
128
|
+
|
|
129
|
+
return normalizeImage(croped);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
public static Bitmap crop(Bitmap bitmap, Rect rect) {
|
|
133
|
+
Bitmap cropped = Bitmap.createBitmap(bitmap, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
|
|
134
|
+
return cropped;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
public static void l2Normalize(float[][] embeddings, double epsilon) {
|
|
138
|
+
for (int i = 0; i < embeddings.length; i++) {
|
|
139
|
+
float squareSum = 0;
|
|
140
|
+
for (int j = 0; j < embeddings[i].length; j++) {
|
|
141
|
+
squareSum += Math.pow(embeddings[i][j], 2);
|
|
142
|
+
}
|
|
143
|
+
float xInvNorm = (float) Math.sqrt(Math.max(squareSum, epsilon));
|
|
144
|
+
for (int j = 0; j < embeddings[i].length; j++) {
|
|
145
|
+
embeddings[i][j] = embeddings[i][j] / xInvNorm;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
public static int[][] convertGreyImg(Bitmap bitmap) {
|
|
151
|
+
int w = bitmap.getWidth();
|
|
152
|
+
int h = bitmap.getHeight();
|
|
153
|
+
|
|
154
|
+
int[] pixels = new int[h * w];
|
|
155
|
+
bitmap.getPixels(pixels, 0, w, 0, 0, w, h);
|
|
156
|
+
|
|
157
|
+
int[][] result = new int[h][w];
|
|
158
|
+
int alpha = 0xFF << 24;
|
|
159
|
+
for(int i = 0; i < h; i++) {
|
|
160
|
+
for(int j = 0; j < w; j++) {
|
|
161
|
+
int val = pixels[w * i + j];
|
|
162
|
+
|
|
163
|
+
int red = ((val >> 16) & 0xFF);
|
|
164
|
+
int green = ((val >> 8) & 0xFF);
|
|
165
|
+
int blue = (val & 0xFF);
|
|
166
|
+
|
|
167
|
+
int grey = (int)((float) red * 0.3 + (float)green * 0.59 + (float)blue * 0.11);
|
|
168
|
+
grey = alpha | (grey << 16) | (grey << 8) | grey;
|
|
169
|
+
result[i][j] = grey;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
package io.github.asephermann.plugins.faceantispoofing.mtcnn;
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap;
|
|
4
|
+
import android.graphics.Canvas;
|
|
5
|
+
import android.graphics.Matrix;
|
|
6
|
+
import android.graphics.Point;
|
|
7
|
+
|
|
8
|
+
public class Align {
|
|
9
|
+
public static Bitmap face_align(Bitmap img, Point[] landmark) {
|
|
10
|
+
// Calculate the average position of eyes
|
|
11
|
+
int x_left_eye = landmark[0].x;
|
|
12
|
+
int y_left_eye = landmark[0].y;
|
|
13
|
+
int x_right_eye = landmark[1].x;
|
|
14
|
+
int y_right_eye = landmark[1].y;
|
|
15
|
+
|
|
16
|
+
// Calculate the angle between the eyes
|
|
17
|
+
double dx = x_right_eye - x_left_eye;
|
|
18
|
+
double dy = y_right_eye - y_left_eye;
|
|
19
|
+
double angle = Math.toDegrees(Math.atan2(dy, dx));
|
|
20
|
+
|
|
21
|
+
// Rotate the image to align the eyes horizontally
|
|
22
|
+
Matrix matrix = new Matrix();
|
|
23
|
+
matrix.postRotate((float) -angle, img.getWidth() / 2f, img.getHeight() / 2f);
|
|
24
|
+
Bitmap rotatedImg = Bitmap.createBitmap(img, 0, 0, img.getWidth(), img.getHeight(), matrix, true);
|
|
25
|
+
|
|
26
|
+
return rotatedImg;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
package io.github.asephermann.plugins.faceantispoofing.mtcnn;
|
|
2
|
+
|
|
3
|
+
import android.graphics.Point;
|
|
4
|
+
import android.graphics.Rect;
|
|
5
|
+
|
|
6
|
+
public class Box {
|
|
7
|
+
public int[] box = new int[4];
|
|
8
|
+
public float score;
|
|
9
|
+
public float[] bbr = new float[4];
|
|
10
|
+
public boolean deleted = false;
|
|
11
|
+
public Point[] landmark = new Point[5];
|
|
12
|
+
|
|
13
|
+
public int left() {
|
|
14
|
+
return box[0];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public int right() {
|
|
18
|
+
return box[2];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public int top() {
|
|
22
|
+
return box[1];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public int bottom() {
|
|
26
|
+
return box[3];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public int width() {
|
|
30
|
+
return box[2] - box[0] + 1;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public int height() {
|
|
34
|
+
return box[3] - box[1] + 1;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public Rect transform2Rect() {
|
|
38
|
+
Rect rect = new Rect(box[0], box[1], box[2], box[3]);
|
|
39
|
+
return rect;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public int area() {
|
|
43
|
+
return width() * height();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public void calibrate() {
|
|
47
|
+
int w = width();
|
|
48
|
+
int h = height();
|
|
49
|
+
box[0] = Math.round(box[0] + bbr[0] * w);
|
|
50
|
+
box[1] = Math.round(box[1] + bbr[1] * h);
|
|
51
|
+
box[2] = Math.round(box[2] + bbr[2] * w);
|
|
52
|
+
box[3] = Math.round(box[3] + bbr[3] * h);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public void toSquareShape() {
|
|
56
|
+
int w = width();
|
|
57
|
+
int h = height();
|
|
58
|
+
int maxSide = Math.max(w, h);
|
|
59
|
+
box[2] = box[0] + maxSide - 1;
|
|
60
|
+
box[3] = box[1] + maxSide - 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public void limitSquare(int w, int h) {
|
|
64
|
+
if (box[0] < 0) box[0] = 0;
|
|
65
|
+
if (box[1] < 0) box[1] = 0;
|
|
66
|
+
if (box[2] >= w) box[2] = w - 1;
|
|
67
|
+
if (box[3] >= h) box[3] = h - 1;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public boolean transbound(int w, int h) {
|
|
71
|
+
return box[0] < 0 || box[1] < 0 || box[2] >= w || box[3] >= h;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
package io.github.asephermann.plugins.faceantispoofing.mtcnn;
|
|
2
|
+
|
|
3
|
+
import android.content.res.AssetManager;
|
|
4
|
+
import android.graphics.Bitmap;
|
|
5
|
+
import android.graphics.Point;
|
|
6
|
+
|
|
7
|
+
import com.google.ai.edge.litert.Interpreter;
|
|
8
|
+
|
|
9
|
+
import java.io.IOException;
|
|
10
|
+
import java.util.HashMap;
|
|
11
|
+
import java.util.Map;
|
|
12
|
+
import java.util.Vector;
|
|
13
|
+
|
|
14
|
+
import io.github.asephermann.plugins.faceantispoofing.MyUtil;
|
|
15
|
+
|
|
16
|
+
public class MTCNN {
|
|
17
|
+
private float factor = 0.709f;
|
|
18
|
+
private float pNetThreshold = 0.6f;
|
|
19
|
+
private float rNetThreshold = 0.7f;
|
|
20
|
+
private float oNetThreshold = 0.7f;
|
|
21
|
+
|
|
22
|
+
private static final String MODEL_FILE_PNET = "pnet.tflite";
|
|
23
|
+
private static final String MODEL_FILE_RNET = "rnet.tflite";
|
|
24
|
+
private static final String MODEL_FILE_ONET = "onet.tflite";
|
|
25
|
+
|
|
26
|
+
private Interpreter pInterpreter;
|
|
27
|
+
private Interpreter rInterpreter;
|
|
28
|
+
private Interpreter oInterpreter;
|
|
29
|
+
|
|
30
|
+
public MTCNN(AssetManager assetManager) throws IOException {
|
|
31
|
+
Interpreter.Options options = new Interpreter.Options();
|
|
32
|
+
options.setNumThreads(4);
|
|
33
|
+
pInterpreter = new Interpreter(MyUtil.loadModelFile(assetManager, MODEL_FILE_PNET), options);
|
|
34
|
+
rInterpreter = new Interpreter(MyUtil.loadModelFile(assetManager, MODEL_FILE_RNET), options);
|
|
35
|
+
oInterpreter = new Interpreter(MyUtil.loadModelFile(assetManager, MODEL_FILE_ONET), options);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public Vector<Box> detectFaces(Bitmap bitmap, int minFaceSize) {
|
|
39
|
+
Vector<Box> boxes;
|
|
40
|
+
try {
|
|
41
|
+
boxes = pNet(bitmap, minFaceSize);
|
|
42
|
+
square_limit(boxes, bitmap.getWidth(), bitmap.getHeight());
|
|
43
|
+
|
|
44
|
+
boxes = rNet(bitmap, boxes);
|
|
45
|
+
square_limit(boxes, bitmap.getWidth(), bitmap.getHeight());
|
|
46
|
+
|
|
47
|
+
boxes = oNet(bitmap, boxes);
|
|
48
|
+
} catch (IllegalArgumentException e) {
|
|
49
|
+
e.printStackTrace();
|
|
50
|
+
boxes = new Vector<>();
|
|
51
|
+
}
|
|
52
|
+
return boxes;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private void square_limit(Vector<Box> boxes, int w, int h) {
|
|
56
|
+
for (int i = 0; i < boxes.size(); i++) {
|
|
57
|
+
boxes.get(i).toSquareShape();
|
|
58
|
+
boxes.get(i).limitSquare(w, h);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private Vector<Box> pNet(Bitmap bitmap, int minSize) {
|
|
63
|
+
int whMin = Math.min(bitmap.getWidth(), bitmap.getHeight());
|
|
64
|
+
float currentFaceSize = minSize;
|
|
65
|
+
Vector<Box> totalBoxes = new Vector<>();
|
|
66
|
+
|
|
67
|
+
while (currentFaceSize <= whMin) {
|
|
68
|
+
float scale = 12.0f / currentFaceSize;
|
|
69
|
+
|
|
70
|
+
Bitmap bm = MyUtil.bitmapResize(bitmap, scale);
|
|
71
|
+
int w = bm.getWidth();
|
|
72
|
+
int h = bm.getHeight();
|
|
73
|
+
|
|
74
|
+
int outW = (int) (Math.ceil(w * 0.5 - 5) + 0.5);
|
|
75
|
+
int outH = (int) (Math.ceil(h * 0.5 - 5) + 0.5);
|
|
76
|
+
float[][][][] prob1 = new float[1][outW][outH][2];
|
|
77
|
+
float[][][][] conv4_2_BiasAdd = new float[1][outW][outH][4];
|
|
78
|
+
pNetForward(bm, prob1, conv4_2_BiasAdd);
|
|
79
|
+
prob1 = MyUtil.transposeBatch(prob1);
|
|
80
|
+
conv4_2_BiasAdd = MyUtil.transposeBatch(conv4_2_BiasAdd);
|
|
81
|
+
|
|
82
|
+
Vector<Box> curBoxes = new Vector<>();
|
|
83
|
+
generateBoxes(prob1, conv4_2_BiasAdd, scale, curBoxes);
|
|
84
|
+
|
|
85
|
+
nms(curBoxes, 0.5f, "Union");
|
|
86
|
+
|
|
87
|
+
for (int i = 0; i < curBoxes.size(); i++)
|
|
88
|
+
if (!curBoxes.get(i).deleted)
|
|
89
|
+
totalBoxes.addElement(curBoxes.get(i));
|
|
90
|
+
|
|
91
|
+
currentFaceSize /= factor;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
nms(totalBoxes, 0.7f, "Union");
|
|
95
|
+
|
|
96
|
+
BoundingBoxReggression(totalBoxes);
|
|
97
|
+
|
|
98
|
+
return updateBoxes(totalBoxes);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private void pNetForward(Bitmap bitmap, float[][][][] prob1, float[][][][] conv4_2_BiasAdd) {
|
|
102
|
+
float[][][] img = MyUtil.normalizeImage(bitmap);
|
|
103
|
+
float[][][][] pNetIn = new float[1][][][];
|
|
104
|
+
pNetIn[0] = img;
|
|
105
|
+
pNetIn = MyUtil.transposeBatch(pNetIn);
|
|
106
|
+
|
|
107
|
+
Map<Integer, Object> outputs = new HashMap<>();
|
|
108
|
+
outputs.put(pInterpreter.getOutputIndex("pnet/prob1"), prob1);
|
|
109
|
+
outputs.put(pInterpreter.getOutputIndex("pnet/conv4-2/BiasAdd"), conv4_2_BiasAdd);
|
|
110
|
+
|
|
111
|
+
pInterpreter.runForMultipleInputsOutputs(new Object[]{pNetIn}, outputs);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private int generateBoxes(float[][][][] prob1, float[][][][] conv4_2_BiasAdd, float scale, Vector<Box> boxes) {
|
|
115
|
+
int h = prob1[0].length;
|
|
116
|
+
int w = prob1[0][0].length;
|
|
117
|
+
|
|
118
|
+
for (int y = 0; y < h; y++) {
|
|
119
|
+
for (int x = 0; x < w; x++) {
|
|
120
|
+
float score = prob1[0][y][x][1];
|
|
121
|
+
if (score > pNetThreshold) {
|
|
122
|
+
Box box = new Box();
|
|
123
|
+
box.score = score;
|
|
124
|
+
box.box[0] = Math.round(x * 2 / scale);
|
|
125
|
+
box.box[1] = Math.round(y * 2 / scale);
|
|
126
|
+
box.box[2] = Math.round((x * 2 + 11) / scale);
|
|
127
|
+
box.box[3] = Math.round((y * 2 + 11) / scale);
|
|
128
|
+
for (int i = 0; i < 4; i++) {
|
|
129
|
+
box.bbr[i] = conv4_2_BiasAdd[0][y][x][i];
|
|
130
|
+
}
|
|
131
|
+
boxes.addElement(box);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private void nms(Vector<Box> boxes, float threshold, String method) {
|
|
139
|
+
for (int i = 0; i < boxes.size(); i++) {
|
|
140
|
+
Box box = boxes.get(i);
|
|
141
|
+
if (!box.deleted) {
|
|
142
|
+
for (int j = i + 1; j < boxes.size(); j++) {
|
|
143
|
+
Box box2 = boxes.get(j);
|
|
144
|
+
if (!box2.deleted) {
|
|
145
|
+
int x1 = Math.max(box.box[0], box2.box[0]);
|
|
146
|
+
int y1 = Math.max(box.box[1], box2.box[1]);
|
|
147
|
+
int x2 = Math.min(box.box[2], box2.box[2]);
|
|
148
|
+
int y2 = Math.min(box.box[3], box2.box[3]);
|
|
149
|
+
if (x2 < x1 || y2 < y1) continue;
|
|
150
|
+
int areaIoU = (x2 - x1 + 1) * (y2 - y1 + 1);
|
|
151
|
+
float iou = 0f;
|
|
152
|
+
if (method.equals("Union"))
|
|
153
|
+
iou = 1.0f * areaIoU / (box.area() + box2.area() - areaIoU);
|
|
154
|
+
else if (method.equals("Min"))
|
|
155
|
+
iou = 1.0f * areaIoU / (Math.min(box.area(), box2.area()));
|
|
156
|
+
if (iou >= threshold) {
|
|
157
|
+
if (box.score > box2.score)
|
|
158
|
+
box2.deleted = true;
|
|
159
|
+
else
|
|
160
|
+
box.deleted = true;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private void BoundingBoxReggression(Vector<Box> boxes) {
|
|
169
|
+
for (int i = 0; i < boxes.size(); i++)
|
|
170
|
+
boxes.get(i).calibrate();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private Vector<Box> rNet(Bitmap bitmap, Vector<Box> boxes) {
|
|
174
|
+
int num = boxes.size();
|
|
175
|
+
float[][][][] rNetIn = new float[num][24][24][3];
|
|
176
|
+
for (int i = 0; i < num; i++) {
|
|
177
|
+
float[][][] curCrop = MyUtil.cropAndResize(bitmap, boxes.get(i), 24);
|
|
178
|
+
curCrop = MyUtil.transposeImage(curCrop);
|
|
179
|
+
rNetIn[i] = curCrop;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
rNetForward(rNetIn, boxes);
|
|
183
|
+
|
|
184
|
+
for (int i = 0; i < num; i++) {
|
|
185
|
+
if (boxes.get(i).score < rNetThreshold) {
|
|
186
|
+
boxes.get(i).deleted = true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
nms(boxes, 0.7f, "Union");
|
|
191
|
+
BoundingBoxReggression(boxes);
|
|
192
|
+
return updateBoxes(boxes);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private void rNetForward(float[][][][] rNetIn, Vector<Box> boxes) {
|
|
196
|
+
int num = rNetIn.length;
|
|
197
|
+
float[][] prob1 = new float[num][2];
|
|
198
|
+
float[][] conv5_2_conv5_2 = new float[num][4];
|
|
199
|
+
|
|
200
|
+
Map<Integer, Object> outputs = new HashMap<>();
|
|
201
|
+
outputs.put(rInterpreter.getOutputIndex("rnet/prob1"), prob1);
|
|
202
|
+
outputs.put(rInterpreter.getOutputIndex("rnet/conv5-2/conv5-2"), conv5_2_conv5_2);
|
|
203
|
+
rInterpreter.runForMultipleInputsOutputs(new Object[]{rNetIn}, outputs);
|
|
204
|
+
|
|
205
|
+
for (int i = 0; i < num; i++) {
|
|
206
|
+
boxes.get(i).score = prob1[i][1];
|
|
207
|
+
for (int j = 0; j < 4; j++) {
|
|
208
|
+
boxes.get(i).bbr[j] = conv5_2_conv5_2[i][j];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private Vector<Box> oNet(Bitmap bitmap, Vector<Box> boxes) {
|
|
214
|
+
int num = boxes.size();
|
|
215
|
+
float[][][][] oNetIn = new float[num][48][48][3];
|
|
216
|
+
for (int i = 0; i < num; i++) {
|
|
217
|
+
float[][][] curCrop = MyUtil.cropAndResize(bitmap, boxes.get(i), 48);
|
|
218
|
+
curCrop = MyUtil.transposeImage(curCrop);
|
|
219
|
+
oNetIn[i] = curCrop;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
oNetForward(oNetIn, boxes);
|
|
223
|
+
|
|
224
|
+
for (int i = 0; i < num; i++) {
|
|
225
|
+
if (boxes.get(i).score < oNetThreshold) {
|
|
226
|
+
boxes.get(i).deleted = true;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
BoundingBoxReggression(boxes);
|
|
230
|
+
nms(boxes, 0.7f, "Min");
|
|
231
|
+
return updateBoxes(boxes);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private void oNetForward(float[][][][] oNetIn, Vector<Box> boxes) {
|
|
235
|
+
int num = oNetIn.length;
|
|
236
|
+
float[][] prob1 = new float[num][2];
|
|
237
|
+
float[][] conv6_2_conv6_2 = new float[num][4];
|
|
238
|
+
float[][] conv6_3_conv6_3 = new float[num][10];
|
|
239
|
+
|
|
240
|
+
Map<Integer, Object> outputs = new HashMap<>();
|
|
241
|
+
outputs.put(oInterpreter.getOutputIndex("onet/prob1"), prob1);
|
|
242
|
+
outputs.put(oInterpreter.getOutputIndex("onet/conv6-2/conv6-2"), conv6_2_conv6_2);
|
|
243
|
+
outputs.put(oInterpreter.getOutputIndex("onet/conv6-3/conv6-3"), conv6_3_conv6_3);
|
|
244
|
+
oInterpreter.runForMultipleInputsOutputs(new Object[]{oNetIn}, outputs);
|
|
245
|
+
|
|
246
|
+
for (int i = 0; i < num; i++) {
|
|
247
|
+
boxes.get(i).score = prob1[i][1];
|
|
248
|
+
for (int j = 0; j < 4; j++) {
|
|
249
|
+
boxes.get(i).bbr[j] = conv6_2_conv6_2[i][j];
|
|
250
|
+
}
|
|
251
|
+
for (int j = 0; j < 5; j++) {
|
|
252
|
+
int x = Math.round(boxes.get(i).left() + (conv6_3_conv6_3[i][j] * boxes.get(i).width()));
|
|
253
|
+
int y = Math.round(boxes.get(i).top() + (conv6_3_conv6_3[i][j + 5] * boxes.get(i).height()));
|
|
254
|
+
boxes.get(i).landmark[j] = new Point(x, y);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
public static Vector<Box> updateBoxes(Vector<Box> boxes) {
|
|
260
|
+
Vector<Box> b = new Vector<>();
|
|
261
|
+
for (int i = 0; i < boxes.size(); i++) {
|
|
262
|
+
if (!boxes.get(i).deleted) {
|
|
263
|
+
b.addElement(boxes.get(i));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return b;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package io.github.asephermann.plugins.faceantispoofing.mtcnn;
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap;
|
|
4
|
+
|
|
5
|
+
public class Utils {
|
|
6
|
+
public static float[][][] cropAndResize(Bitmap bitmap, Box box, int size) {
|
|
7
|
+
return io.github.asephermann.plugins.faceantispoofing.MyUtil.cropAndResize(bitmap, box, size);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public static float[][][] transposeImage(float[][][] in) {
|
|
11
|
+
return io.github.asephermann.plugins.faceantispoofing.MyUtil.transposeImage(in);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public static float[][][][] transposeBatch(float[][][][] in) {
|
|
15
|
+
return io.github.asephermann.plugins.faceantispoofing.MyUtil.transposeBatch(in);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public static float[][][] normalizeImage(Bitmap bitmap) {
|
|
19
|
+
return io.github.asephermann.plugins.faceantispoofing.MyUtil.normalizeImage(bitmap);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public static Bitmap bitmapResize(Bitmap bitmap, float scale) {
|
|
23
|
+
return io.github.asephermann.plugins.faceantispoofing.MyUtil.bitmapResize(bitmap, scale);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
File without changes
|
package/dist/docs.json
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
{
|
|
2
|
+
"api": {
|
|
3
|
+
"name": "FaceAntiSpoofingPlugin",
|
|
4
|
+
"slug": "faceantispoofingplugin",
|
|
5
|
+
"docs": "",
|
|
6
|
+
"tags": [],
|
|
7
|
+
"methods": [
|
|
8
|
+
{
|
|
9
|
+
"name": "detect",
|
|
10
|
+
"signature": "(options: DetectOptions) => Promise<DetectionResult>",
|
|
11
|
+
"parameters": [
|
|
12
|
+
{
|
|
13
|
+
"name": "options",
|
|
14
|
+
"docs": "Image data as base64 string or file URI",
|
|
15
|
+
"type": "DetectOptions"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"returns": "Promise<DetectionResult>",
|
|
19
|
+
"tags": [
|
|
20
|
+
{
|
|
21
|
+
"name": "param",
|
|
22
|
+
"text": "options Image data as base64 string or file URI"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "returns",
|
|
26
|
+
"text": "Promise with detection result containing liveness status"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"docs": "Detect face liveness and anti-spoofing",
|
|
30
|
+
"complexTypes": [
|
|
31
|
+
"DetectionResult",
|
|
32
|
+
"DetectOptions"
|
|
33
|
+
],
|
|
34
|
+
"slug": "detect"
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"properties": []
|
|
38
|
+
},
|
|
39
|
+
"interfaces": [
|
|
40
|
+
{
|
|
41
|
+
"name": "DetectionResult",
|
|
42
|
+
"slug": "detectionresult",
|
|
43
|
+
"docs": "",
|
|
44
|
+
"tags": [],
|
|
45
|
+
"methods": [],
|
|
46
|
+
"properties": [
|
|
47
|
+
{
|
|
48
|
+
"name": "error",
|
|
49
|
+
"tags": [],
|
|
50
|
+
"docs": "",
|
|
51
|
+
"complexTypes": [],
|
|
52
|
+
"type": "boolean"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "liveness",
|
|
56
|
+
"tags": [],
|
|
57
|
+
"docs": "",
|
|
58
|
+
"complexTypes": [],
|
|
59
|
+
"type": "boolean"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"name": "score",
|
|
63
|
+
"tags": [],
|
|
64
|
+
"docs": "",
|
|
65
|
+
"complexTypes": [],
|
|
66
|
+
"type": "string"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "threshold",
|
|
70
|
+
"tags": [],
|
|
71
|
+
"docs": "",
|
|
72
|
+
"complexTypes": [],
|
|
73
|
+
"type": "string"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"name": "message",
|
|
77
|
+
"tags": [],
|
|
78
|
+
"docs": "",
|
|
79
|
+
"complexTypes": [],
|
|
80
|
+
"type": "string"
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"name": "DetectOptions",
|
|
86
|
+
"slug": "detectoptions",
|
|
87
|
+
"docs": "",
|
|
88
|
+
"tags": [],
|
|
89
|
+
"methods": [],
|
|
90
|
+
"properties": [
|
|
91
|
+
{
|
|
92
|
+
"name": "image",
|
|
93
|
+
"tags": [],
|
|
94
|
+
"docs": "Base64 encoded image data (without data:image/...;base64, prefix)\nor file URI to the image",
|
|
95
|
+
"complexTypes": [],
|
|
96
|
+
"type": "string"
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
],
|
|
101
|
+
"enums": [],
|
|
102
|
+
"typeAliases": [],
|
|
103
|
+
"pluginConfigs": []
|
|
104
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface DetectionResult {
|
|
2
|
+
error: boolean;
|
|
3
|
+
liveness: boolean;
|
|
4
|
+
score: string;
|
|
5
|
+
threshold: string;
|
|
6
|
+
message: string;
|
|
7
|
+
}
|
|
8
|
+
export interface DetectOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Base64 encoded image data (without data:image/...;base64, prefix)
|
|
11
|
+
* or file URI to the image
|
|
12
|
+
*/
|
|
13
|
+
image: string;
|
|
14
|
+
}
|
|
15
|
+
export interface FaceAntiSpoofingPlugin {
|
|
16
|
+
/**
|
|
17
|
+
* Detect face liveness and anti-spoofing
|
|
18
|
+
* @param options Image data as base64 string or file URI
|
|
19
|
+
* @returns Promise with detection result containing liveness status
|
|
20
|
+
*/
|
|
21
|
+
detect(options: DetectOptions): Promise<DetectionResult>;
|
|
22
|
+
}
|