diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..db64b6a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+*.swp
+*.iml
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..e69de29
diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser
new file mode 100644
index 0000000..5c92b75
Binary files /dev/null and b/.idea/caches/build_file_checksums.ser differ
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..30aa626
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..5644ccc
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..e0d5b93
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..db8a813
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..7f68460
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..6df551c
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,20 @@
+language: android
+script: "./gradlew assembleDebug"
+
+jdk:
+ - oraclejdk8
+
+android:
+ components:
+ # The BuildTools version used by your project
+ - tools
+ - platform-tools
+ - build-tools-23.0.3
+ # The SDK version used to compile your project
+ - android-23
+ - extra-android-m2repository
+ - extra-android-support
+ # Additional components
+ #- extra-android-m2repository
+ licenses:
+ - android-sdk-license-.+
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..42159e7
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,190 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ Copyright 2015-present Tzutalin
+
+ 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.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..fd1eb11
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,8 @@
+all:
+ ./gradlew assembleDebug
+
+clean:
+ ./gradlew clean
+
+install:
+ adb install ./app/build/outputs/apk/app-debug.apk
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..19f0ce0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,61 @@
+# Android App for Real-time Face Landmark Detection
+
+Fast Face is an android application which detects facial landmark . It detects 68 landmarks of human face
+chin to eyebrow in real-time. Also, it can detect people up to 3 if you guys show your frontal faces.
+
+It is an upgraded version of [dlib-android](https://github.com/tzutalin/dlib-android), Not only revising the code but additional task for optimizing dlib library was needed.
+As a result, Fast Face speeds up 2x or more from the original. Higher resoluton, Higher speed.
+
+I think it is not the best one, there are some issues that can be more speedy one.
+So, if you guys already improved or want to improve this code, feel free to contact me. Test and Enjoy it :)
+
+
+## Screenshot
+
+
+
+## Environments
+* DEVICE : SAMSUNG-A8 2015(@cortex-a53 core)
+* API : 23 (Android 6.0.1)
+* TIME : 50ms ~ 70ms
+
+
+## Features
+
+* Support HOG detector
+* HOG Face detection
+* Facial Landmark/Expression
+
+
+## Sample code
+
+Facial landmark detection
+```java
+
+// detecs every 3 frames
+if(mframeNum % 3 == 0){
+ synchronized (OnGetImageListener.this) {
+ results = mFaceDet.detect(mResizedBitmap);
+ }
+}
+
+// Draw on bitmap
+if (results.size() != 0) {
+ for (final VisionDetRet ret : results) {
+ float resizeRatio = 4.5f;
+ Canvas canvas = new Canvas(mInversedBipmap);
+
+ // Draw landmark
+ ArrayList landmarks = ret.getFaceLandmarks();
+ for (Point point : landmarks) {
+ int pointX = (int) (point.x * resizeRatio);
+ int pointY = (int) (point.y * resizeRatio);
+ canvas.drawCircle(pointX, pointY, 4, mFaceLandmardkPaint);
+ }
+ }
+}
+```
+
+
+## License
+[License](LICENSE.md)
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..91e01db
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,58 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 25
+ buildToolsVersion '25.0.2'
+
+ defaultConfig {
+ applicationId "com.tzutalin.dlibtest"
+ minSdkVersion 21
+ targetSdkVersion 25
+ versionCode 1
+ versionName "${rootProject.ext.releaseVersionName}"
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
+ }
+}
+
+repositories {
+ mavenCentral()
+ mavenLocal()
+}
+
+apply plugin: 'com.neenbedankt.android-apt'
+def AAVersion = '4.0.0'
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile "com.android.support:appcompat-v7:${rootProject.ext.androidSupportSdkVersion}"
+ compile 'com.android.support:design:25.2.0'
+ compile 'com.github.dexafree:materiallist:3.0.1'
+ compile 'com.jakewharton.timber:timber:4.5.1'
+ compile project(':dlib')
+ apt "org.androidannotations:androidannotations:$AAVersion"
+ compile "org.androidannotations:androidannotations-api:$AAVersion"
+
+ // Add AndroidJUnit
+ androidTestCompile "com.android.support:support-annotations:${rootProject.ext.androidSupportSdkVersion}"
+ androidTestCompile 'com.android.support.test:runner:0.5'
+ androidTestCompile 'com.android.support.test:rules:0.5'
+ // Optional -- Hamcrest library
+ androidTestCompile 'org.hamcrest:hamcrest-library:1.3'
+}
+apply plugin: 'com.jakewharton.hugo'
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..acfa448
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /home/darrenl/tools/android-sdk-linux/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/app/src/androidTest/java/DLibFunctionsTest.java b/app/src/androidTest/java/DLibFunctionsTest.java
new file mode 100644
index 0000000..2ea7f18
--- /dev/null
+++ b/app/src/androidTest/java/DLibFunctionsTest.java
@@ -0,0 +1,38 @@
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.tzutalin.dlib.PedestrianDet;
+import com.tzutalin.dlib.VisionDetRet;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DLibFunctionsTest {
+
+ private Context mInstrumantationCtx;
+
+ @Before
+ public void setup() {
+ mInstrumantationCtx = InstrumentationRegistry.getTargetContext();
+ }
+
+ @Test
+ public void testFacialLandmark() {
+ PedestrianDet peopleDet = new PedestrianDet();
+ List results = peopleDet.detect("/sdcard/test.bmp");
+ for (final VisionDetRet ret : results) {
+ String label = ret.getLabel();
+ int rectLeft = ret.getLeft();
+ int rectTop= ret.getTop();
+ int rectRight = ret.getRight();
+ int rectBottom = ret.getBottom();
+ }
+ }
+}
diff --git a/app/src/androidTest/java/FaceDetTest.java b/app/src/androidTest/java/FaceDetTest.java
new file mode 100644
index 0000000..42f1a38
--- /dev/null
+++ b/app/src/androidTest/java/FaceDetTest.java
@@ -0,0 +1,113 @@
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Point;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.tzutalin.dlib.Constants;
+import com.tzutalin.dlib.FaceDet;
+import com.tzutalin.dlib.VisionDetRet;
+
+import junit.framework.Assert;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Created by houzhi on 16-10-20.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class FaceDetTest {
+
+ @Before
+ public void setup() {
+ }
+
+ @After
+ public void tearDown() {
+ }
+
+ @Test
+ public void testDetBitmapFace() {
+ FaceDet faceDet = new FaceDet(Constants.getFaceShapeModelPath());
+ Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/test.jpg");
+ assertThat(bitmap, notNullValue());
+ List results = faceDet.detect(bitmap);
+ for (final VisionDetRet ret : results) {
+ String label = ret.getLabel();
+ int rectLeft = ret.getLeft();
+ int rectTop = ret.getTop();
+ int rectRight = ret.getRight();
+ int rectBottom = ret.getBottom();
+ assertThat(label, is("face"));
+ Assert.assertTrue(rectLeft > 0);
+ }
+ faceDet.release();
+ }
+
+ @Test
+ public void testDetFace() {
+ Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/test.jpg");
+ assertThat(bitmap, notNullValue());
+ FaceDet faceDet = new FaceDet(Constants.getFaceShapeModelPath());
+ List results = faceDet.detect("/sdcard/test.jpg");
+ for (final VisionDetRet ret : results) {
+ String label = ret.getLabel();
+ int rectLeft = ret.getLeft();
+ int rectTop = ret.getTop();
+ int rectRight = ret.getRight();
+ int rectBottom = ret.getBottom();
+ assertThat(label, is("face"));
+ Assert.assertTrue(rectLeft > 0);
+ }
+ faceDet.release();
+ }
+
+ @Test
+ public void testDetBitmapFaceLandmarkDect() {
+ Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/test.jpg");
+ assertThat(bitmap, notNullValue());
+ FaceDet faceDet = new FaceDet(Constants.getFaceShapeModelPath());
+ List results = faceDet.detect(bitmap);
+ for (final VisionDetRet ret : results) {
+ String label = ret.getLabel();
+ int rectLeft = ret.getLeft();
+ int rectTop = ret.getTop();
+ int rectRight = ret.getRight();
+ int rectBottom = ret.getBottom();
+ ArrayList landmarks = ret.getFaceLandmarks();
+ assertThat(label, is("face"));
+ Assert.assertTrue(landmarks.size() > 0);
+ Assert.assertTrue(rectLeft > 0);
+ }
+ faceDet.release();
+ }
+
+ @Test
+ public void testDetFaceLandmark() {
+ FaceDet faceDet = new FaceDet(Constants.getFaceShapeModelPath());
+ List results = faceDet.detect("/sdcard/test.jpg");
+ for (final VisionDetRet ret : results) {
+ String label = ret.getLabel();
+ int rectLeft = ret.getLeft();
+ int rectTop = ret.getTop();
+ int rectRight = ret.getRight();
+ int rectBottom = ret.getBottom();
+ ArrayList landmarks = ret.getFaceLandmarks();
+ assertThat(label, is("face"));
+ Assert.assertTrue(landmarks.size() > 0);
+ Assert.assertTrue(rectLeft > 0);
+ }
+ faceDet.release();
+ }
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..97343b4
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/tzutalin/dlibtest/AutoFitTextureView.java b/app/src/main/java/com/tzutalin/dlibtest/AutoFitTextureView.java
new file mode 100644
index 0000000..bd9c070
--- /dev/null
+++ b/app/src/main/java/com/tzutalin/dlibtest/AutoFitTextureView.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2016-present Tzutalin
+ *
+ * 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.tzutalin.dlibtest;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.TextureView;
+
+/**
+ * A {@link TextureView} that can be adjusted to a specified aspect ratio.
+ */
+public class AutoFitTextureView extends TextureView {
+ private int mRatioWidth = 0;
+ private int mRatioHeight = 0;
+
+ public AutoFitTextureView(final Context context) {
+ this(context, null);
+ }
+
+ public AutoFitTextureView(final Context context, final AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AutoFitTextureView(final Context context, final AttributeSet attrs, final int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ /**
+ * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
+ * calculated from the parameters. Note that the actual sizes of parameters don't matter, that
+ * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
+ *
+ * @param width Relative horizontal size
+ * @param height Relative vertical size
+ */
+ public void setAspectRatio(final int width, final int height) {
+ if (width < 0 || height < 0) {
+ throw new IllegalArgumentException("Size cannot be negative.");
+ }
+ mRatioWidth = width;
+ mRatioHeight = height;
+ requestLayout();
+ }
+
+ @Override
+ protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ final int width = MeasureSpec.getSize(widthMeasureSpec);
+ final int height = MeasureSpec.getSize(heightMeasureSpec);
+ if (0 == mRatioWidth || 0 == mRatioHeight) {
+ setMeasuredDimension(width, height);
+ } else {
+ if (width < height * mRatioWidth / mRatioHeight) {
+ setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
+ } else {
+ setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/tzutalin/dlibtest/CameraActivity.java b/app/src/main/java/com/tzutalin/dlibtest/CameraActivity.java
new file mode 100644
index 0000000..2e2fcdd
--- /dev/null
+++ b/app/src/main/java/com/tzutalin/dlibtest/CameraActivity.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2016-present Tzutalin
+ *
+ * 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.tzutalin.dlibtest;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+/**
+ * Created by darrenl on 2016/5/20.
+ */
+public class CameraActivity extends Activity {
+
+ private static int OVERLAY_PERMISSION_REQ_CODE = 1;
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+ setContentView(R.layout.activity_camera);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (!Settings.canDrawOverlays(this.getApplicationContext())) {
+ Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
+ startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
+ }
+ }
+
+ if (null == savedInstanceState) {
+ getFragmentManager()
+ .beginTransaction()
+ .replace(R.id.container, CameraConnectionFragment.newInstance())
+ .commit();
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (!Settings.canDrawOverlays(this.getApplicationContext())) {
+ Toast.makeText(CameraActivity.this, "CameraActivity\", \"SYSTEM_ALERT_WINDOW, permission not granted...", Toast.LENGTH_SHORT).show();
+ } else {
+ Intent intent = getIntent();
+ finish();
+ startActivity(intent);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tzutalin/dlibtest/CameraConnectionFragment.java b/app/src/main/java/com/tzutalin/dlibtest/CameraConnectionFragment.java
new file mode 100644
index 0000000..ea6c827
--- /dev/null
+++ b/app/src/main/java/com/tzutalin/dlibtest/CameraConnectionFragment.java
@@ -0,0 +1,683 @@
+/*
+ * Copyright 2016-present Tzutalin
+ *
+ * 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.tzutalin.dlibtest;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.graphics.ImageFormat;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.ImageReader;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.support.v4.app.ActivityCompat;
+import android.util.Log;
+import android.util.Range;
+import android.util.Size;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.LayoutInflater;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import hugo.weaving.DebugLog;
+import timber.log.Timber;
+
+public class CameraConnectionFragment extends Fragment {
+
+ /**
+ * The camera preview size will be chosen to be the smallest frame by pixel size capable of
+ * containing a DESIRED_SIZE x DESIRED_SIZE square.
+ */
+ private static final int MINIMUM_PREVIEW_SIZE = 320;
+ private static final String TAG = "CameraConnectionFragment";
+ private static Range[] fpsRanges;
+
+ private TrasparentTitleView mScoreView;
+
+ /**
+ * Conversion from screen rotation to JPEG orientation.
+ */
+ private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
+ private static final String FRAGMENT_DIALOG = "dialog";
+
+ static {
+ ORIENTATIONS.append(Surface.ROTATION_0, 0);
+ ORIENTATIONS.append(Surface.ROTATION_90, 90);
+ ORIENTATIONS.append(Surface.ROTATION_180, 180);
+ ORIENTATIONS.append(Surface.ROTATION_270, 270);
+ }
+
+ /**
+ * {@link android.view.TextureView.SurfaceTextureListener} handles several lifecycle events on a
+ * {@link TextureView}.
+ */
+ private final TextureView.SurfaceTextureListener surfaceTextureListener =
+ new TextureView.SurfaceTextureListener() {
+ @Override
+ public void onSurfaceTextureAvailable(
+ final SurfaceTexture texture, final int width, final int height) {
+ openCamera(width, height);
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(
+ final SurfaceTexture texture, final int width, final int height) {
+ configureTransform(width, height);
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(final SurfaceTexture texture) {
+ return true;
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(final SurfaceTexture texture) {
+ }
+ };
+
+ /**
+ * ID of the current {@link CameraDevice}.
+ */
+ private String cameraId;
+
+ /**
+ * An {@link AutoFitTextureView} for camera preview.
+ */
+ private AutoFitTextureView textureView;
+
+ /**
+ * A {@link CameraCaptureSession } for camera preview.
+ */
+ private CameraCaptureSession captureSession;
+
+ /**
+ * A reference to the opened {@link CameraDevice}.
+ */
+ private CameraDevice cameraDevice;
+
+ /**
+ * The {@link android.util.Size} of camera preview.
+ */
+ private Size previewSize;
+
+ /**
+ * {@link android.hardware.camera2.CameraDevice.StateCallback}
+ * is called when {@link CameraDevice} changes its state.
+ */
+ private final CameraDevice.StateCallback stateCallback =
+ new CameraDevice.StateCallback() {
+ @Override
+ public void onOpened(final CameraDevice cd) {
+ // This method is called when the camera is opened. We start camera preview here.
+ cameraOpenCloseLock.release();
+ cameraDevice = cd;
+ createCameraPreviewSession();
+ }
+
+ @Override
+ public void onDisconnected(final CameraDevice cd) {
+ cameraOpenCloseLock.release();
+ cd.close();
+ cameraDevice = null;
+
+ if (mOnGetPreviewListener != null) {
+ mOnGetPreviewListener.deInitialize();
+ }
+ }
+
+ @Override
+ public void onError(final CameraDevice cd, final int error) {
+ cameraOpenCloseLock.release();
+ cd.close();
+ cameraDevice = null;
+ final Activity activity = getActivity();
+ if (null != activity) {
+ activity.finish();
+ }
+
+ if (mOnGetPreviewListener != null) {
+ mOnGetPreviewListener.deInitialize();
+ }
+ }
+ };
+
+ /**
+ * An additional thread for running tasks that shouldn't block the UI.
+ */
+ private HandlerThread backgroundThread;
+
+ /**
+ * A {@link Handler} for running tasks in the background.
+ */
+ private Handler backgroundHandler;
+
+ /**
+ * An additional thread for running inference so as not to block the camera.
+ */
+ private HandlerThread inferenceThread;
+
+ /**
+ * A {@link Handler} for running tasks in the background.
+ */
+ private Handler inferenceHandler;
+
+ /**
+ * An {@link ImageReader} that handles preview frame capture.
+ */
+ private ImageReader previewReader;
+
+ /**
+ * {@link android.hardware.camera2.CaptureRequest.Builder} for the camera preview
+ */
+ private CaptureRequest.Builder previewRequestBuilder;
+
+ /**
+ * {@link CaptureRequest} generated by {@link #previewRequestBuilder}
+ */
+ private CaptureRequest previewRequest;
+
+ /**
+ * A {@link Semaphore} to prevent the app from exiting before closing the camera.
+ */
+ private final Semaphore cameraOpenCloseLock = new Semaphore(1);
+
+ /**
+ * Shows a {@link Toast} on the UI thread.
+ *
+ * @param text The message to show
+ */
+ private void showToast(final String text) {
+ final Activity activity = getActivity();
+ if (activity != null) {
+ activity.runOnUiThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(activity, text, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+ }
+
+ /**
+ * Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose
+ * width and height are at least as large as the respective requested values, and whose aspect
+ * ratio matches with the specified value.
+ *
+ * @param choices The list of sizes that the camera supports for the intended output class
+ * @param width The minimum desired width
+ * @param height The minimum desired height
+ * @param aspectRatio The aspect ratio
+ * @return The optimal {@code Size}, or an arbitrary one if none were big enough
+ */
+ @SuppressLint("LongLogTag")
+ @DebugLog
+ private static Size chooseOptimalSize(
+ final Size[] choices, final int width, final int height, final Size aspectRatio) {
+ // Collect the supported resolutions that are at least as big as the preview Surface
+ final List bigEnough = new ArrayList();
+ for (final Size option : choices) {
+ if (option.getHeight() >= MINIMUM_PREVIEW_SIZE && option.getWidth() >= MINIMUM_PREVIEW_SIZE) {
+ Timber.tag(TAG).i("Adding size: " + option.getWidth() + "x" + option.getHeight());
+ bigEnough.add(option);
+ } else {
+ Timber.tag(TAG).i("Not adding size: " + option.getWidth() + "x" + option.getHeight());
+ }
+ }
+
+ // Pick the smallest of those, assuming we found any
+ if (bigEnough.size() > 0) {
+ final Size chosenSize = Collections.min(bigEnough, new CompareSizesByArea());
+ Timber.tag(TAG).i("Chosen size: " + chosenSize.getWidth() + "x" + chosenSize.getHeight());
+ return chosenSize;
+ } else {
+ Timber.tag(TAG).e("Couldn't find any suitable preview size");
+ return choices[0];
+ }
+ }
+
+ public static CameraConnectionFragment newInstance() {
+ return new CameraConnectionFragment();
+ }
+
+ @Override
+ public View onCreateView(
+ final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.camera_connection_fragment, container, false);
+ }
+
+ @Override
+ public void onViewCreated(final View view, final Bundle savedInstanceState) {
+ textureView = (AutoFitTextureView) view.findViewById(R.id.texture);
+ mScoreView = (TrasparentTitleView) view.findViewById(R.id.results);
+ }
+
+ @Override
+ public void onActivityCreated(final Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ startBackgroundThread();
+
+ // When the screen is turned off and turned back on, the SurfaceTexture is already
+ // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
+ // a camera and start preview from here (otherwise, we wait until the surface is ready in
+ // the SurfaceTextureListener).
+ if (textureView.isAvailable()) {
+ openCamera(textureView.getWidth(), textureView.getHeight());
+ } else {
+ textureView.setSurfaceTextureListener(surfaceTextureListener);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ closeCamera();
+ stopBackgroundThread();
+ super.onPause();
+ }
+
+ /**
+ * Sets up member variables related to camera.
+ *
+ * @param width The width of available size for camera preview
+ * @param height The height of available size for camera preview
+ */
+ @DebugLog
+ @SuppressLint("LongLogTag")
+ private void setUpCameraOutputs(final int width, final int height) {
+ final Activity activity = getActivity();
+ final CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
+ try {
+ SparseArray cameraFaceTypeMap = new SparseArray<>();
+ // Check the facing types of camera devices
+ for (final String cameraId : manager.getCameraIdList()) {
+ final CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
+ final Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
+
+ if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
+ if (cameraFaceTypeMap.get(CameraCharacteristics.LENS_FACING_FRONT) != null) {
+ cameraFaceTypeMap.append(CameraCharacteristics.LENS_FACING_FRONT, cameraFaceTypeMap.get(CameraCharacteristics.LENS_FACING_FRONT) + 1);
+ } else {
+ cameraFaceTypeMap.append(CameraCharacteristics.LENS_FACING_FRONT, 1);
+ }
+ }
+
+ if (facing != null && facing == CameraCharacteristics.LENS_FACING_BACK) {
+ if (cameraFaceTypeMap.get(CameraCharacteristics.LENS_FACING_FRONT) != null) {
+ cameraFaceTypeMap.append(CameraCharacteristics.LENS_FACING_BACK, cameraFaceTypeMap.get(CameraCharacteristics.LENS_FACING_BACK) + 1);
+ } else {
+ cameraFaceTypeMap.append(CameraCharacteristics.LENS_FACING_BACK, 1);
+ }
+ }
+ }
+
+ Integer num_facing_back_camera = cameraFaceTypeMap.get(CameraCharacteristics.LENS_FACING_BACK);
+ for (final String cameraId : manager.getCameraIdList()) {
+ final CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
+ final Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
+ // If facing back camera or facing external camera exist, we won't use facing front camera
+ if (num_facing_back_camera != null && num_facing_back_camera > 0) {
+ // We don't use a front facing camera in this sample if there are other camera device facing types
+ if (facing != null && facing == CameraCharacteristics.LENS_FACING_BACK) {
+ continue;
+ }
+ }
+
+ final StreamConfigurationMap map =
+ characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+
+ if (map == null) {
+ continue;
+ }
+
+ // For still image captures, we use the largest available size.
+ final Size largest =
+ Collections.max(
+ Arrays.asList(map.getOutputSizes(ImageFormat.YUV_420_888)),
+ new CompareSizesByArea());
+
+ // Danger, W.R.! Attempting to use too large a preview size could exceed the camera
+ // bus' bandwidth limitation, resulting in gorgeous previews but the storage of
+ // garbage capture data.
+ previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height, largest);
+
+ // We fit the aspect ratio of TextureView to the size of preview we picked.
+ final int orientation = getResources().getConfiguration().orientation;
+ if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ textureView.setAspectRatio(previewSize.getWidth(), previewSize.getHeight());
+ } else {
+ textureView.setAspectRatio(previewSize.getHeight(), previewSize.getWidth());
+ }
+
+ CameraConnectionFragment.this.cameraId = cameraId;
+ return;
+ }
+ } catch (final CameraAccessException e) {
+ Timber.tag(TAG).e("Exception!", e);
+ } catch (final NullPointerException e) {
+ // Currently an NPE is thrown when the Camera2API is used but not supported on the
+ // device this code runs.
+ ErrorDialog.newInstance(getString(R.string.camera_error))
+ .show(getChildFragmentManager(), FRAGMENT_DIALOG);
+ }
+ }
+
+ /**
+ * Opens the camera specified by {@link CameraConnectionFragment#cameraId}.
+ */
+ @SuppressLint("LongLogTag")
+ @DebugLog
+ private void openCamera(final int width, final int height) {
+ Log.d(TAG, "width "+width+"height "+height);
+ setUpCameraOutputs(width, height);
+ configureTransform(width, height);
+ final Activity activity = getActivity();
+ final CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
+ try {
+ if (!cameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
+ throw new RuntimeException("Time out waiting to lock camera opening.");
+ }
+ if (ActivityCompat.checkSelfPermission(this.getActivity(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
+ Timber.tag(TAG).w("checkSelfPermission CAMERA");
+ }
+ //we just use facing front camera to detect
+ manager.openCamera("1", stateCallback, backgroundHandler);
+ Timber.tag(TAG).d("open Camera");
+ } catch (final CameraAccessException e) {
+ Timber.tag(TAG).e("Exception!", e);
+ } catch (final InterruptedException e) {
+ throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
+ }
+ }
+
+ /**
+ * Closes the current {@link CameraDevice}.
+ */
+ @DebugLog
+ private void closeCamera() {
+ try {
+ cameraOpenCloseLock.acquire();
+ if (null != captureSession) {
+ captureSession.close();
+ captureSession = null;
+ }
+ if (null != cameraDevice) {
+ cameraDevice.close();
+ cameraDevice = null;
+ }
+ if (null != previewReader) {
+ previewReader.close();
+ previewReader = null;
+ }
+ if (null != mOnGetPreviewListener) {
+ mOnGetPreviewListener.deInitialize();
+ }
+ } catch (final InterruptedException e) {
+ throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
+ } finally {
+ cameraOpenCloseLock.release();
+ }
+ }
+
+ /**
+ * Starts a background thread and its {@link Handler}.
+ */
+ @DebugLog
+ private void startBackgroundThread() {
+ backgroundThread = new HandlerThread("ImageListener");
+ backgroundThread.start();
+ backgroundHandler = new Handler(backgroundThread.getLooper());
+
+ inferenceThread = new HandlerThread("InferenceThread");
+ inferenceThread.start();
+ inferenceHandler = new Handler(inferenceThread.getLooper());
+ }
+
+ /**
+ * Stops the background thread and its {@link Handler}.
+ */
+ @SuppressLint("LongLogTag")
+ @DebugLog
+ private void stopBackgroundThread() {
+ backgroundThread.quitSafely();
+ inferenceThread.quitSafely();
+ try {
+ backgroundThread.join();
+ backgroundThread = null;
+ backgroundHandler = null;
+
+ inferenceThread.join();
+ inferenceThread = null;
+ inferenceThread = null;
+ } catch (final InterruptedException e) {
+ Timber.tag(TAG).e("error", e);
+ }
+ }
+
+ private final OnGetImageListener mOnGetPreviewListener = new OnGetImageListener();
+
+ private final CameraCaptureSession.CaptureCallback captureCallback =
+ new CameraCaptureSession.CaptureCallback() {
+ @Override
+ public void onCaptureProgressed(
+ final CameraCaptureSession session,
+ final CaptureRequest request,
+ final CaptureResult partialResult) {
+ }
+
+ @Override
+ public void onCaptureCompleted(
+ final CameraCaptureSession session,
+ final CaptureRequest request,
+ final TotalCaptureResult result) {
+ }
+ };
+
+ /**
+ * Creates a new {@link CameraCaptureSession} for camera preview.
+ */
+ @SuppressLint("LongLogTag")
+ @DebugLog
+ private void createCameraPreviewSession() {
+ try {
+// final SurfaceTexture texture = textureView.getSurfaceTexture();
+// assert texture != null;
+
+ // We configure the size of default buffer to be the size of camera preview we want.
+// texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
+ //texture.setOnFrameAvailableListener();
+
+ // This is the output Surface we need to start preview.
+// final Surface surface = new Surface(texture);
+
+ // We set up a CaptureRequest.Builder with the output Surface.
+ previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ //previewRequestBuilder.addTarget(surface);
+
+ // 设置预览画面的帧率 视实际情况而定选择一个帧率范围
+// previewRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRanges[0]);
+
+ // Create the reader for the preview frames.
+ previewReader =
+ ImageReader.newInstance(
+ previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 2);
+
+ previewReader.setOnImageAvailableListener(mOnGetPreviewListener, backgroundHandler);
+ previewRequestBuilder.addTarget(previewReader.getSurface());
+
+ // Here, we create a CameraCaptureSession for camera preview.
+ cameraDevice.createCaptureSession(
+ //Arrays.asList(surface,previewReader.getSurface()), new CameraCaptureSession.StateCallback() {
+ Arrays.asList(previewReader.getSurface()), new CameraCaptureSession.StateCallback() {
+
+ @Override
+ public void onConfigured(final CameraCaptureSession cameraCaptureSession) {
+ // The camera is already closed
+ if (null == cameraDevice) {
+ return;
+ }
+
+ // When the session is ready, we start displaying the preview.
+ captureSession = cameraCaptureSession;
+ try {
+ // Auto focus should be continuous for camera preview.
+ previewRequestBuilder.set(
+ CaptureRequest.CONTROL_AF_MODE,
+ CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+ // Flash is automatically enabled when necessary.
+ previewRequestBuilder.set(
+ CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
+
+ // Finally, we start displaying the camera preview.
+ previewRequest = previewRequestBuilder.build();
+ captureSession.setRepeatingRequest(
+ previewRequest, captureCallback, backgroundHandler);
+ } catch (final CameraAccessException e) {
+ Timber.tag(TAG).e("Exception!", e);
+ }
+ }
+
+ @Override
+ public void onConfigureFailed(final CameraCaptureSession cameraCaptureSession) {
+ showToast("Failed");
+ }
+ },
+ null);
+ } catch (final CameraAccessException e) {
+ Timber.tag(TAG).e("Exception!", e);
+ }
+
+ mOnGetPreviewListener.initialize(getActivity().getApplicationContext(), getActivity().getAssets(), mScoreView, inferenceHandler);
+ }
+
+ /**
+ * Configures the necessary {@link android.graphics.Matrix} transformation to `mTextureView`.
+ * This method should be called after the camera preview size is determined in
+ * setUpCameraOutputs and also the size of `mTextureView` is fixed.
+ *
+ * @param viewWidth The width of `mTextureView`
+ * @param viewHeight The height of `mTextureView`
+ */
+ @DebugLog
+ private void configureTransform(final int viewWidth, final int viewHeight) {
+ final Activity activity = getActivity();
+ if (null == textureView || null == previewSize || null == activity) {
+ return;
+ }
+ final int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
+ final Matrix matrix = new Matrix();
+ final RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
+ final RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth());
+ final float centerX = viewRect.centerX();
+ final float centerY = viewRect.centerY();
+ if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
+ bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
+ matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
+ final float scale =
+ Math.max(
+ (float) viewHeight / previewSize.getHeight(),
+ (float) viewWidth / previewSize.getWidth());
+ matrix.postScale(scale, scale, centerX, centerY);
+ matrix.postRotate(90 * (rotation - 2), centerX, centerY);
+ } else if (Surface.ROTATION_180 == rotation) {
+ matrix.postRotate(180, centerX, centerY);
+ }
+ textureView.setTransform(matrix);
+ }
+
+ /**
+ * Compares two {@code Size}s based on their areas.
+ */
+ static class CompareSizesByArea implements Comparator {
+ @Override
+ public int compare(final Size lhs, final Size rhs) {
+ // We cast here to ensure the multiplications won't overflow
+ return Long.signum(
+ (long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight());
+ }
+ }
+
+ /**
+ * Shows an error message dialog.
+ */
+ public static class ErrorDialog extends DialogFragment {
+ private static final String ARG_MESSAGE = "message";
+
+ public static ErrorDialog newInstance(final String message) {
+ final ErrorDialog dialog = new ErrorDialog();
+ final Bundle args = new Bundle();
+ args.putString(ARG_MESSAGE, message);
+ dialog.setArguments(args);
+ return dialog;
+ }
+
+ @Override
+ public Dialog onCreateDialog(final Bundle savedInstanceState) {
+ final Activity activity = getActivity();
+ return new AlertDialog.Builder(activity)
+ .setMessage(getArguments().getString(ARG_MESSAGE))
+ .setPositiveButton(
+ android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialogInterface, final int i) {
+ activity.finish();
+ }
+ })
+ .create();
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tzutalin/dlibtest/DebugLogFileTree.java b/app/src/main/java/com/tzutalin/dlibtest/DebugLogFileTree.java
new file mode 100644
index 0000000..758e4e9
--- /dev/null
+++ b/app/src/main/java/com/tzutalin/dlibtest/DebugLogFileTree.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2017-present. Tzutalin
+ * 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.tzutalin.dlibtest;
+
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.ref.WeakReference;
+import java.net.UnknownHostException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import timber.log.Timber;
+
+/**
+ * Created by tzutalin on 2017/2/23.
+ */
+public class DebugLogFileTree extends Timber.DebugTree {
+
+ private static final String TWO_SPACE = " ";
+ private final String mLogDir;
+ private final String mFilePath;
+ private final LogWriterWorker mLogWriterWorker;
+
+ public DebugLogFileTree(String dir) {
+ mLogDir = dir;
+ mFilePath = mLogDir + File.separator + "dlib.log";
+ mLogWriterWorker = new LogWriterWorker();
+ mLogWriterWorker.start(this);
+ }
+
+ @Override
+ protected void log(int priority, String tag, String message, Throwable t) {
+ super.log(priority, tag, message, t);
+ mLogWriterWorker.put(formatLog(priority, tag, message, t));
+ }
+
+ private String formatLog(int priority, String tag, String message, Throwable throwable) {
+ StringBuilder sb = new StringBuilder();
+ if (throwable != null && message == null) {
+ message = getStackTraceString(throwable);
+ }
+ if (message == null) {
+ message = "No message/exception is set";
+ }
+
+ sb.append(getTimeStamp()).append(TWO_SPACE);
+ sb.append(getThreadSignature()).append(TWO_SPACE);
+ sb.append(tag).append(":").append(getPriorityString(priority)).append(TWO_SPACE);
+ sb.append(message);
+ return sb.toString();
+ }
+
+ private String getStackTraceString(Throwable tr) {
+ if (tr == null) {
+ return "";
+ }
+
+ // This is to reduce the amount of log spew that apps do in the non-error
+ // condition of the network being unavailable.
+ Throwable t = tr;
+ while (t != null) {
+ if (t instanceof UnknownHostException) {
+ return "";
+ }
+ t = t.getCause();
+ }
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ tr.printStackTrace(pw);
+ pw.flush();
+ return sw.toString();
+ }
+
+ private String getThreadSignature() {
+ Thread t = Thread.currentThread();
+ String name = t.getName();
+ int id = android.os.Process.myTid();
+ return String.format("%s:%d", name, id);
+ }
+
+ private String getTimeStamp() {
+ return new SimpleDateFormat("HH:mm:ss.SSS").format(new Date());
+ }
+
+ public String getPriorityString(int priority) {
+ if (priority == Log.ASSERT) {
+ return "A";
+ } else if (priority == Log.ERROR) {
+ return "E";
+ } else if (priority == Log.WARN) {
+ return "W";
+ } else if (priority == Log.INFO) {
+ return "I";
+ } else if (priority == Log.DEBUG) {
+ return "D";
+ } else if (priority == Log.VERBOSE) {
+ return "V";
+ }
+ return "";
+ }
+
+ private static class LogWriterWorker implements Runnable {
+ private WeakReference mWeakRef;
+ private BufferedWriter mBufferedWriter;
+ private BlockingQueue mQueue;
+
+ public void start(@NonNull DebugLogFileTree tree) {
+ if (isStart() == false) {
+ mWeakRef = new WeakReference(tree);
+ mQueue = new LinkedBlockingQueue<>();
+ new Thread(this).start();
+ }
+ }
+
+ // Producer on any thread
+ public void put(@NonNull String msg) {
+ if (mQueue == null) return;
+ try {
+ mQueue.put(msg);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ // Consumer on LogWriterWorker's thread
+ @Override
+ public void run() {
+ // Open a new log file
+ if (isLogFileOpen() == false)
+ open(mWeakRef.get().mFilePath);
+ String log;
+ try {
+ while ((log = mQueue.take()) != null) {
+ appendLog(log);
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } finally {
+ mQueue.clear();
+ mQueue = null;
+ close();
+ }
+ }
+
+ private boolean isStart() {
+ return mQueue != null;
+ }
+
+ private boolean isLogFileOpen() {
+ return mBufferedWriter != null;
+ }
+
+ private boolean open(@NonNull String newFileName) {
+ if (TextUtils.isEmpty(newFileName)) return false;
+ File logFile = new File(newFileName);
+ // Create log file if not exists.
+ if (!logFile.exists()) {
+ try {
+ File parent = logFile.getParentFile();
+ if (!parent.exists()) {
+ parent.mkdirs();
+ }
+ logFile.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ // Create buffered writer.
+ try {
+ mBufferedWriter = new BufferedWriter(new FileWriter(logFile, true));
+ } catch (Exception e) {
+ e.printStackTrace();
+ close();
+ return false;
+ }
+ return true;
+ }
+
+ private boolean close() {
+ if (mBufferedWriter != null) {
+ try {
+ mBufferedWriter.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ } finally {
+ mBufferedWriter = null;
+ }
+ }
+ return true;
+ }
+
+ private void appendLog(@NonNull String flattenedLog) {
+ try {
+ mBufferedWriter.write(flattenedLog);
+ mBufferedWriter.newLine();
+ mBufferedWriter.flush();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/tzutalin/dlibtest/DlibDemoApp.java b/app/src/main/java/com/tzutalin/dlibtest/DlibDemoApp.java
new file mode 100644
index 0000000..96b9e5f
--- /dev/null
+++ b/app/src/main/java/com/tzutalin/dlibtest/DlibDemoApp.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2017. Tzutalin
+ * 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.tzutalin.dlibtest;
+
+import android.app.Application;
+import android.util.Log;
+
+import timber.log.Timber;
+
+/**
+ * Created by tzutalin on 2017/2/23.
+ */
+public class DlibDemoApp extends Application {
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ if (BuildConfig.DEBUG) {
+ Timber.plant(new Timber.DebugTree());
+ //Timber.plant(new DebugLogFileTree(Environment.getExternalStorageDirectory().toString()));
+ } else {
+ Timber.plant(new ReleaseTree());
+ }
+ }
+
+ /**
+ * A tree which logs important information
+ */
+ private static class ReleaseTree extends Timber.DebugTree {
+ @Override
+ protected void log(int priority, String tag, String message, Throwable t) {
+ if (priority == Log.VERBOSE || priority == Log.DEBUG) {
+ return;
+ }
+ super.log(priority, tag, message, t);
+ }
+ }
+}
diff --git a/app/src/main/java/com/tzutalin/dlibtest/FileUtils.java b/app/src/main/java/com/tzutalin/dlibtest/FileUtils.java
new file mode 100644
index 0000000..fdde049
--- /dev/null
+++ b/app/src/main/java/com/tzutalin/dlibtest/FileUtils.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2016-present Tzutalin
+ *
+ * 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.tzutalin.dlibtest;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.RawRes;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Created by Tzutalin on 2016/3/30.
+ */
+public class FileUtils {
+ @NonNull
+ public static final void copyFileFromRawToOthers(@NonNull final Context context, @RawRes int id, @NonNull final String targetPath) {
+ InputStream in = context.getResources().openRawResource(id);
+ FileOutputStream out = null;
+ try {
+ out = new FileOutputStream(targetPath);
+ byte[] buff = new byte[1024];
+ int read = 0;
+ while ((read = in.read(buff)) > 0) {
+ out.write(buff, 0, read);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ if (in != null) {
+ in.close();
+ }
+ if (out != null) {
+ out.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/tzutalin/dlibtest/FloatingCameraWindow.java b/app/src/main/java/com/tzutalin/dlibtest/FloatingCameraWindow.java
new file mode 100644
index 0000000..e854d38
--- /dev/null
+++ b/app/src/main/java/com/tzutalin/dlibtest/FloatingCameraWindow.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2016-present Tzuta Lin
+ *
+ * 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
+ * imitations under the License.
+ */
+
+package com.tzutalin.dlibtest;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.annotation.UiThread;
+import android.util.Log;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Created by Tzutalin on 2016/5/25
+ */
+public class FloatingCameraWindow {
+ private static final String TAG = "FloatingCameraWindow";
+ private Context mContext;
+ private WindowManager.LayoutParams mWindowParam;
+ private WindowManager mWindowManager;
+ private FloatCamView mRootView;
+ private Handler mUIHandler;
+
+ private int mWindowWidth;
+ private int mWindowHeight;
+
+ private int mScreenMaxWidth;
+ private int mScreenMaxHeight;
+
+ private float mScaleWidthRatio = 1.0f;
+ private float mScaleHeightRatio = 1.0f;
+
+ private static final boolean DEBUG = true;
+
+ public FloatingCameraWindow(Context context) {
+ mContext = context;
+ mUIHandler = new Handler(Looper.getMainLooper());
+
+ // Get screen max size
+ Point size = new Point();
+ Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ display.getSize(size);
+ mScreenMaxWidth = size.x;
+ mScreenMaxHeight = size.y;
+ } else {
+ mScreenMaxWidth = display.getWidth();
+ mScreenMaxHeight = display.getHeight();
+ }
+ // Default window size
+ mWindowWidth = mScreenMaxWidth; // / 2;
+ mWindowHeight = mScreenMaxHeight; // / 2;
+
+// mWindowWidth = mWindowWidth > 0 && mWindowWidth < mScreenMaxWidth ? mWindowWidth : mScreenMaxWidth;
+// mWindowHeight = mWindowHeight > 0 && mWindowHeight < mScreenMaxHeight ? mWindowHeight : mScreenMaxHeight;
+ }
+
+ public FloatingCameraWindow(Context context, int windowWidth, int windowHeight) {
+ this(context);
+
+ if (windowWidth < 0 || windowWidth > mScreenMaxWidth || windowHeight < 0 || windowHeight > mScreenMaxHeight) {
+ throw new IllegalArgumentException("Window size is illegal");
+ }
+
+ mScaleWidthRatio = (float) windowWidth / mWindowHeight;
+ mScaleHeightRatio = (float) windowHeight / mWindowHeight;
+
+ if (DEBUG) {
+ Log.d(TAG, "mScaleWidthRatio: " + mScaleWidthRatio);
+ Log.d(TAG, "mScaleHeightRatio: " + mScaleHeightRatio);
+ }
+
+ mWindowWidth = windowWidth;
+ mWindowHeight = windowHeight;
+ }
+
+ private void init() {
+ mUIHandler.postAtFrontOfQueue(new Runnable() {
+ @Override
+ public void run() {
+ if (mWindowManager == null || mRootView == null) {
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ mRootView = new FloatCamView(FloatingCameraWindow.this);
+ mWindowManager.addView(mRootView, initWindowParameter());
+ }
+ }
+ });
+ }
+
+ public void release() {
+ mUIHandler.postAtFrontOfQueue(new Runnable() {
+ @Override
+ public void run() {
+ if (mWindowManager != null) {
+ mWindowManager.removeViewImmediate(mRootView);
+ mRootView = null;
+ }
+ mUIHandler.removeCallbacksAndMessages(null);
+ }
+ });
+ }
+
+ private WindowManager.LayoutParams initWindowParameter() {
+ mWindowParam = new WindowManager.LayoutParams();
+
+ mWindowParam.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+ mWindowParam.format = 1;
+ mWindowParam.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ mWindowParam.flags = mWindowParam.flags | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+ mWindowParam.flags = mWindowParam.flags | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+
+ mWindowParam.alpha = 1.0f;
+
+ mWindowParam.gravity = Gravity.BOTTOM | Gravity.RIGHT;
+ mWindowParam.x = 0;
+ mWindowParam.y = 0;
+ mWindowParam.width = mWindowWidth;
+ mWindowParam.height = mWindowHeight;
+ return mWindowParam;
+ }
+
+ public void setRGBBitmap(final Bitmap rgb) {
+ checkInit();
+ mUIHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mRootView.setRGBImageView(rgb);
+ }
+ });
+ }
+
+ public void setInformation(final String info) {
+ checkInit();
+ mUIHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ checkInit();
+ mRootView.setInformation(info);
+ }
+ });
+ }
+
+ public void setMoreInformation(final String info) {
+ checkInit();
+ mUIHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ checkInit();
+ mRootView.setMoreInformation(info);
+ }
+ });
+ }
+
+ private void checkInit() {
+ if (mRootView == null) {
+ init();
+ }
+ }
+
+ @UiThread
+ private final class FloatCamView extends FrameLayout {
+ private WeakReference mWeakRef;
+ private static final int MOVE_THRESHOLD = 10;
+ private int mLastX;
+ private int mLastY;
+ private int mFirstX;
+ private int mFirstY;
+ private LayoutInflater mLayoutInflater;
+ private ImageView mColorView;
+ private TextView mFPSText;
+ private TextView mInfoText;
+ private boolean mIsMoving = false;
+
+ public FloatCamView(FloatingCameraWindow window) {
+ super(window.mContext);
+ mWeakRef = new WeakReference(window);
+ // mLayoutInflater = LayoutInflater.from(context);
+ mLayoutInflater = (LayoutInflater) window.mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ FrameLayout body = (FrameLayout) this;
+ body.setOnTouchListener(new OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ return false;
+ }
+ });
+
+ View floatView = mLayoutInflater.inflate(R.layout.cam_window_view, body, true);
+ mColorView = (ImageView) findViewById(R.id.imageView_c);
+ mFPSText = (TextView) findViewById(R.id.fps_textview);
+ mInfoText = (TextView) findViewById(R.id.info_textview);
+ mFPSText.setVisibility(View.GONE);
+ mInfoText.setVisibility(View.GONE);
+
+ int colorMaxWidth = (int) (mWindowWidth* window.mScaleWidthRatio);
+ int colorMaxHeight = (int) (mWindowHeight * window.mScaleHeightRatio);
+
+ mColorView.getLayoutParams().width = colorMaxWidth;
+ mColorView.getLayoutParams().height = colorMaxHeight;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mLastX = (int) event.getRawX();
+ mLastY = (int) event.getRawY();
+ mFirstX = mLastX;
+ mFirstY = mLastY;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ int deltaX = (int) event.getRawX() - mLastX;
+ int deltaY = (int) event.getRawY() - mLastY;
+ mLastX = (int) event.getRawX();
+ mLastY = (int) event.getRawY();
+ int totalDeltaX = mLastX - mFirstX;
+ int totalDeltaY = mLastY - mFirstY;
+
+ if (mIsMoving
+ || Math.abs(totalDeltaX) >= MOVE_THRESHOLD
+ || Math.abs(totalDeltaY) >= MOVE_THRESHOLD) {
+ mIsMoving = true;
+ WindowManager windowMgr = mWeakRef.get().mWindowManager;
+ WindowManager.LayoutParams parm = mWeakRef.get().mWindowParam;
+ if (event.getPointerCount() == 1 && windowMgr != null) {
+ parm.x -= deltaX;
+ parm.y -= deltaY;
+ windowMgr.updateViewLayout(this, parm);
+ }
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ mIsMoving = false;
+ break;
+ }
+ return true;
+ }
+
+ public void setRGBImageView(Bitmap rgb) {
+ if (rgb != null && !rgb.isRecycled()) {
+ mColorView.setImageBitmap(rgb);
+ }
+ }
+
+ public void setInformation(String info) {
+ if (mFPSText != null) {
+ if (mFPSText.getVisibility() == View.GONE) {
+ mFPSText.setVisibility(View.VISIBLE);
+ }
+ mFPSText.setText(info);
+ }
+ }
+
+ public void setMoreInformation(String info) {
+ if (mInfoText != null) {
+ if (mInfoText.getVisibility() == View.GONE) {
+ mInfoText.setVisibility(View.VISIBLE);
+ }
+ mInfoText.setText(info);
+ }
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/tzutalin/dlibtest/ImageUtils.java b/app/src/main/java/com/tzutalin/dlibtest/ImageUtils.java
new file mode 100644
index 0000000..42a05d3
--- /dev/null
+++ b/app/src/main/java/com/tzutalin/dlibtest/ImageUtils.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2016-present Tzutalin
+ *
+ * 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.tzutalin.dlibtest;
+
+import android.graphics.Bitmap;
+import android.os.Environment;
+import android.support.annotation.Keep;
+
+import java.io.File;
+import java.io.FileOutputStream;
+
+import timber.log.Timber;
+
+/**
+ * Utility class for manipulating images.
+ **/
+public class ImageUtils {
+ private static final String TAG = ImageUtils.class.getSimpleName();
+
+ /**
+ * Utility method to compute the allocated size in bytes of a YUV420SP image
+ * of the given dimensions.
+ */
+ public static int getYUVByteSize(final int width, final int height) {
+ // The luminance plane requires 1 byte per pixel.
+ final int ySize = width * height;
+
+ // The UV plane works on 2x2 blocks, so dimensions with odd size must be rounded up.
+ // Each 2x2 block takes 2 bytes to encode, one each for U and V.
+ final int uvSize = ((width + 1) / 2) * ((height + 1) / 2) * 2;
+
+ return ySize + uvSize;
+ }
+
+ /**
+ * Saves a Bitmap object to disk for analysis.
+ *
+ * @param bitmap The bitmap to save.
+ */
+ public static void saveBitmap(final Bitmap bitmap) {
+ final String root =
+ Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "dlib";
+ Timber.tag(TAG).d(String.format("Saving %dx%d bitmap to %s.", bitmap.getWidth(), bitmap.getHeight(), root));
+ final File myDir = new File(root);
+
+ if (!myDir.mkdirs()) {
+ Timber.tag(TAG).e("Make dir failed");
+ }
+
+ final String fname = "preview.png";
+ final File file = new File(myDir, fname);
+ if (file.exists()) {
+ file.delete();
+ }
+ try {
+ final FileOutputStream out = new FileOutputStream(file);
+ bitmap.compress(Bitmap.CompressFormat.PNG, 99, out);
+ out.flush();
+ out.close();
+ } catch (final Exception e) {
+ Timber.tag(TAG).e("Exception!", e);
+ }
+ }
+
+ /**
+ * Converts YUV420 semi-planar data to ARGB 8888 data using the supplied width
+ * and height. The input and output must already be allocated and non-null.
+ * For efficiency, no error checking is performed.
+ *
+ * @param input The array of YUV 4:2:0 input data.
+ * @param output A pre-allocated array for the ARGB 8:8:8:8 output data.
+ * @param width The width of the input image.
+ * @param height The height of the input image.
+ * @param halfSize If true, downsample to 50% in each dimension, otherwise not.
+ */
+ public static native void convertYUV420SPToARGB8888(
+ byte[] input, int[] output, int width, int height, boolean halfSize);
+
+ /**
+ * Converts YUV420 semi-planar data to ARGB 8888 data using the supplied width
+ * and height. The input and output must already be allocated and non-null.
+ * For efficiency, no error checking is performed.
+ *
+ * @param y
+ * @param u
+ * @param v
+ * @param uvPixelStride
+ * @param width The width of the input image.
+ * @param height The height of the input image.
+ * @param halfSize If true, downsample to 50% in each dimension, otherwise not.
+ * @param output A pre-allocated array for the ARGB 8:8:8:8 output data.
+ */
+ @Keep
+ public static native void convertYUV420ToARGB8888(
+ byte[] y,
+ byte[] u,
+ byte[] v,
+ int[] output,
+ int width,
+ int height,
+ int yRowStride,
+ int uvRowStride,
+ int uvPixelStride,
+ boolean halfSize);
+
+ /**
+ * Converts YUV420 semi-planar data to RGB 565 data using the supplied width
+ * and height. The input and output must already be allocated and non-null.
+ * For efficiency, no error checking is performed.
+ *
+ * @param input The array of YUV 4:2:0 input data.
+ * @param output A pre-allocated array for the RGB 5:6:5 output data.
+ * @param width The width of the input image.
+ * @param height The height of the input image.
+ */
+ @Keep
+ public static native void convertYUV420SPToRGB565(
+ byte[] input, byte[] output, int width, int height);
+
+ /**
+ * Converts 32-bit ARGB8888 image data to YUV420SP data. This is useful, for
+ * instance, in creating data to feed the classes that rely on raw camera
+ * preview frames.
+ *
+ * @param input An array of input pixels in ARGB8888 format.
+ * @param output A pre-allocated array for the YUV420SP output data.
+ * @param width The width of the input image.
+ * @param height The height of the input image.
+ */
+ @Keep
+ public static native void convertARGB8888ToYUV420SP(
+ int[] input, byte[] output, int width, int height);
+
+ /**
+ * Converts 16-bit RGB565 image data to YUV420SP data. This is useful, for
+ * instance, in creating data to feed the classes that rely on raw camera
+ * preview frames.
+ *
+ * @param input An array of input pixels in RGB565 format.
+ * @param output A pre-allocated array for the YUV420SP output data.
+ * @param width The width of the input image.
+ * @param height The height of the input image.
+ */
+ @Keep
+ public static native void convertRGB565ToYUV420SP(
+ byte[] input, byte[] output, int width, int height);
+}
diff --git a/app/src/main/java/com/tzutalin/dlibtest/MainActivity.java b/app/src/main/java/com/tzutalin/dlibtest/MainActivity.java
new file mode 100644
index 0000000..7b6185b
--- /dev/null
+++ b/app/src/main/java/com/tzutalin/dlibtest/MainActivity.java
@@ -0,0 +1,385 @@
+/*
+* Copyright (C) 2015-present TzuTaLin
+*/
+
+package com.tzutalin.dlibtest;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.support.annotation.NonNull;
+import android.support.design.widget.FloatingActionButton;
+import android.support.v4.app.ActivityCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.View;
+import android.widget.Toast;
+
+import com.dexafree.materialList.card.Card;
+import com.dexafree.materialList.card.provider.BigImageCardProvider;
+import com.dexafree.materialList.view.MaterialListView;
+import com.tzutalin.dlib.Constants;
+import com.tzutalin.dlib.FaceDet;
+import com.tzutalin.dlib.PedestrianDet;
+import com.tzutalin.dlib.VisionDetRet;
+
+import org.androidannotations.annotations.AfterViews;
+import org.androidannotations.annotations.Background;
+import org.androidannotations.annotations.Click;
+import org.androidannotations.annotations.EActivity;
+import org.androidannotations.annotations.UiThread;
+import org.androidannotations.annotations.ViewById;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import hugo.weaving.DebugLog;
+import timber.log.Timber;
+
+@EActivity(R.layout.activity_main)
+public class MainActivity extends AppCompatActivity {
+ private static final int RESULT_LOAD_IMG = 1;
+ private static final int REQUEST_CODE_PERMISSION = 2;
+
+ private static final String TAG = "MainActivity";
+
+ // Storage Permissions
+ private static String[] PERMISSIONS_REQ = {
+ Manifest.permission.READ_EXTERNAL_STORAGE,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.CAMERA
+ };
+
+ protected String mTestImgPath;
+ // UI
+ @ViewById(R.id.material_listview)
+ protected MaterialListView mListView;
+ @ViewById(R.id.fab)
+ protected FloatingActionButton mFabActionBt;
+ @ViewById(R.id.fab_cam)
+ protected FloatingActionButton mFabCamActionBt;
+ @ViewById(R.id.toolbar)
+ protected Toolbar mToolbar;
+
+ FaceDet mFaceDet;
+ PedestrianDet mPersonDet;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mListView = (MaterialListView) findViewById(R.id.material_listview);
+ setSupportActionBar(mToolbar);
+ // Just use hugo to print log
+ isExternalStorageWritable();
+ isExternalStorageReadable();
+
+ // For API 23+ you need to request the read/write permissions even if they are already in your manifest.
+ int currentapiVersion = android.os.Build.VERSION.SDK_INT;
+
+ if (currentapiVersion >= Build.VERSION_CODES.M) {
+ verifyPermissions(this);
+ }
+
+ //we set "noDetection" as default detectionMode
+ SharedPreferences mSharedPreferences = getSharedPreferences("userInfo", MODE_PRIVATE);
+ SharedPreferences.Editor edit = mSharedPreferences.edit();
+ edit.putString("detectionMode", "noDetection");
+ edit.commit();
+
+ }
+
+ @AfterViews
+ protected void setupUI() {
+ mToolbar.setTitle(getString(R.string.app_name));
+ Toast.makeText(MainActivity.this, getString(R.string.description_info), Toast.LENGTH_LONG).show();
+
+ FloatingActionButton mSettingBt = (FloatingActionButton) findViewById(R.id.setting);
+ mSettingBt.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startActivity(new Intent(MainActivity.this, Setting.class));
+ }
+ });
+
+ }
+
+ @Click({R.id.fab})
+ protected void launchGallery() {
+ Toast.makeText(MainActivity.this, "Pick one image", Toast.LENGTH_SHORT).show();
+ Intent galleryIntent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+ startActivityForResult(galleryIntent, RESULT_LOAD_IMG);
+ }
+
+ @Click({R.id.fab_cam})
+ protected void launchCameraPreview() {
+ startActivity(new Intent(this, CameraActivity.class));
+ }
+
+ /**
+ * Checks if the app has permission to write to device storage or open camera
+ * If the app does not has permission then the user will be prompted to grant permissions
+ *
+ * @param activity
+ */
+ @DebugLog
+ private static boolean verifyPermissions(Activity activity) {
+ // Check if we have write permission
+ int write_permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ int read_persmission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ int camera_permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.CAMERA);
+
+ if (write_permission != PackageManager.PERMISSION_GRANTED ||
+ read_persmission != PackageManager.PERMISSION_GRANTED ||
+ camera_permission != PackageManager.PERMISSION_GRANTED) {
+ // We don't have permission so prompt the user
+ ActivityCompat.requestPermissions(
+ activity,
+ PERMISSIONS_REQ,
+ REQUEST_CODE_PERMISSION
+ );
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /* Checks if external storage is available for read and write */
+ @DebugLog
+ private boolean isExternalStorageWritable() {
+ String state = Environment.getExternalStorageState();
+ if (Environment.MEDIA_MOUNTED.equals(state)) {
+ return true;
+ }
+ return false;
+ }
+
+ /* Checks if external storage is available to at least read */
+ @DebugLog
+ private boolean isExternalStorageReadable() {
+ String state = Environment.getExternalStorageState();
+ if (Environment.MEDIA_MOUNTED.equals(state) ||
+ Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
+ return true;
+ }
+ return false;
+ }
+
+ @DebugLog
+ protected void demoStaticImage() {
+ if (mTestImgPath != null) {
+ Timber.tag(TAG).d("demoStaticImage() launch a task to det");
+ runDetectAsync(mTestImgPath);
+ } else {
+ Timber.tag(TAG).d("demoStaticImage() mTestImgPath is null, go to gallery");
+ Toast.makeText(MainActivity.this, "Pick an image to run algorithms", Toast.LENGTH_SHORT).show();
+ // Create intent to Open Image applications like Gallery, Google Photos
+ Intent galleryIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+ startActivityForResult(galleryIntent, RESULT_LOAD_IMG);
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ if (requestCode == REQUEST_CODE_PERMISSION) {
+ Toast.makeText(MainActivity.this, "Demo using static images", Toast.LENGTH_SHORT).show();
+ demoStaticImage();
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ try {
+ // When an Image is picked
+ if (requestCode == RESULT_LOAD_IMG && resultCode == RESULT_OK && null != data) {
+ // Get the Image from data
+ Uri selectedImage = data.getData();
+ String[] filePathColumn = {MediaStore.Images.Media.DATA};
+ // Get the cursor
+ Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null);
+ cursor.moveToFirst();
+ int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
+ mTestImgPath = cursor.getString(columnIndex);
+ cursor.close();
+ if (mTestImgPath != null) {
+ runDetectAsync(mTestImgPath);
+ //Toast.makeText(this, "Img Path:" + mTestImgPath, Toast.LENGTH_SHORT).show();
+ }
+ } else {
+ Toast.makeText(this, "You haven't picked Image", Toast.LENGTH_LONG).show();
+ }
+ } catch (Exception e) {
+ Toast.makeText(this, "Something went wrong", Toast.LENGTH_LONG).show();
+ }
+ }
+
+ // ==========================================================
+ // Tasks inner class
+ // ==========================================================
+ private ProgressDialog mDialog;
+
+ @Background
+ @NonNull
+ protected void runDetectAsync(@NonNull String imgPath) {
+ showDiaglog();
+
+ final String targetPath = Constants.getFaceShapeModelPath();
+ if (!new File(targetPath).exists()) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(MainActivity.this, "Copy landmark model to " + targetPath, Toast.LENGTH_SHORT).show();
+ }
+ });
+ FileUtils.copyFileFromRawToOthers(getApplicationContext(), R.raw.shape_predictor_68_face_landmarks, targetPath);
+ }
+ // Init
+ if (mPersonDet == null) {
+ mPersonDet = new PedestrianDet();
+ }
+ if (mFaceDet == null) {
+ mFaceDet = new FaceDet(Constants.getFaceShapeModelPath());
+ }
+
+ Timber.tag(TAG).d("Image path: " + imgPath);
+ List cardrets = new ArrayList<>();
+ List faceList = mFaceDet.detect(imgPath);
+ if (faceList.size() > 0) {
+ Card card = new Card.Builder(MainActivity.this)
+ .withProvider(BigImageCardProvider.class)
+ .setDrawable(drawRect(imgPath, faceList, Color.GREEN))
+ .setTitle("Face det")
+ .endConfig()
+ .build();
+ cardrets.add(card);
+ } else {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(getApplicationContext(), "No face", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ List personList = mPersonDet.detect(imgPath);
+ if (personList.size() > 0) {
+ Card card = new Card.Builder(MainActivity.this)
+ .withProvider(BigImageCardProvider.class)
+ .setDrawable(drawRect(imgPath, personList, Color.BLUE))
+ .setTitle("Person det")
+ .endConfig()
+ .build();
+ cardrets.add(card);
+ } else {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(getApplicationContext(), "No person", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ addCardListView(cardrets);
+ dismissDialog();
+ }
+
+ @UiThread
+ protected void addCardListView(List cardrets) {
+ for (Card each : cardrets) {
+ mListView.add(each);
+ }
+ }
+
+ @UiThread
+ protected void showDiaglog() {
+ mDialog = ProgressDialog.show(MainActivity.this, "Wait", "Face detection", true);
+ }
+
+ @UiThread
+ protected void dismissDialog() {
+ if (mDialog != null) {
+ mDialog.dismiss();
+ }
+ }
+
+ @DebugLog
+ protected BitmapDrawable drawRect(String path, List results, int color) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inSampleSize = 1;
+ Bitmap bm = BitmapFactory.decodeFile(path, options);
+ android.graphics.Bitmap.Config bitmapConfig = bm.getConfig();
+ // set default bitmap config if none
+ if (bitmapConfig == null) {
+ bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888;
+ }
+ // resource bitmaps are imutable,
+ // so we need to convert it to mutable one
+ bm = bm.copy(bitmapConfig, true);
+ int width = bm.getWidth();
+ int height = bm.getHeight();
+ // By ratio scale
+ float aspectRatio = bm.getWidth() / (float) bm.getHeight();
+
+ final int MAX_SIZE = 512;
+ int newWidth = MAX_SIZE;
+ int newHeight = MAX_SIZE;
+ float resizeRatio = 1;
+ newHeight = Math.round(newWidth / aspectRatio);
+ if (bm.getWidth() > MAX_SIZE && bm.getHeight() > MAX_SIZE) {
+ Timber.tag(TAG).d("Resize Bitmap");
+ bm = getResizedBitmap(bm, newWidth, newHeight);
+ resizeRatio = (float) bm.getWidth() / (float) width;
+ Timber.tag(TAG).d("resizeRatio " + resizeRatio);
+ }
+
+ // Create canvas to draw
+ Canvas canvas = new Canvas(bm);
+ Paint paint = new Paint();
+ paint.setColor(color);
+ paint.setStrokeWidth(2);
+ paint.setStyle(Paint.Style.STROKE);
+ // Loop result list
+ for (VisionDetRet ret : results) {
+ Rect bounds = new Rect();
+ bounds.left = (int) (ret.getLeft() * resizeRatio);
+ bounds.top = (int) (ret.getTop() * resizeRatio);
+ bounds.right = (int) (ret.getRight() * resizeRatio);
+ bounds.bottom = (int) (ret.getBottom() * resizeRatio);
+ canvas.drawRect(bounds, paint);
+ // Get landmark
+ ArrayList landmarks = ret.getFaceLandmarks();
+ for (Point point : landmarks) {
+ int pointX = (int) (point.x * resizeRatio);
+ int pointY = (int) (point.y * resizeRatio);
+ canvas.drawCircle(pointX, pointY, 2, paint);
+ }
+ }
+
+ return new BitmapDrawable(getResources(), bm);
+ }
+
+ @DebugLog
+ protected Bitmap getResizedBitmap(Bitmap bm, int newWidth, int newHeight) {
+ Bitmap resizedBitmap = Bitmap.createScaledBitmap(bm, newWidth, newHeight, true);
+ return resizedBitmap;
+ }
+}
diff --git a/app/src/main/java/com/tzutalin/dlibtest/OnGetImageListener.java b/app/src/main/java/com/tzutalin/dlibtest/OnGetImageListener.java
new file mode 100644
index 0000000..ab1764c
--- /dev/null
+++ b/app/src/main/java/com/tzutalin/dlibtest/OnGetImageListener.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2016-present Tzutalin
+ *
+ * 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.tzutalin.dlibtest;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.media.Image;
+import android.media.Image.Plane;
+import android.media.ImageReader;
+import android.media.ImageReader.OnImageAvailableListener;
+import android.os.Handler;
+import android.os.Trace;
+import android.util.Log;
+import android.view.Display;
+import android.view.WindowManager;
+
+import junit.framework.Assert;
+
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * Class that takes in preview frames and converts the image to Bitmaps to process with dlib lib.
+ */
+public class OnGetImageListener implements OnImageAvailableListener {
+
+ private static int INPUT_SIZE = 960;
+ private static final String TAG = "OnGetImageListener";
+
+ private int mScreenRotation = 90;
+
+ private int mPreviewWdith = 0;
+ private int mPreviewHeight = 0;
+ private byte[][] mYUVBytes;
+ private int[] mRGBBytes = null;
+ private Bitmap mRGBframeBitmap = null;
+ private Bitmap mCroppedBitmap = null;
+ private Bitmap mResizedBitmap = null;
+ private Bitmap mInversedBipmap = null;
+
+ private Handler mInferenceHandler;
+
+ private Context mContext;
+ private TrasparentTitleView mTransparentTitleView;
+
+ private int mframeNum = 0;
+ private float r = 4f;
+ //Queue
+ private ProcessWithQueue processFrameQueue;
+ private LinkedBlockingQueue frameQueue;
+ private LinkedBlockingQueue frameQueueForDisplay;
+
+
+ public void initialize(
+ final Context context,
+ final AssetManager assetManager,
+ final TrasparentTitleView scoreView,
+ final Handler handler) {
+ this.mContext = context;
+ this.mTransparentTitleView = scoreView;
+ this.mInferenceHandler = handler;
+
+ frameQueue = new LinkedBlockingQueue<>();
+ frameQueueForDisplay = new LinkedBlockingQueue<>();
+ processFrameQueue = new ProcessWithQueue(frameQueue, frameQueueForDisplay, mContext, mTransparentTitleView, handler);
+
+ }
+
+ public void deInitialize() {
+ synchronized (OnGetImageListener.this) {
+ if (processFrameQueue != null){
+ processFrameQueue.release();
+ }
+ }
+ }
+
+ private void drawResizedBitmap(final Bitmap src, final Bitmap dst) {
+
+ Display getOrient = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
+ int orientation = Configuration.ORIENTATION_UNDEFINED;
+ Point point = new Point();
+ getOrient.getSize(point);
+ int screen_width = point.x;
+ int screen_height = point.y;
+ Log.d(TAG, String.format("screen size (%d,%d)", screen_width, screen_height));
+ if (screen_width < screen_height) {
+ orientation = Configuration.ORIENTATION_PORTRAIT;
+ mScreenRotation = -90;
+ } else {
+ orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mScreenRotation = 0;
+ }
+
+ Assert.assertEquals(dst.getWidth(), dst.getHeight());
+ final float minDim = Math.min(src.getWidth(), src.getHeight());
+
+ final Matrix matrix = new Matrix();
+
+ // We only want the center square out of the original rectangle.
+ final float translateX = -Math.max(0, (src.getWidth() - minDim) / 2);
+ final float translateY = -Math.max(0, (src.getHeight() - minDim) / 2);
+ matrix.preTranslate(translateX, translateY);
+
+ final float scaleFactor = dst.getHeight() / minDim;
+ matrix.postScale(scaleFactor, scaleFactor);
+
+ // Rotate around the center if necessary.
+ if (mScreenRotation != 0) {
+ matrix.postTranslate(-dst.getWidth() / 2.0f, -dst.getHeight() / 2.0f);
+ matrix.postRotate(mScreenRotation);
+ matrix.postTranslate(dst.getWidth() / 2.0f, dst.getHeight() / 2.0f);
+ }
+
+ final Canvas canvas = new Canvas(dst);
+ canvas.drawBitmap(src, matrix, null);
+ }
+
+ public Bitmap imageSideInversion(Bitmap src){
+ Matrix sideInversion = new Matrix();
+ sideInversion.setScale(-1, 1);
+ Bitmap inversedImage = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), sideInversion, false);
+ return inversedImage;
+ }
+
+ @Override
+ public void onImageAvailable(final ImageReader reader) {
+
+ Image image = null;
+ try {
+ image = reader.acquireNextImage();
+ mframeNum++;
+ Log.i("frameInListener", String.valueOf(mframeNum));
+ if (image == null) {
+ return;
+ }
+
+ Trace.beginSection("imageAvailable");
+
+ final Plane[] planes = image.getPlanes();
+
+ // Initialize the storage bitmaps once when the resolution is known.
+ if (mPreviewWdith != image.getWidth() || mPreviewHeight != image.getHeight()) {
+ mPreviewWdith = image.getWidth();
+ mPreviewHeight = image.getHeight();
+
+ //Log.d(TAG, String.format("Initializing at size %dx%d", mPreviewWdith, mPreviewHeight));
+ mRGBBytes = new int[mPreviewWdith * mPreviewHeight];
+ mRGBframeBitmap = Bitmap.createBitmap(mPreviewWdith, mPreviewHeight, Config.ARGB_8888);
+ mCroppedBitmap = Bitmap.createBitmap(INPUT_SIZE, INPUT_SIZE, Config.ARGB_8888);
+
+ mYUVBytes = new byte[planes.length][];
+ for (int i = 0; i < planes.length; ++i) {
+ mYUVBytes[i] = new byte[planes[i].getBuffer().capacity()];
+ }
+ }
+
+ for (int i = 0; i < planes.length; ++i) {
+ planes[i].getBuffer().get(mYUVBytes[i]);
+ }
+
+ final int yRowStride = planes[0].getRowStride();
+ final int uvRowStride = planes[1].getRowStride();
+ final int uvPixelStride = planes[1].getPixelStride();
+ ImageUtils.convertYUV420ToARGB8888(
+ mYUVBytes[0],
+ mYUVBytes[1],
+ mYUVBytes[2],
+ mRGBBytes,
+ mPreviewWdith,
+ mPreviewHeight,
+ yRowStride,
+ uvRowStride,
+ uvPixelStride,
+ false);
+
+ image.close();
+ } catch (final Exception e) {
+ if (image != null) {
+ image.close();
+ }
+ Log.e(TAG, "Exception!", e);
+ Trace.endSection();
+ return;
+ }
+
+ mRGBframeBitmap.setPixels(mRGBBytes, 0, mPreviewWdith, 0, 0, mPreviewWdith, mPreviewHeight);
+ drawResizedBitmap(mRGBframeBitmap, mCroppedBitmap);
+
+ mInversedBipmap = imageSideInversion(mCroppedBitmap);
+ mResizedBitmap = Bitmap.createScaledBitmap(mInversedBipmap, (int)(INPUT_SIZE/r), (int)(INPUT_SIZE/r), true);
+
+ try {
+ frameQueueForDisplay.put(mInversedBipmap);
+ frameQueue.put(mResizedBitmap);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ Log.i("queueInListener", String.valueOf(frameQueue.size()));
+ }
+}
diff --git a/app/src/main/java/com/tzutalin/dlibtest/ProcessWithQueue.java b/app/src/main/java/com/tzutalin/dlibtest/ProcessWithQueue.java
new file mode 100644
index 0000000..4d9887b
--- /dev/null
+++ b/app/src/main/java/com/tzutalin/dlibtest/ProcessWithQueue.java
@@ -0,0 +1,469 @@
+package com.tzutalin.dlibtest;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.support.v7.app.AppCompatActivity;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.media.Image;
+import android.os.Environment;
+import android.os.Handler;
+import android.util.Log;
+import android.view.Display;
+import android.view.WindowManager;
+
+import com.tzutalin.dlib.Constants;
+import com.tzutalin.dlib.FaceDet;
+import com.tzutalin.dlib.VisionDetRet;
+
+import junit.framework.Assert;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import static android.content.Context.MODE_PRIVATE;
+
+public class ProcessWithQueue extends Thread {
+ private static final String TAG = "Queue";
+ private static final String NoDetection = "noDetection";
+ private static final String EyesBlinkDetection = "eyesBlinkDetection";
+ private static final String headOrientationDetection = "headOrientationDetection";
+
+ private LinkedBlockingQueue mQueue;
+ private LinkedBlockingQueue frameForDisplay;
+
+ private static String checkMode = null;
+
+ private List results;
+
+ private Handler mInferenceHandler;
+ private Context mContext;
+ private FaceDet mFaceDet;
+ private TrasparentTitleView mTransparentTitleView;
+ private FloatingCameraWindow mWindow;
+ private Paint mFaceLandmardkPaint;
+
+ private int mframeNum = 0;
+
+ private double ear = 0;
+ private int x = 0;
+ private boolean ear_array_removed = false;
+ private boolean drop_array_appended = false;
+ private double THRESH = 0.04;
+ private double DROP_THRESH = 0.065;
+ private ArrayList ear_array = new ArrayList<>();
+ private ArrayList ax = new ArrayList<>();
+ private ArrayList ay = new ArrayList<>();
+ private int continuous_Increment = 0;
+ private int continuous_Decrement = 0;
+ private double drop = 0;
+ private ArrayList drop_array = new ArrayList<>();
+ private int temp = 0;
+ private double closeEyes_drop = -5;
+ private double openEyes_drop = 0;
+ private int closeEyes_end = 0;
+ private int openEyes_start = 0;
+ private int blink = 0;
+ private int frames_notFoundFace = 0;
+ private Point keyPoint_right = null;
+ private Point keyPoint_left = null;
+ private Point keyPoint_nose = null;
+ private double rightHalfFace = 0;
+ private double leftHalfFace = 0;
+ private String headToward = "front";
+
+ private double ratio = 0;
+
+ private SharedPreferences mSharedPreferences;
+
+
+ public ProcessWithQueue(LinkedBlockingQueue frameQueue, LinkedBlockingQueue frameQueueForDisplay, Context context, TrasparentTitleView scoreView, Handler handler) {
+ this.mContext = context;
+ this.mTransparentTitleView = scoreView;
+ this.mInferenceHandler = handler;
+
+ mQueue = frameQueue;
+ frameForDisplay = frameQueueForDisplay;
+
+ mFaceDet = new FaceDet(Constants.getFaceShapeModelPath());
+ mWindow = new FloatingCameraWindow(mContext);
+
+ mFaceLandmardkPaint = new Paint();
+ mFaceLandmardkPaint.setColor(Color.GREEN);
+ mFaceLandmardkPaint.setStrokeWidth(2);
+ mFaceLandmardkPaint.setStyle(Paint.Style.STROKE);
+
+ mSharedPreferences = context.getSharedPreferences("userInfo", MODE_PRIVATE);
+ checkMode = mSharedPreferences.getString("detectionMode","");
+
+ start();
+ }
+
+ public void release(){
+ if (mFaceDet != null) {
+ mFaceDet.release();
+ }
+
+ if (mWindow != null) {
+ mWindow.release();
+ }
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ Bitmap frameData = null;
+ Bitmap framefordisplay = null;
+ try {
+ frameData = mQueue.take();
+ framefordisplay = frameForDisplay.take();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ if (frameData == null) {
+ break;
+ }
+ processFrame(frameData, framefordisplay);
+ }
+ }
+
+ private void processFrame(final Bitmap frameData, final Bitmap framefordisplay) {
+
+ if(frameData != null){
+ mInferenceHandler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+
+ if (!new File(Constants.getFaceShapeModelPath()).exists()) {
+ mTransparentTitleView.setText("Copying landmark model to " + Constants.getFaceShapeModelPath());
+ FileUtils.copyFileFromRawToOthers(mContext, R.raw.shape_predictor_68_face_landmarks, Constants.getFaceShapeModelPath());
+ }
+
+ mframeNum++;
+// saveBitmap(frameData, "frames", String.valueOf(mframeNum) + ".jpg");
+
+ switch (checkMode){
+
+ case NoDetection:{
+ mWindow.setRGBBitmap(framefordisplay);
+ mWindow.setInformation("frame: " + String.valueOf(mframeNum));
+ mTransparentTitleView.setText("noDetection");
+ }break;
+
+ case EyesBlinkDetection:{
+
+ long startTime = System.currentTimeMillis();
+
+ results = mFaceDet.detect(frameData);
+
+ if (results.size() != 0) {
+ for (final VisionDetRet ret : results) {
+ float resizeRatio = 4f;
+ Canvas canvas = new Canvas(framefordisplay);
+
+ ArrayList landmarks = ret.getFaceLandmarks();
+
+ int i = 1;
+
+ //get the 6 key point from 68_face_landmarks
+ Point[] leftEye = new Point[6];
+ Point[] rightEye = new Point[6];
+
+ for (Point point : landmarks) {
+ if (i > 36 && i < 43) {
+ //for more efficient procession, the data we process were zoomed out
+ //So the point must be magnified , to display correctly in the original image.
+ int pointX = (int) (point.x * resizeRatio);
+ int pointY = (int) (point.y * resizeRatio);
+ leftEye[i - 37] = new Point(pointX, pointY);
+ //canvas.drawCircle(pointX, pointY, 2, mFaceLandmardkPaint);
+ } else if (i > 42 && i < 49) {
+ int pointX = (int) (point.x * resizeRatio);
+ int pointY = (int) (point.y * resizeRatio);
+ rightEye[i - 43] = new Point(pointX, pointY);
+ //canvas.drawCircle(pointX, pointY, 2, mFaceLandmardkPaint);
+ }
+ if (i > 48) {
+ break;
+ }
+ i++;
+ }
+
+ canvas.drawPath(getPath(leftEye), mFaceLandmardkPaint);
+ canvas.drawPath(getPath(rightEye), mFaceLandmardkPaint);
+ //saveBitmap(frameData, "Pframes", String.valueOf(mframeNum) + ".jpg");
+
+ double leftEAR = eye_aspect_ratio(leftEye);
+ double rightEAR = eye_aspect_ratio(rightEye);
+ ear = (leftEAR + rightEAR) / 2.0;
+ }
+ } else {
+ frames_notFoundFace++;
+ Log.i("frames_notFoundFace", String.valueOf(frames_notFoundFace));
+ }
+
+ if (ear != 0) {
+
+ //codes below are difficult to read,but it dose work
+ x += 1;
+ ear_array.add(ear);
+ ax.add(x);
+ ay.add(ear);
+ ear_array_removed = filter_unexpected_values(ear_array, ax, THRESH);
+
+ if (ear_array.size() > 2 && !ear_array_removed) {
+ if (ear_array.get(ear_array.size() - 2) > ear_array.get(ear_array.size() - 3)) {
+ continuous_Increment += 1;
+ if (continuous_Decrement != 0) {
+ drop = ear_array.get(ear_array.size() - 3) - ear_array.get(ear_array.size() - 3 - continuous_Decrement);
+ if (continuous_Decrement != 1) {
+ drop_array.add(drop);
+ drop_array_appended = true;
+ }
+ temp = continuous_Decrement;
+ continuous_Decrement = 0;
+ }
+ } else if (ear_array.get(ear_array.size() - 2) < ear_array.get(ear_array.size() - 3)) {
+ continuous_Decrement += 1;
+ if (continuous_Increment != 0) {
+ drop = ear_array.get(ear_array.size() - 3) - ear_array.get(ear_array.size() - 3 - continuous_Increment);
+ if (continuous_Increment != 1) {
+ drop_array.add(drop);
+ drop_array_appended = true;
+ }
+ temp = continuous_Increment;
+ continuous_Increment = 0;
+ }
+ }
+ }
+
+ if (drop_array_appended) {
+ if (drop_array.get(drop_array.size() - 1) < -DROP_THRESH) {
+ closeEyes_drop = drop_array.get(drop_array.size() - 1);
+ closeEyes_end = ax.get(ax.size() - 3);
+ }
+ if (drop_array.get(drop_array.size() - 1) > DROP_THRESH) {
+ openEyes_drop = drop_array.get(drop_array.size() - 1);
+ openEyes_start = ax.get(ax.size() - 3 - temp);
+ if (Math.abs(closeEyes_drop + openEyes_drop) < 0.1 && ear_array.get(ear_array.size() - 3 - temp) < 0.21
+ && openEyes_start - closeEyes_end < 20) {
+ blink += 1;
+ closeEyes_drop = -5;
+ }
+ }
+ }
+
+ }
+ long endTime = System.currentTimeMillis();
+ mTransparentTitleView.setText("noFace: " + String.valueOf(frames_notFoundFace) + " blink: " + String.valueOf(blink) + " TimeCost: " + String.valueOf((endTime - startTime) / 1000f));
+
+ //save the ear data to local,for further analysis
+ //When mframeNum is equal to 1, it means that the recognition is restarted, so the previous data is overwritten.
+ //if(mframeNum == 1){
+ //saveStringToTxt(String.valueOf(mframeNum) + " " + doubleToString(ear), "ear.txt", false);
+ //}else{
+ //saveStringToTxt(String.valueOf(mframeNum) + " " + doubleToString(ear), "ear.txt",true);
+ //}
+
+ Log.i("processingFrame", String.valueOf(mframeNum));
+ mWindow.setRGBBitmap(framefordisplay);
+ mWindow.setInformation("frame: " + String.valueOf(mframeNum));
+ mWindow.setMoreInformation("ear: " + String.valueOf(ear));
+
+ }break;
+ case headOrientationDetection:{
+
+ long startTime = System.currentTimeMillis();
+
+ results = mFaceDet.detect(frameData);
+
+ if (results.size() != 0) {
+ for (final VisionDetRet ret : results) {
+ float resizeRatio = 4f;
+ Canvas canvas = new Canvas(framefordisplay);
+
+ ArrayList landmarks = ret.getFaceLandmarks();
+
+
+ for (Point point : landmarks) {
+ int pointX = (int) (point.x * resizeRatio);
+ int pointY = (int) (point.y * resizeRatio);
+ canvas.drawCircle(pointX, pointY, 2, mFaceLandmardkPaint);
+ }
+
+ keyPoint_left = landmarks.get(2);
+ keyPoint_right = landmarks.get(14);
+ keyPoint_nose = landmarks.get(30);
+
+ rightHalfFace = euclidean(keyPoint_nose, keyPoint_right);
+ leftHalfFace = euclidean(keyPoint_nose, keyPoint_left);
+
+ ratio = Math.min(rightHalfFace, leftHalfFace) / Math.max(rightHalfFace, leftHalfFace);
+ }
+ } else {
+ frames_notFoundFace++;
+ Log.i("frames_notFoundFace", String.valueOf(frames_notFoundFace));
+ }
+
+ if (ratio>0.6 && ratio<1){
+ headToward = "front";
+ }else {
+ //if your right face is bigger than the left, then your head toward left
+ headToward = rightHalfFace > leftHalfFace ? "left" : "right";
+ }
+
+ long endTime = System.currentTimeMillis();
+
+ mTransparentTitleView.setText("headToward:" + headToward + " TimeCost: " + String.valueOf((endTime - startTime) / 1000f));
+ Log.i("processingFrame", String.valueOf(mframeNum));
+ mWindow.setInformation("frame: " + String.valueOf(mframeNum));
+ mWindow.setMoreInformation("ratio: " + String.valueOf(ratio));
+ mWindow.setRGBBitmap(framefordisplay);
+ }
+ break;
+
+ }
+ }
+
+ });
+ }
+ }
+
+ //眼睛的高和长的比值
+ private double eye_aspect_ratio(Point[] eye){
+ double ear = 0;
+ double A = euclidean(eye[1],eye[5]);
+ double B = euclidean(eye[2],eye[4]);
+ double C = euclidean(eye[0],eye[3]);
+ ear = (A + B) / (2.0 * C);
+ return ear;
+ }
+
+ //两点间的欧式距离
+ private double euclidean(Point p1, Point p2){
+ double result = 0;
+ result = Math.sqrt(Math.pow((p1.x-p2.x),2)+Math.pow((p1.y-p2.y),2));
+ return result;
+ }
+
+ //保存数据到文件,追加or覆盖
+ private static void saveStringToTxt(String str, String fileName, boolean appendOrNot){
+
+ String filePath = null;
+
+ boolean hasSDCard =Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
+
+ if (hasSDCard) {
+
+ filePath =Environment.getExternalStorageDirectory().toString() + File.separator +fileName;
+
+ } else
+
+ filePath =Environment.getDownloadCacheDirectory().toString() + File.separator +fileName;
+ try {
+ File file = new File(filePath);
+
+ if (!file.exists()) {
+
+ file.createNewFile();
+
+ }
+ FileWriter fw = new FileWriter(file,appendOrNot);//SD卡中的路径
+ fw.flush();
+ fw.write(str+"\r\n");
+ fw.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ //double转string
+ private static String doubleToString(double num){
+ //使用0.00不足位补0,#.##仅保留有效位
+ return new DecimalFormat("0.0000").format(num);
+ }
+
+ /**
+
+ * 过滤异常值,以便更好的计算连续落差Calculate_continuous_drop()
+
+ *
+
+ * @param AL 存有ear值的ArrayList
+ * @param ax ear值对应的帧,AL看作是Y轴的话,那么ax就是X轴,当AL过滤某一ear值时,其对应的帧也应删除
+ * @param THRESH 阈值,小于它才过滤
+ * @return 返回此次调用是否发生了过滤,过滤了返回true,无需过滤返回false
+
+ */
+ private static boolean filter_unexpected_values(ArrayList AL, ArrayList ax, double THRESH){
+ if(AL.size()>2){
+ if(AL.get(AL.size()-3) < AL.get(AL.size()-1)){
+ if(AL.get(AL.size()-3) > AL.get(AL.size()-2) && AL.get(AL.size()-3)-AL.get(AL.size()-2)>THRESH){
+ AL.remove(AL.size()-2);
+ ax.remove(AL.size()-2);
+ return true;
+ }
+ }else if(AL.get(AL.size()-3) > AL.get(AL.size()-1)){
+ if(AL.get(AL.size()-2) > AL.get(AL.size()-3) && AL.get(AL.size()-2)-AL.get(AL.size()-3)>THRESH){
+ AL.remove(AL.size()-2);
+ ax.remove(AL.size()-2);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ //返回点的闭合路径,通过canvas可画出
+ private Path getPath(Point[] points){
+ Path path = new Path();
+ path.moveTo(points[0].x, points[0].y);//起点
+ //添加中间连接点
+ for(int i = 1; i < points.length; i++){
+ path.lineTo(points[i].x, points[i].y);
+ }
+ path.close(); // 使这些点构成封闭的多边形
+ return path;
+ }
+
+ private void saveBitmap(Bitmap bm, String directory, String fileName){
+ String filePath = null;
+
+ boolean hasSDCard =Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
+
+ if (hasSDCard) {
+
+ filePath =Environment.getExternalStorageDirectory().toString() + File.separator + directory+ File.separator+ fileName;
+
+ } else
+
+ filePath =Environment.getDownloadCacheDirectory().toString() + File.separator + directory+ File.separator+ fileName;
+ try {
+ File file = new File(filePath);
+
+ if (!file.exists()) {
+ file.getParentFile().mkdirs();
+ file.createNewFile();
+ }
+ FileOutputStream fos = new FileOutputStream(file);
+ bm.compress(Bitmap.CompressFormat.JPEG, 100, fos);
+ fos.flush();
+ fos.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ }
+
+}
diff --git a/app/src/main/java/com/tzutalin/dlibtest/Setting.java b/app/src/main/java/com/tzutalin/dlibtest/Setting.java
new file mode 100644
index 0000000..1d5d735
--- /dev/null
+++ b/app/src/main/java/com/tzutalin/dlibtest/Setting.java
@@ -0,0 +1,44 @@
+package com.tzutalin.dlibtest;
+
+import android.content.SharedPreferences;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.Spinner;
+import android.widget.Toast;
+
+public class Setting extends AppCompatActivity {
+
+ private SharedPreferences mSharedPreferences;
+ private Spinner mSpinner;
+ private static int spinnerStatus = 0;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_setting);
+
+ mSharedPreferences = getSharedPreferences("userInfo", MODE_PRIVATE);
+ mSpinner = (Spinner) findViewById(R.id.spinner);
+ mSpinner.setSelection(spinnerStatus);
+
+ Button mSettingBt = (Button) findViewById(R.id.button);
+ mSettingBt.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ String selectedItem = mSpinner.getSelectedItem().toString();
+ spinnerStatus = mSpinner.getSelectedItemPosition();
+ String detectionMode = null;
+ detectionMode = selectedItem;
+
+ SharedPreferences.Editor edit = mSharedPreferences.edit();
+ edit.putString("detectionMode", detectionMode);
+ edit.commit();
+ Toast.makeText(Setting.this, "Successfully saved", Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ }
+}
diff --git a/app/src/main/java/com/tzutalin/dlibtest/TrasparentTitleView.java b/app/src/main/java/com/tzutalin/dlibtest/TrasparentTitleView.java
new file mode 100644
index 0000000..e2efa3a
--- /dev/null
+++ b/app/src/main/java/com/tzutalin/dlibtest/TrasparentTitleView.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2016-present Tzutalin
+ *
+ * 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.tzutalin.dlibtest;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.support.annotation.NonNull;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.View;
+
+public class TrasparentTitleView extends View {
+ private static final float TEXT_SIZE_DIP = 24;
+ private String mShowText;
+ private final float mTextSizePx;
+ private final Paint mFgPaint;
+ private final Paint mBgPaint;
+
+ public TrasparentTitleView(final Context context, final AttributeSet set) {
+ super(context, set);
+
+ mTextSizePx =
+ TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP, TEXT_SIZE_DIP, getResources().getDisplayMetrics());
+ mFgPaint = new Paint();
+ mFgPaint.setTextSize(mTextSizePx);
+
+ mBgPaint = new Paint();
+ mBgPaint.setColor(0xcc4285f4);
+ }
+
+ @NonNull
+ public void setText(@NonNull String text) {
+ this.mShowText = text;
+ postInvalidate();
+ }
+
+ @Override
+ public void onDraw(final Canvas canvas) {
+ final int x = 10;
+ int y = (int) (mFgPaint.getTextSize() * 1.5f);
+
+ canvas.drawPaint(mBgPaint);
+
+ if (mShowText != null) {
+ canvas.drawText(mShowText, x, y, mFgPaint);
+ }
+ }
+}
diff --git a/app/src/main/res/drawable/gallery.png b/app/src/main/res/drawable/gallery.png
new file mode 100644
index 0000000..4e9cd50
Binary files /dev/null and b/app/src/main/res/drawable/gallery.png differ
diff --git a/app/src/main/res/drawable/ic_camera_alt_black_36dp.png b/app/src/main/res/drawable/ic_camera_alt_black_36dp.png
new file mode 100644
index 0000000..3c25621
Binary files /dev/null and b/app/src/main/res/drawable/ic_camera_alt_black_36dp.png differ
diff --git a/app/src/main/res/layout/activity_camera.xml b/app/src/main/res/layout/activity_camera.xml
new file mode 100644
index 0000000..91017b8
--- /dev/null
+++ b/app/src/main/res/layout/activity_camera.xml
@@ -0,0 +1,22 @@
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..8ae8d45
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_setting.xml b/app/src/main/res/layout/activity_setting.xml
new file mode 100644
index 0000000..bf0e815
--- /dev/null
+++ b/app/src/main/res/layout/activity_setting.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/cam_window_view.xml b/app/src/main/res/layout/cam_window_view.xml
new file mode 100644
index 0000000..ac2e2c2
--- /dev/null
+++ b/app/src/main/res/layout/cam_window_view.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/camera_connection_fragment.xml b/app/src/main/res/layout/camera_connection_fragment.xml
new file mode 100644
index 0000000..b313e80
--- /dev/null
+++ b/app/src/main/res/layout/camera_connection_fragment.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml
new file mode 100644
index 0000000..17b2128
--- /dev/null
+++ b/app/src/main/res/layout/content_main.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml
new file mode 100644
index 0000000..b1cb908
--- /dev/null
+++ b/app/src/main/res/menu/menu_main.xml
@@ -0,0 +1,6 @@
+
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/raw/shape_predictor_68_face_landmarks.dat b/app/src/main/res/raw/shape_predictor_68_face_landmarks.dat
new file mode 100644
index 0000000..e0ec20d
Binary files /dev/null and b/app/src/main/res/raw/shape_predictor_68_face_landmarks.dat differ
diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000..65d0c39
--- /dev/null
+++ b/app/src/main/res/values-v21/styles.xml
@@ -0,0 +1,8 @@
+>
+
+
diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..4d44345
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #3F51B5
+ #303F9F
+ #ff4081
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..812cb7b
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 16dp
+ 16dp
+ 16dp
+
diff --git a/app/src/main/res/values/model.xml b/app/src/main/res/values/model.xml
new file mode 100644
index 0000000..6b2a500
--- /dev/null
+++ b/app/src/main/res/values/model.xml
@@ -0,0 +1,8 @@
+
+
+
+ - noDetection
item>
+ - eyesBlinkDetection
item>
+ - headOrientationDetection
item>
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..b2638cc
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,9 @@
+
+ Detection
+ Settings
+ Select an image or open camera
+ This sample needs camera permission.
+ This device doesn\'t support Camera2 API.
+ choose detection mode:
+ save
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..44f664f
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..25edef8
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,35 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ mavenCentral()
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.3.2'
+ classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1'
+ classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7'
+ classpath "com.github.dcendents:android-maven-gradle-plugin:1.4.1"
+ }
+}
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
+
+ext {
+ groupName = 'com.github.tzutalin'
+ artifactName = 'dlib-android-app'
+ artifactDescription = 'An Android library to wrap dlib library to use face and landmark detection'
+ artifactLabels = ['dlib', 'android']
+ releaseVersionCode = 1
+ releaseVersionName = '1.0.4'
+
+ androidBuildToolsVersion = '25.0.2'
+ androidSupportSdkVersion = '25.2.0'
+}
\ No newline at end of file
diff --git a/demo/app-debug.apk b/demo/app-debug.apk
new file mode 100644
index 0000000..24cd9a9
Binary files /dev/null and b/demo/app-debug.apk differ
diff --git a/demo/dlib-v1.0.4.aar b/demo/dlib-v1.0.4.aar
new file mode 100644
index 0000000..1a52345
Binary files /dev/null and b/demo/dlib-v1.0.4.aar differ
diff --git a/demo/eyesBlinkDetection.png b/demo/eyesBlinkDetection.png
new file mode 100644
index 0000000..e698846
Binary files /dev/null and b/demo/eyesBlinkDetection.png differ
diff --git a/demo/headOrientationDetection1.png b/demo/headOrientationDetection1.png
new file mode 100644
index 0000000..2661d70
Binary files /dev/null and b/demo/headOrientationDetection1.png differ
diff --git a/demo/headOrientationDetection2.png b/demo/headOrientationDetection2.png
new file mode 100644
index 0000000..e1c6c84
Binary files /dev/null and b/demo/headOrientationDetection2.png differ
diff --git a/demo/headOrientationDetection3.png b/demo/headOrientationDetection3.png
new file mode 100644
index 0000000..96f53e4
Binary files /dev/null and b/demo/headOrientationDetection3.png differ
diff --git a/demo/noDetection.png b/demo/noDetection.png
new file mode 100644
index 0000000..b23d233
Binary files /dev/null and b/demo/noDetection.png differ
diff --git a/demo/setting.png b/demo/setting.png
new file mode 100644
index 0000000..0d4866f
Binary files /dev/null and b/demo/setting.png differ
diff --git a/dlib/.gitignore b/dlib/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/dlib/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/dlib/build.gradle b/dlib/build.gradle
new file mode 100644
index 0000000..2da8f53
--- /dev/null
+++ b/dlib/build.gradle
@@ -0,0 +1,203 @@
+apply plugin: 'com.android.library'
+
+// required by POM
+version = "${rootProject.ext.releaseVersionName}"
+group = "com.tzutalin.dlib-android-app"
+
+android {
+ compileSdkVersion 25
+ buildToolsVersion '25.0.2'
+
+ defaultConfig {
+ minSdkVersion 21
+ targetSdkVersion 25
+ versionCode 1
+ versionName "${rootProject.ext.releaseVersionName}"
+
+ ndk{
+ abiFilters 'arm64-v8a'
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ android {
+ lintOptions {
+ abortOnError false
+ }
+ }
+
+ // Use prebilt *.so. Don't run ndk-build
+ sourceSets {
+ main {
+ jniLibs.srcDirs = ["src/main/jniLibs"]
+ }
+ }
+
+}
+
+configurations {
+ javadocDeps
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile "com.android.support:appcompat-v7:${rootProject.ext.androidSupportSdkVersion}"
+ javadocDeps "com.android.support:appcompat-v7:${rootProject.ext.androidSupportSdkVersion}"
+ compile "com.android.support:support-annotations:${rootProject.ext.androidSupportSdkVersion}"
+ javadocDeps "com.android.support:support-annotations:${rootProject.ext.androidSupportSdkVersion}"
+}
+
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1'
+ }
+}
+apply plugin: 'com.jakewharton.hugo'
+
+// call regular ndk-build(.cmd) script from app directory
+def GetNDKDir() {
+ def localProperties = new Properties()
+
+ def NDK_SEARCH_VARS = ['ANDROID_NDK_HOME', 'ANDROID_NDK_ROOT', 'NDKROOT', 'NDKHOME']
+ def ndk_path = null
+
+ def lim = (Os.isFamily(Os.FAMILY_WINDOWS)) ? '\\' : '/'
+
+ // Search Local.Properties file
+ try {
+ localProperties.load(project.rootProject.file('local.properties').newDataInputStream())
+ def ndkDir = localProperties.getProperty('ndk.dir')
+ ndk_path = ndkDir != null ? ndkDir + lim : null;
+ } catch (java.io.FileNotFoundException e) {
+ println 'local.properties file not found'
+ }
+
+ // Search env var
+ if (ndk_path == null){
+ for (String var : NDK_SEARCH_VARS) {
+ def v = System.getenv(var)
+
+ if (v != null) {
+ ndk_path = v + lim
+ println "found in System Environment *$var = " + ndk_path
+ break
+ }
+ }
+ } else {
+ println 'found in local.properties *NDK_PATH = ' + ndk_path
+ }
+
+ if (ndk_path == null) {
+ println 'No NDK_PATH found'
+ ndk_path = ''
+ }
+ return ndk_path
+}
+
+def NDKCommand() {
+ if (Os.isFamily(Os.FAMILY_WINDOWS)) {
+ return GetNDKDir() + "ndk-build.cmd"
+ } else {
+ return GetNDKDir() + "ndk-build"
+ }
+}
+
+apply plugin: 'com.jfrog.bintray'
+apply plugin: 'com.github.dcendents.android-maven'
+
+def siteUrl = 'https://github.com/tzutalin/dlib-android-app' // Homepage URL of the library
+def gitUrl = 'https://github.com/tzutalin/dlib-android-app.git' // Git repository URL
+
+install {
+ repositories.mavenInstaller {
+ // This generates POM.xml with proper parameters
+ pom {
+ project {
+ packaging 'aar'
+ name 'An Android library to wrap dlib library'
+ url siteUrl
+ // Set your license
+ licenses {
+ license {
+ name 'The Apache Software License, Version 2.0'
+ url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+ }
+ }
+ developers {
+ developer {
+ id 'tzutalin'
+ name 'tzutalin'
+ email 'tzu.ta.lin@gmail.com'
+ }
+ }
+ scm {
+ connection gitUrl
+ developerConnection gitUrl
+ url siteUrl
+
+ }
+ }
+ }
+ }
+}
+
+task generateSourcesJar(type: Jar) {
+ from android.sourceSets.main.java.srcDirs
+ classifier 'sources'
+}
+
+task generateJavadocs(type: Javadoc) {
+ source = android.sourceSets.main.java.srcDirs
+ classpath += project.files(android.getBootClasspath()
+ .join(File.pathSeparator))
+ classpath += configurations.javadocDeps
+}
+
+task generateJavadocsJar(type: Jar) {
+ from generateJavadocs.destinationDir
+ classifier 'javadoc'
+}
+generateJavadocsJar.dependsOn generateJavadocs
+
+artifacts {
+ archives generateJavadocsJar
+ archives generateSourcesJar
+}
+
+Properties properties = new Properties()
+if(project.rootProject.file('local.properties').isFile()) {
+ properties.load(project.rootProject.file('local.properties').newDataInputStream())
+
+ bintray {
+ user = properties.getProperty("bintray.user")
+ key = properties.getProperty("bintray.apikey")
+
+ configurations = ['archives']
+ pkg {
+ repo = "maven"
+ name = "dlib-android-app"
+ version {
+ name = "${rootProject.ext.releaseVersionName}"
+ desc = 'An Android library to wrap dlib library'
+ released = new Date()
+ vcsTag = "${rootProject.ext.releaseVersionName}"
+ }
+
+ websiteUrl = siteUrl
+ vcsUrl = gitUrl
+ licenses = ["Apache-2.0"]
+ publish = true
+ }
+ configurations = ['archives']
+ }
+}
\ No newline at end of file
diff --git a/dlib/proguard-rules.pro b/dlib/proguard-rules.pro
new file mode 100644
index 0000000..acfa448
--- /dev/null
+++ b/dlib/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /home/darrenl/tools/android-sdk-linux/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/dlib/src/main/AndroidManifest.xml b/dlib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..04426b5
--- /dev/null
+++ b/dlib/src/main/AndroidManifest.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/dlib/src/main/java/com/tzutalin/dlib/Constants.java b/dlib/src/main/java/com/tzutalin/dlib/Constants.java
new file mode 100644
index 0000000..8d8abe8
--- /dev/null
+++ b/dlib/src/main/java/com/tzutalin/dlib/Constants.java
@@ -0,0 +1,24 @@
+package com.tzutalin.dlib;
+
+import android.os.Environment;
+
+import java.io.File;
+
+/**
+ * Created by darrenl on 2016/4/22.
+ */
+public final class Constants {
+ private Constants() {
+ // Constants should be prive
+ }
+
+ /**
+ * getFaceShapeModelPath
+ * @return default face shape model path
+ */
+ public static String getFaceShapeModelPath() {
+ File sdcard = Environment.getExternalStorageDirectory();
+ String targetPath = sdcard.getAbsolutePath() + File.separator + "shape_predictor_68_face_landmarks.dat";
+ return targetPath;
+ }
+}
diff --git a/dlib/src/main/java/com/tzutalin/dlib/FaceDet.java b/dlib/src/main/java/com/tzutalin/dlib/FaceDet.java
new file mode 100644
index 0000000..a8227a8
--- /dev/null
+++ b/dlib/src/main/java/com/tzutalin/dlib/FaceDet.java
@@ -0,0 +1,83 @@
+package com.tzutalin.dlib;
+
+import android.graphics.Bitmap;
+import android.support.annotation.Keep;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.WorkerThread;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Created by houzhi on 16-10-20.
+ * Modified by tzutalin on 16-11-15
+ */
+public class FaceDet {
+ private static final String TAG = "dlib";
+
+ // accessed by native methods
+ @SuppressWarnings("unused")
+ private long mNativeFaceDetContext;
+ private String mLandMarkPath = "";
+
+ static {
+ try {
+ System.loadLibrary("android_dlib");
+ jniNativeClassInit();
+ Log.d(TAG, "jniNativeClassInit success");
+ } catch (UnsatisfiedLinkError e) {
+ Log.e(TAG, "library not found");
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public FaceDet() {
+ jniInit(mLandMarkPath);
+ }
+
+ public FaceDet(String landMarkPath) {
+ mLandMarkPath = landMarkPath;
+ jniInit(mLandMarkPath);
+ }
+
+ @Nullable
+ @WorkerThread
+ public List detect(@NonNull String path) {
+ VisionDetRet[] detRets = jniDetect(path);
+ return Arrays.asList(detRets);
+ }
+
+ @Nullable
+ @WorkerThread
+ public List detect(@NonNull Bitmap bitmap) {
+ VisionDetRet[] detRets = jniBitmapDetect(bitmap);
+ return Arrays.asList(detRets);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ release();
+ }
+
+ public void release() {
+ jniDeInit();
+ }
+
+ @Keep
+ private native static void jniNativeClassInit();
+
+ @Keep
+ private synchronized native int jniInit(String landmarkModelPath);
+
+ @Keep
+ private synchronized native int jniDeInit();
+
+ @Keep
+ private synchronized native VisionDetRet[] jniBitmapDetect(Bitmap bitmap);
+
+ @Keep
+ private synchronized native VisionDetRet[] jniDetect(String path);
+}
\ No newline at end of file
diff --git a/dlib/src/main/java/com/tzutalin/dlib/PedestrianDet.java b/dlib/src/main/java/com/tzutalin/dlib/PedestrianDet.java
new file mode 100644
index 0000000..fc80123
--- /dev/null
+++ b/dlib/src/main/java/com/tzutalin/dlib/PedestrianDet.java
@@ -0,0 +1,95 @@
+/*
+* Copyright (C) 2015 TzuTaLin
+*
+* 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.tzutalin.dlib;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.support.annotation.Keep;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.WorkerThread;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static android.R.attr.path;
+
+/**
+ * Created by Tzutalin on 2015/10/20.
+ */
+public class PedestrianDet {
+
+ // accessed by native methods
+ @SuppressWarnings("unused")
+ private long mNativeDetContext;
+ private static final String TAG = "dlib";
+
+ static {
+ try {
+ System.loadLibrary("android_dlib");
+ Log.d(TAG, "jniNativeClassInit success");
+ } catch (UnsatisfiedLinkError e) {
+ Log.e(TAG, "library not found!");
+ }
+ }
+
+ public PedestrianDet() {
+ jniInit();
+ }
+
+ @Nullable
+ @WorkerThread
+ public List detect(@NonNull Bitmap bitmap) {
+ VisionDetRet[] detRets = jniBitmapDetect(bitmap);
+ return Arrays.asList(detRets);
+ }
+
+ @Nullable
+ @WorkerThread
+ public List detect(@NonNull final String path) {
+ VisionDetRet[] detRets = jniDetect(path);
+ return Arrays.asList(detRets);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ release();
+ }
+
+ public void release() {
+ jniDeInit();
+ }
+
+ @Keep
+ private native int jniInit();
+
+ @Keep
+ private synchronized native int jniDeInit();
+
+ @Keep
+ private synchronized native VisionDetRet[] jniDetect(String path);
+
+ @Keep
+ private synchronized native VisionDetRet[] jniBitmapDetect(Bitmap bitmap);
+
+}
diff --git a/dlib/src/main/java/com/tzutalin/dlib/VisionDetRet.java b/dlib/src/main/java/com/tzutalin/dlib/VisionDetRet.java
new file mode 100644
index 0000000..02ef03c
--- /dev/null
+++ b/dlib/src/main/java/com/tzutalin/dlib/VisionDetRet.java
@@ -0,0 +1,134 @@
+/*
+* Copyright (C) 2015 TzuTaLin
+*
+* 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.tzutalin.dlib;
+
+/**
+ * Created by Tzutalin on 2015/10/20.
+ */
+
+import android.graphics.Point;
+
+import java.util.ArrayList;
+
+/**
+ * A VisionDetRet contains all the information identifying the location and confidence value of the detected object in a bitmap.
+ */
+public final class VisionDetRet {
+ private String mLabel;
+ private float mConfidence;
+ private int mLeft;
+ private int mTop;
+ private int mRight;
+ private int mBottom;
+ private ArrayList mLandmarkPoints = new ArrayList<>();
+
+ VisionDetRet() {
+ }
+
+ /**
+ * @param label Label name
+ * @param confidence A confidence factor between 0 and 1. This indicates how certain what has been found is actually the label.
+ * @param l The X coordinate of the left side of the result
+ * @param t The Y coordinate of the top of the result
+ * @param r The X coordinate of the right side of the result
+ * @param b The Y coordinate of the bottom of the result
+ */
+ public VisionDetRet(String label, float confidence, int l, int t, int r, int b) {
+ mLabel = label;
+ mLeft = l;
+ mTop = t;
+ mRight = r;
+ mBottom = b;
+ mConfidence = confidence;
+ }
+
+ /**
+ * @return The X coordinate of the left side of the result
+ */
+ public int getLeft() {
+ return mLeft;
+ }
+
+ /**
+ * @return The Y coordinate of the top of the result
+ */
+ public int getTop() {
+ return mTop;
+ }
+
+ /**
+ * @return The X coordinate of the right side of the result
+ */
+ public int getRight() {
+ return mRight;
+ }
+
+ /**
+ * @return The Y coordinate of the bottom of the result
+ */
+ public int getBottom() {
+ return mBottom;
+ }
+
+ /**
+ * @return A confidence factor between 0 and 1. This indicates how certain what has been found is actually the label.
+ */
+ public float getConfidence() {
+ return mConfidence;
+ }
+
+ /**
+ * @return The label of the result
+ */
+ public String getLabel() {
+ return mLabel;
+ }
+
+ /**
+ * Add landmark to the list. Usually, call by jni
+ * @param x Point x
+ * @param y Point y
+ * @return true if adding landmark successfully
+ */
+ public boolean addLandmark(int x, int y) {
+ return mLandmarkPoints.add(new Point(x, y));
+ }
+
+ /**
+ * Return the list of landmark points
+ * @return ArrayList of android.graphics.Point
+ */
+ public ArrayList getFaceLandmarks() {
+ return mLandmarkPoints;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Left:");
+ sb.append(mLabel);
+ sb.append(", Top:");
+ sb.append(mTop);
+ sb.append(", Right:");
+ sb.append(mRight);
+ sb.append(", Bottom:");
+ sb.append(mBottom);
+ sb.append(", Label:");
+ sb.append(mLabel);
+ return sb.toString();
+ }
+}
diff --git a/dlib/src/main/jniLibs/arm64-v8a/libandroid_dlib.so b/dlib/src/main/jniLibs/arm64-v8a/libandroid_dlib.so
new file mode 100755
index 0000000..4c07f27
Binary files /dev/null and b/dlib/src/main/jniLibs/arm64-v8a/libandroid_dlib.so differ
diff --git a/dlib/src/main/jniLibs/armeabi-v7a/libandroid_dlib.so b/dlib/src/main/jniLibs/armeabi-v7a/libandroid_dlib.so
new file mode 100755
index 0000000..e8e08ca
Binary files /dev/null and b/dlib/src/main/jniLibs/armeabi-v7a/libandroid_dlib.so differ
diff --git a/dlib/src/main/jniLibs/x86/libandroid_dlib.so b/dlib/src/main/jniLibs/x86/libandroid_dlib.so
new file mode 100755
index 0000000..8aededd
Binary files /dev/null and b/dlib/src/main/jniLibs/x86/libandroid_dlib.so differ
diff --git a/dlib/src/main/jniLibs/x86_64/libandroid_dlib.so b/dlib/src/main/jniLibs/x86_64/libandroid_dlib.so
new file mode 100755
index 0000000..ff4444a
Binary files /dev/null and b/dlib/src/main/jniLibs/x86_64/libandroid_dlib.so differ
diff --git a/dlib/src/main/res/values/strings.xml b/dlib/src/main/res/values/strings.xml
new file mode 100644
index 0000000..b8b3350
--- /dev/null
+++ b/dlib/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ DLib
+
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..af459cc
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,22 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+
+org.gradle.parallel=true
+android.useDeprecatedNdk=true
+org.gradle.configureondemand=true
+org.gradle.jvmargs=-Xmx5120m
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..39fc8cd
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri May 05 12:23:33 KST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..3000d02
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':app', ':dlib'
diff --git a/tests/test.bmp b/tests/test.bmp
new file mode 100644
index 0000000..fe7415d
Binary files /dev/null and b/tests/test.bmp differ
diff --git a/tests/test.sh b/tests/test.sh
new file mode 100755
index 0000000..1f78c0d
--- /dev/null
+++ b/tests/test.sh
@@ -0,0 +1,12 @@
+#/bin/bash
+echo "$(adb shell ls /sdcard/ | egrep -i shape_predictor_68_face_landmarks)"
+
+if (adb shell ls /sdcard/ | egrep -i shape_predictor_68_face_landmarks) 2> /dev/null; then
+ echo shape_predictor_68_face_landmarks exists
+else
+ echo shape_predictor_68_face_landmarks does not exist
+ adb push ../app/src/main/res/raw/shape_predictor_68_face_landmarks.dat /sdcard/
+fi
+
+adb push test.bmp /sdcard/
+adb shell am instrument -w -r -e debug false -e class DLibFunctionsTest com.tzutalin.dlibtest.test/android.support.test.runner.AndroidJUnitRunner