parent
66d0f48f05
commit
d8a2e0783f
@ -0,0 +1,22 @@ |
|||||||
|
package com.zwy.opennsfw |
||||||
|
|
||||||
|
import android.support.test.InstrumentationRegistry |
||||||
|
import android.support.test.runner.AndroidJUnit4 |
||||||
|
import org.junit.Assert.assertEquals |
||||||
|
import org.junit.Test |
||||||
|
import org.junit.runner.RunWith |
||||||
|
|
||||||
|
/** |
||||||
|
* Instrumented test, which will execute on an Android device. |
||||||
|
* |
||||||
|
* See [testing documentation](http://d.android.com/tools/testing). |
||||||
|
*/ |
||||||
|
@RunWith(AndroidJUnit4::class) |
||||||
|
class ExampleInstrumentedTest { |
||||||
|
@Test |
||||||
|
fun useAppContext() { |
||||||
|
// Context of the app under test. |
||||||
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext |
||||||
|
assertEquals("com.zwy.opennsfw.test", appContext.packageName) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,2 @@ |
|||||||
|
<manifest |
||||||
|
package="com.zwy.opennsfw"/> |
@ -0,0 +1,205 @@ |
|||||||
|
//package com.zwy.opennsfw |
||||||
|
// |
||||||
|
//import android.content.Context |
||||||
|
//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) { |
||||||
|
// |
||||||
|
// /** |
||||||
|
// * 数据宽高 |
||||||
|
// */ |
||||||
|
// private val INPUT_WIDTH = 224 |
||||||
|
// |
||||||
|
// /** |
||||||
|
// * 数据宽高 |
||||||
|
// */ |
||||||
|
// private val INPUT_HEIGHT = 224 |
||||||
|
// |
||||||
|
// /** |
||||||
|
// * 通道 |
||||||
|
// */ |
||||||
|
// private val BYTES_PER_CHANNEL_NUM = 4 |
||||||
|
// |
||||||
|
// /** |
||||||
|
// * Resize后的数据源 |
||||||
|
// */ |
||||||
|
// private val intValues = IntArray(INPUT_WIDTH * INPUT_HEIGHT) |
||||||
|
// |
||||||
|
// /** |
||||||
|
// * 载入模型的客户端 |
||||||
|
// */ |
||||||
|
// private var tfliteModel: MappedByteBuffer? = null |
||||||
|
// |
||||||
|
// /** |
||||||
|
// * GPU代理 |
||||||
|
// */ |
||||||
|
// private var gpuDelegate: GpuDelegate? = null |
||||||
|
// |
||||||
|
// /** |
||||||
|
// * Tensorflow Lite |
||||||
|
// */ |
||||||
|
// private var tflite: Interpreter? = null |
||||||
|
// |
||||||
|
// /** |
||||||
|
// * 喂入模型的最终数据源 |
||||||
|
// */ |
||||||
|
// 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 context:Context |
||||||
|
// 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,180 @@ |
|||||||
|
package com.zwy.opennsfw.core |
||||||
|
|
||||||
|
import NsfwBean |
||||||
|
import android.content.Context |
||||||
|
import android.graphics.Bitmap |
||||||
|
import android.graphics.Color |
||||||
|
import android.os.SystemClock |
||||||
|
import d |
||||||
|
import mClassifier |
||||||
|
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(config: Config) { |
||||||
|
|
||||||
|
/** |
||||||
|
* DIM_BATCH_SIZE |
||||||
|
*/ |
||||||
|
private val DIM_BATCH_SIZE = 1 |
||||||
|
|
||||||
|
/** |
||||||
|
* DIM_PIXEL_SIZE |
||||||
|
*/ |
||||||
|
private val DIM_PIXEL_SIZE = 3 |
||||||
|
|
||||||
|
/** |
||||||
|
* 数据宽高 |
||||||
|
*/ |
||||||
|
private val INPUT_WIDTH = 224 |
||||||
|
|
||||||
|
/** |
||||||
|
* 数据宽高 |
||||||
|
*/ |
||||||
|
private val INPUT_HEIGHT = 224 |
||||||
|
|
||||||
|
/** |
||||||
|
* 通道 |
||||||
|
*/ |
||||||
|
private val BYTES_PER_CHANNEL_NUM = 4 |
||||||
|
|
||||||
|
/** |
||||||
|
* Resize后的数据源 |
||||||
|
*/ |
||||||
|
private val intValues = IntArray(INPUT_WIDTH * INPUT_HEIGHT) |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Tensorflow Lite |
||||||
|
*/ |
||||||
|
private lateinit var tflite: Interpreter |
||||||
|
|
||||||
|
/** |
||||||
|
* 喂入模型的最终数据源 |
||||||
|
*/ |
||||||
|
private lateinit var imgData: ByteBuffer |
||||||
|
|
||||||
|
companion object { |
||||||
|
private var config = Config(context = null) |
||||||
|
private var instance: Classifier? = null |
||||||
|
get() { |
||||||
|
if (field == null) { |
||||||
|
if (config.context == null) throw RuntimeException("context函数未调用,请使用Classifier.Build().context(context)初始化") |
||||||
|
field = Classifier(config) |
||||||
|
mClassifier = field |
||||||
|
} |
||||||
|
return field |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
private fun get(config: Config): Classifier { |
||||||
|
this.config = config |
||||||
|
return instance!! |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
class Build { |
||||||
|
private val config = Config(context = null) |
||||||
|
|
||||||
|
fun isOpenGPU(isOpenGPU: Boolean): Build { |
||||||
|
config.isOpenGPU = isOpenGPU |
||||||
|
return this |
||||||
|
} |
||||||
|
|
||||||
|
fun numThreads(numThreads: Int): Build { |
||||||
|
config.numThreads = numThreads |
||||||
|
return this |
||||||
|
} |
||||||
|
|
||||||
|
fun context(context: Context): Build { |
||||||
|
config.context = context |
||||||
|
return this |
||||||
|
} |
||||||
|
|
||||||
|
fun build(): Classifier { |
||||||
|
return get(config) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
init { |
||||||
|
|
||||||
|
val tfliteOptions = Interpreter.Options() |
||||||
|
if (config.isOpenGPU) tfliteOptions.addDelegate(GpuDelegate()) |
||||||
|
tfliteOptions.setNumThreads(config.numThreads) |
||||||
|
tflite = Interpreter(loadModelFile(config.context!!), tfliteOptions) |
||||||
|
|
||||||
|
imgData = ByteBuffer.allocateDirect( |
||||||
|
DIM_BATCH_SIZE |
||||||
|
* INPUT_WIDTH |
||||||
|
* INPUT_HEIGHT |
||||||
|
* DIM_PIXEL_SIZE |
||||||
|
* BYTES_PER_CHANNEL_NUM |
||||||
|
) |
||||||
|
|
||||||
|
imgData.order(ByteOrder.LITTLE_ENDIAN) |
||||||
|
} |
||||||
|
|
||||||
|
private fun loadModelFile(context: Context): MappedByteBuffer { |
||||||
|
val fileDescriptor = context.assets.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) |
||||||
|
} |
||||||
|
|
||||||
|
private fun convertBitmapToByteBuffer(bitmap_: Bitmap) { |
||||||
|
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) |
||||||
|
|
||||||
|
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() |
||||||
|
"数据装载成功,耗时:${(endTime - startTime)} ms".d() |
||||||
|
} |
||||||
|
|
||||||
|
fun run(bitmap: Bitmap): NsfwBean { |
||||||
|
|
||||||
|
val bitmap_256 = Bitmap.createScaledBitmap(bitmap, 256, 256, true) |
||||||
|
|
||||||
|
convertBitmapToByteBuffer(bitmap_256) |
||||||
|
|
||||||
|
val startTime = SystemClock.uptimeMillis() |
||||||
|
// out |
||||||
|
val outArray = Array(1) { FloatArray(2) } |
||||||
|
|
||||||
|
tflite.run(imgData, outArray) |
||||||
|
|
||||||
|
val endTime = SystemClock.uptimeMillis() |
||||||
|
|
||||||
|
"扫描完成,耗时: ${(endTime - startTime)} ms".d() |
||||||
|
return NsfwBean(outArray[0][0], outArray[0][1]) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
package com.zwy.opennsfw.core |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
|
||||||
|
data class Config( |
||||||
|
/** |
||||||
|
* 是否开启GPU加速 |
||||||
|
*/ |
||||||
|
var isOpenGPU: Boolean = false, |
||||||
|
var numThreads: Int = 1, |
||||||
|
var context: Context? |
||||||
|
) |
@ -0,0 +1,45 @@ |
|||||||
|
import android.graphics.Bitmap |
||||||
|
import android.graphics.BitmapFactory |
||||||
|
import android.util.Log |
||||||
|
import com.zwy.opennsfw.core.Classifier |
||||||
|
import java.io.File |
||||||
|
import java.math.RoundingMode |
||||||
|
import java.text.DecimalFormat |
||||||
|
|
||||||
|
private val TAG = "OpenNSFWLogTag" |
||||||
|
|
||||||
|
var mClassifier: Classifier? = null |
||||||
|
|
||||||
|
fun String.d() = Log.d(TAG, this) |
||||||
|
|
||||||
|
fun String.e() = Log.e(TAG, this) |
||||||
|
|
||||||
|
class NsfwBean(val sfw: Float, val nsfw: Float) { |
||||||
|
override fun toString(): String { |
||||||
|
val floatFormat = DecimalFormat("0.0000") |
||||||
|
floatFormat.roundingMode = RoundingMode.HALF_UP |
||||||
|
return "适宜度(非色情程度):${floatFormat.format(sfw * 100)}%,不适宜度(涉黄程度):${floatFormat.format(nsfw * 100)}%" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun Bitmap.getNsfwScore(): NsfwBean { |
||||||
|
checkNull() |
||||||
|
return mClassifier!!.run(this) |
||||||
|
} |
||||||
|
|
||||||
|
fun File.getNsfwScore(): NsfwBean { |
||||||
|
checkNull() |
||||||
|
val bitmap = try { |
||||||
|
BitmapFactory.decodeFile(this.path) |
||||||
|
} catch (e: Exception) { |
||||||
|
val m = "File2Bitmap过程失败,请确认文件是否存在或是否为图片" |
||||||
|
m.e() |
||||||
|
throw RuntimeException(m) |
||||||
|
} |
||||||
|
return bitmap.getNsfwScore() |
||||||
|
} |
||||||
|
|
||||||
|
private fun checkNull() { |
||||||
|
if (mClassifier == null) throw RuntimeException("Classifier未初始化,请使用Classifier.Build().context(context)初始化后再试") |
||||||
|
} |
||||||
|
|
@ -0,0 +1,16 @@ |
|||||||
|
package com.zwy.opennsfw |
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals |
||||||
|
import org.junit.Test |
||||||
|
|
||||||
|
/** |
||||||
|
* Example local unit test, which will execute on the development machine (host). |
||||||
|
* |
||||||
|
* See [testing documentation](http://d.android.com/tools/testing). |
||||||
|
*/ |
||||||
|
class ExampleUnitTest { |
||||||
|
@Test |
||||||
|
fun addition_isCorrect() { |
||||||
|
assertEquals(4, 2 + 2) |
||||||
|
} |
||||||
|
} |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
@ -0,0 +1,11 @@ |
|||||||
|
package com.example.open_nsfw_android |
||||||
|
|
||||||
|
import android.app.Application |
||||||
|
import com.zwy.opennsfw.core.Classifier |
||||||
|
|
||||||
|
class DemoApplication : Application() { |
||||||
|
override fun onCreate() { |
||||||
|
super.onCreate() |
||||||
|
Classifier.Build().context(this).build() |
||||||
|
} |
||||||
|
} |
@ -1,174 +0,0 @@ |
|||||||
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 |
|
||||||
import android.os.Bundle |
|
||||||
import android.support.v4.app.ActivityCompat |
|
||||||
import android.support.v4.content.ContextCompat |
|
||||||
import android.support.v7.app.AppCompatActivity |
|
||||||
import android.support.v7.widget.LinearLayoutManager |
|
||||||
import android.util.Log |
|
||||||
import android.view.View |
|
||||||
import android.widget.Toast |
|
||||||
import com.bumptech.glide.Glide |
|
||||||
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.core.NSFWConfig |
|
||||||
import com.zwy.nsfw.kotlin.getNsfwScore |
|
||||||
import kotlinx.android.synthetic.main.activity_main.* |
|
||||||
import java.io.File |
|
||||||
import kotlin.concurrent.thread |
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity(), View.OnClickListener { |
|
||||||
|
|
||||||
var nsfwHelper: NSFWHelper? = null |
|
||||||
var mainAdapter: MainAdapter? = null |
|
||||||
var index = 0 |
|
||||||
var listData: ArrayList<MyNsfwBean> = ArrayList() |
|
||||||
var selectList: List<LocalMedia>? = null |
|
||||||
|
|
||||||
var progressDialog: ProgressDialog? = null |
|
||||||
@SuppressLint("SetTextI18n") |
|
||||||
override fun onCreate(savedInstanceState: Bundle?) { |
|
||||||
super.onCreate(savedInstanceState) |
|
||||||
setContentView(R.layout.activity_main) |
|
||||||
initNsfwHelper() |
|
||||||
initAdapter() |
|
||||||
initClickListener() |
|
||||||
tv_version.text = "当前版本:${this.packageManager.getPackageInfo(packageName, 0).versionName}" |
|
||||||
if (ContextCompat.checkSelfPermission( |
|
||||||
this, |
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE |
|
||||||
) != PackageManager.PERMISSION_GRANTED |
|
||||||
) { //表示未授权时 |
|
||||||
//进行授权 |
|
||||||
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
override fun onClick(v: View) { |
|
||||||
when (v.id) { |
|
||||||
R.id.bt_sc_assets -> { |
|
||||||
reScAssetsImgs() |
|
||||||
} |
|
||||||
R.id.bt_sc_from_other -> { |
|
||||||
PictureSelector.create(this) |
|
||||||
.openGallery(PictureMimeType.ofImage())//全部.ofAll()、图片.、视频.ofVideo()、音频.ofAudio() |
|
||||||
.maxSelectNum(20)// 最大图片选择数量 int |
|
||||||
.minSelectNum(1)// 最小选择数量 int |
|
||||||
.imageSpanCount(3)// 每行显示个数 int |
|
||||||
.selectionMode(PictureConfig.MULTIPLE)// 多选 or 单选 or PictureConfig.SINGLE |
|
||||||
.previewImage(true)// 是否可预览图片 true or false |
|
||||||
.isCamera(false)// 是否显示拍照按钮 true or false |
|
||||||
.isZoomAnim(true)// 图片列表点击 缩放效果 默认true |
|
||||||
.selectionMedia(selectList) |
|
||||||
.sizeMultiplier(0.5f)// glide 加载图片大小 0~1之间 如设置 .glideOverride()无效 |
|
||||||
.previewEggs(true)// 预览图片时 是否增强左右滑动图片体验(图片滑动一半即可看到上一张是否选中) true or false |
|
||||||
.forResult(0x01);//结果回调onActivityResult code |
|
||||||
} |
|
||||||
R.id.bt_sc_from_internet ->{ |
|
||||||
startActivity(Intent(this,Main2Activity::class.java)) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { |
|
||||||
super.onActivityResult(requestCode, resultCode, data) |
|
||||||
if (requestCode == 0x01 && resultCode == RESULT_OK) { |
|
||||||
selectList = PictureSelector.obtainMultipleResult(data) |
|
||||||
if (selectList != null && selectList?.size ?: 0 > 0) |
|
||||||
reScFromImgs(selectList!!) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
private fun initClickListener() { |
|
||||||
bt_sc_assets.setOnClickListener(this) |
|
||||||
bt_sc_from_other.setOnClickListener(this) |
|
||||||
bt_sc_from_internet.setOnClickListener(this) |
|
||||||
} |
|
||||||
|
|
||||||
private fun initAdapter() { |
|
||||||
mainAdapter = MainAdapter(null) |
|
||||||
rv.layoutManager = LinearLayoutManager(this) |
|
||||||
rv.adapter = mainAdapter |
|
||||||
} |
|
||||||
|
|
||||||
private fun initNsfwHelper() { |
|
||||||
nsfwHelper = NSFWHelper.init(NSFWConfig(assets)) |
|
||||||
} |
|
||||||
|
|
||||||
private fun reScFromImgs(list: List<LocalMedia>) { |
|
||||||
progressDialog = ProgressDialog.show(this, "提示", "请稍后") |
|
||||||
index = 0 |
|
||||||
mainAdapter?.setNewData(null) |
|
||||||
listData = ArrayList<MyNsfwBean>() |
|
||||||
Thread(Runnable { |
|
||||||
for (lm in list) { |
|
||||||
val bitmap = BitmapFactory.decodeFile(lm.path) |
|
||||||
|
|
||||||
val nsfwScore = bitmap.getNsfwScore(assets) |
|
||||||
|
|
||||||
// listData.add(MyNsfwBean(0.0f, 0.0f, lm.path, bitmap)) |
|
||||||
listData.add(MyNsfwBean(nsfwScore.sfw, nsfwScore.nsfw, lm.path, bitmap)) |
|
||||||
// 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<MyNsfwBean>() |
|
||||||
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() { |
|
||||||
finish() |
|
||||||
} |
|
||||||
|
|
||||||
override fun onDestroy() { |
|
||||||
super.onDestroy() |
|
||||||
nsfwHelper?.destroyFactory() |
|
||||||
} |
|
||||||
} |
|
@ -1,4 +1,4 @@ |
|||||||
package com.example.open_nsfw_android |
package com.example.open_nsfw_android.util |
||||||
|
|
||||||
import android.graphics.Bitmap |
import android.graphics.Bitmap |
||||||
|
|
@ -0,0 +1,143 @@ |
|||||||
|
package com.example.open_nsfw_android.view |
||||||
|
|
||||||
|
import android.Manifest |
||||||
|
import android.content.Intent |
||||||
|
import android.content.pm.PackageManager |
||||||
|
import android.graphics.BitmapFactory |
||||||
|
import android.os.Bundle |
||||||
|
import android.support.v4.app.ActivityCompat |
||||||
|
import android.support.v4.content.ContextCompat |
||||||
|
import android.support.v7.app.AppCompatActivity |
||||||
|
import android.support.v7.widget.LinearLayoutManager |
||||||
|
import android.widget.Toast |
||||||
|
import com.example.open_nsfw_android.R |
||||||
|
import com.example.open_nsfw_android.util.MainAdapter |
||||||
|
import com.example.open_nsfw_android.util.MyNsfwBean |
||||||
|
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 getNsfwScore |
||||||
|
import kotlinx.android.synthetic.main.activity_main.* |
||||||
|
import java.io.File |
||||||
|
|
||||||
|
class MainAty : AppCompatActivity() { |
||||||
|
|
||||||
|
var selectList: List<LocalMedia>? = null |
||||||
|
|
||||||
|
lateinit var mMainAdapter: MainAdapter |
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) { |
||||||
|
super.onCreate(savedInstanceState) |
||||||
|
setContentView(R.layout.activity_main) |
||||||
|
|
||||||
|
init() |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
fun init() { |
||||||
|
//检测权限 |
||||||
|
checkPermissions() |
||||||
|
//初始化适配器 |
||||||
|
initAdapter() |
||||||
|
//识别assets目录 |
||||||
|
bt_sc_assets.setOnClickListener { assetsListSc() } |
||||||
|
//选择相册进行扫描 |
||||||
|
bt_sc_from_other.setOnClickListener { selectImgFromD() } |
||||||
|
//跳转网络图片识别页面 |
||||||
|
bt_sc_from_internet.setOnClickListener { startActivity(Intent(this, Main2Activity::class.java)) } |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
private fun initAdapter() { |
||||||
|
mMainAdapter = MainAdapter(null) |
||||||
|
rv.layoutManager = LinearLayoutManager(this) |
||||||
|
rv.adapter = mMainAdapter |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 相册选择 |
||||||
|
*/ |
||||||
|
private fun selectImgFromD() { |
||||||
|
PictureSelector.create(this) |
||||||
|
.openGallery(PictureMimeType.ofImage())//全部.ofAll()、图片.、视频.ofVideo()、音频.ofAudio() |
||||||
|
.maxSelectNum(20)// 最大图片选择数量 int |
||||||
|
.minSelectNum(1)// 最小选择数量 int |
||||||
|
.imageSpanCount(3)// 每行显示个数 int |
||||||
|
.selectionMode(PictureConfig.MULTIPLE)// 多选 or 单选 or PictureConfig.SINGLE |
||||||
|
.previewImage(true)// 是否可预览图片 true or false |
||||||
|
.isCamera(false)// 是否显示拍照按钮 true or false |
||||||
|
.isZoomAnim(true)// 图片列表点击 缩放效果 默认true |
||||||
|
.selectionMedia(selectList) |
||||||
|
.sizeMultiplier(0.5f)// glide 加载图片大小 0~1之间 如设置 .glideOverride()无效 |
||||||
|
.previewEggs(true)// 预览图片时 是否增强左右滑动图片体验(图片滑动一半即可看到上一张是否选中) true or false |
||||||
|
.forResult(0x01);//结果回调onActivityResult code } |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 扫描assets目录 |
||||||
|
*/ |
||||||
|
private fun assetsListSc() { |
||||||
|
mMainAdapter.setNewData(null) |
||||||
|
Toast.makeText(this, "请稍等...", Toast.LENGTH_LONG).show() |
||||||
|
Thread(Runnable { |
||||||
|
resources.assets.list("img")?.forEach { |
||||||
|
val bitmap = BitmapFactory.decodeStream(resources.assets.open("img/$it")) |
||||||
|
val nsfwScore = bitmap.getNsfwScore() |
||||||
|
addDataToAdapter( |
||||||
|
MyNsfwBean( |
||||||
|
nsfwScore.sfw, |
||||||
|
nsfwScore.nsfw, |
||||||
|
"img/$it", |
||||||
|
bitmap |
||||||
|
) |
||||||
|
) |
||||||
|
} |
||||||
|
}).start() |
||||||
|
} |
||||||
|
|
||||||
|
private fun addDataToAdapter(mMyNsfwBean: MyNsfwBean) { |
||||||
|
runOnUiThread { mMainAdapter.addData(mMyNsfwBean) } |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 检测权限 |
||||||
|
*/ |
||||||
|
private fun checkPermissions() { |
||||||
|
if (ContextCompat.checkSelfPermission( |
||||||
|
this, |
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE |
||||||
|
) != PackageManager.PERMISSION_GRANTED |
||||||
|
) { |
||||||
|
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { |
||||||
|
super.onActivityResult(requestCode, resultCode, data) |
||||||
|
if (requestCode == 0x01 && resultCode == RESULT_OK) { |
||||||
|
selectList = PictureSelector.obtainMultipleResult(data) |
||||||
|
selectList?.let { |
||||||
|
mMainAdapter.setNewData(null) |
||||||
|
Toast.makeText(this, "请稍等...", Toast.LENGTH_LONG).show() |
||||||
|
} |
||||||
|
Thread(Runnable { |
||||||
|
selectList?.forEach { |
||||||
|
val file = File(it.path) |
||||||
|
val nsfwScore = file.getNsfwScore() |
||||||
|
addDataToAdapter( |
||||||
|
MyNsfwBean( |
||||||
|
nsfwScore.sfw, |
||||||
|
nsfwScore.nsfw, |
||||||
|
it.path, |
||||||
|
BitmapFactory.decodeStream(file.inputStream()) |
||||||
|
) |
||||||
|
) |
||||||
|
} |
||||||
|
}).start() |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -1,5 +1,5 @@ |
|||||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> |
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> |
||||||
<background android:drawable="@drawable/ic_launcher_background" /> |
<background android:drawable="@drawable/ic_launcher_background"/> |
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" /> |
<foreground android:drawable="@drawable/ic_launcher_foreground"/> |
||||||
</adaptive-icon> |
</adaptive-icon> |
@ -1,5 +1,5 @@ |
|||||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> |
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> |
||||||
<background android:drawable="@drawable/ic_launcher_background" /> |
<background android:drawable="@drawable/ic_launcher_background"/> |
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" /> |
<foreground android:drawable="@drawable/ic_launcher_foreground"/> |
||||||
</adaptive-icon> |
</adaptive-icon> |
@ -1,26 +0,0 @@ |
|||||||
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()); |
|
||||||
} |
|
||||||
} |
|
@ -1,2 +0,0 @@ |
|||||||
<manifest package="com.zwy.nsfw" xmlns:android="http://schemas.android.com/apk/res/android"> |
|
||||||
</manifest> |
|
@ -1,42 +0,0 @@ |
|||||||
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,197 +0,0 @@ |
|||||||
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) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,10 +0,0 @@ |
|||||||
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) |
|
@ -1,3 +0,0 @@ |
|||||||
package com.zwy.nsfw.core |
|
||||||
|
|
||||||
class NsfwBean(val sfw: Float, val nsfw: Float) |
|
@ -1,28 +0,0 @@ |
|||||||
package com.zwy.nsfw.kotlin |
|
||||||
|
|
||||||
import android.content.res.AssetManager |
|
||||||
import android.graphics.Bitmap |
|
||||||
import android.graphics.BitmapFactory |
|
||||||
import com.zwy.nsfw.api.NSFWHelper |
|
||||||
import com.zwy.nsfw.core.NSFWConfig |
|
||||||
import com.zwy.nsfw.core.NsfwBean |
|
||||||
import java.io.File |
|
||||||
|
|
||||||
|
|
||||||
fun Bitmap.getNsfwScore(mAssetManager: AssetManager): NsfwBean { |
|
||||||
val nsfwBean = NSFWHelper.init(NSFWConfig(mAssetManager)).scanBitmap(this) |
|
||||||
NSFWHelper.destroyFactory() |
|
||||||
return nsfwBean |
|
||||||
} |
|
||||||
|
|
||||||
fun File.getNsfwScore(mAssetManager: AssetManager): NsfwBean { |
|
||||||
val bitmap = try { |
|
||||||
BitmapFactory.decodeFile(this.path) |
|
||||||
} catch (e: Exception) { |
|
||||||
return NsfwBean(0f, 0f) |
|
||||||
} |
|
||||||
|
|
||||||
val nsfwBean = NSFWHelper.init(NSFWConfig(mAssetManager)).scanBitmap(bitmap) |
|
||||||
NSFWHelper.destroyFactory() |
|
||||||
return nsfwBean |
|
||||||
} |
|
@ -1,3 +0,0 @@ |
|||||||
<resources> |
|
||||||
<string name="app_name">nsfw</string> |
|
||||||
</resources> |
|
@ -1,17 +0,0 @@ |
|||||||
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', ':nsfw' |
include ':app', ':OpenNSFW' |
||||||
|
Loading…
Reference in new issue