From 5149ed1e3195b4ca1524d73e0afe8f5480385829 Mon Sep 17 00:00:00 2001 From: Kristian Sloth Lauszus Date: Wed, 6 Jun 2018 20:33:31 +0200 Subject: [PATCH] Use CameraKit for camera view instead of buggy OpenCV implementation Can now also toggle the camera by double tapping on the screen See: https://github.com/CameraKit/camerakit-android --- app/build.gradle | 5 +- app/proguard-rules.pro | 2 + .../CameraBridgeViewBase.java | 724 ------------------ .../CameraViewDoubleTap.java | 54 ++ .../FaceRecognitionAppActivity.java | 367 +++++---- .../facerecognitionapp/JavaCameraView.java | 379 --------- .../facerecognitionapp/SeekBarArrows.java | 7 +- app/src/main/res/layout/app_bar_main.xml | 12 +- 8 files changed, 274 insertions(+), 1276 deletions(-) delete mode 100644 app/src/main/java/com/lauszus/facerecognitionapp/CameraBridgeViewBase.java create mode 100644 app/src/main/java/com/lauszus/facerecognitionapp/CameraViewDoubleTap.java delete mode 100644 app/src/main/java/com/lauszus/facerecognitionapp/JavaCameraView.java diff --git a/app/build.gradle b/app/build.gradle index f9e4c30..f8681f7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -70,8 +70,9 @@ android { } dependencies { - implementation 'com.android.support:appcompat-v7:27.1.1' - implementation 'com.android.support:design:27.1.1' + implementation 'com.android.support:appcompat-v7:27.0.2' + implementation 'com.android.support:design:27.0.2' + implementation 'com.wonderkiln:camerakit:0.13.2' implementation project(':opencv') } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index dd56300..b919512 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -16,6 +16,8 @@ # public *; #} +-dontwarn com.google.android.gms.** + -assumenosideeffects class android.util.Log { public static *** v(...); public static *** i(...); diff --git a/app/src/main/java/com/lauszus/facerecognitionapp/CameraBridgeViewBase.java b/app/src/main/java/com/lauszus/facerecognitionapp/CameraBridgeViewBase.java deleted file mode 100644 index db2e4ef..0000000 --- a/app/src/main/java/com/lauszus/facerecognitionapp/CameraBridgeViewBase.java +++ /dev/null @@ -1,724 +0,0 @@ -// Based on: http://stackoverflow.com/questions/14816166/rotate-camera-preview-to-portrait-android-opencv-camera/38210157#38210157 - -package com.lauszus.facerecognitionapp; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.pm.ActivityInfo; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.os.Build; -import android.os.Environment; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.Surface; -import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.WindowManager; - -import org.opencv.R; -import org.opencv.android.FpsMeter; -import org.opencv.android.Utils; -import org.opencv.core.Mat; -import org.opencv.core.Size; - -import java.io.File; -import java.util.List; - -/** - * This is a basic class, implementing the interaction with Camera and OpenCV library. - * The main responsibility of it - is to control when camera can be enabled, process the frame, - * call external listener to make any adjustments to the frame and then draw the resulting - * frame to the screen. - * The clients shall implement CvCameraViewListener. - */ -@SuppressWarnings({"unused", "UnnecessaryInterfaceModifier"}) -public abstract class CameraBridgeViewBase extends SurfaceView implements SurfaceHolder.Callback { - - private static final String TAG = "CameraBridge"; - private static final int MAX_UNSPECIFIED = -1; - private static final int STOPPED = 0; - private static final int STARTED = 1; - - private int mState = STOPPED; - private Bitmap mCacheBitmap; - private CvCameraViewListener2 mListener; - private boolean mSurfaceExist; - private final Object mSyncObject = new Object(); - - protected int mFrameWidth; - protected int mFrameHeight; - protected int mMaxHeight; - protected int mMaxWidth; - protected float mScale = 0; - protected int mPreviewFormat = RGBA; - protected int mCameraIndex; - protected boolean mEnabled; - protected FpsMeter mFpsMeter = null; - - public static final int CAMERA_ID_ANY = -1; - public static final int CAMERA_ID_BACK = 99; - public static final int CAMERA_ID_FRONT = 98; - public static final int RGBA = 1; - public static final int GRAY = 2; - - private WindowManager mWindowManager; - - public CameraBridgeViewBase(Context context, int cameraId) { - super(context); - mCameraIndex = cameraId; - getHolder().addCallback(this); - mMaxWidth = MAX_UNSPECIFIED; - mMaxHeight = MAX_UNSPECIFIED; - } - - public CameraBridgeViewBase(Context context, AttributeSet attrs) { - super(context, attrs); - - int count = attrs.getAttributeCount(); - Log.d(TAG, "Attr count: " + count); - - TypedArray styledAttrs = getContext().obtainStyledAttributes(attrs, R.styleable.CameraBridgeViewBase); - if (styledAttrs.getBoolean(R.styleable.CameraBridgeViewBase_show_fps, false)) - enableFpsMeter(); - - mCameraIndex = styledAttrs.getInt(R.styleable.CameraBridgeViewBase_camera_id, CAMERA_ID_ANY); - - getHolder().addCallback(this); - mMaxWidth = MAX_UNSPECIFIED; - mMaxHeight = MAX_UNSPECIFIED; - styledAttrs.recycle(); - - mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - } - - public void flipCamera() { - disableView(); - if (this.mCameraIndex == CAMERA_ID_FRONT) - setCameraIndex(CAMERA_ID_BACK); - else - setCameraIndex(CAMERA_ID_FRONT); - enableView(); - } - - /** - * Sets the camera index - * @param cameraIndex new camera index - */ - public void setCameraIndex(int cameraIndex) { - this.mCameraIndex = cameraIndex; - } - - public interface CvCameraViewListener { - /** - * This method is invoked when camera preview has started. After this method is invoked - * the frames will start to be delivered to client via the onCameraFrame() callback. - * @param width - the width of the frames that will be delivered - * @param height - the height of the frames that will be delivered - */ - public void onCameraViewStarted(int width, int height); - - /** - * This method is invoked when camera preview has been stopped for some reason. - * No frames will be delivered via onCameraFrame() callback after this method is called. - */ - public void onCameraViewStopped(); - - /** - * This method is invoked when delivery of the frame needs to be done. - * The returned values - is a modified frame which needs to be displayed on the screen. - * TODO: pass the parameters specifying the format of the frame (BPP, YUV or RGB and etc) - */ - public Mat onCameraFrame(Mat inputFrame); - } - - public interface CvCameraViewListener2 { - /** - * This method is invoked when camera preview has started. After this method is invoked - * the frames will start to be delivered to client via the onCameraFrame() callback. - * @param width - the width of the frames that will be delivered - * @param height - the height of the frames that will be delivered - */ - public void onCameraViewStarted(int width, int height); - - /** - * This method is invoked when camera preview has been stopped for some reason. - * No frames will be delivered via onCameraFrame() callback after this method is called. - */ - public void onCameraViewStopped(); - - /** - * This method is invoked when delivery of the frame needs to be done. - * The returned values - is a modified frame which needs to be displayed on the screen. - * TODO: pass the parameters specifying the format of the frame (BPP, YUV or RGB and etc) - */ - public Mat onCameraFrame(CvCameraViewFrame inputFrame); - } - - @SuppressWarnings("WeakerAccess") - protected class CvCameraViewListenerAdapter implements CvCameraViewListener2 { - public CvCameraViewListenerAdapter(CvCameraViewListener oldStypeListener) { - mOldStyleListener = oldStypeListener; - } - - public void onCameraViewStarted(int width, int height) { - mOldStyleListener.onCameraViewStarted(width, height); - } - - public void onCameraViewStopped() { - mOldStyleListener.onCameraViewStopped(); - } - - public Mat onCameraFrame(CvCameraViewFrame inputFrame) { - Mat result = null; - switch (mPreviewFormat) { - case RGBA: - result = mOldStyleListener.onCameraFrame(inputFrame.rgba()); - break; - case GRAY: - result = mOldStyleListener.onCameraFrame(inputFrame.gray()); - break; - default: - Log.e(TAG, "Invalid frame format! Only RGBA and Gray Scale are supported!"); - } - - return result; - } - - public void setFrameFormat(int format) { - mPreviewFormat = format; - } - - private int mPreviewFormat = RGBA; - private CvCameraViewListener mOldStyleListener; - } - - /** - * This class interface is abstract representation of single frame from camera for onCameraFrame callback - * Attention: Do not use objects, that represents this interface out of onCameraFrame callback! - */ - public interface CvCameraViewFrame { - - /** - * This method returns RGBA Mat with frame - */ - public Mat rgba(); - - /** - * This method returns single channel gray scale Mat with frame - */ - public Mat gray(); - } - - public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) { - Log.d(TAG, "call surfaceChanged event"); - synchronized(mSyncObject) { - if (!mSurfaceExist) { - mSurfaceExist = true; - checkCurrentState(); - } else { - /* Surface changed. We need to stop camera and restart with new parameters */ - /* Pretend that old surface has been destroyed */ - mSurfaceExist = false; - checkCurrentState(); - /* Now use new surface. Say we have it now */ - mSurfaceExist = true; - checkCurrentState(); - } - } - } - - public void surfaceCreated(SurfaceHolder holder) { - /* Do nothing. Wait until surfaceChanged delivered */ - } - - public void surfaceDestroyed(SurfaceHolder holder) { - synchronized(mSyncObject) { - mSurfaceExist = false; - checkCurrentState(); - } - } - - /** - * This method is provided for clients, so they can enable the camera connection. - * The actual onCameraViewStarted callback will be delivered only after both this method is called and surface is available - */ - public void enableView() { - synchronized(mSyncObject) { - mEnabled = true; - checkCurrentState(); - } - } - - /** - * This method is provided for clients, so they can disable camera connection and stop - * the delivery of frames even though the surface view itself is not destroyed and still stays on the scren - */ - public void disableView() { - synchronized(mSyncObject) { - mEnabled = false; - checkCurrentState(); - } - } - - /** - * This method enables label with fps value on the screen - */ - public void enableFpsMeter() { - if (mFpsMeter == null) { - mFpsMeter = new FpsMeter(); - mFpsMeter.setResolution(mFrameWidth, mFrameHeight); - } - } - - public void disableFpsMeter() { - mFpsMeter = null; - } - - /** - * - * @param listener - set the CvCameraViewListener2 - */ - public void setCvCameraViewListener(CvCameraViewListener2 listener) { - mListener = listener; - } - - public void setCvCameraViewListener(CvCameraViewListener listener) { - CvCameraViewListenerAdapter adapter = new CvCameraViewListenerAdapter(listener); - adapter.setFrameFormat(mPreviewFormat); - mListener = adapter; - } - - /** - * This method sets the maximum size that camera frame is allowed to be. When selecting - * size - the biggest size which less or equal the size set will be selected. - * As an example - we set setMaxFrameSize(200,200) and we have 176x152 and 320x240 sizes. The - * preview frame will be selected with 176x152 size. - * This method is useful when need to restrict the size of preview frame for some reason (for example for video recording) - * @param maxWidth - the maximum width allowed for camera frame. - * @param maxHeight - the maximum height allowed for camera frame - */ - public void setMaxFrameSize(int maxWidth, int maxHeight) { - mMaxWidth = maxWidth; - mMaxHeight = maxHeight; - } - - public void SetCaptureFormat(int format) - { - mPreviewFormat = format; - if (mListener instanceof CvCameraViewListenerAdapter) { - CvCameraViewListenerAdapter adapter = (CvCameraViewListenerAdapter) mListener; - adapter.setFrameFormat(mPreviewFormat); - } - } - - /** - * Called when mSyncObject lock is held - */ - private void checkCurrentState() { - Log.d(TAG, "call checkCurrentState"); - int targetState; - - if (mEnabled && mSurfaceExist && getVisibility() == VISIBLE) { - targetState = STARTED; - } else { - targetState = STOPPED; - } - - if (targetState != mState) { - /* The state change detected. Need to exit the current state and enter target state */ - processExitState(mState); - mState = targetState; - processEnterState(mState); - } - } - - private void processEnterState(int state) { - Log.d(TAG, "call processEnterState: " + state); - switch(state) { - case STARTED: - onEnterStartedState(); - if (mListener != null) { - mListener.onCameraViewStarted(mFrameWidth, mFrameHeight); - } - break; - case STOPPED: - onEnterStoppedState(); - if (mListener != null) { - mListener.onCameraViewStopped(); - } - break; - } - } - - private void processExitState(int state) { - Log.d(TAG, "call processExitState: " + state); - switch(state) { - case STARTED: - onExitStartedState(); - break; - case STOPPED: - onExitStoppedState(); - break; - } - } - - private void onEnterStoppedState() { - /* nothing to do */ - } - - private void onExitStoppedState() { - /* nothing to do */ - } - - // NOTE: The order of bitmap constructor and camera connection is important for android 4.1.x - // Bitmap must be constructed before surface - private void onEnterStartedState() { - Log.d(TAG, "call onEnterStartedState"); - /* Connect camera */ - if (!connectCamera(getWidth(), getHeight())) { - AlertDialog ad = new AlertDialog.Builder(getContext()).create(); - ad.setCancelable(false); // This blocks the 'BACK' button - ad.setMessage("It seems that you device does not support camera (or it is locked). Application will be closed."); - ad.setButton(DialogInterface.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - ((Activity) getContext()).finish(); - } - }); - ad.show(); - - } - } - - private void onExitStartedState() { - disconnectCamera(); - if (mCacheBitmap != null) { - mCacheBitmap.recycle(); - } - } - - private float getRatio(int widthSource, int heightSource, int widthTarget, int heightTarget) { - if (widthTarget <= heightTarget) { - return (float) heightTarget / (float) heightSource; - } else { - return (float) widthTarget / (float) widthSource; - } - } - - private static int rating = -1; - - /** - * Used to check if the app is running on an emulator or not. - * See: https://github.com/gingo/android-emulator-detector - * @return True if the app is running on an emulator. - */ - public boolean isEmulator() { - if (BuildConfig.DEBUG) { - int newRating = 0; - if (rating < 0) { - if (Build.PRODUCT.contains("sdk") || - Build.PRODUCT.contains("Andy") || - Build.PRODUCT.contains("ttVM_Hdragon") || - Build.PRODUCT.contains("google_sdk") || - Build.PRODUCT.contains("Droid4X") || - Build.PRODUCT.contains("nox") || - Build.PRODUCT.contains("sdk_x86") || - Build.PRODUCT.contains("sdk_google") || - Build.PRODUCT.contains("vbox86p")) { - Log.d(TAG, "Build.PRODUCT: " + Build.PRODUCT); - newRating++; - } - - if (Build.MANUFACTURER.equals("unknown") || - Build.MANUFACTURER.equals("Genymotion") || - Build.MANUFACTURER.contains("Andy") || - Build.MANUFACTURER.contains("MIT") || - Build.MANUFACTURER.contains("nox") || - Build.MANUFACTURER.contains("TiantianVM")){ - newRating++; - Log.d(TAG, "Build.MANUFACTURER: " + Build.MANUFACTURER); - } - - if (Build.BRAND.equals("generic") || - Build.BRAND.equals("generic_x86") || - Build.BRAND.equals("TTVM") || - Build.BRAND.equals("google") || - Build.BRAND.contains("Andy")) { - newRating++; - Log.d(TAG, "Build.BRAND: " + Build.BRAND); - } - - if (Build.DEVICE.contains("generic") || - Build.DEVICE.contains("generic_x86") || - Build.DEVICE.contains("Andy") || - Build.DEVICE.contains("ttVM_Hdragon") || - Build.DEVICE.contains("Droid4X") || - Build.DEVICE.contains("nox") || - Build.DEVICE.contains("generic_x86_64") || - Build.DEVICE.contains("vbox86p")) { - newRating++; - Log.d(TAG, "Build.DEVICE: " + Build.DEVICE); - } - - if (Build.MODEL.equals("sdk") || - Build.MODEL.equals("google_sdk") || - Build.MODEL.contains("Droid4X") || - Build.MODEL.contains("TiantianVM") || - Build.MODEL.contains("Andy") || - Build.MODEL.equals("Android SDK built for x86_64") || - Build.MODEL.equals("Android SDK built for x86")) { - newRating++; - Log.d(TAG, "Build.MODEL: " + Build.MODEL); - } - - if (Build.HARDWARE.equals("goldfish") || - Build.HARDWARE.equals("vbox86") || - Build.HARDWARE.contains("nox") || - Build.HARDWARE.contains("ttVM_x86")) { - Log.d(TAG, "Build.HARDWARE: " + Build.HARDWARE); - newRating++; - } - - if (Build.FINGERPRINT.contains("generic/sdk/generic") || - Build.FINGERPRINT.contains("generic_x86/sdk_x86/generic_x86") || - Build.FINGERPRINT.contains("Andy") || - Build.FINGERPRINT.contains("ttVM_Hdragon") || - Build.FINGERPRINT.contains("generic_x86_64") || - Build.FINGERPRINT.contains("generic/google_sdk/generic") || - Build.FINGERPRINT.contains("vbox86p") || - Build.FINGERPRINT.contains("generic/vbox86p/vbox86p")) { - Log.d(TAG, "Build.FINGERPRINT: " + Build.FINGERPRINT); - newRating++; - } - - try { - String opengl = android.opengl.GLES20.glGetString(android.opengl.GLES20.GL_RENDERER); - if (opengl != null){ - if( opengl.contains("Bluestacks") || - opengl.contains("Translator") - ) - newRating += 10; - } - } catch (Exception e) { - e.printStackTrace(); - } - - try { - File sharedFolder = new File(Environment - .getExternalStorageDirectory().toString() - + File.separatorChar - + "windows" - + File.separatorChar - + "BstSharedFolder"); - - if (sharedFolder.exists()) { - newRating += 10; - } - } catch (Exception e) { - e.printStackTrace(); - } - rating = newRating; - } - return rating > 3; - } else - return false; // Always return false if it is a release build - } - - /** - * Determine current orientation of the device. - * Source: http://stackoverflow.com/a/10383164/2175837 - * @return Returns the current orientation of the device. - */ - public int getScreenOrientation() { - int rotation = mWindowManager.getDefaultDisplay().getRotation(); - DisplayMetrics dm = new DisplayMetrics(); - mWindowManager.getDefaultDisplay().getMetrics(dm); - int width = dm.widthPixels; - int height = dm.heightPixels; - int orientation; - // if the device's natural orientation is portrait: - if ((rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) && height > width || (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) && width > height) { - switch(rotation) { - case Surface.ROTATION_0: - orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; - break; - case Surface.ROTATION_90: - orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; - break; - case Surface.ROTATION_180: - orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; - break; - case Surface.ROTATION_270: - orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; - break; - default: - Log.e(TAG, "Unknown screen orientation. Defaulting to portrait"); - orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; - break; - } - } else { // If the device's natural orientation is landscape or if the device is square: - switch(rotation) { - case Surface.ROTATION_0: - orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; - break; - case Surface.ROTATION_90: - orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; - break; - case Surface.ROTATION_180: - orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; - break; - case Surface.ROTATION_270: - orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; - break; - default: - Log.e(TAG, "Unknown screen orientation. Defaulting to landscape"); - orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; - break; - } - } - - return orientation; - } - - /** - * This method shall be called by the subclasses when they have valid - * object and want it to be delivered to external client (via callback) and - * then displayed on the screen. - * @param frame - the current frame to be delivered - */ - protected void deliverAndDrawFrame(CvCameraViewFrame frame) { - Mat modified; - - if (mListener != null) { - modified = mListener.onCameraFrame(frame); - } else { - modified = frame.rgba(); - } - - boolean bmpValid = true; - if (modified != null) { - try { - Utils.matToBitmap(modified, mCacheBitmap); - } catch(Exception e) { - Log.e(TAG, "Mat type: " + modified); - Log.e(TAG, "Bitmap type: " + mCacheBitmap.getWidth() + "*" + mCacheBitmap.getHeight()); - Log.e(TAG, "Utils.matToBitmap() throws an exception: " + e.getMessage()); - bmpValid = false; - } - } - - if (bmpValid && mCacheBitmap != null) { - Canvas canvas = getHolder().lockCanvas(); - if (canvas != null) { - canvas.drawColor(0, android.graphics.PorterDuff.Mode.CLEAR); - int degrees = 0; - - if (!isEmulator()) { // Rotation is always reported as portrait on the emulator for some reason - int orientation = getScreenOrientation(); - switch (orientation) { - case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT: - degrees = -90; - break; - case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE: - break; - case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT: - degrees = 90; - break; - case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE: - degrees = 180; - break; - } - } - - Matrix matrix = new Matrix(); - matrix.postRotate(degrees); - Bitmap outputBitmap = Bitmap.createBitmap(mCacheBitmap, 0, 0, mCacheBitmap.getWidth(), mCacheBitmap.getHeight(), matrix, true); - - if (outputBitmap.getWidth() <= canvas.getWidth()) { - mScale = getRatio(outputBitmap.getWidth(), outputBitmap.getHeight(), canvas.getWidth(), canvas.getHeight()); - } else { - mScale = getRatio(canvas.getWidth(), canvas.getHeight(), outputBitmap.getWidth(), outputBitmap.getHeight()); - } - - if (mScale != 0) { - canvas.scale(mScale, mScale, 0, 0); - } - - if (BuildConfig.DEBUG) - Log.v(TAG, "mStretch value: " + mScale); - - canvas.drawBitmap(outputBitmap, 0, 0, null); - - if (mFpsMeter != null) { - mFpsMeter.measure(); - mFpsMeter.draw(canvas, 20, 30); - } - getHolder().unlockCanvasAndPost(canvas); - } - } - } - - /** - * This method is invoked shall perform concrete operation to initialize the camera. - * CONTRACT: as a result of this method variables mFrameWidth and mFrameHeight MUST be - * initialized with the size of the Camera frames that will be delivered to external processor. - * @param width - the width of this SurfaceView - * @param height - the height of this SurfaceView - */ - protected abstract boolean connectCamera(int width, int height); - - /** - * Disconnects and release the particular camera object being connected to this surface view. - * Called when syncObject lock is held - */ - protected abstract void disconnectCamera(); - - // NOTE: On Android 4.1.x the function must be called before SurfaceTexture constructor! - protected void AllocateCache() - { - mCacheBitmap = Bitmap.createBitmap(mFrameWidth, mFrameHeight, Bitmap.Config.ARGB_8888); - } - - public interface ListItemAccessor { - public int getWidth(Object obj); - public int getHeight(Object obj); - } - - /** - * This helper method can be called by subclasses to select camera preview size. - * It goes over the list of the supported preview sizes and selects the maximum one which - * fits both values set via setMaxFrameSize() and surface frame allocated for this view - * @param supportedSizes - list of android.hardware.Camera.Size - * @param accessor - accessor used to get the width and height - * @param surfaceWidth - width of the surface - * @param surfaceHeight - height of the surface - * @return optimal frame size - */ - protected Size calculateCameraFrameSize(List supportedSizes, ListItemAccessor accessor, int surfaceWidth, int surfaceHeight) { - int calcWidth = 0; - int calcHeight = 0; - - int maxAllowedWidth = (mMaxWidth != MAX_UNSPECIFIED && mMaxWidth < surfaceWidth)? mMaxWidth : surfaceWidth; - int maxAllowedHeight = (mMaxHeight != MAX_UNSPECIFIED && mMaxHeight < surfaceHeight)? mMaxHeight : surfaceHeight; - - for (Object size : supportedSizes) { - int width = accessor.getWidth(size); - int height = accessor.getHeight(size); - - if (width <= maxAllowedWidth && height <= maxAllowedHeight) { - if (width >= calcWidth && height >= calcHeight) { - calcWidth = width; - calcHeight = height; - } - } - } - - return new Size(calcWidth, calcHeight); - } -} diff --git a/app/src/main/java/com/lauszus/facerecognitionapp/CameraViewDoubleTap.java b/app/src/main/java/com/lauszus/facerecognitionapp/CameraViewDoubleTap.java new file mode 100644 index 0000000..3706dc7 --- /dev/null +++ b/app/src/main/java/com/lauszus/facerecognitionapp/CameraViewDoubleTap.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (C) 2016 Kristian Sloth Lauszus. All rights reserved. + * + * This software may be distributed and modified under the terms of the GNU + * General Public License version 2 (GPL2) as published by the Free Software + * Foundation and appearing in the file GPL2.TXT included in the packaging of + * this file. Please note that GPL2 Section 2[b] requires that all works based + * on this software must also be made publicly available under the terms of + * the GPL2 ("Copyleft"). + * + * Contact information + * ------------------- + * + * Kristian Sloth Lauszus + * Web : http://www.lauszus.com + * e-mail : lauszus@gmail.com + ******************************************************************************/ + +package com.lauszus.facerecognitionapp; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.AttributeSet; + +import com.wonderkiln.camerakit.CameraView; + +public class CameraViewDoubleTap extends CameraView { + public CameraViewDoubleTap(@NonNull Context context) { + super(context); + } + public CameraViewDoubleTap(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + public CameraViewDoubleTap(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + interface OnToggleFacingListener { + void onToggleFacing(); + } + + private OnToggleFacingListener mOnToggleFacingListener; + + public void setOnToggleFacingListener(OnToggleFacingListener l) { + mOnToggleFacingListener = l; + } + + @Override + protected void onToggleFacing() { // This is called when the view is double tapped + toggleFacing(); + mOnToggleFacingListener.onToggleFacing(); + } +} diff --git a/app/src/main/java/com/lauszus/facerecognitionapp/FaceRecognitionAppActivity.java b/app/src/main/java/com/lauszus/facerecognitionapp/FaceRecognitionAppActivity.java index a96ea20..96d52ec 100644 --- a/app/src/main/java/com/lauszus/facerecognitionapp/FaceRecognitionAppActivity.java +++ b/app/src/main/java/com/lauszus/facerecognitionapp/FaceRecognitionAppActivity.java @@ -21,11 +21,13 @@ import android.Manifest; import android.animation.Animator; import android.animation.ObjectAnimator; +import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; +import android.graphics.Bitmap; import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; @@ -41,12 +43,14 @@ import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.text.InputType; +import android.util.DisplayMetrics; import android.util.Log; import android.view.Menu; import android.view.MenuItem; -import android.view.SurfaceView; +import android.view.Surface; import android.view.View; import android.view.ViewGroup; +import android.view.Window; import android.view.WindowManager; import android.widget.AdapterView; import android.widget.ArrayAdapter; @@ -57,9 +61,14 @@ import android.widget.TextView; import android.widget.Toast; +import com.wonderkiln.camerakit.CameraKit; +import com.wonderkiln.camerakit.CameraKitEventCallback; +import com.wonderkiln.camerakit.CameraKitImage; + import org.opencv.android.BaseLoaderCallback; import org.opencv.android.LoaderCallbackInterface; import org.opencv.android.OpenCVLoader; +import org.opencv.android.Utils; import org.opencv.core.Core; import org.opencv.core.CvType; import org.opencv.core.Mat; @@ -76,14 +85,13 @@ import java.util.Locale; import java.util.Set; -public class FaceRecognitionAppActivity extends AppCompatActivity implements CameraBridgeViewBase.CvCameraViewListener2 { +public class FaceRecognitionAppActivity extends AppCompatActivity { private static final String TAG = FaceRecognitionAppActivity.class.getSimpleName(); private static final int PERMISSIONS_REQUEST_CODE = 0; private ArrayList images; private ArrayList imagesLabels; private String[] uniqueLabels; - private CameraBridgeViewBase mOpenCvCameraView; - private Mat mRgba, mGray; + private CameraViewDoubleTap mCameraView; private Toast mToast; private boolean useEigenfaces; private SeekBarArrows mThresholdFace, mThresholdDistance, mMaximumImages; @@ -93,6 +101,7 @@ public class FaceRecognitionAppActivity extends AppCompatActivity implements Cam private TinyDB tinydb; private Toolbar mToolbar; private NativeMethods.TrainFacesTask mTrainFacesTask; + private WindowManager mWindowManager; private void showToast(String message, int duration) { if (duration != Toast.LENGTH_SHORT && duration != Toast.LENGTH_LONG) @@ -226,7 +235,9 @@ public void onClick(DialogInterface dialog, int which) { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { dialog.dismiss(); - addLabel(arrayAdapter.getItem(position)); + String label = arrayAdapter.getItem(position); + if (label != null) + addLabel(label); } }); } else @@ -272,7 +283,9 @@ public void onClick(View view) { }); // Show keyboard, so the user can start typing straight away - dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + Window mWindow = dialog.getWindow(); + if (mWindow != null) + mWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); dialog.show(); } @@ -283,17 +296,19 @@ protected void onCreate(Bundle savedInstanceState) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); + setContentView(R.layout.activity_face_recognition_app); - mToolbar = (Toolbar) findViewById(R.id.toolbar); + mToolbar = findViewById(R.id.toolbar); setSupportActionBar(mToolbar); // Sets the Toolbar to act as the ActionBar for this Activity window - DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); + DrawerLayout drawer = findViewById(R.id.drawer_layout); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, mToolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawer.addDrawerListener(toggle); toggle.syncState(); - final RadioButton mRadioButtonEigenfaces = (RadioButton) findViewById(R.id.eigenfaces); - final RadioButton mRadioButtonFisherfaces = (RadioButton) findViewById(R.id.fisherfaces); + final RadioButton mRadioButtonEigenfaces = findViewById(R.id.eigenfaces); + final RadioButton mRadioButtonFisherfaces = findViewById(R.id.fisherfaces); mRadioButtonEigenfaces.setOnClickListener(new View.OnClickListener() { @Override @@ -328,7 +343,7 @@ public void onClick(View v) { tinydb = new TinyDB(this); // Used to store ArrayLists in the shared preferences - mThresholdFace = (SeekBarArrows) findViewById(R.id.threshold_face); + mThresholdFace = findViewById(R.id.threshold_face); mThresholdFace.setOnSeekBarArrowsChangeListener(new SeekBarArrows.OnSeekBarArrowsChangeListener() { @Override public void onProgressChanged(float progress) { @@ -338,7 +353,7 @@ public void onProgressChanged(float progress) { }); faceThreshold = mThresholdFace.getProgress(); // Get initial value - mThresholdDistance = (SeekBarArrows) findViewById(R.id.threshold_distance); + mThresholdDistance = findViewById(R.id.threshold_distance); mThresholdDistance.setOnSeekBarArrowsChangeListener(new SeekBarArrows.OnSeekBarArrowsChangeListener() { @Override public void onProgressChanged(float progress) { @@ -348,7 +363,7 @@ public void onProgressChanged(float progress) { }); distanceThreshold = mThresholdDistance.getProgress(); // Get initial value - mMaximumImages = (SeekBarArrows) findViewById(R.id.maximum_images); + mMaximumImages = findViewById(R.id.maximum_images); mMaximumImages.setOnSeekBarArrowsChangeListener(new SeekBarArrows.OnSeekBarArrowsChangeListener() { @Override public void onProgressChanged(float progress) { @@ -375,6 +390,19 @@ public void onClick(View v) { } }); + mCameraView = findViewById(R.id.camera); + mCameraView.setOnToggleFacingListener(new CameraViewDoubleTap.OnToggleFacingListener() { + @Override + public void onToggleFacing() { + // Show flip animation when the camera is flipped due to a double tap + flipCameraAnimation(); + } + }); + //mCameraView.setMethod(CameraKit.Constants.METHOD_SPEED); + mCameraView.setFacing(prefs.getInt("facing", CameraKit.Constants.FACING_FRONT)); + mCameraView.setPinchToZoom(false); + mCameraView.setPermissions(CameraKit.Constants.PERMISSIONS_PICTURE); + findViewById(R.id.take_picture_button).setOnClickListener(new View.OnClickListener() { NativeMethods.MeasureDistTask mMeasureDistTask; @@ -391,36 +419,56 @@ public void onClick(View v) { return; } - Log.i(TAG, "Gray height: " + mGray.height() + " Width: " + mGray.width() + " total: " + mGray.total()); - if (mGray.total() == 0) - return; - Size imageSize = new Size(200, 200.0f / ((float) mGray.width() / (float) mGray.height())); // Scale image in order to decrease computation time - Imgproc.resize(mGray, mGray, imageSize); - Log.i(TAG, "Small gray height: " + mGray.height() + " Width: " + mGray.width() + " total: " + mGray.total()); - //SaveImage(mGray); - - Mat image = mGray.reshape(0, (int) mGray.total()); // Create column vector - Log.i(TAG, "Vector height: " + image.height() + " Width: " + image.width() + " total: " + image.total()); - images.add(image); // Add current image to the array - - if (images.size() > maximumImages) { - images.remove(0); // Remove first image - imagesLabels.remove(0); // Remove first label - Log.i(TAG, "The number of images is limited to: " + images.size()); - } + mCameraView.captureImage(new CameraKitEventCallback() { + @Override + public void callback(CameraKitImage cameraKitImage) { + Mat mRgba = new Mat(); + Bitmap bmp = cameraKitImage.getBitmap(); + Utils.bitmapToMat(bmp, mRgba); + + // Flip image so it is always pointing upward + if (mCameraView.isFacingFront()) { + int orientation = getScreenOrientation(); + switch (orientation) { + case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT: + case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT: + Core.flip(mRgba, mRgba, -1); // Flip along both axis + break; + } + } + + Mat mGray = new Mat(); + Imgproc.cvtColor(mRgba, mGray, Imgproc.COLOR_RGB2GRAY); + + //SaveImage(mRgba); + //SaveImage(mGray); + + Log.i(TAG, "Gray height: " + mGray.height() + " Width: " + mGray.width() + " total: " + mGray.total()); + if (mGray.total() == 0) + return; + Size imageSize = new Size(200, 200.0f / ((float) mGray.width() / (float) mGray.height())); // Scale image in order to decrease computation time + Imgproc.resize(mGray, mGray, imageSize); + Log.i(TAG, "Small gray height: " + mGray.height() + " Width: " + mGray.width() + " total: " + mGray.total()); + + Mat image = mGray.reshape(0, (int) mGray.total()); // Create column vector + Log.i(TAG, "Vector height: " + image.height() + " Width: " + image.width() + " total: " + image.total()); + images.add(image); // Add current image to the array + + if (images.size() > maximumImages) { + images.remove(0); // Remove first image + imagesLabels.remove(0); // Remove first label + Log.i(TAG, "The number of images is limited to: " + images.size()); + } - // Calculate normalized Euclidean distance - mMeasureDistTask = new NativeMethods.MeasureDistTask(useEigenfaces, measureDistTaskCallback); - mMeasureDistTask.execute(image); + // Calculate normalized Euclidean distance + mMeasureDistTask = new NativeMethods.MeasureDistTask(useEigenfaces, measureDistTaskCallback); + mMeasureDistTask.execute(image); - showLabelsDialog(); + showLabelsDialog(); + } + }); } }); - - mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.camera_java_surface_view); - mOpenCvCameraView.setCameraIndex(prefs.getInt("mCameraIndex", CameraBridgeViewBase.CAMERA_ID_FRONT)); - mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE); - mOpenCvCameraView.setCvCameraViewListener(this); } private NativeMethods.MeasureDistTask.Callback measureDistTaskCallback = new NativeMethods.MeasureDistTask.Callback() { @@ -460,27 +508,6 @@ else if (minDist < distanceThreshold) // 3. Distant from face space and near a f } }; - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { - switch (requestCode) { - case PERMISSIONS_REQUEST_CODE: - // If request is cancelled, the result arrays are empty. - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - loadOpenCV(); - } else { - showToast("Permission required!", Toast.LENGTH_LONG); - finish(); - } - } - } - - @Override - public void onPause() { - super.onPause(); - if (mOpenCvCameraView != null) - mOpenCvCameraView.disableView(); - } - @Override public void onStart() { super.onStart(); @@ -496,14 +523,15 @@ public void onStart() { @Override public void onStop() { - super.onStop(); + mCameraView.stop(); + // Store threshold values Editor editor = prefs.edit(); editor.putFloat("faceThreshold", faceThreshold); editor.putFloat("distanceThreshold", distanceThreshold); editor.putInt("maximumImages", maximumImages); editor.putBoolean("useEigenfaces", useEigenfaces); - editor.putInt("mCameraIndex", mOpenCvCameraView.mCameraIndex); + editor.putInt("facing", mCameraView.getFacing()); editor.apply(); // Store ArrayLists containing the images and labels @@ -511,6 +539,29 @@ public void onStop() { tinydb.putListMat("images", images); tinydb.putListString("imagesLabels", imagesLabels); } + + super.onStop(); + } + + @Override + public void onPause() { + mCameraView.stop(); + super.onPause(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { + switch (requestCode) { + case PERMISSIONS_REQUEST_CODE: + // If request is cancelled, the result arrays are empty. + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) + loadOpenCV(); + else { + showToast("Camera permission required", Toast.LENGTH_LONG); + finish(); + } + break; + } } @Override @@ -531,7 +582,7 @@ public void onManagerConnected(int status) { case LoaderCallbackInterface.SUCCESS: NativeMethods.loadNativeLibraries(); // Load native libraries after(!) OpenCV initialization Log.i(TAG, "OpenCV loaded successfully"); - mOpenCvCameraView.enableView(); + mCameraView.start(); // Read images and labels from shared preferences images = tinydb.getListMat("images"); @@ -562,78 +613,7 @@ private void loadOpenCV() { } } - @Override - public void onDestroy() { - super.onDestroy(); - if (mOpenCvCameraView != null) - mOpenCvCameraView.disableView(); - } - - public void onCameraViewStarted(int width, int height) { - mGray = new Mat(); - mRgba = new Mat(); - } - - public void onCameraViewStopped() { - mGray.release(); - mRgba.release(); - } - - public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) { - Mat mGrayTmp = inputFrame.gray(); - Mat mRgbaTmp = inputFrame.rgba(); - - // Flip image to get mirror effect - int orientation = mOpenCvCameraView.getScreenOrientation(); - if (mOpenCvCameraView.isEmulator()) // Treat emulators as a special case - Core.flip(mRgbaTmp, mRgbaTmp, 1); // Flip along y-axis - else { - switch (orientation) { // RGB image - case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT: - case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT: - if (mOpenCvCameraView.mCameraIndex == CameraBridgeViewBase.CAMERA_ID_FRONT) - Core.flip(mRgbaTmp, mRgbaTmp, 0); // Flip along x-axis - else - Core.flip(mRgbaTmp, mRgbaTmp, -1); // Flip along both axis - break; - case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE: - case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE: - if (mOpenCvCameraView.mCameraIndex == CameraBridgeViewBase.CAMERA_ID_FRONT) - Core.flip(mRgbaTmp, mRgbaTmp, 1); // Flip along y-axis - break; - } - switch (orientation) { // Grayscale image - case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT: - Core.transpose(mGrayTmp, mGrayTmp); // Rotate image - if (mOpenCvCameraView.mCameraIndex == CameraBridgeViewBase.CAMERA_ID_FRONT) - Core.flip(mGrayTmp, mGrayTmp, -1); // Flip along both axis - else - Core.flip(mGrayTmp, mGrayTmp, 1); // Flip along y-axis - break; - case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT: - Core.transpose(mGrayTmp, mGrayTmp); // Rotate image - if (mOpenCvCameraView.mCameraIndex == CameraBridgeViewBase.CAMERA_ID_BACK) - Core.flip(mGrayTmp, mGrayTmp, 0); // Flip along x-axis - break; - case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE: - if (mOpenCvCameraView.mCameraIndex == CameraBridgeViewBase.CAMERA_ID_FRONT) - Core.flip(mGrayTmp, mGrayTmp, 1); // Flip along y-axis - break; - case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE: - Core.flip(mGrayTmp, mGrayTmp, 0); // Flip along x-axis - if (mOpenCvCameraView.mCameraIndex == CameraBridgeViewBase.CAMERA_ID_BACK) - Core.flip(mGrayTmp, mGrayTmp, 1); // Flip along y-axis - break; - } - } - - mGray = mGrayTmp; - mRgba = mRgbaTmp; - - return mRgba; - } - - @SuppressWarnings("ResultOfMethodCallIgnored") + @SuppressWarnings({"ResultOfMethodCallIgnored", "unused"}) public void SaveImage(Mat mat) { Mat mIntermediateMat = new Mat(); @@ -657,7 +637,7 @@ public void SaveImage(Mat mat) { @Override public void onBackPressed() { - DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); + DrawerLayout drawer = findViewById(R.id.drawer_layout); if (drawer.isDrawerOpen(GravityCompat.START)) drawer.closeDrawer(GravityCompat.START); else @@ -669,47 +649,106 @@ public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_face_recognition_app, menu); // Show rear camera icon if front camera is currently used and front camera icon if back camera is used MenuItem menuItem = menu.findItem(R.id.flip_camera); - if (mOpenCvCameraView.mCameraIndex == CameraBridgeViewBase.CAMERA_ID_FRONT) - menuItem.setIcon(R.drawable.ic_camera_rear_white_24dp); - else + if (mCameraView.isFacingFront()) menuItem.setIcon(R.drawable.ic_camera_front_white_24dp); + else + menuItem.setIcon(R.drawable.ic_camera_rear_white_24dp); return true; } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.flip_camera: - mOpenCvCameraView.flipCamera(); + private void flipCameraAnimation() { + // Do flip camera animation + View v = mToolbar.findViewById(R.id.flip_camera); + ObjectAnimator animator = ObjectAnimator.ofFloat(v, "rotationY", v.getRotationY() + 180.0f); + animator.setDuration(500); + animator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { - // Do flip camera animation - View v = mToolbar.findViewById(R.id.flip_camera); - ObjectAnimator animator = ObjectAnimator.ofFloat(v, "rotationY", v.getRotationY() + 180.0f); - animator.setDuration(500); - animator.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { + } - } + @Override + public void onAnimationEnd(Animator animation) { + supportInvalidateOptionsMenu(); // This will call onCreateOptionsMenu() + } - @Override - public void onAnimationEnd(Animator animation) { - supportInvalidateOptionsMenu(); // This will call onCreateOptionsMenu() - } + @Override + public void onAnimationCancel(Animator animation) { - @Override - public void onAnimationCancel(Animator animation) { + } - } + @Override + public void onAnimationRepeat(Animator animation) { - @Override - public void onAnimationRepeat(Animator animation) { + } + }); + animator.start(); + } - } - }); - animator.start(); + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.flip_camera: + mCameraView.toggleFacing(); + flipCameraAnimation(); return true; } return super.onOptionsItemSelected(item); } + + /** + * Determine current orientation of the device. + * Source: http://stackoverflow.com/a/10383164/2175837 + * @return Returns the current orientation of the device. + */ + public int getScreenOrientation() { + int rotation = mWindowManager.getDefaultDisplay().getRotation(); + DisplayMetrics dm = new DisplayMetrics(); + mWindowManager.getDefaultDisplay().getMetrics(dm); + int width = dm.widthPixels; + int height = dm.heightPixels; + int orientation; + // If the device's natural orientation is portrait: + if ((rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) && height > width || (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) && width > height) { + switch(rotation) { + case Surface.ROTATION_0: + orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + break; + case Surface.ROTATION_90: + orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; + break; + case Surface.ROTATION_180: + orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; + break; + case Surface.ROTATION_270: + orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; + break; + default: + Log.e(TAG, "Unknown screen orientation. Defaulting to portrait"); + orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + break; + } + } else { // If the device's natural orientation is landscape or if the device is square: + switch(rotation) { + case Surface.ROTATION_0: + orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; + break; + case Surface.ROTATION_90: + orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + break; + case Surface.ROTATION_180: + orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; + break; + case Surface.ROTATION_270: + orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; + break; + default: + Log.e(TAG, "Unknown screen orientation. Defaulting to landscape"); + orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; + break; + } + } + + return orientation; + } } diff --git a/app/src/main/java/com/lauszus/facerecognitionapp/JavaCameraView.java b/app/src/main/java/com/lauszus/facerecognitionapp/JavaCameraView.java deleted file mode 100644 index 29ef531..0000000 --- a/app/src/main/java/com/lauszus/facerecognitionapp/JavaCameraView.java +++ /dev/null @@ -1,379 +0,0 @@ -// Based on: https://github.com/opencv/opencv/blob/master/modules/java/generator/android/java/org/opencv/android/JavaCameraView.java - -package com.lauszus.facerecognitionapp; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.ImageFormat; -import android.graphics.SurfaceTexture; -import android.hardware.Camera; -import android.hardware.Camera.PreviewCallback; -import android.os.Build; -import android.util.AttributeSet; -import android.util.Log; -import android.view.ViewGroup.LayoutParams; - -import org.opencv.BuildConfig; -import org.opencv.core.CvType; -import org.opencv.core.Mat; -import org.opencv.core.Size; -import org.opencv.imgproc.Imgproc; - -import java.util.List; - -/** - * This class is an implementation of the Bridge View between OpenCV and Java Camera. - * This class relays on the functionality available in base class and only implements - * required functions: - * connectCamera - opens Java camera and sets the PreviewCallback to be delivered. - * disconnectCamera - closes the camera and stops preview. - * When frame is delivered via callback from Camera - it processed via OpenCV to be - * converted to RGBA32 and then passed to the external callback for modifications if required. - */ -public class JavaCameraView extends CameraBridgeViewBase implements PreviewCallback { - - private static final int MAGIC_TEXTURE_ID = 10; - private static final String TAG = "JavaCameraView"; - - private byte mBuffer[]; - private Mat[] mFrameChain; - private int mChainIdx = 0; - private Thread mThread; - private boolean mStopThread; - - protected Camera mCamera; - protected JavaCameraFrame[] mCameraFrame; - private int mPreviewFormat = ImageFormat.NV21; - - @SuppressWarnings("FieldCanBeLocal") // This slows down the frame rate significantly - private SurfaceTexture mSurfaceTexture; - - public static class JavaCameraSizeAccessor implements ListItemAccessor { - - @Override - public int getWidth(Object obj) { - Camera.Size size = (Camera.Size) obj; - return size.width; - } - - @Override - public int getHeight(Object obj) { - Camera.Size size = (Camera.Size) obj; - return size.height; - } - } - - @SuppressWarnings("unused") - public JavaCameraView(Context context, int cameraId) { - super(context, cameraId); - } - - public JavaCameraView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @SuppressLint("ObsoleteSdkInt") - protected boolean initializeCamera(int width, int height) { - Log.d(TAG, "Initialize java camera"); - boolean result = true; - synchronized (this) { - mCamera = null; - - if (mCameraIndex == CAMERA_ID_ANY || isEmulator()) { // Just open any camera on emulators - Log.d(TAG, "Trying to open camera with old open()"); - try { - mCamera = Camera.open(); - } - catch (Exception e){ - Log.e(TAG, "Camera is not available (in use or does not exist): " + e.getLocalizedMessage()); - } - - if(mCamera == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { - boolean connected = false; - for (int camIdx = 0; camIdx < Camera.getNumberOfCameras(); ++camIdx) { - Log.d(TAG, "Trying to open camera with new open(" + camIdx + ")"); - try { - mCamera = Camera.open(camIdx); - connected = true; - } catch (RuntimeException e) { - Log.e(TAG, "Camera #" + camIdx + "failed to open: " + e.getLocalizedMessage()); - } - if (connected) break; - } - } - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { - int localCameraIndex = mCameraIndex; - if (mCameraIndex == CAMERA_ID_BACK) { - Log.i(TAG, "Trying to open back camera"); - Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); - for (int camIdx = 0; camIdx < Camera.getNumberOfCameras(); ++camIdx) { - Camera.getCameraInfo( camIdx, cameraInfo ); - if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) { - localCameraIndex = camIdx; - break; - } - } - } else if (mCameraIndex == CAMERA_ID_FRONT) { - Log.i(TAG, "Trying to open front camera"); - Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); - for (int camIdx = 0; camIdx < Camera.getNumberOfCameras(); ++camIdx) { - Camera.getCameraInfo( camIdx, cameraInfo ); - if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { - localCameraIndex = camIdx; - break; - } - } - } - if (localCameraIndex == CAMERA_ID_BACK) { - Log.e(TAG, "Back camera not found!"); - } else if (localCameraIndex == CAMERA_ID_FRONT) { - Log.e(TAG, "Front camera not found!"); - } else { - Log.d(TAG, "Trying to open camera with new open(" + localCameraIndex + ")"); - try { - mCamera = Camera.open(localCameraIndex); - } catch (RuntimeException e) { - Log.e(TAG, "Camera #" + localCameraIndex + "failed to open: " + e.getLocalizedMessage()); - } - } - } - } - - if (mCamera == null) - return false; - - /* Now set camera parameters */ - try { - Camera.Parameters params = mCamera.getParameters(); - Log.d(TAG, "getSupportedPreviewSizes()"); - List sizes = params.getSupportedPreviewSizes(); - - if (sizes != null) { - /* Select the size that fits surface considering maximum size allowed */ - Size frameSize = calculateCameraFrameSize(sizes, new JavaCameraSizeAccessor(), width, height); - - /* Image format NV21 causes issues in the Android emulators */ - if (isEmulator()) - params.setPreviewFormat(ImageFormat.YV12); - else - params.setPreviewFormat(ImageFormat.NV21); - - Log.d(TAG, "Set preview size to " + frameSize.width + "x" + frameSize.height); - params.setPreviewSize((int)frameSize.width, (int)frameSize.height); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH && !android.os.Build.MODEL.equals("GT-I9100")) - params.setRecordingHint(true); - - List FocusModes = params.getSupportedFocusModes(); - if (FocusModes != null && FocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) - { - params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); - } - - mCamera.setParameters(params); - params = mCamera.getParameters(); - mPreviewFormat = params.getPreviewFormat(); - - mFrameWidth = params.getPreviewSize().width; - mFrameHeight = params.getPreviewSize().height; - - if ((getLayoutParams().width == LayoutParams.MATCH_PARENT) && (getLayoutParams().height == LayoutParams.MATCH_PARENT)) - mScale = Math.min(((float)height)/mFrameHeight, ((float)width)/mFrameWidth); - else - mScale = 0; - - if (mFpsMeter != null) { - mFpsMeter.setResolution(mFrameWidth, mFrameHeight); - } - - int size = mFrameWidth * mFrameHeight; - size = size * ImageFormat.getBitsPerPixel(params.getPreviewFormat()) / 8; - mBuffer = new byte[size]; - - mCamera.addCallbackBuffer(mBuffer); - mCamera.setPreviewCallbackWithBuffer(this); - - mFrameChain = new Mat[2]; - mFrameChain[0] = new Mat(mFrameHeight + (mFrameHeight/2), mFrameWidth, CvType.CV_8UC1); - mFrameChain[1] = new Mat(mFrameHeight + (mFrameHeight/2), mFrameWidth, CvType.CV_8UC1); - - AllocateCache(); - - mCameraFrame = new JavaCameraFrame[2]; - mCameraFrame[0] = new JavaCameraFrame(mFrameChain[0], mFrameWidth, mFrameHeight); - mCameraFrame[1] = new JavaCameraFrame(mFrameChain[1], mFrameWidth, mFrameHeight); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - mSurfaceTexture = new SurfaceTexture(MAGIC_TEXTURE_ID); - mCamera.setPreviewTexture(mSurfaceTexture); - } else - mCamera.setPreviewDisplay(null); - - /* Finally we are ready to start the preview */ - Log.d(TAG, "startPreview"); - mCamera.startPreview(); - } - else - result = false; - } catch (Exception e) { - result = false; - e.printStackTrace(); - } - } - - return result; - } - - protected void releaseCamera() { - synchronized (this) { - if (mCamera != null) { - mCamera.stopPreview(); - mCamera.setPreviewCallback(null); - - mCamera.release(); - } - mCamera = null; - if (mFrameChain != null) { - mFrameChain[0].release(); - mFrameChain[1].release(); - } - if (mCameraFrame != null) { - mCameraFrame[0].release(); - mCameraFrame[1].release(); - } - } - } - - private boolean mCameraFrameReady = false; - - @Override - protected boolean connectCamera(int width, int height) { - - /* 1. We need to instantiate camera - * 2. We need to start thread which will be getting frames - */ - /* First step - initialize camera connection */ - Log.d(TAG, "Connecting to camera"); - if (!initializeCamera(width, height)) - return false; - - mCameraFrameReady = false; - - /* now we can start update thread */ - Log.d(TAG, "Starting processing thread"); - mStopThread = false; - mThread = new Thread(new CameraWorker()); - mThread.start(); - - return true; - } - - @Override - protected void disconnectCamera() { - /* 1. We need to stop thread which updating the frames - * 2. Stop camera and release it - */ - Log.d(TAG, "Disconnecting from camera"); - try { - mStopThread = true; - Log.d(TAG, "Notify thread"); - synchronized (this) { - this.notify(); - } - Log.d(TAG, "Waiting for thread"); - if (mThread != null) - mThread.join(); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - mThread = null; - } - - /* Now release camera */ - releaseCamera(); - - mCameraFrameReady = false; - } - - @Override - public void onPreviewFrame(byte[] frame, Camera arg1) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Preview Frame received. Frame size: " + frame.length); - synchronized (this) { - mFrameChain[mChainIdx].put(0, 0, frame); - mCameraFrameReady = true; - this.notify(); - } - if (mCamera != null) - mCamera.addCallbackBuffer(mBuffer); - } - - private class JavaCameraFrame implements CvCameraViewFrame { - @Override - public Mat gray() { - return mYuvFrameData.submat(0, mHeight, 0, mWidth); - } - - @Override - public Mat rgba() { - if (mPreviewFormat == ImageFormat.NV21) - Imgproc.cvtColor(mYuvFrameData, mRgba, Imgproc.COLOR_YUV2RGBA_NV21, 4); - else if (mPreviewFormat == ImageFormat.YV12) - Imgproc.cvtColor(mYuvFrameData, mRgba, Imgproc.COLOR_YUV2RGB_I420, 4); // COLOR_YUV2RGBA_YV12 produces inverted colors - else - throw new IllegalArgumentException("Preview Format can be NV21 or YV12"); - - return mRgba; - } - - @SuppressWarnings("WeakerAccess") - public JavaCameraFrame(Mat Yuv420sp, int width, int height) { - super(); - mWidth = width; - mHeight = height; - mYuvFrameData = Yuv420sp; - mRgba = new Mat(); - } - - public void release() { - mRgba.release(); - } - - private Mat mYuvFrameData; - private Mat mRgba; - private int mWidth; - private int mHeight; - } - - private class CameraWorker implements Runnable { - - @Override - public void run() { - do { - boolean hasFrame = false; - synchronized (JavaCameraView.this) { - try { - while (!mCameraFrameReady && !mStopThread) { - JavaCameraView.this.wait(); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - if (mCameraFrameReady) - { - mChainIdx = 1 - mChainIdx; - mCameraFrameReady = false; - hasFrame = true; - } - } - - if (!mStopThread && hasFrame) { - if (!mFrameChain[1 - mChainIdx].empty()) - deliverAndDrawFrame(mCameraFrame[1 - mChainIdx]); - } - } while (!mStopThread); - Log.d(TAG, "Finish processing thread"); - } - } -} diff --git a/app/src/main/java/com/lauszus/facerecognitionapp/SeekBarArrows.java b/app/src/main/java/com/lauszus/facerecognitionapp/SeekBarArrows.java index 560af4d..e087614 100644 --- a/app/src/main/java/com/lauszus/facerecognitionapp/SeekBarArrows.java +++ b/app/src/main/java/com/lauszus/facerecognitionapp/SeekBarArrows.java @@ -18,6 +18,7 @@ package com.lauszus.facerecognitionapp; +import android.annotation.SuppressLint; import android.content.Context; import android.content.res.TypedArray; import android.os.Handler; @@ -52,9 +53,9 @@ public SeekBarArrows(Context context, AttributeSet attrs) { float max = styledAttrs.getFloat(R.styleable.SeekBarArrows_max, 0); nValues = styledAttrs.getInt(R.styleable.SeekBarArrows_n_values, 0); - mSeekBar = (SeekBar) findViewById(R.id.seekBar); + mSeekBar = findViewById(R.id.seekBar); ((TextView) findViewById(R.id.text)).setText(mSeekBarText); - mSeekBarValue = (TextView) findViewById(R.id.value); + mSeekBarValue = findViewById(R.id.value); setMax(max); // Set maximum value mSeekBar.setOnSeekBarChangeListener(this); // Set listener @@ -139,6 +140,7 @@ private class OnArrowListener implements View.OnClickListener, View.OnLongClickL private SeekBar mSeekbar; private boolean positive; + @SuppressLint("ClickableViewAccessibility") OnArrowListener(View v, SeekBar mSeekbar, boolean positive) { Button mButton = (Button) v; this.mSeekbar = mSeekbar; @@ -177,6 +179,7 @@ public boolean onLongClick(View v) { return true; } + @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { diff --git a/app/src/main/res/layout/app_bar_main.xml b/app/src/main/res/layout/app_bar_main.xml index 4e1f1f1..69bfd15 100644 --- a/app/src/main/res/layout/app_bar_main.xml +++ b/app/src/main/res/layout/app_bar_main.xml @@ -7,11 +7,6 @@ android:fitsSystemWindows="true" tools:context=".FaceRecognitionAppActivity"> - - + +