parent
c0a152cc0c
commit
a2a2f37998
Binary file not shown.
@ -0,0 +1 @@ |
|||||||
|
/build |
@ -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:+' |
||||||
|
} |
@ -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.**{*;} |
@ -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 <a href="http://d.android.com/tools/testing">Testing documentation</a> |
||||||
|
*/ |
||||||
|
@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()); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
<manifest package="com.zwy.nsfw"/> |
Binary file not shown.
@ -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<String> 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(); |
||||||
|
|
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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 + |
||||||
|
'}'; |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
Binary file not shown.
@ -0,0 +1,3 @@ |
|||||||
|
<resources> |
||||||
|
<string name="app_name">nsfw</string> |
||||||
|
</resources> |
@ -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 <a href="http://d.android.com/tools/testing">Testing documentation</a> |
||||||
|
*/ |
||||||
|
public class ExampleUnitTest { |
||||||
|
@Test |
||||||
|
public void addition_isCorrect() { |
||||||
|
assertEquals(4, 2 + 2); |
||||||
|
} |
||||||
|
} |
@ -1 +1 @@ |
|||||||
include ':app' |
include ':app',':nsfw' |
||||||
|
Loading…
Reference in new issue