重构库,升级到1.3.0

pull/34/head 1.3.0
5 years ago
parent 66d0f48f05
commit d8a2e0783f
  1. 3
      .idea/codeStyles/Project.xml
  2. 2
      .idea/gradle.xml
  3. 0
      OpenNSFW/.gitignore
  4. 35
      OpenNSFW/build.gradle
  5. 0
      OpenNSFW/consumer-rules.pro
  6. 1
      OpenNSFW/proguard-rules.pro
  7. 22
      OpenNSFW/src/androidTest/java/com/zwy/opennsfw/ExampleInstrumentedTest.kt
  8. 2
      OpenNSFW/src/main/AndroidManifest.xml
  9. 0
      OpenNSFW/src/main/assets/nsfw.tflite
  10. 205
      OpenNSFW/src/main/java/com/zwy/opennsfw/Classifier.kt
  11. 180
      OpenNSFW/src/main/java/com/zwy/opennsfw/core/Classifier.kt
  12. 12
      OpenNSFW/src/main/java/com/zwy/opennsfw/core/Config.kt
  13. 45
      OpenNSFW/src/main/java/com/zwy/opennsfw/utils/kt.kt
  14. 16
      OpenNSFW/src/test/java/com/zwy/opennsfw/ExampleUnitTest.kt
  15. 5
      app/build.gradle
  16. 4
      app/src/androidTest/java/com/example/open_nsfw_android/ExampleInstrumentedTest.kt
  17. 19
      app/src/main/AndroidManifest.xml
  18. 0
      app/src/main/assets/img/test_img1.jpeg
  19. 0
      app/src/main/assets/img/test_img2.jpeg
  20. 0
      app/src/main/assets/img/test_img3.jpg
  21. 11
      app/src/main/java/com/example/open_nsfw_android/DemoApplication.kt
  22. 174
      app/src/main/java/com/example/open_nsfw_android/MainActivity.kt
  23. 11
      app/src/main/java/com/example/open_nsfw_android/util/MainAdapter.kt
  24. 2
      app/src/main/java/com/example/open_nsfw_android/util/kt.kt
  25. 20
      app/src/main/java/com/example/open_nsfw_android/view/Main2Activity.kt
  26. 143
      app/src/main/java/com/example/open_nsfw_android/view/MainAty.kt
  27. 46
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  28. 334
      app/src/main/res/drawable/ic_launcher_background.xml
  29. 21
      app/src/main/res/layout/activity_main.xml
  30. 19
      app/src/main/res/layout/activity_main2.xml
  31. 22
      app/src/main/res/layout/main_item.xml
  32. 4
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  33. 4
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  34. 4
      app/src/main/res/values/styles.xml
  35. 3
      app/src/test/java/com/example/open_nsfw_android/ExampleUnitTest.kt
  36. 10
      build.gradle
  37. 202
      gradlew
  38. 26
      nsfw/src/androidTest/java/com/zwy/nsfw/ExampleInstrumentedTest.java
  39. 2
      nsfw/src/main/AndroidManifest.xml
  40. 42
      nsfw/src/main/java/com/zwy/nsfw/api/NSFWHelper.kt
  41. 197
      nsfw/src/main/java/com/zwy/nsfw/core/Classifier.kt
  42. 10
      nsfw/src/main/java/com/zwy/nsfw/core/NSFWConfig.kt
  43. 3
      nsfw/src/main/java/com/zwy/nsfw/core/NsfwBean.kt
  44. 28
      nsfw/src/main/java/com/zwy/nsfw/kotlin/Factory.kt
  45. 3
      nsfw/src/main/res/values/strings.xml
  46. 17
      nsfw/src/test/java/com/zwy/nsfw/ExampleUnitTest.java
  47. 2
      settings.gradle

@ -1,5 +1,8 @@
<component name="ProjectCodeStyleConfiguration"> <component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173"> <code_scheme name="Project" version="173">
<AndroidXmlCodeStyleSettings>
<option name="USE_CUSTOM_SETTINGS" value="true" />
</AndroidXmlCodeStyleSettings>
<JetCodeStyleSettings> <JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings> </JetCodeStyleSettings>

