From de513b60e34f81dc883054666533a247c6376f33 Mon Sep 17 00:00:00 2001 From: "dev_zwy@aliyun.com" Date: Fri, 26 Jul 2019 11:49:35 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?=E6=96=B9=E5=BC=8F=EF=BC=8C=E5=88=9D=E5=A7=8B=E5=8C=96=E6=97=B6?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E5=85=B3=E9=97=ADGPU=E5=8A=A0=E9=80=9F?= =?UTF-8?q?=E9=98=B2=E6=AD=A2=E9=83=A8=E5=88=86=E6=89=8B=E6=9C=BA=E5=A5=94?= =?UTF-8?q?=E6=BA=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/misc.xml | 2 +- app/build.gradle | 8 +- .../example/open_nsfw_android/MainActivity.kt | 69 +++--- .../example/open_nsfw_android/MyNsfwBean.kt | 5 +- build.gradle | 4 +- gradle.properties | 1 + nsfw/build.gradle | 17 +- .../main/java/com/zwy/nsfw/Classifier.java | 210 ------------------ .../main/java/com/zwy/nsfw/api/NSFWHelper.kt | 42 ++++ .../main/java/com/zwy/nsfw/api/NsfwBean.java | 27 --- .../java/com/zwy/nsfw/api/NsfwHelper.java | 90 -------- .../main/java/com/zwy/nsfw/core/Classifier.kt | 197 ++++++++++++++++ .../main/java/com/zwy/nsfw/core/NSFWConfig.kt | 10 + .../main/java/com/zwy/nsfw/core/NsfwBean.kt | 3 + 14 files changed, 318 insertions(+), 367 deletions(-) delete mode 100644 nsfw/src/main/java/com/zwy/nsfw/Classifier.java create mode 100644 nsfw/src/main/java/com/zwy/nsfw/api/NSFWHelper.kt delete mode 100644 nsfw/src/main/java/com/zwy/nsfw/api/NsfwBean.java delete mode 100644 nsfw/src/main/java/com/zwy/nsfw/api/NsfwHelper.java create mode 100644 nsfw/src/main/java/com/zwy/nsfw/core/Classifier.kt create mode 100644 nsfw/src/main/java/com/zwy/nsfw/core/NSFWConfig.kt create mode 100644 nsfw/src/main/java/com/zwy/nsfw/core/NsfwBean.kt diff --git a/.idea/misc.xml b/.idea/misc.xml index dcecd355..25f24431 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -33,7 +33,7 @@ - + diff --git a/app/build.gradle b/app/build.gradle index 36996ec5..37c250ee 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "com.example.open_nsfw_android" minSdkVersion 15 targetSdkVersion 28 - versionCode 4 - versionName "1.2.7" + versionCode 5 + versionName "1.2.8" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { @@ -38,6 +38,6 @@ dependencies { implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.30' implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.2.3' -// implementation project(path: ':nsfw') - implementation 'com.github.devzwy:open_nsfw_android:1.2.7' + implementation project(path: ':nsfw') +// implementation 'com.github.devzwy:open_nsfw_android:1.2.7' } 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 48bfdbe9..5897310f 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 @@ -2,6 +2,7 @@ package com.example.open_nsfw_android import android.Manifest import android.annotation.SuppressLint +import android.app.ProgressDialog import android.content.Intent import android.content.pm.PackageManager import android.graphics.BitmapFactory @@ -15,18 +16,21 @@ import com.luck.picture.lib.PictureSelector import com.luck.picture.lib.config.PictureConfig import com.luck.picture.lib.config.PictureMimeType import com.luck.picture.lib.entity.LocalMedia -import com.zwy.nsfw.api.NsfwHelper +import com.zwy.nsfw.api.NSFWHelper +import com.zwy.nsfw.core.NSFWConfig import kotlinx.android.synthetic.main.activity_main.* +import kotlin.concurrent.thread class MainActivity : AppCompatActivity(), View.OnClickListener { - var nsfwHelper: NsfwHelper? = null + var nsfwHelper: NSFWHelper? = null var mainAdapter: MainAdapter? = null var index = 0 - var listData: ArrayList = ArrayList() + var listData: ArrayList = ArrayList() var selectList: List? = null + var progressDialog: ProgressDialog? = null @SuppressLint("SetTextI18n") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -35,10 +39,13 @@ class MainActivity : AppCompatActivity(), View.OnClickListener { initAdapter() initClickListener() tv_version.text = "当前版本:${this.packageManager.getPackageInfo(packageName, 0).versionName}" - if (ContextCompat.checkSelfPermission(this, - Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){ //表示未授权时 + if (ContextCompat.checkSelfPermission( + this, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) != PackageManager.PERMISSION_GRANTED + ) { //表示未授权时 //进行授权 - ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),1); + ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1); } } @@ -88,10 +95,11 @@ class MainActivity : AppCompatActivity(), View.OnClickListener { } private fun initNsfwHelper() { - nsfwHelper = NsfwHelper.getInstance(this, true, 4) + nsfwHelper = NSFWHelper.init(NSFWConfig(assets)) } private fun reScFromImgs(list: List) { + progressDialog = ProgressDialog.show(this, "提示", "请稍后") index = 0 mainAdapter?.setNewData(null) listData = ArrayList() @@ -99,35 +107,44 @@ class MainActivity : AppCompatActivity(), View.OnClickListener { for (lm in list) { val bitmap = BitmapFactory.decodeFile(lm.path) listData.add(MyNsfwBean(0.0f, 0.0f, lm.path, bitmap)) - nsfwHelper?.scanBitmap(bitmap) { sfw, nsfw -> - listData[index].sfw = sfw - listData[index].nsfw = nsfw - mainAdapter?.addData(listData[index]) - mainAdapter?.notifyItemInserted(index) - rv.scrollToPosition(index) - index++ - } + val nsfwBean = nsfwHelper?.scanBitmap(bitmap)!! + listData[index].sfw = nsfwBean.sfw + listData[index].nsfw = nsfwBean.nsfw +// rv.scrollToPosition(index) + index++ + } + runOnUiThread { + mainAdapter?.setNewData(listData) + mainAdapter?.notifyDataSetChanged() + progressDialog?.dismiss() } }).start() } private fun reScAssetsImgs() { + progressDialog = ProgressDialog.show(this, "提示", "请稍后") index = 0 mainAdapter?.setNewData(null) listData = ArrayList() - for (a in resources.assets.list("img")) { - val path = "img/${a}" - val b = BitmapFactory.decodeStream(resources.assets.open(path)) - listData.add(MyNsfwBean(0f, 0f, path, b)) - nsfwHelper?.scanBitmap(b) { sfw, nsfw -> - listData[index].sfw = sfw - listData[index].nsfw = nsfw - mainAdapter?.addData(listData[index]) - mainAdapter?.notifyItemInserted(index) - rv.scrollToPosition(index) + thread(true) { + for (a in resources.assets.list("img")) { + val path = "img/${a}" + val b = BitmapFactory.decodeStream(resources.assets.open(path)) + listData.add(MyNsfwBean(0f, 0f, path, b)) + val nsfwBean = nsfwHelper?.scanBitmap(b)!! + listData[index].sfw = nsfwBean.sfw + listData[index].nsfw = nsfwBean.nsfw + index++ } + runOnUiThread { + mainAdapter?.setNewData(listData) + mainAdapter?.notifyDataSetChanged() + progressDialog?.dismiss() +// rv.scrollToPosition(index) + } } + } override fun onBackPressed() { @@ -136,6 +153,6 @@ class MainActivity : AppCompatActivity(), View.OnClickListener { override fun onDestroy() { super.onDestroy() - nsfwHelper?.destroy() + nsfwHelper?.destroyFactory() } } diff --git a/app/src/main/java/com/example/open_nsfw_android/MyNsfwBean.kt b/app/src/main/java/com/example/open_nsfw_android/MyNsfwBean.kt index 8fe1cd7d..fe2ee235 100644 --- a/app/src/main/java/com/example/open_nsfw_android/MyNsfwBean.kt +++ b/app/src/main/java/com/example/open_nsfw_android/MyNsfwBean.kt @@ -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) { - -} \ No newline at end of file +data class MyNsfwBean(var sfw: Float, var nsfw: Float, val path: String, val bitmap: Bitmap) \ No newline at end of file diff --git a/build.gradle b/build.gradle index cfc4cb88..157668c3 100644 --- a/build.gradle +++ b/build.gradle @@ -1,14 +1,14 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.31' + ext.kotlin_version = '1.3.41' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.4.1' + classpath 'com.android.tools.build:gradle:3.4.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle.properties b/gradle.properties index 85be9ead..a201e9aa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,3 +13,4 @@ org.gradle.jvmargs=-Xmx1536m # org.gradle.parallel=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official +kotlin.coroutines=enable \ No newline at end of file diff --git a/nsfw/build.gradle b/nsfw/build.gradle index 6d1e9347..f5b95b8b 100644 --- a/nsfw/build.gradle +++ b/nsfw/build.gradle @@ -1,4 +1,6 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' android { compileSdkVersion 28 @@ -6,17 +8,22 @@ android { defaultConfig { minSdkVersion 15 targetSdkVersion 28 - versionCode 1 - versionName "1.0" + versionCode 5 + versionName "1.2.8" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { - debug{ + debug { minifyEnabled false //开启混淆® proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } + kotlin { + experimental { + coroutines 'enable' + } + } } dependencies { @@ -27,4 +34,8 @@ dependencies { androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' implementation 'org.tensorflow:tensorflow-lite:+' implementation 'org.tensorflow:tensorflow-lite-gpu:+' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} +repositories { + mavenCentral() } \ No newline at end of file diff --git a/nsfw/src/main/java/com/zwy/nsfw/Classifier.java b/nsfw/src/main/java/com/zwy/nsfw/Classifier.java deleted file mode 100644 index a23d94af..00000000 --- a/nsfw/src/main/java/com/zwy/nsfw/Classifier.java +++ /dev/null @@ -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 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."); - } - -} diff --git a/nsfw/src/main/java/com/zwy/nsfw/api/NSFWHelper.kt b/nsfw/src/main/java/com/zwy/nsfw/api/NSFWHelper.kt new file mode 100644 index 00000000..f6b8496b --- /dev/null +++ b/nsfw/src/main/java/com/zwy/nsfw/api/NSFWHelper.kt @@ -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() + } + + +} \ No newline at end of file diff --git a/nsfw/src/main/java/com/zwy/nsfw/api/NsfwBean.java b/nsfw/src/main/java/com/zwy/nsfw/api/NsfwBean.java deleted file mode 100644 index 0c907cef..00000000 --- a/nsfw/src/main/java/com/zwy/nsfw/api/NsfwBean.java +++ /dev/null @@ -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 + - '}'; - } -} diff --git a/nsfw/src/main/java/com/zwy/nsfw/api/NsfwHelper.java b/nsfw/src/main/java/com/zwy/nsfw/api/NsfwHelper.java deleted file mode 100644 index 2e94a577..00000000 --- a/nsfw/src/main/java/com/zwy/nsfw/api/NsfwHelper.java +++ /dev/null @@ -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); - } - - -} diff --git a/nsfw/src/main/java/com/zwy/nsfw/core/Classifier.kt b/nsfw/src/main/java/com/zwy/nsfw/core/Classifier.kt new file mode 100644 index 00000000..ef156e17 --- /dev/null +++ b/nsfw/src/main/java/com/zwy/nsfw/core/Classifier.kt @@ -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) + } + + } + +} diff --git a/nsfw/src/main/java/com/zwy/nsfw/core/NSFWConfig.kt b/nsfw/src/main/java/com/zwy/nsfw/core/NSFWConfig.kt new file mode 100644 index 00000000..e361b71b --- /dev/null +++ b/nsfw/src/main/java/com/zwy/nsfw/core/NSFWConfig.kt @@ -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) \ No newline at end of file diff --git a/nsfw/src/main/java/com/zwy/nsfw/core/NsfwBean.kt b/nsfw/src/main/java/com/zwy/nsfw/core/NsfwBean.kt new file mode 100644 index 00000000..1585001e --- /dev/null +++ b/nsfw/src/main/java/com/zwy/nsfw/core/NsfwBean.kt @@ -0,0 +1,3 @@ +package com.zwy.nsfw.core + +class NsfwBean(val sfw: Float, val nsfw: Float) \ No newline at end of file