add super basic face recognition back
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/shared/FaceDetector.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/shared/FaceDetector.java
index d4d1b0c..ed6673e 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/shared/FaceDetector.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/shared/FaceDetector.java
@@ -27,7 +27,7 @@
private static final boolean TF_FD_API_IS_QUANTIZED = true;
private static final String TF_FD_API_MODEL_FILE = "detect-class1.tflite";
private static final String TF_FD_API_LABELS_FILE = "file:///android_asset/detect-class1.txt";
- private static final float MINIMUM_CONFIDENCE_TF_FD_API = 0.5f;
+ private static final float MINIMUM_CONFIDENCE_TF_FD_API = 0.6f;
private static final boolean MAINTAIN_ASPECT = false;
public static class InputImage {
diff --git a/app/src/main/java/com/libremobileos/facedetect/FaceBoundsOverlayView.java b/app/src/main/java/com/libremobileos/facedetect/FaceBoundsOverlayView.java
index 63e6e3e..cc3fba3 100644
--- a/app/src/main/java/com/libremobileos/facedetect/FaceBoundsOverlayView.java
+++ b/app/src/main/java/com/libremobileos/facedetect/FaceBoundsOverlayView.java
@@ -7,16 +7,19 @@
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
+import android.util.Pair;
import android.view.View;
import androidx.annotation.Nullable;
import com.libremobileos.yifan.face.shared.ImageUtils;
+import java.util.List;
+
public class FaceBoundsOverlayView extends View {
- private RectF[] bounds = null;
- private Paint paint = null;
+ private List<Pair<RectF, String>> bounds = null;
+ private Paint paint, textPaint;
private Matrix transform = null;
private int extraw, extrah, viewraww, viewrawh, sensorWidth, sensorHeight;
@@ -41,8 +44,10 @@
super.onDraw(canvas);
if (bounds == null || transform == null || paint == null)
return; // am I ready yet?
- for (RectF bound : bounds) {
- canvas.drawRect(bound, paint);
+ for (Pair<RectF, String> bound : bounds) {
+ canvas.drawRect(bound.first, paint);
+ if (bound.second != null)
+ canvas.drawText(bound.second, bound.first.left, bound.first.bottom, textPaint);
}
}
@@ -55,7 +60,7 @@
}
// please give me RectF's that wont be used otherwise as I modify them
- public void updateBounds(RectF[] inputBounds, int sensorWidth, int sensorHeight) {
+ public void updateBounds(List<Pair<RectF, String>> inputBounds, int sensorWidth, int sensorHeight) {
this.bounds = inputBounds;
// if we have no paint yet, make one
if (paint == null) {
@@ -64,6 +69,11 @@
paint.setStrokeWidth(10f);
paint.setColor(Color.RED);
}
+ if (textPaint == null) {
+ textPaint = new Paint();
+ textPaint.setColor(Color.RED);
+ textPaint.setTextSize(100);
+ }
// if camera size or view size changed, recalculate it
if (this.sensorWidth != sensorWidth || this.sensorHeight != sensorHeight || (viewraww + viewrawh) > 0) {
this.sensorWidth = sensorWidth;
@@ -88,9 +98,9 @@
viewraww = 0; viewrawh = 0;
}
// map bounds to view size
- for (RectF bound : bounds) {
- transform.mapRect(bound);
- bound.offset(extraw, extrah);
+ for (Pair<RectF, String> bound : bounds) {
+ transform.mapRect(bound.first);
+ bound.first.offset(extraw, extrah);
}
invalidate();
}
diff --git a/app/src/main/java/com/libremobileos/facedetect/MainActivity.java b/app/src/main/java/com/libremobileos/facedetect/MainActivity.java
index ba6fb38..0b2a8bf 100644
--- a/app/src/main/java/com/libremobileos/facedetect/MainActivity.java
+++ b/app/src/main/java/com/libremobileos/facedetect/MainActivity.java
@@ -4,15 +4,19 @@
import android.graphics.Matrix;
import android.graphics.RectF;
import android.os.Bundle;
-import android.util.Log;
import android.util.Pair;
import android.util.Size;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
+import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
-import androidx.camera.core.Camera;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ExperimentalGetImage;
import androidx.camera.core.ImageAnalysis;
@@ -27,7 +31,9 @@
import com.libremobileos.yifan.face.shared.FaceDetector;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.ExecutionException;
public class MainActivity extends AppCompatActivity {
@@ -39,6 +45,8 @@
private final Size desiredInputSize = new Size(640, 480);
private final int selectedCamera = CameraSelector.LENS_FACING_FRONT;
private int previewWidth, previewHeight;
+ private HashMap<String, float[][]> knownFaces = new HashMap<>();
+ private boolean addPending = false;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -47,15 +55,8 @@
previewView = findViewById(R.id.viewFinder);
previewView.setScaleType(PreviewView.ScaleType.FIT_CENTER);
overlayView = findViewById(R.id.overlay);
-
- /* cameras are landscape */
- if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
- previewWidth = desiredInputSize.getHeight();
- previewHeight = desiredInputSize.getWidth();
- } else {
- previewWidth = desiredInputSize.getWidth();
- previewHeight = desiredInputSize.getHeight();
- }
+ overlayView.setOnClickListener(v -> addPending = true);
+ setTitle("Tap anywhere to add face");
cameraProviderFuture = ProcessCameraProvider.getInstance(this);
cameraProviderFuture.addListener(() -> {
@@ -81,6 +82,15 @@
preview.setSurfaceProvider(previewView.getSurfaceProvider());
+ /* cameras are landscape */
+ if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
+ previewWidth = desiredInputSize.getHeight();
+ previewHeight = desiredInputSize.getWidth();
+ } else {
+ previewWidth = desiredInputSize.getWidth();
+ previewHeight = desiredInputSize.getHeight();
+ }
+
ImageAnalysis imageAnalysis =
new ImageAnalysis.Builder()
.setTargetResolution(new Size(previewWidth, previewHeight))
@@ -89,7 +99,7 @@
imageAnalysis.setAnalyzer(getMainExecutor(), imageProxy -> {
Pair<List<Pair<FaceDetector.Face, FaceScanner.Face>>, Long> data = faceFinder.process(BitmapUtils.getBitmap(imageProxy));
- ArrayList<RectF> bounds = new ArrayList<>();
+ ArrayList<Pair<RectF, String>> bounds = new ArrayList<>();
for (Pair<FaceDetector.Face, FaceScanner.Face> faceFacePair : data.first) {
RectF boundingBox = new RectF(faceFacePair.first.getLocation());
if (selectedCamera == CameraSelector.LENS_FACING_FRONT) {
@@ -99,15 +109,53 @@
flip.postScale(-1, 1, previewWidth / 2.0f, previewHeight / 2.0f);
flip.mapRect(boundingBox);
}
- bounds.add(boundingBox);
+ FaceScanner.Face detected = null;
+ for (Map.Entry<String, float[][]> possible : knownFaces.entrySet()) {
+ float newdistance = faceFacePair.second.compare(possible.getValue());
+ if (newdistance <= FaceScanner.MAXIMUM_DISTANCE_TF_OD_API && (detected == null || newdistance < detected.getDistance())) {
+ detected = new FaceScanner.Face(faceFacePair.first.getId(), possible.getKey(), newdistance, faceFacePair.first.getLocation(), faceFacePair.second.getCrop(), possible.getValue());
+ }
+ }
+ bounds.add(new Pair<>(boundingBox, detected == null ? faceFacePair.first.getTitle() + " " + faceFacePair.first.getConfidence() : detected.getTitle() + " " + detected.getDistance()));
+ if (addPending) {
+ runOnUiThread(() ->
+ showAddFaceDialog(faceFacePair.second));
+ addPending = false;
+ }
}
- overlayView.updateBounds(bounds.toArray(new RectF[0]), previewWidth, previewHeight);
+ overlayView.updateBounds(bounds, previewWidth, previewHeight);
imageProxy.close();
});
- Camera camera = cameraProvider.bindToLifecycle((LifecycleOwner)this, cameraSelector, imageAnalysis, preview);
+ /* Camera camera = */ cameraProvider.bindToLifecycle((LifecycleOwner)this, cameraSelector, imageAnalysis, preview);
faceFinder = FaceFinder.create(this, previewWidth, previewHeight, 0);
}
+ private void showAddFaceDialog(FaceScanner.Face rec) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ LayoutInflater inflater = getLayoutInflater();
+ View dialogLayout = inflater.inflate(R.layout.image_edit_dialog, null);
+ ImageView ivFace = dialogLayout.findViewById(R.id.dlg_image);
+ TextView tvTitle = dialogLayout.findViewById(R.id.dlg_title);
+ EditText etName = dialogLayout.findViewById(R.id.dlg_input);
+
+ tvTitle.setText("Add Face");
+ ivFace.setImageBitmap(rec.getCrop());
+ etName.setHint("Input name");
+
+ builder.setPositiveButton("OK", (dlg, i) -> {
+ String name = etName.getText().toString();
+ if (name.isEmpty()) {
+ return;
+ }
+ //detector.register(name, rec);
+ knownFaces.put(name, rec.getExtra());
+ dlg.dismiss();
+ });
+ builder.setView(dialogLayout);
+ builder.show();
+
+ }
+
}
diff --git a/app/src/main/res/layout/image_edit_dialog.xml b/app/src/main/res/layout/image_edit_dialog.xml
new file mode 100644
index 0000000..60e46ca
--- /dev/null
+++ b/app/src/main/res/layout/image_edit_dialog.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:orientation="vertical"
+ android:padding="16dp">
+
+ <TextView
+ android:id="@+id/dlg_title"
+ android:layout_gravity="center"
+ android:textSize="20sp"
+ tools:text="The dialog title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <ImageView
+ android:layout_gravity="center"
+ android:id="@+id/dlg_image"
+ android:layout_width="200dp"
+ android:layout_height="200dp"
+ android:scaleType="centerCrop"
+ android:adjustViewBounds="true"
+ />
+
+ <EditText
+ android:layout_gravity="center"
+ android:id="@+id/dlg_input"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ tools:hint="The dialog hint"
+ />
+
+
+</LinearLayout>
\ No newline at end of file