@ -11,8 +11,8 @@
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/OpenNSFW" />
<option value="$PROJECT_DIR$/app" /> <option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/nsfw" />
</set> </set>
</option> </option>
<option name="resolveModulePerSourceSet" value="false" /> <option name="resolveModulePerSourceSet" value="false" />

@ -3,39 +3,36 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
android { android {
compileSdkVersion 28 compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig { defaultConfig {
minSdkVersion 19 minSdkVersion 16
targetSdkVersion 28 targetSdkVersion 29
versionCode 6 versionCode 1
versionName "1.2.9" versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
} }
buildTypes { buildTypes {
debug { release {
minifyEnabled false //® minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
} }
kotlin {
experimental {
coroutines 'enable'
}
}
} }
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
//noinspection GradleCompatible
implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support:appcompat-v7:28.0.0'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.13'
androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'org.tensorflow:tensorflow-lite:1.14.0' implementation 'org.tensorflow:tensorflow-lite:2.1.0'
implementation 'org.tensorflow:tensorflow-lite-gpu:1.14.0' implementation 'org.tensorflow:tensorflow-lite-gpu:2.1.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
repositories {
mavenCentral()
} }

@ -19,4 +19,3 @@
# If you keep the line number information, uncomment this to # If you keep the line number information, uncomment this to
# hide the original source file name. # hide the original source file name.
#-renamesourcefileattribute SourceFile #-renamesourcefileattribute SourceFile
-keep class com.zwy.nsfw.api.**{*;}

@ -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)
}
}

@ -11,7 +11,7 @@ android {
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 28 targetSdkVersion 28
versionCode 5 versionCode 5
versionName "1.2.9" versionName "1.3.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
@ -53,6 +53,7 @@ dependencies {
implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.50' implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.50'
implementation 'com.github.LuckSiege.PictureSelector:picture_library:2.2.5' implementation 'com.github.LuckSiege.PictureSelector:picture_library:2.2.5'
implementation project(path: ':nsfw') // implementation project(path: ':nsfw')
implementation project(path: ':OpenNSFW')
// implementation 'com.github.devzwy:open_nsfw_android:1.2.7' // implementation 'com.github.devzwy:open_nsfw_android:1.2.7'
} }

