parent
0123fd9243
commit
de513b60e3
@ -1,8 +1,5 @@ |
||||
package com.example.open_nsfw_android |
||||
|
||||
import android.graphics.Bitmap |
||||
import com.zwy.nsfw.api.NsfwBean |
||||
|
||||
data class MyNsfwBean(var sfw: Float,var nsfw: Float, val path: String,val bitmap:Bitmap) { |
||||
|
||||
} |
||||
data class MyNsfwBean(var sfw: Float, var nsfw: Float, val path: String, val bitmap: Bitmap) |
@ -1,210 +0,0 @@ |
||||
|
||||
package com.zwy.nsfw; |
||||
|
||||
import android.app.Activity; |
||||
import android.content.res.AssetFileDescriptor; |
||||
import android.graphics.*; |
||||
import android.os.Environment; |
||||
import android.os.SystemClock; |
||||
import android.util.Log; |
||||
import com.zwy.nsfw.api.NsfwBean; |
||||
import org.tensorflow.lite.Interpreter; |
||||
import org.tensorflow.lite.Tensor; |
||||
import org.tensorflow.lite.gpu.GpuDelegate; |
||||
|
||||
import java.io.*; |
||||
import java.nio.ByteBuffer; |
||||
import java.nio.ByteOrder; |
||||
import java.nio.MappedByteBuffer; |
||||
import java.nio.channels.FileChannel; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import static java.lang.Math.max; |
||||
|
||||
public class Classifier { |
||||
|
||||
public static final String TAG = "open_nsfw_android"; |
||||
|
||||
/** |
||||
* tensor input img size |
||||
*/ |
||||
private final int INPUT_WIDTH = 224; |
||||
private final int INPUT_HEIGHT = 224; |
||||
|
||||
/** |
||||
* BytesPerChannel |
||||
*/ |
||||
private final int BYTES_PER_CHANNEL_NUM = 4; |
||||
/** |
||||
* 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[INPUT_WIDTH * INPUT_HEIGHT]; |
||||
List<Integer> list = new ArrayList<>(); |
||||
/** |
||||
* The loaded TensorFlow Lite model. |
||||
*/ |
||||
private MappedByteBuffer tfliteModel; |
||||
|
||||
/** |
||||
* Optional GPU delegate for accleration. |
||||
*/ |
||||
private GpuDelegate gpuDelegate = null; |
||||
|
||||
/** |
||||
* An instance of the driver class to run model inference with Tensorflow Lite. |
||||
*/ |
||||
private Interpreter tflite; |
||||
|
||||
/** |
||||
* A ByteBuffer to hold image data, to be feed into Tensorflow Lite as inputs. |
||||
*/ |
||||
private ByteBuffer imgData; |
||||
|
||||
/** |
||||
* Creates a classifier with the provided configuration. |
||||
* |
||||
* @param activity The current Activity. |
||||
* @param isAddGpuDelegate Add gpu delegate |
||||
* @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 Classifier(activity, isAddGpuDelegate, numThreads); |
||||
} |
||||
|
||||
private Classifier(Activity activity, Boolean isGPU, int numThreads) throws IOException { |
||||
tfliteModel = loadModelFile(activity); |
||||
Interpreter.Options tfliteOptions = new Interpreter.Options(); |
||||
if (isGPU) { |
||||
gpuDelegate = new GpuDelegate(); |
||||
tfliteOptions.addDelegate(gpuDelegate); |
||||
} |
||||
tfliteOptions.setNumThreads(numThreads); |
||||
tflite = new Interpreter(tfliteModel, tfliteOptions); |
||||
|
||||
Tensor tensor = tflite.getInputTensor(tflite.getInputIndex("input")); |
||||
String stringBuilder = " \n" |
||||
+ "dataType : " + |
||||
tensor.dataType() + |
||||
"\n" + |
||||
"numBytes : " + |
||||
tensor.numBytes() + |
||||
"\n" + |
||||
"numDimensions : " + |
||||
tensor.numDimensions() + |
||||
"\n" + |
||||
"numElements : " + |
||||
tensor.numElements() + |
||||
"\n" + |
||||
"shape : " + |
||||
tensor.shape().length; |
||||
Log.d(TAG, stringBuilder); |
||||
|
||||
imgData = |
||||
ByteBuffer.allocateDirect( |
||||
DIM_BATCH_SIZE |
||||
* INPUT_WIDTH |
||||
* INPUT_HEIGHT |
||||
* DIM_PIXEL_SIZE |
||||
* BYTES_PER_CHANNEL_NUM); |
||||
|
||||
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("nsfw.tflite"); |
||||
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; |
||||
} |
||||
imgData.rewind(); |
||||
int W = bitmap_.getWidth(); |
||||
int H = bitmap_.getHeight(); |
||||
|
||||
int w_off = max((W - INPUT_WIDTH) / 2, 0); |
||||
int h_off = max((H - INPUT_HEIGHT) / 2, 0); |
||||
|
||||
//把每个像素的颜色值转为int 存入intValues
|
||||
bitmap_.getPixels(intValues, 0, INPUT_WIDTH, h_off, w_off, INPUT_WIDTH, INPUT_HEIGHT); |
||||
// Convert the image to floating point.
|
||||
long startTime = SystemClock.uptimeMillis(); |
||||
for (final int color : intValues) { |
||||
int r1 = Color.red(color); |
||||
int g1 = Color.green(color); |
||||
int b1 = Color.blue(color); |
||||
|
||||
int rr1 = r1 - 123; |
||||
int gg1 = g1 - 117; |
||||
int bb1 = b1 - 104; |
||||
|
||||
imgData.putFloat(bb1); |
||||
imgData.putFloat(gg1); |
||||
imgData.putFloat(rr1); |
||||
} |
||||
long endTime = SystemClock.uptimeMillis(); |
||||
Log.d(TAG, "Timecost to put values into ByteBuffer: " + (endTime - startTime) + "ms"); |
||||
} |
||||
|
||||
public NsfwBean run(Bitmap bitmap) { |
||||
|
||||
Bitmap bitmap_256 = Bitmap.createScaledBitmap(bitmap, 256, 256,true); |
||||
|
||||
//Writes image data into byteBuffer
|
||||
convertBitmapToByteBuffer(bitmap_256); |
||||
|
||||
long startTime = SystemClock.uptimeMillis(); |
||||
// out
|
||||
float[][] outArray = new float[1][2]; |
||||
|
||||
tflite.run(imgData, outArray); |
||||
|
||||
long endTime = SystemClock.uptimeMillis(); |
||||
|
||||
Log.d(TAG, "SFW score :" + outArray[0][0] + ",NSFW score :" + outArray[0][1]); |
||||
Log.d(TAG, "Timecost to run model inference: " + (endTime - startTime) + "ms"); |
||||
return new NsfwBean(outArray[0][0], outArray[0][1]); |
||||
} |
||||
|
||||
/** |
||||
* Closes the interpreter and model to release resources. |
||||
*/ |
||||
public void close() { |
||||
if (tflite != null) { |
||||
tflite.close(); |
||||
tflite = null; |
||||
Log.d(TAG, "Tensorflow Lite Image Classifier close."); |
||||
} |
||||
if (gpuDelegate != null) { |
||||
gpuDelegate.close(); |
||||
Log.d(TAG, "Tensorflow Lite Image gpuDelegate close."); |
||||
gpuDelegate = null; |
||||
} |
||||
tfliteModel = null; |
||||
Log.d(TAG, "Tensorflow Lite destroyed."); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,42 @@ |
||||
package com.zwy.nsfw.api |
||||
|
||||
import android.graphics.Bitmap |
||||
import com.zwy.nsfw.core.Classifier |
||||
import com.zwy.nsfw.core.NSFWConfig |
||||
import com.zwy.nsfw.core.NsfwBean |
||||
|
||||
object NSFWHelper { |
||||
|
||||
private lateinit var classifier: Classifier |
||||
|
||||
private var isInit = false |
||||
|
||||
@Synchronized |
||||
fun init(nsfwConfig: NSFWConfig): NSFWHelper { |
||||
classifier = Classifier.create(nsfwConfig.assetMannager, nsfwConfig.userGPU, 10) |
||||
isInit = true |
||||
return this |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 同步扫描图片(建议使用者自己放在线程中处理) |
||||
*/ |
||||
@Synchronized |
||||
fun scanBitmap(bitmap: Bitmap): NsfwBean { |
||||
if (!isInit) { |
||||
throw ExceptionInInitializerError("Initialize the scanned object 'NSFWHelper.init(nsfwConfig: NSFWConfig)' by calling the function first.") |
||||
} |
||||
return classifier.run(bitmap) |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 不会销毁该单利和配置的参数,建议页面处理完成后调用该代码对扫描器进行关闭,关闭后下次扫描不需要再次调用init方法 |
||||
*/ |
||||
fun destroyFactory() { |
||||
classifier.close() |
||||
} |
||||
|
||||
|
||||
} |
@ -1,27 +0,0 @@ |
||||
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 + |
||||
'}'; |
||||
} |
||||
} |
@ -1,90 +0,0 @@ |
||||
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; |
||||
|
||||
/** |
||||
* Creates a classifier with the provided configuration. |
||||
* |
||||
* @param activity The current Activity. |
||||
* @param isAddGpuDelegate Add gpu delegate |
||||
* @param numThreads The number of threads to use for classification. |
||||
* @return A classifier with the desired configuration. |
||||
*/ |
||||
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); |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 同步扫描 |
||||
* |
||||
* @param bitmap |
||||
* @return nsfw/sfw |
||||
*/ |
||||
public NsfwBean scanBitmapSyn(Bitmap bitmap) { |
||||
synchronized (NsfwHelper.class) { |
||||
return classifier.run(bitmap); |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 异步扫描 |
||||
* |
||||
* @param bitmap |
||||
* @param onScanBitmapListener void onSuccess(float sfw, float nsfw); |
||||
*/ |
||||
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 interface OnScanBitmapListener { |
||||
void onSuccess(float sfw, float nsfw); |
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,197 @@ |
||||
package com.zwy.nsfw.core |
||||
|
||||
import android.content.res.AssetManager |
||||
import android.graphics.Bitmap |
||||
import android.graphics.Color |
||||
import android.os.SystemClock |
||||
import android.util.Log |
||||
import org.tensorflow.lite.Interpreter |
||||
import org.tensorflow.lite.gpu.GpuDelegate |
||||
import java.io.FileInputStream |
||||
import java.lang.Math.max |
||||
import java.nio.ByteBuffer |
||||
import java.nio.ByteOrder |
||||
import java.nio.MappedByteBuffer |
||||
import java.nio.channels.FileChannel |
||||
|
||||
class Classifier |
||||
private constructor(assetManager: AssetManager, isGPU: Boolean?, numThreads: Int) { |
||||
|
||||
/** |
||||
* tensor input img size |
||||
*/ |
||||
private val INPUT_WIDTH = 224 |
||||
private val INPUT_HEIGHT = 224 |
||||
|
||||
/** |
||||
* BytesPerChannel |
||||
*/ |
||||
private val BYTES_PER_CHANNEL_NUM = 4 |
||||
|
||||
/** |
||||
* Preallocated buffers for storing image data in. |
||||
*/ |
||||
private val intValues = IntArray(INPUT_WIDTH * INPUT_HEIGHT) |
||||
/** |
||||
* The loaded TensorFlow Lite model. |
||||
*/ |
||||
private var tfliteModel: MappedByteBuffer? = null |
||||
|
||||
/** |
||||
* Optional GPU delegate for accleration. |
||||
*/ |
||||
private var gpuDelegate: GpuDelegate? = null |
||||
|
||||
/** |
||||
* An instance of the driver class to run model inference with Tensorflow Lite. |
||||
*/ |
||||
private var tflite: Interpreter? = null |
||||
|
||||
/** |
||||
* A ByteBuffer to hold image data, to be feed into Tensorflow Lite as inputs. |
||||
*/ |
||||
private val imgData: ByteBuffer? |
||||
|
||||
init { |
||||
tfliteModel = loadModelFile(assetManager) |
||||
val tfliteOptions = Interpreter.Options() |
||||
if (isGPU == true) { |
||||
gpuDelegate = GpuDelegate() |
||||
tfliteOptions.addDelegate(gpuDelegate) |
||||
} |
||||
tfliteOptions.setNumThreads(numThreads) |
||||
tflite = Interpreter(tfliteModel!!, tfliteOptions) |
||||
|
||||
val tensor = tflite!!.getInputTensor(tflite!!.getInputIndex("input")) |
||||
val stringBuilder = (" \n" |
||||
+ "dataType : " + |
||||
tensor.dataType() + |
||||
"\n" + |
||||
"numBytes : " + |
||||
tensor.numBytes() + |
||||
"\n" + |
||||
"numDimensions : " + |
||||
tensor.numDimensions() + |
||||
"\n" + |
||||
"numElements : " + |
||||
tensor.numElements() + |
||||
"\n" + |
||||
"shape : " + |
||||
tensor.shape().size) |
||||
Log.d(TAG, stringBuilder) |
||||
|
||||
imgData = ByteBuffer.allocateDirect( |
||||
DIM_BATCH_SIZE |
||||
* INPUT_WIDTH |
||||
* INPUT_HEIGHT |
||||
* DIM_PIXEL_SIZE |
||||
* BYTES_PER_CHANNEL_NUM |
||||
) |
||||
|
||||
imgData!!.order(ByteOrder.LITTLE_ENDIAN) |
||||
Log.d(TAG, "Tensorflow Lite Image Classifier Initialization Success.") |
||||
} |
||||
|
||||
/** |
||||
* Memory-map the model file in Assets. |
||||
*/ |
||||
private fun loadModelFile(assetManager: AssetManager): MappedByteBuffer { |
||||
val fileDescriptor = assetManager.openFd("nsfw.tflite") |
||||
val inputStream = FileInputStream(fileDescriptor.fileDescriptor) |
||||
val fileChannel = inputStream.channel |
||||
val startOffset = fileDescriptor.startOffset |
||||
val declaredLength = fileDescriptor.declaredLength |
||||
return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength) |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Writes Image data into a `ByteBuffer`. |
||||
*/ |
||||
private fun convertBitmapToByteBuffer(bitmap_: Bitmap) { |
||||
if (imgData == null || bitmap_ == null) { |
||||
return |
||||
} |
||||
imgData.rewind() |
||||
val W = bitmap_.width |
||||
val H = bitmap_.height |
||||
|
||||
val w_off = max((W - INPUT_WIDTH) / 2, 0) |
||||
val h_off = max((H - INPUT_HEIGHT) / 2, 0) |
||||
|
||||
//把每个像素的颜色值转为int 存入intValues
|
||||
bitmap_.getPixels(intValues, 0, INPUT_WIDTH, h_off, w_off, INPUT_WIDTH, INPUT_HEIGHT) |
||||
// Convert the image to floating point.
|
||||
val startTime = SystemClock.uptimeMillis() |
||||
for (color in intValues) { |
||||
val r1 = Color.red(color) |
||||
val g1 = Color.green(color) |
||||
val b1 = Color.blue(color) |
||||
|
||||
val rr1 = r1 - 123 |
||||
val gg1 = g1 - 117 |
||||
val bb1 = b1 - 104 |
||||
|
||||
imgData.putFloat(bb1.toFloat()) |
||||
imgData.putFloat(gg1.toFloat()) |
||||
imgData.putFloat(rr1.toFloat()) |
||||
} |
||||
val endTime = SystemClock.uptimeMillis() |
||||
Log.d(TAG, "Timecost to put values into ByteBuffer: " + (endTime - startTime) + "ms") |
||||
} |
||||
|
||||
fun run(bitmap: Bitmap): NsfwBean { |
||||
|
||||
val bitmap_256 = Bitmap.createScaledBitmap(bitmap, 256, 256, true) |
||||
|
||||
//Writes image data into byteBuffer
|
||||
convertBitmapToByteBuffer(bitmap_256) |
||||
|
||||
val startTime = SystemClock.uptimeMillis() |
||||
// out
|
||||
val outArray = Array(1) { FloatArray(2) } |
||||
|
||||
tflite!!.run(imgData, outArray) |
||||
|
||||
val endTime = SystemClock.uptimeMillis() |
||||
|
||||
Log.d(TAG, "SFW score :" + outArray[0][0] + ",NSFW score :" + outArray[0][1]) |
||||
Log.d(TAG, "Timecost to run model inference: " + (endTime - startTime) + "ms") |
||||
return NsfwBean(outArray[0][0], outArray[0][1]) |
||||
} |
||||
|
||||
/** |
||||
* Closes the interpreter and model to release resources. |
||||
*/ |
||||
fun close() { |
||||
if (tflite != null) { |
||||
tflite!!.close() |
||||
tflite = null |
||||
Log.d(TAG, "Tensorflow Lite Image Classifier close.") |
||||
} |
||||
if (gpuDelegate != null) { |
||||
gpuDelegate!!.close() |
||||
Log.d(TAG, "Tensorflow Lite Image gpuDelegate close.") |
||||
gpuDelegate = null |
||||
} |
||||
tfliteModel = null |
||||
Log.d(TAG, "Tensorflow Lite destroyed.") |
||||
} |
||||
|
||||
companion object { |
||||
|
||||
val TAG = "open_nsfw_android" |
||||
/** |
||||
* Dimensions of inputs. |
||||
*/ |
||||
private val DIM_BATCH_SIZE = 1 |
||||
|
||||
private val DIM_PIXEL_SIZE = 3 |
||||
|
||||
fun create(assetManager: AssetManager, isAddGpuDelegate: Boolean?, numThreads: Int): Classifier { |
||||
return Classifier(assetManager, isAddGpuDelegate!!, numThreads) |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,10 @@ |
||||
package com.zwy.nsfw.core |
||||
|
||||
import android.content.res.AssetManager |
||||
|
||||
/** |
||||
* 配置扫描参数 |
||||
* [assetMannager]资源管理器,用于读取lib下的tfLite文件 |
||||
* [userGPU]是否开启GPU加速 默认关闭,部分手机开启后会有奔溃 |
||||
*/ |
||||
data class NSFWConfig(val assetMannager: AssetManager, val userGPU: Boolean = false) |
@ -0,0 +1,3 @@ |
||||
package com.zwy.nsfw.core |
||||
|
||||
class NsfwBean(val sfw: Float, val nsfw: Float) |
Loading…
Reference in new issue