├── AndroidFaceMask.jpg
├── AndroidFaceMask
├── .gitignore
├── app
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── androidfacemask
│ │ │ └── ExampleInstrumentedTest.java
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── assets
│ │ │ └── face_mask_detection.tflite
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── androidfacemask
│ │ │ │ ├── Config.java
│ │ │ │ ├── facemask
│ │ │ │ ├── Box.java
│ │ │ │ └── FaceMask.java
│ │ │ │ ├── util
│ │ │ │ ├── ImageUtils.java
│ │ │ │ └── comparator
│ │ │ │ │ └── CompareSizesByArea.java
│ │ │ │ └── view
│ │ │ │ ├── AutoFitTextureView.java
│ │ │ │ ├── CameraActivity.java
│ │ │ │ ├── CameraConnectionFragment.java
│ │ │ │ ├── FaceActivity.java
│ │ │ │ ├── OverlayView.java
│ │ │ │ ├── TextToSpeechActivity.java
│ │ │ │ └── components
│ │ │ │ ├── BorderedText.java
│ │ │ │ └── ErrorDialog.java
│ │ └── res
│ │ │ ├── drawable-v24
│ │ │ └── ic_launcher_foreground.xml
│ │ │ ├── drawable
│ │ │ └── ic_launcher_background.xml
│ │ │ ├── layout
│ │ │ ├── activity_camera.xml
│ │ │ └── camera_connection_fragment.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ └── values
│ │ │ ├── colors.xml
│ │ │ ├── dimens.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── example
│ │ └── androidfacemask
│ │ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
├── LICENSE
└── README.md
/AndroidFaceMask.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ma-Dan/AndroidFaceMask/07e3fe09f5b509a77249ba586d389e3164506232/AndroidFaceMask.jpg
--------------------------------------------------------------------------------
/AndroidFaceMask/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 29
5 | buildToolsVersion "29.0.2"
6 | defaultConfig {
7 | applicationId "com.example.androidfacemask"
8 | minSdkVersion 18
9 | targetSdkVersion 29
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | compileOptions {
21 | sourceCompatibility JavaVersion.VERSION_1_8
22 | targetCompatibility JavaVersion.VERSION_1_8
23 | }
24 | aaptOptions {
25 | noCompress "tflite"
26 | noCompress "lite"
27 | }
28 | }
29 |
30 | dependencies {
31 | implementation fileTree(dir: 'libs', include: ['*.jar'])
32 | implementation 'androidx.appcompat:appcompat:1.1.0'
33 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
34 | implementation 'com.google.android.material:material:1.0.0'
35 | testImplementation 'junit:junit:4.12'
36 | androidTestImplementation 'androidx.test.ext:junit:1.1.0'
37 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
38 | implementation 'org.tensorflow:tensorflow-lite:+'
39 | }
40 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/androidTest/java/com/example/androidfacemask/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.example.androidfacemask;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 |
25 | assertEquals("com.example.androidfacemask", appContext.getPackageName());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/assets/face_mask_detection.tflite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ma-Dan/AndroidFaceMask/07e3fe09f5b509a77249ba586d389e3164506232/AndroidFaceMask/app/src/main/assets/face_mask_detection.tflite
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/java/com/example/androidfacemask/Config.java:
--------------------------------------------------------------------------------
1 | package com.example.androidfacemask;
2 |
3 | public interface Config {
4 | String LOGGING_TAG = "FaceMask";
5 | }
6 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/java/com/example/androidfacemask/facemask/Box.java:
--------------------------------------------------------------------------------
1 | package com.example.androidfacemask.facemask;
2 |
3 | import android.graphics.Point;
4 | import android.graphics.Rect;
5 |
6 | import static java.lang.Math.max;
7 |
8 | /**
9 | * 人脸框
10 | */
11 | public class Box {
12 | public float[] box; // left:box[0],top:box[1],right:box[2],bottom:box[3]
13 | public float score; // probability
14 | public int index;
15 | public boolean deleted;
16 | public int cls;
17 | public String title;
18 |
19 | public Box() {
20 | box = new float[4];
21 | deleted = false;
22 | title = "";
23 | }
24 |
25 | public float left() {
26 | return box[0];
27 | }
28 |
29 | public float right() {
30 | return box[2];
31 | }
32 |
33 | public float top() {
34 | return box[1];
35 | }
36 |
37 | public float bottom() {
38 | return box[2];
39 | }
40 |
41 | public float width() {
42 | return box[2] - box[0] + 1;
43 | }
44 |
45 | public float height() {
46 | return box[3] - box[1] + 1;
47 | }
48 |
49 | // 转为rect
50 | public Rect transform2Rect() {
51 | Rect rect = new Rect();
52 | rect.left = (int)box[0];
53 | rect.top = (int)box[1];
54 | rect.right = (int)box[2];
55 | rect.bottom = (int)box[3];
56 | return rect;
57 | }
58 |
59 | // 面积
60 | public int area() {
61 | return (int)(width() * height());
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/java/com/example/androidfacemask/facemask/FaceMask.java:
--------------------------------------------------------------------------------
1 | package com.example.androidfacemask.facemask;
2 |
3 | import android.content.res.AssetManager;
4 | import android.graphics.Bitmap;
5 | import android.util.Log;
6 |
7 | import org.tensorflow.lite.Interpreter;
8 |
9 | import java.io.IOException;
10 | import java.util.ArrayList;
11 | import java.util.HashMap;
12 | import java.util.Map;
13 | import java.util.Vector;
14 |
15 | import com.example.androidfacemask.util.ImageUtils;
16 |
17 | import static com.example.androidfacemask.Config.LOGGING_TAG;
18 |
19 | /**
20 | * 口罩检测
21 | */
22 | public class FaceMask {
23 | private static final String MODEL_FILE = "face_mask_detection.tflite";
24 |
25 | public static final int INPUT_IMAGE_SIZE = 260; // 需要feed数据的placeholder的图片宽高
26 | public static final float CONF_THRESHOLD = 0.5f; // 置信度阈值
27 | public static final float IOU_THRESHOLD = 0.4f; // IoU阈值
28 |
29 | private static final int[] feature_map_sizes = new int[] {33, 17, 9, 5, 3};
30 | private static final float[][] anchor_sizes = new float[][] {{0.04f, 0.056f}, {0.08f, 0.11f}, {0.16f, 0.22f}, {0.32f, 0.45f}, {0.64f, 0.72f}};
31 | private static final float[] anchor_ratios = new float[] {1.0f, 0.62f, 0.42f};
32 |
33 | private float[][] anchors;
34 |
35 | private Interpreter interpreter;
36 |
37 | public FaceMask(AssetManager assetManager) throws IOException {
38 | Interpreter.Options options = new Interpreter.Options();
39 | options.setNumThreads(4);
40 | interpreter = new Interpreter(ImageUtils.loadModelFile(assetManager, MODEL_FILE), options);
41 | generateAnchors();
42 | }
43 |
44 | public Vector detectFaceMasks(Bitmap bitmap) {
45 | int[] ddims = {1, INPUT_IMAGE_SIZE, INPUT_IMAGE_SIZE, 3};
46 | float[][][][] datasets = new float[ddims[0]][ddims[1]][ddims[2]][ddims[3]];
47 |
48 | datasets[0] = ImageUtils.normalizeImage(bitmap);
49 |
50 | float[][][] loc = new float[1][5972][4];
51 | float[][][] cls = new float[1][5972][2];
52 |
53 | Map outputs = new HashMap<>();
54 | outputs.put(interpreter.getOutputIndex("loc_branch_concat_1/concat"), loc);
55 | outputs.put(interpreter.getOutputIndex("cls_branch_concat_1/concat"), cls);
56 | interpreter.runForMultipleInputsOutputs(new Object[]{datasets}, outputs);
57 |
58 | //先通过score筛选Box,减少后续计算
59 | Vector filteredBoxes = new Vector<>();
60 | for(int i=0; i<5972; i++) {
61 | int idxCls = -1;
62 | if(cls[0][i][0] > cls[0][i][1]) {
63 | idxCls = 0;
64 | } else {
65 | idxCls = 1;
66 | }
67 |
68 | if(cls[0][i][idxCls] > CONF_THRESHOLD) {
69 | Box box = new Box();
70 | // core
71 | box.score = cls[0][i][idxCls];
72 | // box
73 | box.box[0] = loc[0][i][0];
74 | box.box[1] = loc[0][i][1];
75 | box.box[2] = loc[0][i][2];
76 | box.box[3] = loc[0][i][3];
77 |
78 | box.cls = idxCls;
79 |
80 | if(idxCls == 0) {
81 | box.title = "有口罩";
82 | } else {
83 | box.title = "无口罩";
84 | }
85 |
86 | box.index = i;
87 |
88 | filteredBoxes.add(box);
89 | }
90 | }
91 |
92 | //解码Box参数
93 | decodeBBox(filteredBoxes);
94 |
95 | //NMS
96 | nms(filteredBoxes, IOU_THRESHOLD, "Union");
97 |
98 | //Log.i(LOGGING_TAG, String.format("Detected: %d", filteredBoxes.size()));
99 |
100 | return filteredBoxes;
101 | }
102 |
103 | private void generateAnchors() {
104 | int anchorTotal = 0;
105 | for(int i=0; i<5; i++) {
106 | anchorTotal += feature_map_sizes[i] * feature_map_sizes[i];
107 | }
108 | anchorTotal *= 4;
109 |
110 | anchors = new float[anchorTotal][4];
111 |
112 | int index = 0;
113 | for(int i=0; i<5; i++) {
114 | float center[] = new float[feature_map_sizes[i]];
115 | for(int j=0; j boxes) {
144 | for(int i=0; i boxes, float threshold, String method) {
164 | // NMS.两两比对
165 | // int delete_cnt = 0;
166 | for (int i = 0; i < boxes.size(); i++) {
167 | Box box = boxes.get(i);
168 | if (!box.deleted) {
169 | // score<0表示当前矩形框被删除
170 | for (int j = i + 1; j < boxes.size(); j++) {
171 | Box box2 = boxes.get(j);
172 | if ((!box2.deleted) && (box2.cls==box.cls)) {
173 | float x1 = Math.max(box.box[0], box2.box[0]);
174 | float y1 = Math.max(box.box[1], box2.box[1]);
175 | float x2 = Math.min(box.box[2], box2.box[2]);
176 | float y2 = Math.min(box.box[3], box2.box[3]);
177 | if (x2 < x1 || y2 < y1) continue;
178 | float areaIoU = (x2 - x1 + 1) * (y2 - y1 + 1);
179 | float iou = 0f;
180 | if (method.equals("Union"))
181 | iou = 1.0f * areaIoU / (box.area() + box2.area() - areaIoU);
182 | else if (method.equals("Min"))
183 | iou = 1.0f * areaIoU / (Math.min(box.area(), box2.area()));
184 | if (iou >= threshold) { // 删除prob小的那个框
185 | if (box.score > box2.score)
186 | box2.deleted = true;
187 | else
188 | box.deleted = true;
189 | }
190 | }
191 | }
192 | }
193 | }
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/java/com/example/androidfacemask/util/ImageUtils.java:
--------------------------------------------------------------------------------
1 | package com.example.androidfacemask.util;
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 | import android.media.Image;
11 |
12 | import com.example.androidfacemask.facemask.Box;
13 |
14 | import java.io.FileInputStream;
15 | import java.io.IOException;
16 | import java.io.InputStream;
17 | import java.nio.ByteBuffer;
18 | import java.nio.MappedByteBuffer;
19 | import java.nio.channels.FileChannel;
20 |
21 | import static java.lang.Math.max;
22 | import static java.lang.Math.min;
23 |
24 | public class ImageUtils {
25 | // This value is 2 ^ 18 - 1, and is used to clamp the RGB values before their ranges
26 | // are normalized to eight bits.
27 | static final int kMaxChannelValue = 262143;
28 |
29 | public static int[] convertYUVToARGB(final Image image, final int previewWidth, final int previewHeight) {
30 | final Image.Plane[] planes = image.getPlanes();
31 | byte[][] yuvBytes = fillBytes(planes);
32 | return ImageUtils.convertYUV420ToARGB8888(yuvBytes[0], yuvBytes[1], yuvBytes[2], previewWidth,
33 | previewHeight, planes[0].getRowStride(), planes[1].getRowStride(), planes[1].getPixelStride());
34 | }
35 |
36 | private static byte[][] fillBytes(final Image.Plane[] planes) {
37 | byte[][] yuvBytes = new byte[3][];
38 | for (int i = 0; i < planes.length; ++i) {
39 | final ByteBuffer buffer = planes[i].getBuffer();
40 | if (yuvBytes[i] == null) {
41 | yuvBytes[i] = new byte[buffer.capacity()];
42 | }
43 | buffer.get(yuvBytes[i]);
44 | }
45 |
46 | return yuvBytes;
47 | }
48 |
49 | private static int[] convertYUV420ToARGB8888(byte[] yData, byte[] uData, byte[] vData, int width, int height,
50 | int yRowStride, int uvRowStride, int uvPixelStride) {
51 | int[] out = new int[width * height];
52 | int i = 0;
53 | for (int y = 0; y < height; y++) {
54 | int pY = yRowStride * y;
55 | int uv_row_start = uvRowStride * (y >> 1);
56 | int pU = uv_row_start;
57 | int pV = uv_row_start;
58 |
59 | for (int x = 0; x < width; x++) {
60 | int uv_offset = (x >> 1) * uvPixelStride;
61 | out[i++] = YUV2RGB(
62 | convertByteToInt(yData, pY + x),
63 | convertByteToInt(uData, pU + uv_offset),
64 | convertByteToInt(vData, pV + uv_offset));
65 | }
66 | }
67 |
68 | return out;
69 | }
70 |
71 | private static int convertByteToInt(byte[] arr, int pos) {
72 | return arr[pos] & 0xFF;
73 | }
74 |
75 | private static int YUV2RGB(int nY, int nU, int nV) {
76 | nY -= 16;
77 | nU -= 128;
78 | nV -= 128;
79 | if (nY < 0) nY = 0;
80 |
81 | int nR = 1192 * nY + 1634 * nV;
82 | int nG = 1192 * nY - 833 * nV - 400 * nU;
83 | int nB = 1192 * nY + 2066 * nU;
84 |
85 | nR = Math.min(kMaxChannelValue, Math.max(0, nR));
86 | nG = Math.min(kMaxChannelValue, Math.max(0, nG));
87 | nB = Math.min(kMaxChannelValue, Math.max(0, nB));
88 |
89 | nR = (nR >> 10) & 0xff;
90 | nG = (nG >> 10) & 0xff;
91 | nB = (nB >> 10) & 0xff;
92 |
93 | return 0xff000000 | (nR << 16) | (nG << 8) | nB;
94 | }
95 |
96 | public static Matrix getTransformationMatrix(final int srcWidth, final int srcHeight,
97 | final int dstWidth, final int dstHeight,
98 | final int applyRotation, final boolean maintainAspectRatio) {
99 | final Matrix matrix = new Matrix();
100 |
101 | if (applyRotation != 0) {
102 | // Translate so center of image is at origin.
103 | matrix.postTranslate(-srcWidth / 2.0f, -srcHeight / 2.0f);
104 |
105 | // Rotate around origin.
106 | matrix.postRotate(applyRotation);
107 | }
108 |
109 | // Account for the already applied rotation, if any, and then determine how
110 | // much scaling is needed for each axis.
111 | final boolean transpose = (Math.abs(applyRotation) + 90) % 180 == 0;
112 |
113 | final int inWidth = transpose ? srcHeight : srcWidth;
114 | final int inHeight = transpose ? srcWidth : srcHeight;
115 |
116 | // Apply scaling if necessary.
117 | if (inWidth != dstWidth || inHeight != dstHeight) {
118 | final float scaleFactorX = dstWidth / (float) inWidth;
119 | final float scaleFactorY = dstHeight / (float) inHeight;
120 |
121 | if (maintainAspectRatio) {
122 | // Scale by minimum factor so that dst is filled completely while
123 | // maintaining the aspect ratio. Some image may fall off the edge.
124 | final float scaleFactor = Math.max(scaleFactorX, scaleFactorY);
125 | matrix.postScale(scaleFactor, scaleFactor);
126 | } else {
127 | // Scale exactly to fill dst from src.
128 | matrix.postScale(scaleFactorX, scaleFactorY);
129 | }
130 | }
131 |
132 | if (applyRotation != 0) {
133 | // Translate back from origin centered reference to destination frame.
134 | matrix.postTranslate(dstWidth / 2.0f, dstHeight / 2.0f);
135 | }
136 |
137 | return matrix;
138 | }
139 |
140 | /**
141 | * 从assets中读取图片
142 | *
143 | * @param context
144 | * @param filename
145 | * @return
146 | */
147 | public static Bitmap readFromAssets(Context context, String filename) {
148 | Bitmap bitmap;
149 | AssetManager asm = context.getAssets();
150 | try {
151 | InputStream is = asm.open(filename);
152 | bitmap = BitmapFactory.decodeStream(is);
153 | is.close();
154 | } catch (IOException e) {
155 | e.printStackTrace();
156 | return null;
157 | }
158 | return bitmap;
159 | }
160 |
161 | /**
162 | * 给rect增加margin
163 | *
164 | * @param bitmap
165 | * @param rect
166 | * @param marginX
167 | * @param marginY
168 | */
169 | public static void rectExtend(Bitmap bitmap, Rect rect, int marginX, int marginY) {
170 | rect.left = max(0, rect.left - marginX / 2);
171 | rect.right = min(bitmap.getWidth() - 1, rect.right + marginX / 2);
172 | rect.top = max(0, rect.top - marginY / 2);
173 | rect.bottom = min(bitmap.getHeight() - 1, rect.bottom + marginY / 2);
174 | }
175 |
176 | /**
177 | * 加载模型文件
178 | *
179 | * @param assetManager
180 | * @param modelPath
181 | * @return
182 | * @throws IOException
183 | */
184 | public static MappedByteBuffer loadModelFile(AssetManager assetManager, String modelPath) throws IOException {
185 | AssetFileDescriptor fileDescriptor = assetManager.openFd(modelPath);
186 | FileInputStream inputStream = new FileInputStream(fileDescriptor.getFileDescriptor());
187 | FileChannel fileChannel = inputStream.getChannel();
188 | long startOffset = fileDescriptor.getStartOffset();
189 | long declaredLength = fileDescriptor.getDeclaredLength();
190 | return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength);
191 | }
192 |
193 | /**
194 | * 归一化图片到[0, 1]
195 | *
196 | * @param bitmap
197 | * @return
198 | */
199 | public static float[][][] normalizeImage(Bitmap bitmap) {
200 | int h = bitmap.getHeight();
201 | int w = bitmap.getWidth();
202 | float[][][] floatValues = new float[h][w][3];
203 |
204 | float imageMean = 0.0f;
205 | float imageStd = 255.0f;
206 |
207 | int[] pixels = new int[h * w];
208 | bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, w, h);
209 | for (int i = 0; i < h; i++) { // 注意是先高后宽
210 | for (int j = 0; j < w; j++) {
211 | final int val = pixels[i * w + j];
212 | float r = (((val >> 16) & 0xFF) - imageMean) / imageStd;
213 | float g = (((val >> 8) & 0xFF) - imageMean) / imageStd;
214 | float b = ((val & 0xFF) - imageMean) / imageStd;
215 | float[] arr = {r, g, b};
216 | floatValues[i][j] = arr;
217 | }
218 | }
219 | return floatValues;
220 | }
221 |
222 | /**
223 | * 缩放图片
224 | *
225 | * @param bitmap
226 | * @param scale
227 | * @return
228 | */
229 | public static Bitmap bitmapResize(Bitmap bitmap, float scale) {
230 | int width = bitmap.getWidth();
231 | int height = bitmap.getHeight();
232 | Matrix matrix = new Matrix();
233 | matrix.postScale(scale, scale);
234 | return Bitmap.createBitmap(
235 | bitmap, 0, 0, width, height, matrix, true);
236 | }
237 |
238 | /**
239 | * 图片矩阵宽高转置
240 | *
241 | * @param in
242 | * @return
243 | */
244 | public static float[][][] transposeImage(float[][][] in) {
245 | int h = in.length;
246 | int w = in[0].length;
247 | int channel = in[0][0].length;
248 | float[][][] out = new float[w][h][channel];
249 | for (int i = 0; i < h; i++) {
250 | for (int j = 0; j < w; j++) {
251 | out[j][i] = in[i][j];
252 | }
253 | }
254 | return out;
255 | }
256 |
257 | /**
258 | * 4维图片batch矩阵宽高转置
259 | *
260 | * @param in
261 | * @return
262 | */
263 | public static float[][][][] transposeBatch(float[][][][] in) {
264 | int batch = in.length;
265 | int h = in[0].length;
266 | int w = in[0][0].length;
267 | int channel = in[0][0][0].length;
268 | float[][][][] out = new float[batch][w][h][channel];
269 | for (int i = 0; i < batch; i++) {
270 | for (int j = 0; j < h; j++) {
271 | for (int k = 0; k < w; k++) {
272 | out[i][k][j] = in[i][j][k];
273 | }
274 | }
275 | }
276 | return out;
277 | }
278 |
279 | /**
280 | * 截取box中指定的矩形框(越界要处理),并resize到size*size大小,返回数据存放到data中。
281 | *
282 | * @param bitmap
283 | * @param box
284 | * @param size return
285 | */
286 | public static float[][][] cropAndResize(Bitmap bitmap, Box box, int size) {
287 | // crop and resize
288 | Matrix matrix = new Matrix();
289 | float scaleW = 1.0f * size / box.width();
290 | float scaleH = 1.0f * size / box.height();
291 | matrix.postScale(scaleW, scaleH);
292 | Rect rect = box.transform2Rect();
293 | Bitmap croped = Bitmap.createBitmap(
294 | bitmap, rect.left, rect.top, (int)box.width(), (int)box.height(), matrix, true);
295 |
296 | return normalizeImage(croped);
297 | }
298 |
299 | /**
300 | * 按照rect的大小裁剪
301 | *
302 | * @param bitmap
303 | * @param rect
304 | * @return
305 | */
306 | public static Bitmap crop(Bitmap bitmap, Rect rect) {
307 | int x = rect.left;
308 | if (x < 0) {
309 | x = 0;
310 | }
311 | int y = rect.top;
312 | if (y < 0) {
313 | y = 0;
314 | }
315 | int width = rect.right - rect.left;
316 | if (x + width > bitmap.getWidth()) {
317 | width = bitmap.getWidth() - x;
318 | }
319 | int height = rect.bottom - rect.top;
320 | if (y + height > bitmap.getHeight()) {
321 | height = bitmap.getHeight() - y;
322 | }
323 | Bitmap cropped = Bitmap.createBitmap(bitmap, x, y, width, height);
324 | return cropped;
325 | }
326 |
327 | /**
328 | * l2正则化
329 | *
330 | * @param embeddings
331 | * @param epsilon 惩罚项
332 | * @return
333 | */
334 | public static void l2Normalize(float[][] embeddings, double epsilon) {
335 | for (int i = 0; i < embeddings.length; i++) {
336 | float squareSum = 0;
337 | for (int j = 0; j < embeddings[i].length; j++) {
338 | squareSum += Math.pow(embeddings[i][j], 2);
339 | }
340 | float xInvNorm = (float) Math.sqrt(Math.max(squareSum, epsilon));
341 | for (int j = 0; j < embeddings[i].length; j++) {
342 | embeddings[i][j] = embeddings[i][j] / xInvNorm;
343 | }
344 | }
345 | }
346 | }
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/java/com/example/androidfacemask/util/comparator/CompareSizesByArea.java:
--------------------------------------------------------------------------------
1 | package com.example.androidfacemask.util.comparator;
2 |
3 | import android.util.Size;
4 |
5 | import java.util.Comparator;
6 |
7 |
8 | public class CompareSizesByArea implements Comparator {
9 | @Override
10 | public int compare(final Size lhs, final Size rhs) {
11 | // We cast here to ensure the multiplications won't overflow
12 | return Long.signum((long) lhs.getWidth() * lhs.getHeight()
13 | - (long) rhs.getWidth() * rhs.getHeight());
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/java/com/example/androidfacemask/view/AutoFitTextureView.java:
--------------------------------------------------------------------------------
1 | package com.example.androidfacemask.view;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.TextureView;
6 |
7 | public class AutoFitTextureView extends TextureView {
8 | private int ratioWidth = 0;
9 | private int ratioHeight = 0;
10 |
11 | public AutoFitTextureView(final Context context) {
12 | this(context, null);
13 | }
14 |
15 | public AutoFitTextureView(final Context context, final AttributeSet attrs) {
16 | this(context, attrs, 0);
17 | }
18 |
19 | public AutoFitTextureView(final Context context, final AttributeSet attrs, final int defStyle) {
20 | super(context, attrs, defStyle);
21 | }
22 |
23 | /**
24 | * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
25 | * calculated from the parameters. Note that the actual sizes of parameters don't matter, that
26 | * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
27 | *
28 | * @param width Relative horizontal size
29 | * @param height Relative vertical size
30 | */
31 | public void setAspectRatio(final int width, final int height) {
32 | if (width < 0 || height < 0) {
33 | throw new IllegalArgumentException("Size cannot be negative.");
34 | }
35 | ratioWidth = width;
36 | ratioHeight = height;
37 | requestLayout();
38 | }
39 |
40 | @Override
41 | protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
42 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
43 | final int width = MeasureSpec.getSize(widthMeasureSpec);
44 | final int height = MeasureSpec.getSize(heightMeasureSpec);
45 | if (0 == ratioWidth || 0 == ratioHeight) {
46 | setMeasuredDimension(width, height);
47 | } else {
48 | if (width < height * ratioWidth / ratioHeight) {
49 | setMeasuredDimension(width, width * ratioHeight / ratioWidth);
50 | } else {
51 | setMeasuredDimension(height * ratioWidth / ratioHeight, height);
52 | }
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/java/com/example/androidfacemask/view/CameraActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.androidfacemask.view;
2 |
3 | import android.Manifest;
4 | import android.app.Activity;
5 | import android.content.pm.PackageManager;
6 | import android.media.ImageReader.OnImageAvailableListener;
7 | import android.os.Build;
8 | import android.os.Bundle;
9 | import android.os.Handler;
10 | import android.os.HandlerThread;
11 | import android.util.Log;
12 | import android.util.Size;
13 | import android.view.WindowManager;
14 | import android.widget.Toast;
15 |
16 | import com.example.androidfacemask.R;
17 |
18 | import static com.example.androidfacemask.Config.LOGGING_TAG;
19 |
20 | public abstract class CameraActivity extends Activity implements OnImageAvailableListener {
21 | private static final int PERMISSIONS_REQUEST = 1;
22 |
23 | private Handler handler;
24 | private HandlerThread handlerThread;
25 |
26 | @Override
27 | protected void onCreate(final Bundle savedInstanceState) {
28 | super.onCreate(null);
29 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
30 |
31 | setContentView(R.layout.activity_camera);
32 |
33 | if (hasPermission()) {
34 | setFragment();
35 | } else {
36 | requestPermission();
37 | }
38 | }
39 |
40 | @Override
41 | public synchronized void onResume() {
42 | super.onResume();
43 |
44 | handlerThread = new HandlerThread("inference");
45 | handlerThread.start();
46 | handler = new Handler(handlerThread.getLooper());
47 | }
48 |
49 | @Override
50 | public synchronized void onPause() {
51 | if (!isFinishing()) {
52 | finish();
53 | }
54 |
55 | handlerThread.quitSafely();
56 | try {
57 | handlerThread.join();
58 | handlerThread = null;
59 | handler = null;
60 | } catch (final InterruptedException ex) {
61 | Log.e(LOGGING_TAG, "Exception: " + ex.getMessage());
62 | }
63 |
64 | super.onPause();
65 | }
66 |
67 | protected synchronized void runInBackground(final Runnable runnable) {
68 | if (handler != null) {
69 | handler.post(runnable);
70 | }
71 | }
72 |
73 | @Override
74 | public void onRequestPermissionsResult(final int requestCode, final String[] permissions,
75 | final int[] grantResults) {
76 | switch (requestCode) {
77 | case PERMISSIONS_REQUEST: {
78 | if (grantResults.length > 0
79 | && grantResults[0] == PackageManager.PERMISSION_GRANTED
80 | && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
81 | setFragment();
82 | } else {
83 | requestPermission();
84 | }
85 | }
86 | }
87 | }
88 |
89 | private boolean hasPermission() {
90 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
91 | return checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
92 | && checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
93 | } else {
94 | return true;
95 | }
96 | }
97 |
98 | private void requestPermission() {
99 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
100 | if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)
101 | || shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
102 | Toast.makeText(CameraActivity.this,
103 | "Camera AND storage permission are required for this demo", Toast.LENGTH_LONG).show();
104 | }
105 | requestPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSIONS_REQUEST);
106 | }
107 | }
108 |
109 | protected void setFragment() {
110 | CameraConnectionFragment cameraConnectionFragment = new CameraConnectionFragment();
111 | cameraConnectionFragment.addConnectionListener((final Size size, final int rotation) ->
112 | CameraActivity.this.onPreviewSizeChosen(size, rotation));
113 | cameraConnectionFragment.addImageAvailableListener(this);
114 |
115 | getFragmentManager()
116 | .beginTransaction()
117 | .replace(R.id.container, cameraConnectionFragment)
118 | .commit();
119 | }
120 |
121 | public void requestRender() {
122 | final OverlayView overlay = (OverlayView) findViewById(R.id.overlay);
123 | if (overlay != null) {
124 | overlay.postInvalidate();
125 | }
126 | }
127 |
128 | public void addCallback(final OverlayView.DrawCallback callback) {
129 | final OverlayView overlay = (OverlayView) findViewById(R.id.overlay);
130 | if (overlay != null) {
131 | overlay.addCallback(callback);
132 | }
133 | }
134 |
135 | protected abstract void onPreviewSizeChosen(final Size size, final int rotation);
136 | }
137 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/java/com/example/androidfacemask/view/CameraConnectionFragment.java:
--------------------------------------------------------------------------------
1 | package com.example.androidfacemask.view;
2 |
3 | import android.Manifest;
4 | import android.app.Activity;
5 | import android.app.Fragment;
6 | import android.content.Context;
7 | import android.content.pm.PackageManager;
8 | import android.content.res.Configuration;
9 | import android.graphics.ImageFormat;
10 | import android.graphics.Matrix;
11 | import android.graphics.RectF;
12 | import android.graphics.SurfaceTexture;
13 | import android.hardware.camera2.CameraAccessException;
14 | import android.hardware.camera2.CameraCaptureSession;
15 | import android.hardware.camera2.CameraCharacteristics;
16 | import android.hardware.camera2.CameraDevice;
17 | import android.hardware.camera2.CameraManager;
18 | import android.hardware.camera2.CaptureRequest;
19 | import android.hardware.camera2.params.StreamConfigurationMap;
20 | import android.media.ImageReader;
21 | import android.media.ImageReader.OnImageAvailableListener;
22 | import android.os.Bundle;
23 | import android.os.Handler;
24 | import android.os.HandlerThread;
25 | import android.util.Log;
26 | import android.util.Size;
27 | import android.util.SparseIntArray;
28 | import android.view.LayoutInflater;
29 | import android.view.Surface;
30 | import android.view.TextureView;
31 | import android.view.View;
32 | import android.view.ViewGroup;
33 | import android.widget.Toast;
34 |
35 | import com.example.androidfacemask.R;
36 | import com.example.androidfacemask.util.comparator.CompareSizesByArea;
37 | import com.example.androidfacemask.view.components.ErrorDialog;
38 |
39 | import java.util.ArrayList;
40 | import java.util.Arrays;
41 | import java.util.Collections;
42 | import java.util.List;
43 | import java.util.concurrent.Semaphore;
44 | import java.util.concurrent.TimeUnit;
45 |
46 | import static com.example.androidfacemask.Config.LOGGING_TAG;
47 |
48 |
49 | public class CameraConnectionFragment extends Fragment {
50 | private static final Size DESIRED_PREVIEW_SIZE = new Size(640, 480);
51 | /**
52 | * The camera preview size will be chosen to be the smallest frame by pixel size capable of
53 | * containing a DESIRED_SIZE x DESIRED_SIZE square.
54 | */
55 | private static final int MINIMUM_PREVIEW_SIZE = 320;
56 |
57 | /**
58 | * Conversion from screen rotation to JPEG orientation.
59 | */
60 | private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
61 |
62 | static {
63 | ORIENTATIONS.append(Surface.ROTATION_0, 90);
64 | ORIENTATIONS.append(Surface.ROTATION_90, 0);
65 | ORIENTATIONS.append(Surface.ROTATION_180, 270);
66 | ORIENTATIONS.append(Surface.ROTATION_270, 180);
67 | }
68 |
69 | private static final String FRAGMENT_DIALOG = "dialog";
70 |
71 | /**
72 | * A {@link Semaphore} to prevent the app from exiting before closing the camera.
73 | */
74 | private final Semaphore cameraOpenCloseLock = new Semaphore(1);
75 | /**
76 | * A {@link OnImageAvailableListener} to receive frames as they are available.
77 | */
78 | private OnImageAvailableListener imageListener;
79 |
80 | /**
81 | * A {@link ConnectionListener} to notify the caller activity
82 | */
83 | private ConnectionListener cameraConnectionListener;
84 |
85 | /**
86 | * ID of the current {@link CameraDevice}.
87 | */
88 | private String cameraId;
89 | /**
90 | * An {@link AutoFitTextureView} for camera preview.
91 | */
92 | private AutoFitTextureView textureView;
93 | /**
94 | * A {@link CameraCaptureSession } for camera preview.
95 | */
96 | private CameraCaptureSession captureSession;
97 | /**
98 | * A reference to the opened {@link CameraDevice}.
99 | */
100 | private CameraDevice cameraDevice;
101 | /**
102 | * The rotation in degrees of the camera sensor from the display.
103 | */
104 | private Integer sensorOrientation;
105 | /**
106 | * The {@link android.util.Size} of camera preview.
107 | */
108 | private Size previewSize;
109 | /**
110 | * An additional thread for running tasks that shouldn't block the UI.
111 | */
112 | private HandlerThread backgroundThread;
113 | /**
114 | * A {@link Handler} for running tasks in the background.
115 | */
116 | private Handler backgroundHandler;
117 | /**
118 | * An {@link ImageReader} that handles preview frame capture.
119 | */
120 | private ImageReader previewReader;
121 | /**
122 | * {@link android.hardware.camera2.CaptureRequest.Builder} for the camera preview
123 | */
124 | private CaptureRequest.Builder previewRequestBuilder;
125 | /**
126 | * {@link CaptureRequest} generated by {@link #previewRequestBuilder}
127 | */
128 | private CaptureRequest previewRequest;
129 |
130 | @Override
131 | public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
132 | return inflater.inflate(R.layout.camera_connection_fragment, container, false);
133 | }
134 |
135 | @Override
136 | public void onViewCreated(final View view, final Bundle savedInstanceState) {
137 | textureView = (AutoFitTextureView) view.findViewById(R.id.texture);
138 | }
139 |
140 | @Override
141 | public void onActivityCreated(final Bundle savedInstanceState) {
142 | super.onActivityCreated(savedInstanceState);
143 | }
144 |
145 | @Override
146 | public void onResume() {
147 | super.onResume();
148 | startBackgroundThread();
149 |
150 | // When the screen is turned off and turned back on, the SurfaceTexture is already
151 | // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
152 | // a camera and start preview from here (otherwise, we wait until the surface is ready in
153 | // the SurfaceTextureListener).
154 | if (textureView.isAvailable()) {
155 | openCamera(textureView.getWidth(), textureView.getHeight());
156 | } else {
157 | textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
158 | @Override
159 | public void onSurfaceTextureAvailable(final SurfaceTexture texture, final int width, final int height) {
160 | openCamera(width, height);
161 | }
162 |
163 | @Override
164 | public void onSurfaceTextureSizeChanged(final SurfaceTexture texture, final int width, final int height) {
165 | configureTransform(width, height);
166 | }
167 |
168 | @Override
169 | public boolean onSurfaceTextureDestroyed(final SurfaceTexture texture) {
170 | return true;
171 | }
172 |
173 | @Override
174 | public void onSurfaceTextureUpdated(final SurfaceTexture texture) {
175 | }
176 | });
177 | }
178 | }
179 |
180 | @Override
181 | public void onPause() {
182 | closeCamera();
183 | stopBackgroundThread();
184 | super.onPause();
185 | }
186 |
187 | public void addConnectionListener(final ConnectionListener cameraConnectionListener) {
188 | this.cameraConnectionListener = cameraConnectionListener;
189 | }
190 |
191 | public void addImageAvailableListener(final OnImageAvailableListener imageListener) {
192 | this.imageListener = imageListener;
193 | }
194 |
195 | /**
196 | * Callback for Activities to use to initialize their data once the
197 | * selected preview size is known.
198 | */
199 | public interface ConnectionListener {
200 | void onPreviewSizeChosen(Size size, int cameraRotation);
201 | }
202 |
203 | /**
204 | * Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose
205 | * width and height are at least as large as the minimum of both, or an exact match if possible.
206 | *
207 | * @param choices The list of sizes that the camera supports for the intended output class
208 | * @return The optimal {@code Size}, or an arbitrary one if none were big enough
209 | */
210 | private static Size chooseOptimalSize(final Size[] choices) {
211 | final int minSize = Math.max(Math.min(DESIRED_PREVIEW_SIZE.getWidth(),
212 | DESIRED_PREVIEW_SIZE.getHeight()), MINIMUM_PREVIEW_SIZE);
213 |
214 | // Collect the supported resolutions that are at least as big as the preview Surface
215 | final List bigEnough = new ArrayList();
216 | for (final Size option : choices) {
217 | if (option.equals(DESIRED_PREVIEW_SIZE)) {
218 | return DESIRED_PREVIEW_SIZE;
219 | }
220 |
221 | if (option.getHeight() >= minSize && option.getWidth() >= minSize) {
222 | bigEnough.add(option);
223 | }
224 | }
225 |
226 | // Pick the smallest of those, assuming we found any
227 | return (bigEnough.size() > 0) ? Collections.min(bigEnough, new CompareSizesByArea()) : choices[0];
228 | }
229 |
230 | /**
231 | * Shows a {@link Toast} on the UI thread.
232 | *
233 | * @param text The message to show
234 | */
235 | private void showToast(final String text) {
236 | final Activity activity = getActivity();
237 | if (activity != null) {
238 | activity.runOnUiThread(() -> Toast.makeText(activity, text, Toast.LENGTH_SHORT).show());
239 | }
240 | }
241 |
242 | /**
243 | * Sets up member variables related to camera.
244 | */
245 | private void setUpCameraOutputs() {
246 | final CameraManager manager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);
247 | try {
248 | for (final String cameraId : manager.getCameraIdList()) {
249 | final CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
250 |
251 | // We don't use a front facing camera in this sample.
252 | final Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
253 | if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
254 | continue;
255 | }
256 |
257 | final StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
258 |
259 | if (map == null) {
260 | continue;
261 | }
262 |
263 | sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
264 |
265 | // Danger, W.R.! Attempting to use too large a preview size could exceed the camera
266 | // bus' bandwidth limitation, resulting in gorgeous previews but the storage of
267 | // garbage capture data.
268 | previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class));
269 |
270 | // We fit the aspect ratio of TextureView to the size of preview we picked.
271 | final int orientation = getResources().getConfiguration().orientation;
272 | if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
273 | textureView.setAspectRatio(previewSize.getWidth(), previewSize.getHeight());
274 | } else {
275 | textureView.setAspectRatio(previewSize.getHeight(), previewSize.getWidth());
276 | }
277 |
278 | this.cameraId = cameraId;
279 | }
280 | } catch (final CameraAccessException ex) {
281 | Log.e(LOGGING_TAG, "Exception: " + ex.getMessage());
282 | } catch (final NullPointerException ex) {
283 | ErrorDialog.newInstance(getString(R.string.camera_error))
284 | .show(getChildFragmentManager(), FRAGMENT_DIALOG);
285 | throw new RuntimeException(getString(R.string.camera_error));
286 | }
287 |
288 | cameraConnectionListener.onPreviewSizeChosen(previewSize, sensorOrientation);
289 | }
290 |
291 | /**
292 | * Opens the camera specified by {@link CameraConnectionFragment#cameraId}.
293 | */
294 | private void openCamera(final int width, final int height) {
295 | setUpCameraOutputs();
296 | configureTransform(width, height);
297 | final Activity activity = getActivity();
298 | /**
299 | * {@link android.hardware.camera2.CameraDevice.StateCallback}
300 | * is called when {@link CameraDevice} changes its state.
301 | */
302 | final CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
303 | try {
304 | if (!cameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
305 | throw new RuntimeException("Time out waiting to lock camera opening.");
306 | }
307 | if (activity.checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
308 | manager.openCamera(cameraId, new CameraDevice.StateCallback() {
309 | @Override
310 | public void onOpened(final CameraDevice cameraDevice) {
311 | // This method is called when the camera is opened. We start camera preview here.
312 | cameraOpenCloseLock.release();
313 | CameraConnectionFragment.this.cameraDevice = cameraDevice;
314 | createCameraPreviewSession();
315 | }
316 |
317 | @Override
318 | public void onDisconnected(final CameraDevice cameraDevice) {
319 | cameraOpenCloseLock.release();
320 | cameraDevice.close();
321 | CameraConnectionFragment.this.cameraDevice = null;
322 | }
323 |
324 | @Override
325 | public void onError(final CameraDevice cameraDevice, final int error) {
326 | cameraOpenCloseLock.release();
327 | cameraDevice.close();
328 | CameraConnectionFragment.this.cameraDevice = null;
329 | final Activity activity = getActivity();
330 | if (null != activity) {
331 | activity.finish();
332 | }
333 | }
334 | }, backgroundHandler);
335 | } else {
336 | requestPermissions(new String[]{Manifest.permission.CAMERA}, 1);
337 | }
338 | } catch (final CameraAccessException ex) {
339 | Log.e(LOGGING_TAG, "Exception: " + ex.getMessage());
340 | } catch (final InterruptedException ex) {
341 | throw new RuntimeException("Interrupted while trying to lock camera opening.", ex);
342 | }
343 | }
344 |
345 | /**
346 | * Closes the current {@link CameraDevice}.
347 | */
348 | private void closeCamera() {
349 | try {
350 | cameraOpenCloseLock.acquire();
351 | if (null != captureSession) {
352 | captureSession.close();
353 | captureSession = null;
354 | }
355 | if (null != cameraDevice) {
356 | cameraDevice.close();
357 | cameraDevice = null;
358 | }
359 | if (null != previewReader) {
360 | previewReader.close();
361 | previewReader = null;
362 | }
363 | } catch (final InterruptedException e) {
364 | throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
365 | } finally {
366 | cameraOpenCloseLock.release();
367 | }
368 | }
369 |
370 | /**
371 | * Starts a background thread and its {@link Handler}.
372 | */
373 | private void startBackgroundThread() {
374 | backgroundThread = new HandlerThread("ImageListener");
375 | backgroundThread.start();
376 | backgroundHandler = new Handler(backgroundThread.getLooper());
377 | }
378 |
379 | /**
380 | * Stops the background thread and its {@link Handler}.
381 | */
382 | private void stopBackgroundThread() {
383 | backgroundThread.quitSafely();
384 | try {
385 | backgroundThread.join();
386 | backgroundThread = null;
387 | backgroundHandler = null;
388 | } catch (final InterruptedException ex) {
389 | Log.e(LOGGING_TAG, "Exception: " + ex.getMessage());
390 | }
391 | }
392 |
393 | /**
394 | * Creates a new {@link CameraCaptureSession} for camera preview.
395 | */
396 | private void createCameraPreviewSession() {
397 | try {
398 | final SurfaceTexture texture = textureView.getSurfaceTexture();
399 | assert texture != null;
400 |
401 | // We configure the size of default buffer to be the size of camera preview we want.
402 | texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
403 |
404 | // This is the output Surface we need to start preview.
405 | final Surface surface = new Surface(texture);
406 |
407 | // We set up a CaptureRequest.Builder with the output Surface.
408 | previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
409 | previewRequestBuilder.addTarget(surface);
410 |
411 | Log.i(LOGGING_TAG, String.format("Opening camera preview: "
412 | + previewSize.getWidth() + "x" + previewSize.getHeight()));
413 |
414 | // Create the reader for the preview frames.
415 | previewReader = ImageReader.newInstance(previewSize.getWidth(), previewSize.getHeight(),
416 | ImageFormat.YUV_420_888, 2);
417 |
418 | previewReader.setOnImageAvailableListener(imageListener, backgroundHandler);
419 | previewRequestBuilder.addTarget(previewReader.getSurface());
420 |
421 | // Here, we create a CameraCaptureSession for camera preview.
422 | cameraDevice.createCaptureSession(Arrays.asList(surface, previewReader.getSurface()),
423 | getCaptureSessionStateCallback(), null);
424 | } catch (final CameraAccessException ex) {
425 | Log.e(LOGGING_TAG, "Exception: " + ex.getMessage());
426 | }
427 | }
428 |
429 | private CameraCaptureSession.StateCallback getCaptureSessionStateCallback() {
430 | return new CameraCaptureSession.StateCallback() {
431 |
432 | @Override
433 | public void onConfigured(final CameraCaptureSession cameraCaptureSession) {
434 | // The camera is already closed
435 | if (null == cameraDevice) {
436 | return;
437 | }
438 |
439 | // When the session is ready, we start displaying the preview.
440 | captureSession = cameraCaptureSession;
441 | try {
442 | // Auto focus should be continuous for camera preview.
443 | previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
444 | CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
445 | // Flash is automatically enabled when necessary.
446 | previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
447 | CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
448 |
449 | // Finally, we start displaying the camera preview.
450 | previewRequest = previewRequestBuilder.build();
451 | captureSession.setRepeatingRequest(previewRequest, null, backgroundHandler);
452 | } catch (final CameraAccessException ex) {
453 | Log.e(LOGGING_TAG, "Exception: " + ex.getMessage());
454 | }
455 | }
456 |
457 | @Override
458 | public void onConfigureFailed(final CameraCaptureSession cameraCaptureSession) {
459 | showToast("Failed");
460 | }
461 | };
462 | }
463 |
464 | /**
465 | * Configures the necessary {@link android.graphics.Matrix} transformation to `mTextureView`.
466 | * This method should be called after the camera preview size is determined in
467 | * setUpCameraOutputs and also the size of `mTextureView` is fixed.
468 | *
469 | * @param viewWidth The width of `mTextureView`
470 | * @param viewHeight The height of `mTextureView`
471 | */
472 | private void configureTransform(final int viewWidth, final int viewHeight) {
473 | final Activity activity = getActivity();
474 | if (null == textureView || null == previewSize || null == activity) {
475 | return;
476 | }
477 | final int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
478 | final Matrix matrix = new Matrix();
479 | final RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
480 | final RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth());
481 | final float centerX = viewRect.centerX();
482 | final float centerY = viewRect.centerY();
483 | if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
484 | bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
485 | matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
486 | final float scale =
487 | Math.max(
488 | (float) viewHeight / previewSize.getHeight(),
489 | (float) viewWidth / previewSize.getWidth());
490 | matrix.postScale(scale, scale, centerX, centerY);
491 | matrix.postRotate(90 * (rotation - 2), centerX, centerY);
492 | } else if (Surface.ROTATION_180 == rotation) {
493 | matrix.postRotate(180, centerX, centerY);
494 | }
495 | textureView.setTransform(matrix);
496 | }
497 | }
498 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/java/com/example/androidfacemask/view/FaceActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.androidfacemask.view;
2 |
3 | import android.graphics.Bitmap;
4 | import android.graphics.Bitmap.Config;
5 | import android.graphics.BitmapFactory;
6 | import android.graphics.Canvas;
7 | import android.graphics.Matrix;
8 | import android.graphics.Typeface;
9 | import android.media.Image;
10 | import android.media.ImageReader;
11 | import android.media.ImageReader.OnImageAvailableListener;
12 | import android.os.Environment;
13 | import android.os.SystemClock;
14 | import android.util.Log;
15 | import android.util.Size;
16 | import android.util.TypedValue;
17 |
18 | import com.example.androidfacemask.R;
19 | import com.example.androidfacemask.facemask.FaceMask;
20 | import com.example.androidfacemask.facemask.Box;
21 | import com.example.androidfacemask.util.ImageUtils;
22 | import com.example.androidfacemask.view.components.BorderedText;
23 |
24 | import java.io.File;
25 | import java.io.FileInputStream;
26 | import java.io.IOException;
27 | import java.util.ArrayList;
28 | import java.util.Vector;
29 |
30 | import static com.example.androidfacemask.Config.LOGGING_TAG;
31 |
32 | public class FaceActivity extends com.example.androidfacemask.view.TextToSpeechActivity implements OnImageAvailableListener {
33 | private boolean MAINTAIN_ASPECT = true;
34 | private float TEXT_SIZE_DIP = 10;
35 |
36 | private Integer sensorOrientation;
37 | private int previewWidth = 0;
38 | private int previewHeight = 0;
39 | private Bitmap croppedBitmap = null;
40 | private boolean computing = false;
41 | private Matrix frameToCropTransform;
42 |
43 | private OverlayView overlayView;
44 | private BorderedText borderedText;
45 | private long lastProcessingTimeMs;
46 |
47 | private FaceMask facemask; // 口罩检测
48 |
49 | @Override
50 | public void onPreviewSizeChosen(final Size size, final int rotation) {
51 | final float textSizePx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
52 | TEXT_SIZE_DIP, getResources().getDisplayMetrics());
53 | borderedText = new BorderedText(textSizePx);
54 | borderedText.setTypeface(Typeface.MONOSPACE);
55 |
56 | overlayView = findViewById(R.id.overlay);
57 | previewWidth = size.getWidth();
58 | previewHeight = size.getHeight();
59 |
60 | final int screenOrientation = getWindowManager().getDefaultDisplay().getRotation();
61 |
62 | Log.i(LOGGING_TAG, String.format("Sensor orientation: %d, Screen orientation: %d",
63 | rotation, screenOrientation));
64 |
65 | sensorOrientation = rotation + screenOrientation;
66 |
67 | Log.i(LOGGING_TAG, String.format("Initializing at size %dx%d", previewWidth, previewHeight));
68 |
69 | croppedBitmap = Bitmap.createBitmap(FaceMask.INPUT_IMAGE_SIZE, FaceMask.INPUT_IMAGE_SIZE, Config.ARGB_8888);
70 |
71 | frameToCropTransform = ImageUtils.getTransformationMatrix(previewWidth, previewHeight,
72 | FaceMask.INPUT_IMAGE_SIZE, FaceMask.INPUT_IMAGE_SIZE, sensorOrientation, MAINTAIN_ASPECT);
73 | frameToCropTransform.invert(new Matrix());
74 |
75 | try {
76 | facemask = new FaceMask(getAssets());
77 | } catch (IOException e) {
78 | e.printStackTrace();
79 | }
80 |
81 | addCallback((final Canvas canvas) -> renderAdditionalInformation(canvas));
82 | }
83 |
84 | @Override
85 | public void onImageAvailable(final ImageReader reader) {
86 | Image image = null;
87 |
88 | try {
89 | image = reader.acquireLatestImage();
90 |
91 | if (image == null) {
92 | return;
93 | }
94 |
95 | if (computing) {
96 | image.close();
97 | return;
98 | }
99 |
100 | computing = true;
101 | fillCroppedBitmap(image);
102 | image.close();
103 | } catch (final Exception ex) {
104 | if (image != null) {
105 | image.close();
106 | }
107 | Log.e(LOGGING_TAG, ex.getMessage());
108 | }
109 |
110 | runInBackground(() -> {
111 | final long startTime = SystemClock.uptimeMillis();
112 | Vector facemask_boxes = null;
113 | try {
114 | facemask_boxes = facemask.detectFaceMasks(croppedBitmap);
115 | } catch (Exception e) {
116 | e.printStackTrace();
117 | }
118 | lastProcessingTimeMs = SystemClock.uptimeMillis() - startTime;
119 | if (facemask_boxes != null) {
120 | overlayView.setResults(facemask_boxes);
121 | } else {
122 | overlayView.setResults(null);
123 | }
124 | requestRender();
125 | computing = false;
126 | });
127 | }
128 |
129 | private void fillCroppedBitmap(final Image image) {
130 | Bitmap rgbFrameBitmap = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888);
131 | rgbFrameBitmap.setPixels(ImageUtils.convertYUVToARGB(image, previewWidth, previewHeight),
132 | 0, previewWidth, 0, 0, previewWidth, previewHeight);
133 | new Canvas(croppedBitmap).drawBitmap(rgbFrameBitmap, frameToCropTransform, null);
134 | }
135 |
136 | @Override
137 | protected void onDestroy() {
138 | super.onDestroy();
139 | }
140 |
141 | private void renderAdditionalInformation(final Canvas canvas) {
142 | final Vector lines = new Vector();
143 |
144 | lines.add("Frame: " + previewWidth + "x" + previewHeight);
145 | lines.add("View: " + canvas.getWidth() + "x" + canvas.getHeight());
146 | lines.add("Rotation: " + sensorOrientation);
147 | lines.add("Inference time: " + lastProcessingTimeMs + "ms");
148 |
149 | borderedText.drawLines(canvas, 10, 10, lines);
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/java/com/example/androidfacemask/view/OverlayView.java:
--------------------------------------------------------------------------------
1 | package com.example.androidfacemask.view;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.graphics.RectF;
8 | import android.util.AttributeSet;
9 | import android.util.TypedValue;
10 | import android.view.View;
11 |
12 | import com.example.androidfacemask.Config;
13 |
14 | import com.example.androidfacemask.facemask.Box;
15 |
16 | import java.util.LinkedList;
17 | import java.util.List;
18 |
19 | public class OverlayView extends View {
20 | private final Paint paint;
21 | private final List callbacks = new LinkedList();
22 | private List results;
23 |
24 | public static final int IMAGE_WIDTH = 1;
25 | public static final int IMAGE_HEIGHT = 1;
26 |
27 | public OverlayView(final Context context, final AttributeSet attrs) {
28 | super(context, attrs);
29 | paint = new Paint();
30 | paint.setColor(Color.GREEN);
31 | paint.setStyle(Paint.Style.STROKE);
32 | paint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
33 | 15, getResources().getDisplayMetrics()));
34 | }
35 |
36 | public void addCallback(final DrawCallback callback) {
37 | callbacks.add(callback);
38 | }
39 |
40 | @Override
41 | public synchronized void onDraw(final Canvas canvas) {
42 | for (final DrawCallback callback : callbacks) {
43 | callback.drawCallback(canvas);
44 | }
45 |
46 | if (results != null) {
47 | for (int i = 0; i < results.size(); i++) {
48 | if(results.get(i).deleted) {
49 | continue;
50 | }
51 | RectF box = reCalcSize(results.get(i));
52 | String title = String.format("%s:%f", results.get(i).title, results.get(i).score);
53 | if(results.get(i).cls == 0) {
54 | paint.setColor(0xff00ff00);
55 | } else {
56 | paint.setColor(0xffff0000);
57 | }
58 | canvas.drawRect(box, paint);
59 | canvas.drawText(title, box.left, box.top, paint);
60 | }
61 | }
62 | }
63 |
64 | public void setResults(final List results) {
65 | this.results = results;
66 | postInvalidate();
67 | }
68 |
69 | private RectF reCalcSize(Box box) {
70 | int padding = 5;
71 | float overlayViewHeight = this.getHeight();
72 | float sizeMultiplier = Math.min((float) this.getWidth() / (float) IMAGE_WIDTH,
73 | overlayViewHeight / (float) IMAGE_HEIGHT);
74 |
75 | float offsetX = (this.getWidth() - IMAGE_WIDTH * sizeMultiplier) / 2;
76 | float offsetY = (overlayViewHeight - IMAGE_HEIGHT * sizeMultiplier) / 2;
77 |
78 | float left = Math.max(padding, sizeMultiplier * box.box[0] + offsetX);
79 | float top = Math.max(offsetY + padding, sizeMultiplier * box.box[1] + offsetY);
80 |
81 | float right = Math.min(box.box[2] * sizeMultiplier, this.getWidth() - padding);
82 | float bottom = Math.min(box.box[3] * sizeMultiplier + offsetY, this.getHeight() - padding);
83 |
84 | return new RectF(left, top, right, bottom);
85 | }
86 |
87 | /**
88 | * Interface defining the callback for client classes.
89 | */
90 | public interface DrawCallback {
91 | void drawCallback(final Canvas canvas);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/java/com/example/androidfacemask/view/TextToSpeechActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.androidfacemask.view;
2 |
3 | import android.os.Bundle;
4 | import android.speech.tts.TextToSpeech;
5 | import android.util.Log;
6 |
7 | import java.util.Locale;
8 |
9 | import static com.example.androidfacemask.Config.LOGGING_TAG;
10 |
11 |
12 | public abstract class TextToSpeechActivity extends CameraActivity implements TextToSpeech.OnInitListener {
13 | private TextToSpeech textToSpeech;
14 | private String lastRecognizedClass = "";
15 |
16 | @Override
17 | public void onInit(int status) {
18 | if (status == TextToSpeech.SUCCESS) {
19 | int result = textToSpeech.setLanguage(Locale.US);
20 | if (result == TextToSpeech.LANG_MISSING_DATA
21 | || result == TextToSpeech.LANG_NOT_SUPPORTED) {
22 | Log.e(LOGGING_TAG, "Text to speech error: This Language is not supported");
23 | }
24 | } else {
25 | Log.e(LOGGING_TAG, "Text to speech: Initilization Failed!");
26 | }
27 | }
28 |
29 | @Override
30 | protected void onCreate(Bundle savedInstanceState) {
31 | super.onCreate(savedInstanceState);
32 | textToSpeech = new TextToSpeech(this, this);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/java/com/example/androidfacemask/view/components/BorderedText.java:
--------------------------------------------------------------------------------
1 | package com.example.androidfacemask.view.components;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Color;
5 | import android.graphics.Paint;
6 | import android.graphics.Paint.Align;
7 | import android.graphics.Paint.Style;
8 | import android.graphics.Rect;
9 | import android.graphics.Typeface;
10 |
11 | import java.util.Vector;
12 |
13 | public class BorderedText {
14 | private final Paint interiorPaint;
15 | private final Paint exteriorPaint;
16 |
17 | private final float textSize;
18 |
19 | /**
20 | * Creates a left-aligned bordered text object with a white interior, and a black exterior with
21 | * the specified text size.
22 | *
23 | * @param textSize text size in pixels
24 | */
25 | public BorderedText(final float textSize) {
26 | this(Color.WHITE, Color.BLACK, textSize);
27 | }
28 |
29 | /**
30 | * Create a bordered text object with the specified interior and exterior colors, text size and
31 | * alignment.
32 | *
33 | * @param interiorColor the interior text color
34 | * @param exteriorColor the exterior text color
35 | * @param textSize text size in pixels
36 | */
37 | public BorderedText(final int interiorColor, final int exteriorColor, final float textSize) {
38 | interiorPaint = new Paint();
39 | interiorPaint.setTextSize(textSize);
40 | interiorPaint.setColor(interiorColor);
41 | interiorPaint.setStyle(Style.FILL);
42 | interiorPaint.setAntiAlias(false);
43 | interiorPaint.setAlpha(255);
44 |
45 | exteriorPaint = new Paint();
46 | exteriorPaint.setTextSize(textSize);
47 | exteriorPaint.setColor(exteriorColor);
48 | exteriorPaint.setStyle(Style.FILL_AND_STROKE);
49 | exteriorPaint.setStrokeWidth(textSize / 8);
50 | exteriorPaint.setAntiAlias(false);
51 | exteriorPaint.setAlpha(255);
52 |
53 | this.textSize = textSize;
54 | }
55 |
56 | public void setTypeface(Typeface typeface) {
57 | interiorPaint.setTypeface(typeface);
58 | exteriorPaint.setTypeface(typeface);
59 | }
60 |
61 | public void drawText(final Canvas canvas, final float posX, final float posY, final String text) {
62 | canvas.drawText(text, posX, posY, exteriorPaint);
63 | canvas.drawText(text, posX, posY, interiorPaint);
64 | }
65 |
66 | public void drawLines(Canvas canvas, final float posX, final float posY, Vector lines) {
67 | int lineNum = 0;
68 | for (final String line : lines) {
69 | drawText(canvas, posX, posY + getTextSize() * lineNum, line);
70 | ++lineNum;
71 | }
72 | }
73 |
74 | public void setInteriorColor(final int color) {
75 | interiorPaint.setColor(color);
76 | }
77 |
78 | public void setExteriorColor(final int color) {
79 | exteriorPaint.setColor(color);
80 | }
81 |
82 | public float getTextSize() {
83 | return textSize;
84 | }
85 |
86 | public void setAlpha(final int alpha) {
87 | interiorPaint.setAlpha(alpha);
88 | exteriorPaint.setAlpha(alpha);
89 | }
90 |
91 | public void getTextBounds(
92 | final String line, final int index, final int count, final Rect lineBounds) {
93 | interiorPaint.getTextBounds(line, index, count, lineBounds);
94 | }
95 |
96 | public void setTextAlign(final Align align) {
97 | interiorPaint.setTextAlign(align);
98 | exteriorPaint.setTextAlign(align);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/java/com/example/androidfacemask/view/components/ErrorDialog.java:
--------------------------------------------------------------------------------
1 | package com.example.androidfacemask.view.components;
2 |
3 | import android.app.Activity;
4 | import android.app.AlertDialog;
5 | import android.app.Dialog;
6 | import android.app.DialogFragment;
7 | import android.content.DialogInterface;
8 | import android.os.Bundle;
9 |
10 | public class ErrorDialog extends DialogFragment {
11 | private static final String ARG_MESSAGE = "message";
12 |
13 | public static ErrorDialog newInstance(final String message) {
14 | final ErrorDialog dialog = new ErrorDialog();
15 | final Bundle args = new Bundle();
16 | args.putString(ARG_MESSAGE, message);
17 | dialog.setArguments(args);
18 | return dialog;
19 | }
20 |
21 | @Override
22 | public Dialog onCreateDialog(final Bundle savedInstanceState) {
23 | final Activity activity = getActivity();
24 | return new AlertDialog.Builder(activity)
25 | .setMessage(getArguments().getString(ARG_MESSAGE))
26 | .setPositiveButton(android.R.string.ok,
27 | (final DialogInterface dialogInterface, final int i) -> activity.finish())
28 | .create();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/res/layout/activity_camera.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/res/layout/camera_connection_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ma-Dan/AndroidFaceMask/07e3fe09f5b509a77249ba586d389e3164506232/AndroidFaceMask/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ma-Dan/AndroidFaceMask/07e3fe09f5b509a77249ba586d389e3164506232/AndroidFaceMask/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ma-Dan/AndroidFaceMask/07e3fe09f5b509a77249ba586d389e3164506232/AndroidFaceMask/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ma-Dan/AndroidFaceMask/07e3fe09f5b509a77249ba586d389e3164506232/AndroidFaceMask/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ma-Dan/AndroidFaceMask/07e3fe09f5b509a77249ba586d389e3164506232/AndroidFaceMask/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ma-Dan/AndroidFaceMask/07e3fe09f5b509a77249ba586d389e3164506232/AndroidFaceMask/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ma-Dan/AndroidFaceMask/07e3fe09f5b509a77249ba586d389e3164506232/AndroidFaceMask/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ma-Dan/AndroidFaceMask/07e3fe09f5b509a77249ba586d389e3164506232/AndroidFaceMask/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ma-Dan/AndroidFaceMask/07e3fe09f5b509a77249ba586d389e3164506232/AndroidFaceMask/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ma-Dan/AndroidFaceMask/07e3fe09f5b509a77249ba586d389e3164506232/AndroidFaceMask/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 |
4 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | AndroidFaceMask
3 | AndroidFaceMask
4 | This sample needs camera permission.
5 | This device doesn\'t support Camera2 API.
6 |
7 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/AndroidFaceMask/app/src/test/java/com/example/androidfacemask/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.example.androidfacemask;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/AndroidFaceMask/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | google()
6 | jcenter()
7 |
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.5.3'
11 |
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | google()
20 | jcenter()
21 |
22 | }
23 | }
24 |
25 | task clean(type: Delete) {
26 | delete rootProject.buildDir
27 | }
28 |
--------------------------------------------------------------------------------
/AndroidFaceMask/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 |
21 |
--------------------------------------------------------------------------------
/AndroidFaceMask/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ma-Dan/AndroidFaceMask/07e3fe09f5b509a77249ba586d389e3164506232/AndroidFaceMask/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/AndroidFaceMask/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Dec 20 22:07:24 CST 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/AndroidFaceMask/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/AndroidFaceMask/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/AndroidFaceMask/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | rootProject.name='AndroidFaceMask'
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 AIZOOTech
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AndroidFaceMask
2 |
3 | 安卓上的口罩检测。
4 |
5 | 
6 |
7 | ## 代码参考
8 |
9 | - 模型: [https://github.com/AIZOOTech/FaceMaskDetection](https://github.com/AIZOOTech/FaceMaskDetection)
10 |
11 | - 界面: [https://github.com/syaringan357/Android-MobileFaceNet-MTCNN-FaceAntiSpoofing](https://github.com/syaringan357/Android-MobileFaceNet-MTCNN-FaceAntiSpoofing)
12 |
--------------------------------------------------------------------------------