@ -2,12 +2,10 @@ package com.example.open_nsfw_android
import android.support.test.InstrumentationRegistry import android.support.test.InstrumentationRegistry
import android.support.test.runner.AndroidJUnit4 import android.support.test.runner.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.Assert.*
/** /**
* Instrumented test, which will execute on an Android device. * Instrumented test, which will execute on an Android device.
* *

@ -1,26 +1,27 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.open_nsfw_android"> package="com.example.open_nsfw_android">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET"/>
<application <application
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/a" android:icon="@mipmap/a"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/a" android:roundIcon="@mipmap/a"
android:name=".DemoApplication"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity <activity
android:name=".Main2Activity" android:name=".view.Main2Activity"
android:label="@string/title_activity_main2"></activity> android:label="@string/title_activity_main2"/>
<activity android:name=".MainActivity"> <activity android:name=".view.MainAty">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
</activity> </activity>
</application> </application>

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.annotation.SuppressLint import android.annotation.SuppressLint
import android.support.v4.content.ContextCompat import android.support.v4.content.ContextCompat
@ -7,6 +7,9 @@ import android.widget.RelativeLayout
import android.widget.TextView import android.widget.TextView
import com.chad.library.adapter.base.BaseQuickAdapter import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.BaseViewHolder import com.chad.library.adapter.base.BaseViewHolder
import com.example.open_nsfw_android.R
import java.math.RoundingMode
import java.text.DecimalFormat
class MainAdapter(nsfwList: List<MyNsfwBean>?) : class MainAdapter(nsfwList: List<MyNsfwBean>?) :
BaseQuickAdapter<MyNsfwBean, BaseViewHolder>(R.layout.main_item, nsfwList) { BaseQuickAdapter<MyNsfwBean, BaseViewHolder>(R.layout.main_item, nsfwList) {
@ -25,8 +28,12 @@ class MainAdapter(nsfwList: List<MyNsfwBean>?) :
color = ContextCompat.getColor(mContext, R.color.nsfw2) color = ContextCompat.getColor(mContext, R.color.nsfw2)
} }
} }
val floatFormat = DecimalFormat("0.0000000")
floatFormat.roundingMode = RoundingMode.HALF_UP
textView.text = textView.text =
"path = ${"img/${item.path}"} \n\nSFW score: ${item.sfw}\n\nNSFW score: ${item.nsfw}" "\n适宜度(非色情程度):${floatFormat.format(item.sfw * 100)}%\n\n不适宜度(涉黄程度):${floatFormat.format(item.nsfw * 100)}%"
imageView.setImageBitmap(item.bitmap) imageView.setImageBitmap(item.bitmap)
view.setBackgroundColor(color) view.setBackgroundColor(color)
} }

@ -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

@ -1,16 +1,16 @@
package com.example.open_nsfw_android package com.example.open_nsfw_android.view
import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.os.Bundle import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.widget.Toast import android.widget.Toast
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.zwy.nsfw.kotlin.getNsfwScore import com.example.open_nsfw_android.R
import getNsfwScore
import kotlinx.android.synthetic.main.activity_main2.* import kotlinx.android.synthetic.main.activity_main2.*
import java.math.RoundingMode
import java.text.DecimalFormat
class Main2Activity : AppCompatActivity() { class Main2Activity : AppCompatActivity() {
@ -24,10 +24,16 @@ class Main2Activity : AppCompatActivity() {
val file = Glide.with(this) val file = Glide.with(this)
.load(et.text.toString()) .load(et.text.toString())
.downloadOnly(120, 120).get() .downloadOnly(120, 120).get()
val nsfwBean = file.getNsfwScore(assets) val nsfwBean = file.getNsfwScore()
runOnUiThread { runOnUiThread {
iv.setImageBitmap(BitmapFactory.decodeFile(file.path)) iv.setImageBitmap(BitmapFactory.decodeFile(file.path))
tv.text = "nsfw:${nsfwBean.nsfw} \n sfw:${nsfwBean.sfw} " val floatFormat = DecimalFormat("0.0000000")
floatFormat.roundingMode = RoundingMode.HALF_UP
tv.text =
"\n适宜度(非色情程度):${floatFormat.format(nsfwBean.sfw * 100)}%\n\n不适宜度(涉黄程度):${floatFormat.format(
nsfwBean.nsfw * 100
)}%"
} }
} catch (e: Exception) { } catch (e: Exception) {
runOnUiThread { runOnUiThread {

@ -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,34 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt" xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp" android:width="108dp"
android:height="108dp" android:height="108dp"
android:viewportWidth="108" android:viewportWidth="108"
android:viewportHeight="108"> android:viewportHeight="108">
<path <path
android:fillType="evenOdd" android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z" android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeWidth="1" android:strokeWidth="1"
android:strokeColor="#00000000"> android:strokeColor="#00000000">
<aapt:attr name="android:fillColor"> <aapt:attr name="android:fillColor">
<gradient <gradient
android:endX="78.5885" android:endX="78.5885"
android:endY="90.9159" android:endY="90.9159"
android:startX="48.7653" android:startX="48.7653"
android:startY="61.0927" android:startY="61.0927"
android:type="linear"> android:type="linear">
<item <item
android:color="#44000000" android:color="#44000000"
android:offset="0.0" /> android:offset="0.0"/>
<item <item
android:color="#00000000" android:color="#00000000"
android:offset="1.0" /> android:offset="1.0"/>
</gradient> </gradient>
</aapt:attr> </aapt:attr>
</path> </path>
<path <path
android:fillColor="#FFFFFF" android:fillColor="#FFFFFF"
android:fillType="nonZero" android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z" android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeWidth="1" android:strokeWidth="1"
android:strokeColor="#00000000" /> android:strokeColor="#00000000"/>
</vector> </vector>

@ -1,170 +1,170 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp" android:width="108dp"
android:height="108dp" android:height="108dp"
android:viewportWidth="108" android:viewportWidth="108"
android:viewportHeight="108"> android:viewportHeight="108">
<path <path
android:fillColor="#008577" android:fillColor="#008577"
android:pathData="M0,0h108v108h-108z" /> android:pathData="M0,0h108v108h-108z"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M9,0L9,108" android:pathData="M9,0L9,108"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M19,0L19,108" android:pathData="M19,0L19,108"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M29,0L29,108" android:pathData="M29,0L29,108"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M39,0L39,108" android:pathData="M39,0L39,108"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M49,0L49,108" android:pathData="M49,0L49,108"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M59,0L59,108" android:pathData="M59,0L59,108"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M69,0L69,108" android:pathData="M69,0L69,108"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M79,0L79,108" android:pathData="M79,0L79,108"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M89,0L89,108" android:pathData="M89,0L89,108"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M99,0L99,108" android:pathData="M99,0L99,108"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M0,9L108,9" android:pathData="M0,9L108,9"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M0,19L108,19" android:pathData="M0,19L108,19"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M0,29L108,29" android:pathData="M0,29L108,29"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M0,39L108,39" android:pathData="M0,39L108,39"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M0,49L108,49" android:pathData="M0,49L108,49"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M0,59L108,59" android:pathData="M0,59L108,59"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M0,69L108,69" android:pathData="M0,69L108,69"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M0,79L108,79" android:pathData="M0,79L108,79"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M0,89L108,89" android:pathData="M0,89L108,89"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M0,99L108,99" android:pathData="M0,99L108,99"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M19,29L89,29" android:pathData="M19,29L89,29"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M19,39L89,39" android:pathData="M19,39L89,39"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M19,49L89,49" android:pathData="M19,49L89,49"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M19,59L89,59" android:pathData="M19,59L89,59"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M19,69L89,69" android:pathData="M19,69L89,69"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M19,79L89,79" android:pathData="M19,79L89,79"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M29,19L29,89" android:pathData="M29,19L29,89"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M39,19L39,89" android:pathData="M39,19L39,89"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M49,19L49,89" android:pathData="M49,19L49,89"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M59,19L59,89" android:pathData="M59,19L59,89"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M69,19L69,89" android:pathData="M69,19L69,89"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M79,19L79,89" android:pathData="M79,19L79,89"
android:strokeWidth="0.8" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF"/>
</vector> </vector>

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -18,7 +18,7 @@
android:gravity="center" android:gravity="center"
android:layout_gravity="center" android:layout_gravity="center"
android:padding="15dp" android:padding="15dp"
android:text="@string/str_bt1" /> android:text="@string/str_bt1"/>
<Button <Button
android:layout_width="0dp" android:layout_width="0dp"
@ -29,7 +29,8 @@
android:gravity="center" android:gravity="center"
android:layout_gravity="center" android:layout_gravity="center"
android:padding="15dp" android:padding="15dp"
android:text="从相册选取" /> android:text="从相册选取"/>
<Button <Button
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -39,7 +40,7 @@
android:gravity="center" android:gravity="center"
android:layout_gravity="center" android:layout_gravity="center"
android:padding="15dp" android:padding="15dp"
android:text="识别网络图片" /> android:text="识别网络图片"/>
</LinearLayout> </LinearLayout>
<TextView <TextView
@ -50,14 +51,14 @@
android:textSize="15sp" android:textSize="15sp"
android:textColor="@color/blue" android:textColor="@color/blue"
android:padding="5dp" android:padding="5dp"
android:text="版本号:" /> android:text="版本号:"/>
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="sfw:适宜在公共场所浏览,nsfw:不适宜在公共场所浏览,两者都是0-1中间的浮点型值,nsfw数值越大表示色情程度越高,sfw反之" android:text="sfw:适宜在公共场所浏览,nsfw:不适宜在公共场所浏览,两者都是0-1中间的浮点型值,nsfw数值越大表示色情程度越高,sfw反之"
android:padding="15dp" android:padding="15dp"
android:textSize="14sp" /> android:textSize="14sp"/>
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -65,10 +66,10 @@
android:text="所有图片均来源百度,如有侵权请联系我删除。大尺度图片请自行google来测试" android:text="所有图片均来源百度,如有侵权请联系我删除。大尺度图片请自行google来测试"
android:textColor="#FF0404" android:textColor="#FF0404"
android:padding="15dp" android:padding="15dp"
android:textSize="12sp" /> android:textSize="12sp"/>
<android.support.v7.widget.RecyclerView <android.support.v7.widget.RecyclerView
android:id="@+id/rv" android:id="@+id/rv"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content"/>
</LinearLayout> </LinearLayout>

@ -1,11 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_width="match_parent" android:layout_height="match_parent"
android:layout_height="match_parent" android:orientation="vertical"
android:orientation="vertical" tools:context=".Main2Activity">
tools:context=".Main2Activity">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -19,20 +18,20 @@
android:layout_weight="1" android:layout_weight="1"
android:background="@null" android:background="@null"
android:hint="粘贴网络图片路径" android:hint="粘贴网络图片路径"
android:padding="15dp" /> android:padding="15dp"/>
<Button <Button
android:id="@+id/bt" android:id="@+id/bt"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="50dp" android:layout_height="50dp"
android:layout_weight="0.3" android:layout_weight="0.3"
android:text="识别" /> android:text="识别"/>
</LinearLayout> </LinearLayout>
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
android:background="#504d4d4d" /> android:background="#504d4d4d"/>
<TextView <TextView
android:id="@+id/tv" android:id="@+id/tv"
@ -41,7 +40,7 @@
android:gravity="center" android:gravity="center"
android:textColor="#FA3C00" android:textColor="#FA3C00"
android:padding="20dp" android:padding="20dp"
android:text="这里显示检测结果" /> android:text="这里显示检测结果"/>
<ImageView <ImageView
android:id="@+id/iv" android:id="@+id/iv"

@ -6,13 +6,17 @@
android:padding="20dp" android:padding="20dp"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<TextView android:layout_width="match_parent"
android:id="@+id/tv_text" <TextView
android:layout_toLeftOf="@+id/iv" android:layout_width="match_parent"
android:layout_height="wrap_content"/> android:id="@+id/tv_text"
<ImageView android:layout_width="100dp" android:layout_toLeftOf="@+id/iv"
android:layout_height="100dp" android:layout_height="wrap_content"/>
android:id="@+id/iv"
android:scaleType="centerCrop" <ImageView
android:layout_alignParentRight="true"/> android:layout_width="100dp"
android:layout_height="100dp"
android:id="@+id/iv"
android:scaleType="centerCrop"
android:layout_alignParentRight="true"/>
</RelativeLayout> </RelativeLayout>

@ -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>

@ -13,8 +13,8 @@
<item name="windowNoTitle">true</item> <item name="windowNoTitle">true</item>
</style> </style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" /> <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/>
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" /> <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light"/>
</resources> </resources>

@ -1,9 +1,8 @@
package com.example.open_nsfw_android package com.example.open_nsfw_android
import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
import org.junit.Assert.*
/** /**
* Example local unit test, which will execute on the development machine (host). * Example local unit test, which will execute on the development machine (host).
* *

@ -1,17 +1,12 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.3.50' ext.kotlin_version = '1.3.72'
repositories { repositories {
google() google()
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.6.0-rc01' classpath 'com.android.tools.build:gradle:3.6.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 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
} }
} }
@ -19,6 +14,7 @@ allprojects {
repositories { repositories {
google() google()
jcenter() jcenter()
mavenCentral()
maven { maven {
url 'https://jitpack.io' url 'https://jitpack.io'
} }

202
gradlew vendored

@ -10,22 +10,22 @@
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
PRG="$0" PRG="$0"
# Need this for relative symlinks. # Need this for relative symlinks.
while [ -h "$PRG" ] ; do while [ -h "$PRG" ]; do
ls=`ls -ld "$PRG"` ls=$(ls -ld "$PRG")
link=`expr "$ls" : '.*-> \(.*\)$'` link=$(expr "$ls" : '.*-> \(.*\)$')
if expr "$link" : '/.*' > /dev/null; then if expr "$link" : '/.*' >/dev/null; then
PRG="$link" PRG="$link"
else else
PRG=`dirname "$PRG"`"/$link" PRG=$(dirname "$PRG")"/$link"
fi fi
done done
SAVED="`pwd`" SAVED="$(pwd)"
cd "`dirname \"$PRG\"`/" >/dev/null cd "$(dirname \"$PRG\")/" >/dev/null
APP_HOME="`pwd -P`" APP_HOME="$(pwd -P)"
cd "$SAVED" >/dev/null cd "$SAVED" >/dev/null
APP_NAME="Gradle" APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"` APP_BASE_NAME=$(basename "$0")
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS="" DEFAULT_JVM_OPTS=""
@ -33,15 +33,15 @@ DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD="maximum"
warn () { warn() {
echo "$*" echo "$*"
} }
die () { die() {
echo echo
echo "$*" echo "$*"
echo echo
exit 1 exit 1
} }
# OS specific support (must be 'true' or 'false'). # OS specific support (must be 'true' or 'false').
@ -49,115 +49,115 @@ cygwin=false
msys=false msys=false
darwin=false darwin=false
nonstop=false nonstop=false
case "`uname`" in case "$(uname)" in
CYGWIN* ) CYGWIN*)
cygwin=true cygwin=true
;; ;;
Darwin* ) Darwin*)
darwin=true darwin=true
;; ;;
MINGW* ) MINGW*)
msys=true msys=true
;; ;;
NONSTOP* ) NONSTOP*)
nonstop=true nonstop=true
;; ;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables # IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java" JAVACMD="$JAVA_HOME/jre/sh/java"
else else
JAVACMD="$JAVA_HOME/bin/java" JAVACMD="$JAVA_HOME/bin/java"
fi fi
if [ ! -x "$JAVACMD" ] ; then if [ ! -x "$JAVACMD" ]; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
else else
JAVACMD="java" JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ]; then
MAX_FD_LIMIT=`ulimit -H -n` MAX_FD_LIMIT=$(ulimit -H -n)
if [ $? -eq 0 ] ; then if [ $? -eq 0 ]; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ]; then
MAX_FD="$MAX_FD_LIMIT" MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi fi
ulimit -n $MAX_FD
if [ $? -ne 0 ]; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi fi
# For Darwin, add options to specify how the application appears in the dock # For Darwin, add options to specify how the application appears in the dock
if $darwin; then if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi fi
# For Cygwin, switch paths to Windows format before running java # For Cygwin, switch paths to Windows format before running java
if $cygwin ; then if $cygwin; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"` APP_HOME=$(cygpath --path --mixed "$APP_HOME")
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` CLASSPATH=$(cygpath --path --mixed "$CLASSPATH")
JAVACMD=`cygpath --unix "$JAVACMD"` JAVACMD=$(cygpath --unix "$JAVACMD")
# We build the pattern for arguments to be converted via cygpath # We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` ROOTDIRSRAW=$(find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null)
SEP="" SEP=""
for dir in $ROOTDIRSRAW ; do for dir in $ROOTDIRSRAW; do
ROOTDIRS="$ROOTDIRS$SEP$dir" ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|" SEP="|"
done done
OURCYGPATTERN="(^($ROOTDIRS))" OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments # Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then if [ "$GRADLE_CYGPATTERN" != "" ]; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@"; do
CHECK=$(echo "$arg" | egrep -c "$OURCYGPATTERN" -)
CHECK2=$(echo "$arg" | egrep -c "^-") ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ]; then ### Added a condition
eval $(echo args$i)=$(cygpath --path --ignore --mixed "$arg")
else
eval $(echo args$i)="\"$arg\""
fi fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh i=$((i + 1))
i=0 done
for arg in "$@" ; do case $i in
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 0) set -- ;;
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 3) set -- "$args0" "$args1" "$args2" ;;
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
else 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
eval `echo args$i`="\"$arg\"" 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
fi 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
i=$((i+1)) 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
done 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
case $i in esac
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi fi
# Escape application args # Escape application args
save () { save() {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done for i; do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"; done
echo " " echo " "
} }
APP_ARGS=$(save "$@") APP_ARGS=$(save "$@")

@ -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…
Cancel
Save