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