Cleanup & bug fixes
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/AutoFitTextureView.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/AutoFitTextureView.java
index 39ae130..39bd645 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/AutoFitTextureView.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/AutoFitTextureView.java
@@ -1,5 +1,6 @@
/*
* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
+ * Copyright 2023 LibreMobileOS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/DirectoryFaceStorageBackend.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/DirectoryFaceStorageBackend.java
index 5a3d8d6..e5f3f4a 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/DirectoryFaceStorageBackend.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/DirectoryFaceStorageBackend.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2023 LibreMobileOS
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.libremobileos.yifan.face;
import android.util.Log;
@@ -15,6 +31,9 @@
import java.util.Objects;
import java.util.Set;
+/**
+ * {@link FaceStorageBackend} to store data in a directory. Directory must not contain files other than these created by this class!
+ */
public class DirectoryFaceStorageBackend extends FaceStorageBackend {
private final File dir;
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceDataEncoder.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceDataEncoder.java
index 3bda40f..851d7bc 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceDataEncoder.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceDataEncoder.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2023 LibreMobileOS
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.libremobileos.yifan.face;
import java.nio.ByteBuffer;
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceDetector.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceDetector.java
index 8510f09..570c957 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceDetector.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceDetector.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright 2023 LibreMobileOS
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -93,7 +93,16 @@
ImageUtils.getTransformationMatrix(
inputWidth, inputHeight,
TF_FD_API_INPUT_SIZE, TF_FD_API_INPUT_SIZE,
- sensorOrientation, MAINTAIN_ASPECT);
+ 0, MAINTAIN_ASPECT);
+ if (sensorOrientation != 0) {
+ Matrix myRotationMatrix =
+ ImageUtils.getTransformationMatrix(
+ inputWidth, inputHeight,
+ sensorOrientation % 180 != 0 ? inputHeight : inputWidth,
+ sensorOrientation % 180 != 0 ? inputWidth : inputHeight,
+ sensorOrientation % 360, false);
+ frameToCropTransform.setConcat(frameToCropTransform, myRotationMatrix);
+ }
frameToCropTransform.invert(cropToFrameTransform);
}
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceFinder.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceFinder.java
index ee6de41..6b2ba04 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceFinder.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceFinder.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright 2023 LibreMobileOS
*
* Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceRecognizer.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceRecognizer.java
index 8206678..8fb0ae9 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceRecognizer.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceRecognizer.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright 2023 LibreMobileOS
*
* Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceScanner.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceScanner.java
index 0483c88..66e31f7 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceScanner.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceScanner.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright 2023 LibreMobileOS
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -89,7 +89,6 @@
* @see InputImage
*/
public static class InputImageProcessor {
- private final int sensorOrientation;
private final Bitmap portraitBmp;
private final Matrix transform;
@@ -99,7 +98,6 @@
* @param sensorOrientation rotation if the image should be rotated, or 0.
*/
public InputImageProcessor(Bitmap rawImage, int sensorOrientation) {
- this.sensorOrientation = sensorOrientation;
Bitmap portraitBmp = Bitmap.createBitmap(
(sensorOrientation % 180) == 90 ? rawImage.getHeight() : rawImage.getWidth(),
(sensorOrientation % 180) == 90 ? rawImage.getWidth() : rawImage.getHeight(), Bitmap.Config.ARGB_8888);
@@ -108,8 +106,17 @@
rawImage.getHeight(),
rawImage.getWidth(),
rawImage.getHeight(),
- sensorOrientation,
+ 0,
MAINTAIN_ASPECT);
+ if (sensorOrientation != 0) {
+ Matrix myRotationMatrix =
+ ImageUtils.getTransformationMatrix(
+ rawImage.getWidth(), rawImage.getHeight(),
+ sensorOrientation % 180 != 0 ? rawImage.getHeight() : rawImage.getWidth(),
+ sensorOrientation % 180 != 0 ? rawImage.getWidth() : rawImage.getHeight(),
+ sensorOrientation % 360, false);
+ transform.setConcat(myRotationMatrix, transform);
+ }
final Canvas cv = new Canvas(portraitBmp);
cv.drawBitmap(rawImage, transform, null);
this.portraitBmp = portraitBmp;
@@ -126,7 +133,16 @@
ImageUtils.getTransformationMatrix(
input.getWidth(), input.getHeight(),
TF_OD_API_INPUT_SIZE, TF_OD_API_INPUT_SIZE,
- sensorOrientation, MAINTAIN_ASPECT);
+ 0, MAINTAIN_ASPECT);
+ if (sensorOrientation != 0) {
+ Matrix myRotationMatrix =
+ ImageUtils.getTransformationMatrix(
+ input.getWidth(), input.getHeight(),
+ sensorOrientation % 180 != 0 ? input.getHeight() : input.getWidth(),
+ sensorOrientation % 180 != 0 ? input.getWidth() : input.getHeight(),
+ sensorOrientation % 360, false);
+ frameToCropTransform.setConcat(frameToCropTransform, myRotationMatrix);
+ }
Bitmap croppedBitmap = Bitmap.createBitmap(TF_OD_API_INPUT_SIZE, TF_OD_API_INPUT_SIZE, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(croppedBitmap);
canvas.drawBitmap(input, frameToCropTransform, null);
@@ -141,7 +157,7 @@
* @see #process(Bitmap, int)
*/
public InputImage process(Bitmap input) {
- return process(input, sensorOrientation);
+ return process(input, 0);
}
/**
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceStorageBackend.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceStorageBackend.java
index 4121eec..7551861 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceStorageBackend.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/FaceStorageBackend.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright 2023 LibreMobileOS
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,8 +18,6 @@
import androidx.annotation.Nullable;
-import java.nio.ByteBuffer;
-import java.nio.FloatBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/ImageUtils.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/ImageUtils.java
index eb7e561..526d058 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/ImageUtils.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/ImageUtils.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright 2023 LibreMobileOS
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -89,7 +89,7 @@
private static int YUV2RGB(int y, int u, int v) {
// Adjust and check YUV values
- y = (y - 16) < 0 ? 0 : (y - 16);
+ y = Math.max((y - 16), 0);
u -= 128;
v -= 128;
@@ -104,9 +104,9 @@
int b = (y1192 + 2066 * u);
// Clipping RGB values to be inside boundaries [ 0 , kMaxChannelValue ]
- r = r > kMaxChannelValue ? kMaxChannelValue : (r < 0 ? 0 : r);
- g = g > kMaxChannelValue ? kMaxChannelValue : (g < 0 ? 0 : g);
- b = b > kMaxChannelValue ? kMaxChannelValue : (b < 0 ? 0 : b);
+ r = r > kMaxChannelValue ? kMaxChannelValue : (Math.max(r, 0));
+ g = g > kMaxChannelValue ? kMaxChannelValue : (Math.max(g, 0));
+ b = b > kMaxChannelValue ? kMaxChannelValue : (Math.max(b, 0));
return 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
}
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/SharedPreferencesFaceStorageBackend.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/SharedPreferencesFaceStorageBackend.java
index f55d113..df89724 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/SharedPreferencesFaceStorageBackend.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/SharedPreferencesFaceStorageBackend.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2023 LibreMobileOS
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.libremobileos.yifan.face;
import android.content.SharedPreferences;
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/SimilarityClassifier.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/SimilarityClassifier.java
index ee1676f..1c07077 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/SimilarityClassifier.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/SimilarityClassifier.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright 2019 The TensorFlow Authors
* Copyright 2023 LibreMobileOS
*
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/TFLiteObjectDetectionAPIModel.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/TFLiteObjectDetectionAPIModel.java
index a24a5b7..dee6d53 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/TFLiteObjectDetectionAPIModel.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/TFLiteObjectDetectionAPIModel.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright 2019 The TensorFlow Authors
* Copyright 2023 LibreMobileOS
*
diff --git a/FaceShared/src/main/java/com/libremobileos/yifan/face/VolatileFaceStorageBackend.java b/FaceShared/src/main/java/com/libremobileos/yifan/face/VolatileFaceStorageBackend.java
index 5cc24ee..502ad2b 100644
--- a/FaceShared/src/main/java/com/libremobileos/yifan/face/VolatileFaceStorageBackend.java
+++ b/FaceShared/src/main/java/com/libremobileos/yifan/face/VolatileFaceStorageBackend.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright 2023 LibreMobileOS
*
* Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ca591be..f5c0732 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,6 +3,10 @@
xmlns:tools="http://schemas.android.com/tools"
android:sharedUserId="android.uid.system">
+ <uses-feature
+ android:name="android.hardware.camera"
+ android:required="true" />
+
<uses-permission android:name="android.permission.CAMERA" />
<application
diff --git a/app/src/main/java/com/libremobileos/facedetect/BitmapUtils.java b/app/src/main/java/com/libremobileos/facedetect/BitmapUtils.java
deleted file mode 100644
index 707ca20..0000000
--- a/app/src/main/java/com/libremobileos/facedetect/BitmapUtils.java
+++ /dev/null
@@ -1,238 +0,0 @@
-/**
- * Copyright 2020 Google LLC. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * File imported without required modifications, only fixing IDE warnings.
- * Source: https://github.com/googlesamples/mlkit/blob/d10c447f8259b59262582c30c1608cdf38f4e4a0/android/vision-quickstart/app/src/main/java/com/google/mlkit/vision/demo/BitmapUtils.java
- */
-
-package com.libremobileos.facedetect;
-
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.ImageFormat;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.YuvImage;
-import android.media.Image;
-import android.media.Image.Plane;
-import android.util.Log;
-import androidx.annotation.Nullable;
-
-import java.io.ByteArrayOutputStream;
-import java.nio.ByteBuffer;
-import java.util.Objects;
-
-/** Utils functions for bitmap conversions. */
-public class BitmapUtils {
- /** Describing a frame info. */
- public static class FrameMetadata {
-
- private final int width;
- private final int height;
- private final int rotation;
-
- public int getWidth() {
- return width;
- }
-
- public int getHeight() {
- return height;
- }
-
- public int getRotation() {
- return rotation;
- }
-
- private FrameMetadata(int width, int height, int rotation) {
- this.width = width;
- this.height = height;
- this.rotation = rotation;
- }
-
- /** Builder of {@link FrameMetadata}. */
- public static class Builder {
-
- private int width;
- private int height;
- private int rotation;
-
- public Builder setWidth(int width) {
- this.width = width;
- return this;
- }
-
- public Builder setHeight(int height) {
- this.height = height;
- return this;
- }
-
- public Builder setRotation(int rotation) {
- this.rotation = rotation;
- return this;
- }
-
- public FrameMetadata build() {
- return new FrameMetadata(width, height, rotation);
- }
- }
- }
-
- /** Converts NV21 format byte buffer to bitmap. */
- @Nullable
- public static Bitmap getBitmap(ByteBuffer data, FrameMetadata metadata) {
- data.rewind();
- byte[] imageInBuffer = new byte[data.limit()];
- data.get(imageInBuffer, 0, imageInBuffer.length);
- try {
- YuvImage image =
- new YuvImage(
- imageInBuffer, ImageFormat.NV21, metadata.getWidth(), metadata.getHeight(), null);
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- image.compressToJpeg(new Rect(0, 0, metadata.getWidth(), metadata.getHeight()), 80, stream);
-
- Bitmap bmp = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());
-
- stream.close();
- return rotateBitmap(bmp, metadata.getRotation());
- } catch (Exception e) {
- Log.e("VisionProcessorBase", "Error: " + e.getMessage());
- }
- return null;
- }
-
- /** Rotates a bitmap if it is converted from a bytebuffer. */
- private static Bitmap rotateBitmap(
- Bitmap bitmap, int rotationDegrees) {
- Matrix matrix = new Matrix();
-
- // Rotate the image back to straight.
- matrix.postRotate(rotationDegrees);
-
- Bitmap rotatedBitmap =
- Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
-
- // Recycle the old bitmap if it has changed.
- if (rotatedBitmap != bitmap) {
- bitmap.recycle();
- }
- return rotatedBitmap;
- }
-
- /**
- * Converts YUV_420_888 to NV21 bytebuffer.
- *
- * <p>The NV21 format consists of a single byte array containing the Y, U and V values. For an
- * image of size S, the first S positions of the array contain all the Y values. The remaining
- * positions contain interleaved V and U values. U and V are subsampled by a factor of 2 in both
- * dimensions, so there are S/4 U values and S/4 V values. In summary, the NV21 array will contain
- * S Y values followed by S/4 VU values: YYYYYYYYYYYYYY(...)YVUVUVUVU(...)VU
- *
- * <p>YUV_420_888 is a generic format that can describe any YUV image where U and V are subsampled
- * by a factor of 2 in both dimensions. {@link Image#getPlanes} returns an array with the Y, U and
- * V planes. The Y plane is guaranteed not to be interleaved, so we can just copy its values into
- * the first part of the NV21 array. The U and V planes may already have the representation in the
- * NV21 format. This happens if the planes share the same buffer, the V buffer is one position
- * before the U buffer and the planes have a pixelStride of 2. If this is case, we can just copy
- * them to the NV21 array.
- */
- private static ByteBuffer yuv420ThreePlanesToNV21(
- Plane[] yuv420888planes, int width, int height) {
- int imageSize = width * height;
- byte[] out = new byte[imageSize + 2 * (imageSize / 4)];
-
- if (areUVPlanesNV21(yuv420888planes, width, height)) {
- // Copy the Y values.
- yuv420888planes[0].getBuffer().get(out, 0, imageSize);
-
- ByteBuffer uBuffer = yuv420888planes[1].getBuffer();
- ByteBuffer vBuffer = yuv420888planes[2].getBuffer();
- // Get the first V value from the V buffer, since the U buffer does not contain it.
- vBuffer.get(out, imageSize, 1);
- // Copy the first U value and the remaining VU values from the U buffer.
- uBuffer.get(out, imageSize + 1, 2 * imageSize / 4 - 1);
- } else {
- // Fallback to copying the UV values one by one, which is slower but also works.
- // Unpack Y.
- unpackPlane(yuv420888planes[0], width, height, out, 0, 1);
- // Unpack U.
- unpackPlane(yuv420888planes[1], width, height, out, imageSize + 1, 2);
- // Unpack V.
- unpackPlane(yuv420888planes[2], width, height, out, imageSize, 2);
- }
-
- return ByteBuffer.wrap(out);
- }
-
- /** Checks if the UV plane buffers of a YUV_420_888 image are in the NV21 format. */
- private static boolean areUVPlanesNV21(Plane[] planes, int width, int height) {
- int imageSize = width * height;
-
- ByteBuffer uBuffer = planes[1].getBuffer();
- ByteBuffer vBuffer = planes[2].getBuffer();
-
- // Backup buffer properties.
- int vBufferPosition = vBuffer.position();
- int uBufferLimit = uBuffer.limit();
-
- // Advance the V buffer by 1 byte, since the U buffer will not contain the first V value.
- vBuffer.position(vBufferPosition + 1);
- // Chop off the last byte of the U buffer, since the V buffer will not contain the last U value.
- uBuffer.limit(uBufferLimit - 1);
-
- // Check that the buffers are equal and have the expected number of elements.
- boolean areNV21 =
- (vBuffer.remaining() == (2 * imageSize / 4 - 2)) && (vBuffer.compareTo(uBuffer) == 0);
-
- // Restore buffers to their initial state.
- vBuffer.position(vBufferPosition);
- uBuffer.limit(uBufferLimit);
-
- return areNV21;
- }
-
- /**
- * Unpack an image plane into a byte array.
- *
- * <p>The input plane data will be copied in 'out', starting at 'offset' and every pixel will be
- * spaced by 'pixelStride'. Note that there is no row padding on the output.
- */
- private static void unpackPlane(
- Plane plane, int width, int height, byte[] out, int offset, int pixelStride) {
- ByteBuffer buffer = plane.getBuffer();
- buffer.rewind();
-
- // Compute the size of the current plane.
- // We assume that it has the aspect ratio as the original image.
- int numRow = (buffer.limit() + plane.getRowStride() - 1) / plane.getRowStride();
- if (numRow == 0) {
- return;
- }
- int scaleFactor = height / numRow;
- int numCol = width / scaleFactor;
-
- // Extract the data in the output buffer.
- int outputPos = offset;
- int rowStart = 0;
- for (int row = 0; row < numRow; row++) {
- int inputPos = rowStart;
- for (int col = 0; col < numCol; col++) {
- out[outputPos] = buffer.get(inputPos);
- outputPos += pixelStride;
- inputPos += plane.getPixelStride();
- }
- rowStart += plane.getRowStride();
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/libremobileos/facedetect/CameraActivity.java b/app/src/main/java/com/libremobileos/facedetect/CameraActivity.java
index c208b62..8d12a61 100644
--- a/app/src/main/java/com/libremobileos/facedetect/CameraActivity.java
+++ b/app/src/main/java/com/libremobileos/facedetect/CameraActivity.java
@@ -5,7 +5,6 @@
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.RectF;
@@ -54,32 +53,25 @@
*/
private static final int MINIMUM_PREVIEW_SIZE = 320;
- protected AutoFitTextureView previewView;
+ private AutoFitTextureView previewView;
private Handler mBackgroundHandler;
private HandlerThread mBackgroundThread;
- private String cameraId;
- protected CameraDevice cameraDevice;
- protected CameraCaptureSession cameraCaptureSessions;
- protected CaptureRequest captureRequest;
- protected CaptureRequest.Builder captureRequestBuilder;
+ private CameraDevice cameraDevice;
+ private CameraCaptureSession cameraCaptureSessions;
+ private CaptureRequest captureRequest;
+ private CaptureRequest.Builder captureRequestBuilder;
private ImageReader previewReader;
- private byte[][] yuvBytes = new byte[3][];
+ private final byte[][] yuvBytes = new byte[3][];
private int[] rgbBytes = null;
private boolean isProcessingFrame = false;
private int yRowStride;
private Runnable postInferenceCallback;
private Runnable imageConverter;
- private Integer sensorOrientation;
private Bitmap rgbFrameBitmap = null;
- private Bitmap croppedBitmap = null;
- private Bitmap cropCopyBitmap = null;
- private Matrix frameToCropTransform;
- private Matrix cropToFrameTransform;
- private static final boolean MAINTAIN_ASPECT = false;
-
+ private int imageOrientation;
+ private Size previewSize;
protected final Size desiredInputSize = new Size(640, 480);
- private Size previewSize;
// The calculated actual processing width & height
protected int width, height;
@@ -94,7 +86,7 @@
previewView.setSurfaceTextureListener(textureListener);
}
- TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
+ private final TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
//open your camera here
@@ -137,13 +129,13 @@
}
};
- protected void startBackgroundThread() {
+ private void startBackgroundThread() {
mBackgroundThread = new HandlerThread("Camera Background");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
- protected void stopBackgroundThread() {
+ private void stopBackgroundThread() {
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
@@ -154,7 +146,7 @@
}
}
- protected void createCameraPreview() {
+ private void createCameraPreview() {
try {
SurfaceTexture texture = previewView.getSurfaceTexture();
assert texture != null;
@@ -242,7 +234,7 @@
}
/** Compares two {@code Size}s based on their areas. */
- static class CompareSizesByArea implements Comparator<Size> {
+ private static class CompareSizesByArea implements Comparator<Size> {
@Override
public int compare(final Size lhs, final Size rhs) {
// We cast here to ensure the multiplications won't overflow
@@ -260,14 +252,14 @@
* @param height The minimum desired height
* @return The optimal {@code Size}, or an arbitrary one if none were big enough
*/
- protected static Size chooseOptimalSize(final Size[] choices, final int width, final int height) {
+ private static Size chooseOptimalSize(final Size[] choices, final int width, final int height) {
final int minSize = Math.max(Math.min(width, height), MINIMUM_PREVIEW_SIZE);
final Size desiredSize = new Size(width, height);
// Collect the supported resolutions that are at least as big as the preview Surface
boolean exactSizeFound = false;
- final List<Size> bigEnough = new ArrayList<Size>();
- final List<Size> tooSmall = new ArrayList<Size>();
+ final List<Size> bigEnough = new ArrayList<>();
+ final List<Size> tooSmall = new ArrayList<>();
for (final Size option : choices) {
if (option.equals(desiredSize)) {
// Set the size but don't return yet so that remaining sizes will still be logged.
@@ -305,7 +297,7 @@
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
Log.e(TAG, "is camera open");
try {
- cameraId = manager.getCameraIdList()[0];
+ String cameraId = manager.getCameraIdList()[0];
for (String id : manager.getCameraIdList()) {
CameraCharacteristics characteristics = manager.getCameraCharacteristics(id);
if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) {
@@ -315,7 +307,7 @@
}
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
- sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
+ Integer sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
assert map != null;
@@ -337,31 +329,10 @@
previewView.setAspectRatio(previewSize.getHeight(), previewSize.getWidth());
}
- int imageOrientation = sensorOrientation + getScreenOrientation();
+ imageOrientation = sensorOrientation + getScreenOrientation();
rgbFrameBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- int targetW, targetH;
- if (imageOrientation == 90 || imageOrientation == 270) {
- targetH = width;
- targetW = height;
- }
- else {
- targetW = width;
- targetH = height;
- }
- int cropW = (int) (targetW / 2.0);
- int cropH = (int) (targetH / 2.0);
- croppedBitmap = Bitmap.createBitmap(cropW, cropH, Bitmap.Config.ARGB_8888);
-
- frameToCropTransform =
- ImageUtils.getTransformationMatrix(
- width, height,
- cropW, cropH,
- imageOrientation, MAINTAIN_ASPECT);
- cropToFrameTransform = new Matrix();
- frameToCropTransform.invert(cropToFrameTransform);
-
- setupFaceRecognizer(new Size(cropW, cropH));
+ setupFaceRecognizer(new Size(width, height), imageOrientation);
// Add permission for camera and let user grant the permission
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
@@ -375,6 +346,10 @@
Log.e(TAG, "openCamera X");
}
+ protected int getImageRotation() {
+ return imageOrientation;
+ }
+
private void closeCamera() {
if (null != cameraDevice) {
cameraDevice.close();
@@ -401,7 +376,7 @@
super.onPause();
}
- protected void fillBytes(final Image.Plane[] planes, final byte[][] yuvBytes) {
+ private void fillBytes(final Image.Plane[] planes, final byte[][] yuvBytes) {
// Because of the variable row stride it's not possible to know in
// advance the actual necessary dimensions of the yuv planes.
for (int i = 0; i < planes.length; ++i) {
@@ -414,16 +389,16 @@
}
}
- protected int[] getRgbBytes() {
+ private int[] getRgbBytes() {
imageConverter.run();
return rgbBytes;
}
- protected Bitmap getCroppedBitmap() {
- return croppedBitmap;
+ protected Bitmap getBitmap() {
+ return rgbFrameBitmap;
}
- protected int getScreenOrientation() {
+ private int getScreenOrientation() {
switch (getWindowManager().getDefaultDisplay().getRotation()) {
case Surface.ROTATION_270:
return 270;
@@ -470,36 +445,26 @@
final int uvPixelStride = planes[1].getPixelStride();
imageConverter =
- new Runnable() {
- @Override
- public void run() {
- ImageUtils.convertYUV420ToARGB8888(
- yuvBytes[0],
- yuvBytes[1],
- yuvBytes[2],
- previewWidth,
- previewHeight,
- yRowStride,
- uvRowStride,
- uvPixelStride,
- rgbBytes);
- }
- };
+ () -> ImageUtils.convertYUV420ToARGB8888(
+ yuvBytes[0],
+ yuvBytes[1],
+ yuvBytes[2],
+ previewWidth,
+ previewHeight,
+ yRowStride,
+ uvRowStride,
+ uvPixelStride,
+ rgbBytes);
postInferenceCallback =
- new Runnable() {
- @Override
- public void run() {
- image.close();
- isProcessingFrame = false;
- }
+ () -> {
+ image.close();
+ isProcessingFrame = false;
};
rgbFrameBitmap.setPixels(getRgbBytes(), 0, previewWidth, 0, 0, previewWidth, previewHeight);
- final Canvas canvas = new Canvas(croppedBitmap);
- canvas.drawBitmap(rgbFrameBitmap, frameToCropTransform, null);
- runOnUiThread(() -> processImage());
+ runOnUiThread(this::processImage);
} catch (final Exception e) {
Log.e(TAG, "Exception!", e);
Trace.endSection();
@@ -508,7 +473,7 @@
Trace.endSection();
}
- protected abstract void setupFaceRecognizer(final Size bitmapSize);
+ protected abstract void setupFaceRecognizer(final Size bitmapSize, final int imageRotation);
protected abstract void processImage();
diff --git a/app/src/main/java/com/libremobileos/facedetect/CircleOverlayView.java b/app/src/main/java/com/libremobileos/facedetect/CircleOverlayView.java
index 6ee7a23..f46bfad 100644
--- a/app/src/main/java/com/libremobileos/facedetect/CircleOverlayView.java
+++ b/app/src/main/java/com/libremobileos/facedetect/CircleOverlayView.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright 2023 LibreMobileOS
*
* Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/app/src/main/java/com/libremobileos/facedetect/FaceBoundsOverlayView.java b/app/src/main/java/com/libremobileos/facedetect/FaceBoundsOverlayView.java
index ba62ab6..db28189 100644
--- a/app/src/main/java/com/libremobileos/facedetect/FaceBoundsOverlayView.java
+++ b/app/src/main/java/com/libremobileos/facedetect/FaceBoundsOverlayView.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright 2023 LibreMobileOS
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -103,10 +103,10 @@
int newWidth = (int)((oldHeight / (float)sensorHeight) * sensorWidth);
// calculate out black bars
if (newWidth > oldWidth) {
- extraHeight = (oldHeight - newHeight) / 2;
+ //extraHeight = (oldHeight - newHeight) / 2;
viewHeight = newHeight;
} else {
- extraWidth = (oldWidth - newWidth) / 2;
+ //extraWidth = (oldWidth - newWidth) / 2;
viewWidth = newWidth;
}
// scale from image size to view size
@@ -115,8 +115,10 @@
}
// map bounds to view size
for (Pair<RectF, String> bound : bounds) {
+ android.util.Log.i("got0", String.valueOf(bound.first));
transform.mapRect(bound.first);
bound.first.offset(extraWidth, extraHeight);
+ android.util.Log.i("got", String.valueOf(bound.first));
}
invalidate();
}
diff --git a/app/src/main/java/com/libremobileos/facedetect/MainActivity.java b/app/src/main/java/com/libremobileos/facedetect/MainActivity.java
index cc532b9..fdaaa12 100644
--- a/app/src/main/java/com/libremobileos/facedetect/MainActivity.java
+++ b/app/src/main/java/com/libremobileos/facedetect/MainActivity.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright 2023 LibreMobileOS
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -54,10 +54,10 @@
}
@Override
- protected void setupFaceRecognizer(final Size bitmapSize) {
- // Store registered Faces in Memory
- //FaceStorageBackend faceStorage = new VolatileFaceStorageBackend();
- //FaceStorageBackend faceStorage = new SharedPreferencesFaceStorageBackend(getSharedPreferences("faces", 0));
+ protected void setupFaceRecognizer(final Size bitmapSize, final int imageRotation) {
+ // Store registered Faces
+ // example for in-memory: FaceStorageBackend faceStorage = new VolatileFaceStorageBackend();
+ // example for shared preferences: FaceStorageBackend faceStorage = new SharedPreferencesFaceStorageBackend(getSharedPreferences("faces", 0));
FaceStorageBackend faceStorage = new DirectoryFaceStorageBackend(getFilesDir());
// Create AI-based face detection
@@ -66,7 +66,7 @@
0.6f, /* minimum confidence to consider object as face */
bitmapSize.getWidth(), /* bitmap width */
bitmapSize.getHeight(), /* bitmap height */
- 0, /* We rotates the image, so IGNORE sensorRotation altogether */
+ imageRotation,
0.7f, /* maximum distance (to saved face model, not from camera) to track face */
1 /* minimum model count to track face */
);
@@ -80,7 +80,7 @@
return;
}
computingDetection = true;
- List<FaceRecognizer.Face> data = faceRecognizer.recognize(getCroppedBitmap());
+ List<FaceRecognizer.Face> data = faceRecognizer.recognize(getBitmap());
computingDetection = false;
ArrayList<Pair<RectF, String>> bounds = new ArrayList<>();
@@ -91,7 +91,9 @@
// Camera is frontal so the image is flipped horizontally,
// so flip it again.
Matrix flip = new Matrix();
- flip.postScale(-1, 1, width / 2.0f, height / 2.0f);
+ flip.postRotate((360 - getImageRotation()) % 360); // Preview is rotated 360 - cameraRotation degrees
+ flip.postScale(-1, 1, width, height);
+ android.util.Log.i("got00", String.valueOf(boundingBox) + " "+ width + " "+height);
flip.mapRect(boundingBox);
// Generate UI text for face
@@ -104,7 +106,7 @@
// Show detected object type (always "Face") and how confident the AI is that this is a Face
uiText = face.getTitle() + " " + face.getDetectionConfidence();
}
- bounds.add(new Pair<>(boundingBox, uiText));
+ bounds.add(new Pair<>(new RectF(0, 0, width, height), uiText));
}
// Pass bounds to View drawing rectangles
diff --git a/app/src/main/java/com/libremobileos/facedetect/ScanActivity.java b/app/src/main/java/com/libremobileos/facedetect/ScanActivity.java
index cf5811b..6d58e0d 100644
--- a/app/src/main/java/com/libremobileos/facedetect/ScanActivity.java
+++ b/app/src/main/java/com/libremobileos/facedetect/ScanActivity.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright 2023 LibreMobileOS
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -66,13 +66,13 @@
}
@Override
- protected void setupFaceRecognizer(final Size bitmapSize) {
+ protected void setupFaceRecognizer(final Size bitmapSize, final int imageRotation) {
// Create AI-based face detection
faceRecognizer = FaceFinder.create(this,
0.6f, /* minimum confidence to consider object as face */
bitmapSize.getWidth(), /* bitmap width */
bitmapSize.getHeight(), /* bitmap height */
- 0 /* We rotates the image, so IGNORE sensorRotation altogether */
+ imageRotation
);
}
@@ -91,7 +91,7 @@
}
// Return list of detected faces
- List<Pair<FaceDetector.Face, FaceScanner.Face>> data = faceRecognizer.process(getCroppedBitmap(), false);
+ List<Pair<FaceDetector.Face, FaceScanner.Face>> data = faceRecognizer.process(getBitmap(), false);
computingDetection = false;
if (data.size() > 1) {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0b1191c..7081895 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -9,7 +9,7 @@
<string name="cant_find_face">Unable to find any face</string>
<string name="cant_scan_face">Can\'t properly scan your face, try to look into the camera directly and make sure your face is well-lit</string>
<string name="scan_face_now">Scan your face now</string>
- <string name="register_failed">Registering your face has failed. We are sorry for the inconvience. Please try again later.</string>
+ <string name="register_failed">Registering your face has failed. We are sorry for the inconvenience. Please try again later.</string>
<string name="welcome_text">**welcome text placeholder uwu**</string>
<string name="finish_msg">**finish text placeholder uwu**</string>
</resources>
\ No newline at end of file