diff --git a/.gitignore b/.gitignore
index 9b7f1951..ebab6897 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,4 @@
.DS_Store
/build
/captures
-.externalNativeBuild
-/nsfw
-nsfw
\ No newline at end of file
+.externalNativeBuild
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 4ab9d323..b0bb1ce5 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -10,6 +10,7 @@
+
diff --git a/app/build.gradle b/app/build.gradle
index 24dab761..5116e621 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -15,14 +15,8 @@ android {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
- release {
- minifyEnabled true //开启混淆
- zipAlignEnabled true //压缩优化
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- }
debug{
- minifyEnabled true //开启混淆
- zipAlignEnabled true //压缩优化
+ minifyEnabled false //开启混淆
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
@@ -40,8 +34,5 @@ dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
- implementation files('libs/nsfw.aar')
-
- implementation 'org.tensorflow:tensorflow-lite:+'
- implementation 'org.tensorflow:tensorflow-lite-gpu:+'
+ implementation project(path: ':nsfw')
}
diff --git a/app/libs/nsfw.aar b/app/libs/nsfw.aar
deleted file mode 100644
index d6caa9d4..00000000
Binary files a/app/libs/nsfw.aar and /dev/null differ
diff --git a/app/src/main/java/com/example/open_nsfw_android/MainActivity.kt b/app/src/main/java/com/example/open_nsfw_android/MainActivity.kt
index cbea5b3a..b55c08d9 100644
--- a/app/src/main/java/com/example/open_nsfw_android/MainActivity.kt
+++ b/app/src/main/java/com/example/open_nsfw_android/MainActivity.kt
@@ -1,5 +1,6 @@
package com.example.open_nsfw_android
+import android.annotation.SuppressLint
import android.graphics.BitmapFactory
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
@@ -9,14 +10,15 @@ import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
- var nsfwHelper: NsfwHelper? = null;
+ var nsfwHelper: NsfwHelper? = null
+ @SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//assets 目录下的timg-10.jpeg为正常静态图片 ccc.gif 为动态正常图片 可用作测试
- val b = BitmapFactory.decodeStream(getResources().getAssets().open("aaa.png"))
+ val b = BitmapFactory.decodeStream(resources.assets.open("aaa.png"))
iv.setImageBitmap(b)
nsfwHelper = NsfwHelper.getInstance(this, true, 1)
@@ -25,11 +27,11 @@ class MainActivity : AppCompatActivity() {
//同步识别
val nsfwBean = nsfwHelper?.scanBitmapSyn(b)
Log.d("demo", nsfwBean.toString())
- tvv.setText("识别成功:\n\tSFW score : ${nsfwBean?.sfw}\n\tNSFW score : ${nsfwBean?.nsfw}")
+ tvv.text = "识别成功:\n\tSFW score : ${nsfwBean?.sfw}\n\tNSFW score : ${nsfwBean?.nsfw}"
if (nsfwBean?.nsfw ?: 0f > 0.7) {
- tvv.text = "${tvv.text.toString()} \n \t - 色情图片"
- }else {
- tvv.text = "${tvv.text.toString()} \n \t - 正常图片"
+ tvv.text = "${tvv.text} \n \t - 色情图片"
+ } else {
+ tvv.text = "${tvv.text} \n \t - 正常图片"
}
// //异步识别,接口回调识别结果
// nsfwHelper?.scanBitmap(b) { sfw, nsfw ->
diff --git a/nsfw/.gitignore b/nsfw/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/nsfw/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/nsfw/build.gradle b/nsfw/build.gradle
new file mode 100644
index 00000000..7942e778
--- /dev/null
+++ b/nsfw/build.gradle
@@ -0,0 +1,35 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 28
+
+
+ defaultConfig {
+ minSdkVersion 15
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+ }
+
+ buildTypes {
+ debug{
+ minifyEnabled false //开启混淆
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ implementation 'com.android.support:appcompat-v7:28.0.0'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+ implementation 'org.tensorflow:tensorflow-lite:+'
+ implementation 'org.tensorflow:tensorflow-lite-gpu:+'
+}
diff --git a/nsfw/proguard-rules.pro b/nsfw/proguard-rules.pro
new file mode 100644
index 00000000..4c8374f1
--- /dev/null
+++ b/nsfw/proguard-rules.pro
@@ -0,0 +1,22 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# 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 *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+-keep class com.zwy.nsfw.api.**{*;}
\ No newline at end of file
diff --git a/nsfw/src/androidTest/java/com/zwy/nsfw/ExampleInstrumentedTest.java b/nsfw/src/androidTest/java/com/zwy/nsfw/ExampleInstrumentedTest.java
new file mode 100644
index 00000000..fbc508a8
--- /dev/null
+++ b/nsfw/src/androidTest/java/com/zwy/nsfw/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.zwy.nsfw;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("com.zwy.nsfw.test", appContext.getPackageName());
+ }
+}
diff --git a/nsfw/src/main/AndroidManifest.xml b/nsfw/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..6ed4072d
--- /dev/null
+++ b/nsfw/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/nsfw/src/main/assets/nsfw.tflite b/nsfw/src/main/assets/nsfw.tflite
new file mode 100644
index 00000000..a13a741b
Binary files /dev/null and b/nsfw/src/main/assets/nsfw.tflite differ
diff --git a/nsfw/src/main/java/com/zwy/nsfw/Classifier.java b/nsfw/src/main/java/com/zwy/nsfw/Classifier.java
new file mode 100644
index 00000000..766af2ca
--- /dev/null
+++ b/nsfw/src/main/java/com/zwy/nsfw/Classifier.java
@@ -0,0 +1,212 @@
+
+package com.zwy.nsfw;
+
+import android.app.Activity;
+import android.content.res.AssetFileDescriptor;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.os.SystemClock;
+import android.util.Log;
+import com.zwy.nsfw.api.NsfwBean;
+import org.tensorflow.lite.Interpreter;
+import org.tensorflow.lite.gpu.GpuDelegate;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+
+/**
+ * A classifier specialized to label images using TensorFlow Lite.
+ */
+public abstract class Classifier {
+
+ public static final String TAG = "open_nsfw_android";
+
+ /**
+ * Dimensions of inputs.
+ */
+ private static final int DIM_BATCH_SIZE = 1;
+
+ private static final int DIM_PIXEL_SIZE = 3;
+
+ /**
+ * Preallocated buffers for storing image data in.
+ */
+ private int[] intValues = new int[getImageSizeX() * getImageSizeY()];
+
+ /**
+ * Options for configuring the Interpreter.
+ */
+ private final Interpreter.Options tfliteOptions = new Interpreter.Options();
+
+ /**
+ * The loaded TensorFlow Lite model.
+ */
+ private MappedByteBuffer tfliteModel;
+
+ /** Labels corresponding to the output of the vision model. */
+// private List labels;
+
+ /**
+ * Optional GPU delegate for accleration.
+ */
+ private GpuDelegate gpuDelegate = null;
+
+ /**
+ * An instance of the driver class to run model inference with Tensorflow Lite.
+ */
+ protected Interpreter tflite;
+
+ /**
+ * A ByteBuffer to hold image data, to be feed into Tensorflow Lite as inputs.
+ */
+ protected ByteBuffer imgData = null;
+
+ /**
+ * Creates a classifier with the provided configuration.
+ *
+ * @param activity The current Activity.
+ * @param numThreads The number of threads to use for classification.
+ * @return A classifier with the desired configuration.
+ */
+ public static Classifier create(Activity activity, Boolean isAddGpuDelegate, int numThreads)
+ throws IOException {
+ return new ClassifierFloatMobileNet(activity, isAddGpuDelegate, numThreads);
+ }
+
+ /**
+ * An immutable result returned by a Classifier describing what was recognized.
+ */
+
+ /**
+ * Initializes a {@code Classifier}.
+ */
+ protected Classifier(Activity activity, Boolean isGPU, int numThreads) throws IOException {
+ tfliteModel = loadModelFile(activity);
+ if (isGPU) {
+ gpuDelegate = new GpuDelegate();
+ tfliteOptions.addDelegate(gpuDelegate);
+ }
+ tfliteOptions.setNumThreads(numThreads);
+ tflite = new Interpreter(tfliteModel, tfliteOptions);
+ imgData =
+ ByteBuffer.allocateDirect(
+ DIM_BATCH_SIZE
+ * getImageSizeX()
+ * getImageSizeY()
+ * DIM_PIXEL_SIZE
+ * getNumBytesPerChannel());
+ imgData.order(ByteOrder.LITTLE_ENDIAN);
+ Log.d(TAG, "Tensorflow Lite Image Classifier Initialization Success.");
+ }
+
+ /**
+ * Memory-map the model file in Assets.
+ */
+ private MappedByteBuffer loadModelFile(Activity activity) throws IOException {
+ AssetFileDescriptor fileDescriptor = activity.getAssets().openFd(getModelPath());
+ FileInputStream inputStream = new FileInputStream(fileDescriptor.getFileDescriptor());
+ FileChannel fileChannel = inputStream.getChannel();
+ long startOffset = fileDescriptor.getStartOffset();
+ long declaredLength = fileDescriptor.getDeclaredLength();
+ return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength);
+ }
+
+ /**
+ * Writes Image data into a {@code ByteBuffer}.
+ */
+ private void convertBitmapToByteBuffer(Bitmap bitmap_) {
+ if (imgData == null || bitmap_ == null) {
+ return;
+ }
+ Bitmap bitmap = Bitmap.createScaledBitmap(bitmap_, 224, 224, false);
+
+ imgData.rewind();
+// intValues= ImageUtil.bitmap2BGR(bitmap);
+ //把每个像素的颜色值转为int 存入intValues
+ bitmap.getPixels(intValues, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
+// bitmap.copyPixelsToBuffer(imgData);
+ // Convert the image to floating point.
+ int pixel = 0;
+ long startTime = SystemClock.uptimeMillis();
+ for (int i = 0; i < getImageSizeX(); ++i) {
+ for (int j = 0; j < getImageSizeY(); ++j) {
+
+ //操作每一个像素
+ //拿出每一个像素点对应的R、G、B的int值
+ //对每一个int值减去阈值 R-123 B-104 G-117
+ //将R、G、B 利用 B、G、R顺序重新写入数组
+ //将数组传入tflite获取回传结果
+ final int color = intValues[pixel++];
+ int r1 = Color.red(color) - 123;
+ int g1 = Color.green(color) - 117;
+ int b1 = Color.blue(color) - 104;
+
+ imgData.putFloat(b1);
+ imgData.putFloat(g1);
+ imgData.putFloat(r1);
+ }
+ }
+ long endTime = SystemClock.uptimeMillis();
+ Log.d(TAG, "Timecost to put values into ByteBuffer: " + (endTime - startTime) + "ms");
+ }
+
+ public NsfwBean run(Bitmap bitmap) {
+ convertBitmapToByteBuffer(bitmap);
+ long startTime = SystemClock.uptimeMillis();
+ float[][] labelProbArray = new float[1][2];
+ tflite.run(imgData, labelProbArray);
+ long endTime = SystemClock.uptimeMillis();
+ Log.d(TAG, "Timecost to run model inference: " + (endTime - startTime) + "ms");
+ return new NsfwBean(labelProbArray[0][0], labelProbArray[0][1]);
+ }
+
+
+ /**
+ * Closes the interpreter and model to release resources.
+ */
+ public void close() {
+ if (tflite != null) {
+ tflite.close();
+ tflite = null;
+ }
+ if (gpuDelegate != null) {
+ gpuDelegate.close();
+ gpuDelegate = null;
+ }
+ tfliteModel = null;
+ }
+
+ /**
+ * Get the image size along the x axis.
+ *
+ * @return
+ */
+ public abstract int getImageSizeX();
+
+ /**
+ * Get the image size along the y axis.
+ *
+ * @return
+ */
+ public abstract int getImageSizeY();
+
+ /**
+ * Get the name of the model file stored in Assets.
+ *
+ * @return
+ */
+ protected abstract String getModelPath();
+
+
+ /**
+ * Get the number of bytes that is used to store a single color channel value.
+ *
+ * @return
+ */
+ protected abstract int getNumBytesPerChannel();
+
+}
diff --git a/nsfw/src/main/java/com/zwy/nsfw/ClassifierFloatMobileNet.java b/nsfw/src/main/java/com/zwy/nsfw/ClassifierFloatMobileNet.java
new file mode 100644
index 00000000..3a07e81b
--- /dev/null
+++ b/nsfw/src/main/java/com/zwy/nsfw/ClassifierFloatMobileNet.java
@@ -0,0 +1,53 @@
+/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+package com.zwy.nsfw;
+
+import android.app.Activity;
+
+import java.io.IOException;
+
+/**
+ * This TensorFlowLite classifier works with the float MobileNet model.
+ */
+public class ClassifierFloatMobileNet extends Classifier {
+
+
+ public ClassifierFloatMobileNet(Activity activity, Boolean isAddGpuDelegate, int numThreads)
+ throws IOException {
+ super(activity, isAddGpuDelegate, numThreads);
+ }
+
+ @Override
+ public int getImageSizeX() {
+ return 224;
+ }
+
+ @Override
+ public int getImageSizeY() {
+ return 224;
+ }
+
+ @Override
+ protected String getModelPath() {
+ return "nsfw.tflite";
+ }
+
+ @Override
+ protected int getNumBytesPerChannel() {
+ return 4;
+ }
+
+}
diff --git a/nsfw/src/main/java/com/zwy/nsfw/api/NsfwBean.java b/nsfw/src/main/java/com/zwy/nsfw/api/NsfwBean.java
new file mode 100644
index 00000000..0c907cef
--- /dev/null
+++ b/nsfw/src/main/java/com/zwy/nsfw/api/NsfwBean.java
@@ -0,0 +1,27 @@
+package com.zwy.nsfw.api;
+
+public class NsfwBean {
+ private float sfw;
+ private float nsfw;
+
+ public NsfwBean(float sfw, float nsfw) {
+ this.sfw = sfw;
+ this.nsfw = nsfw;
+ }
+
+ public float getSfw() {
+ return sfw;
+ }
+
+ public float getNsfw() {
+ return nsfw;
+ }
+
+ @Override
+ public String toString() {
+ return "NsfwBean{" +
+ "sfw=" + sfw +
+ ", nsfw=" + nsfw +
+ '}';
+ }
+}
diff --git a/nsfw/src/main/java/com/zwy/nsfw/api/NsfwHelper.java b/nsfw/src/main/java/com/zwy/nsfw/api/NsfwHelper.java
new file mode 100644
index 00000000..e057a7a5
--- /dev/null
+++ b/nsfw/src/main/java/com/zwy/nsfw/api/NsfwHelper.java
@@ -0,0 +1,67 @@
+package com.zwy.nsfw.api;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.util.Log;
+import com.zwy.nsfw.Classifier;
+
+import java.io.IOException;
+
+public class NsfwHelper {
+ @SuppressLint("StaticFieldLeak")
+ private static NsfwHelper nsfwHelper;
+ private Activity activity;
+ private Classifier classifier;
+
+ public static NsfwHelper getInstance(Activity activity, Boolean isAddGpuDelegate, int numThreads) {
+ synchronized (NsfwHelper.class) {
+ if (nsfwHelper == null) {
+ nsfwHelper = new NsfwHelper(activity, isAddGpuDelegate, numThreads);
+ }
+ }
+ return nsfwHelper;
+ }
+
+ private NsfwHelper(Activity activity, Boolean isAddGpuDelegate, int numThreads) {
+ try {
+ this.activity = activity;
+ classifier = Classifier.create(activity, isAddGpuDelegate, numThreads);
+ } catch (IOException e) {
+ e.printStackTrace();
+ Log.e(Classifier.TAG, "Tensorflow Lite Image Classifier Initialization Error,e:" + e);
+ }
+ }
+
+ public NsfwBean scanBitmapSyn(Bitmap bitmap) {
+ return classifier.run(bitmap);
+ }
+
+
+ public void scanBitmap(final Bitmap bitmap, final OnScanBitmapListener onScanBitmapListener) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ final NsfwBean nsfwBean = scanBitmapSyn(bitmap);
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ onScanBitmapListener.onSuccess(nsfwBean.getSfw(), nsfwBean.getNsfw());
+ }
+ });
+ }
+ }).start();
+ }
+
+ public void destroy() {
+ classifier.close();
+ activity = null;
+ nsfwHelper = null;
+ }
+
+ public static interface OnScanBitmapListener {
+ void onSuccess(float sfw, float nsfw);
+ }
+
+
+}
diff --git a/nsfw/src/main/res/raw/nsfw.tflite b/nsfw/src/main/res/raw/nsfw.tflite
new file mode 100644
index 00000000..a13a741b
Binary files /dev/null and b/nsfw/src/main/res/raw/nsfw.tflite differ
diff --git a/nsfw/src/main/res/values/strings.xml b/nsfw/src/main/res/values/strings.xml
new file mode 100644
index 00000000..62709f68
--- /dev/null
+++ b/nsfw/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ nsfw
+
diff --git a/nsfw/src/test/java/com/zwy/nsfw/ExampleUnitTest.java b/nsfw/src/test/java/com/zwy/nsfw/ExampleUnitTest.java
new file mode 100644
index 00000000..878354d0
--- /dev/null
+++ b/nsfw/src/test/java/com/zwy/nsfw/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.zwy.nsfw;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index e7b4def4..bd5affeb 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1 @@
-include ':app'
+include ':app',':nsfw'