parent
e09312c5a5
commit
0317d3e8de
@ -0,0 +1,32 @@ |
||||
package com.zww.sample; |
||||
|
||||
import android.annotation.SuppressLint; |
||||
import android.app.Application; |
||||
import android.widget.Toast; |
||||
|
||||
import io.github.devzwy.nsfw.NSFWHelper; |
||||
|
||||
import kotlin.Unit; |
||||
import kotlin.jvm.functions.Function0; |
||||
import kotlin.jvm.functions.Function1; |
||||
|
||||
public class JavaApp extends Application { |
||||
@SuppressLint("NewApi") |
||||
@Override |
||||
public void onCreate() { |
||||
super.onCreate(); |
||||
NSFWHelper.INSTANCE.initHelper(this, this.getFilesDir().getPath() + "/nsfw.tflite", true, 4, new Function0<Unit>() { |
||||
@Override |
||||
public Unit invoke() { |
||||
Toast.makeText(JavaApp.this, "初始化成功", Toast.LENGTH_SHORT).show(); |
||||
return null; |
||||
} |
||||
}, new Function1<String, Unit>() { |
||||
@Override |
||||
public Unit invoke(String s) { |
||||
Toast.makeText(JavaApp.this, s, Toast.LENGTH_SHORT).show(); |
||||
return null; |
||||
} |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,22 @@ |
||||
package com.zww.sample |
||||
|
||||
import android.app.Application |
||||
import android.widget.Toast |
||||
import io.github.devzwy.nsfw.NSFWHelper |
||||
|
||||
class KtApp:Application() { |
||||
override fun onCreate() { |
||||
super.onCreate() |
||||
NSFWHelper.openDebugLog() |
||||
NSFWHelper.initHelper( |
||||
context = this, |
||||
modelPath = "${this.filesDir.path}/nsfw.tflite", |
||||
isOpenGPU = true, |
||||
onInitError = { |
||||
Toast.makeText(this, it, Toast.LENGTH_SHORT).show() |
||||
}, |
||||
onInitSuccess = { |
||||
Toast.makeText(this, "初始化成功", Toast.LENGTH_SHORT).show() |
||||
}) |
||||
} |
||||
} |
@ -1,207 +0,0 @@ |
||||
package com.zwy.nsfw |
||||
|
||||
import android.content.Context |
||||
import android.content.res.Resources |
||||
import android.graphics.Bitmap |
||||
import android.graphics.Color |
||||
import android.os.SystemClock |
||||
import org.tensorflow.lite.Interpreter |
||||
import org.tensorflow.lite.gpu.GpuDelegate |
||||
import java.io.ByteArrayOutputStream |
||||
import java.io.File |
||||
import java.io.FileInputStream |
||||
import java.io.FileNotFoundException |
||||
import java.nio.ByteBuffer |
||||
import java.nio.ByteOrder |
||||
import java.nio.MappedByteBuffer |
||||
import java.nio.channels.FileChannel |
||||
import java.text.DecimalFormat |
||||
|
||||
object NSFWHelper { |
||||
|
||||
private lateinit var mInterpreter: Interpreter |
||||
|
||||
private lateinit var imgData: ByteBuffer |
||||
|
||||
/** |
||||
* 数据宽高 |
||||
*/ |
||||
private val INPUT_WIDTH = 224 |
||||
|
||||
/** |
||||
* 数据宽高 |
||||
*/ |
||||
private val INPUT_HEIGHT = 224 |
||||
|
||||
/** |
||||
* SDK是否初始化完成 |
||||
*/ |
||||
private var isSDKInit = false |
||||
|
||||
/** |
||||
* 手动初始化时必须传入模型文件的路径否则会有异常抛出 |
||||
* 自动初始化需要将模型存放在资源文件assets根目录下,且名称必须为:'nsfw.tflite',不可改动 |
||||
*/ |
||||
fun init(context: Context? = null, modelPath: String? = null) { |
||||
if (context == null && modelPath==null) throw RuntimeException("初始化失败,您必须选择一种初始化方式,初始化方式一:NSFWHelper.init(context = this@Application),初始化方式二:[implementation 'com.zwy.nsfw:nsfw_initializer:+'],初始化方式三:NSFWHelper.init(modelPath = \"模型文件存放路径\")。说明:方式一和方式二均需要手动将模型文件放在Assets根目录下,并命名为nsfw.tflite,方式三适用于产品对apk大小控制严格,无法将模型文件直接放在apk中,可在用户打开Apk后台静默下载后指定模型路径进行初始化,三种方式任选其一即可") |
||||
if (modelPath != null && modelPath.isNotEmpty()) { |
||||
log("手动初始化...") |
||||
File(modelPath).let { modelFile -> |
||||
if (!modelFile.exists()) throw FileNotFoundException("模型文件路径配置错误,请重新配置,如果确定路径正确请检测文件权限是否申请") |
||||
mInterpreter = try { |
||||
initInterpreterByFile(modelFile, true) |
||||
} catch (e: Exception) { |
||||
initInterpreterByFile(modelFile) |
||||
} |
||||
} |
||||
} else { |
||||
loadModelFile(context!!).let { |
||||
if (it == null) throw Resources.NotFoundException("资源文件下未找到模型文件,请检测模型是否在assets目录下,名称是否为nsfw.tflite") |
||||
mInterpreter = try { |
||||
initInterpreterByMappedByteBuffer(it, true).also { |
||||
log("NSFW自动初始化成功,已开启GPU加速") |
||||
} |
||||
} catch (e: Exception) { |
||||
initInterpreterByMappedByteBuffer(it).also { |
||||
log("NSFW自动初始化成功,未开启GPU加速") |
||||
} |
||||
} |
||||
} |
||||
} |
||||
imgData = ByteBuffer.allocateDirect(1 * INPUT_WIDTH * INPUT_HEIGHT * 3 * 4) |
||||
imgData.order(ByteOrder.LITTLE_ENDIAN) |
||||
isSDKInit = true |
||||
} |
||||
|
||||
// |
||||
//初始化方式一:NSFWHelper.init(context = this@Application),初始化方式二:[implementation 'com.zwy.nsfw:nsfw_initializer:+'],初始化方式三:NSFWHelper.init(modelPath = "模型文件存放路径")。说明:方式一和方式二均需要手动将模型文件放在Assets根目录下,并命名为nsfw.tflite,方式三适用于产品对apk大小控制严格,无法将模型文件直接放在apk中,可在用户打开Apk后台静默下载后指定模型路径进行初始化,三种方式任选其一即可 |
||||
// |
||||
// |
||||
/** |
||||
* 关闭日志输出 |
||||
*/ |
||||
fun disEnableLog() { |
||||
isOpenLog = false |
||||
} |
||||
|
||||
/** |
||||
* 装载扫描数据 |
||||
*/ |
||||
private fun convertBitmapToByteBuffer(bitmap_: Bitmap): Long { |
||||
SystemClock.uptimeMillis().let { startTime -> |
||||
imgData.rewind() |
||||
IntArray(INPUT_WIDTH * INPUT_HEIGHT).let { |
||||
//把每个像素的颜色值转为int 存入intValues |
||||
bitmap_.getPixels( |
||||
it, |
||||
0, |
||||
INPUT_WIDTH, |
||||
Math.max((bitmap_.height - INPUT_HEIGHT) / 2, 0), |
||||
Math.max((bitmap_.width - INPUT_WIDTH) / 2, 0), |
||||
INPUT_WIDTH, |
||||
INPUT_HEIGHT |
||||
) |
||||
for (color in it) { |
||||
imgData.putFloat((Color.blue(color) - 104).toFloat()) |
||||
imgData.putFloat((Color.green(color) - 117).toFloat()) |
||||
imgData.putFloat((Color.red(color) - 123).toFloat()) |
||||
} |
||||
} |
||||
return SystemClock.uptimeMillis() - startTime |
||||
} |
||||
} |
||||
|
||||
// # 根据路径获取图片 Image.open(path) |
||||
// # 判断图片的像素格式是否为RGB,如果不是RGB则转换为RGB(24位彩色图像,每个像素用24个bit表示,分别表示红色、绿色和蓝色三个通道) |
||||
// # 重制图片大小为256*256并使用 官方解释:若要调整大小,请对所有可能影响输出值的像素使用线性插值计算输出像素值。对于其他变换,使用输入图像中2x2环境上的线性插值。 |
||||
// # 对resize的结果转换为io流并保存为JPEG格式 |
||||
// # Convert to 64-bit floating point.asType float32 定义变量存储转换后的32位float图片数据 |
||||
// # 获取图片的宽高,截取 224*224大小 x从16位开始,取到16+224位置 y亦是如此 |
||||
// # 将取的数值转换位float32 |
||||
// # 将每一个颜色值*255 |
||||
// # 将每一个颜色减去一定的阈值 104.... |
||||
// # [[[127.64.-18]]] 转换为 [[[[127.64.-18]]]] |
||||
// # 使用index关键字喂入模型 |
||||
// # 删除所有单维度的条目 |
||||
// # 输出扫描结果 |
||||
fun getNSFWScore(bitmap: Bitmap): NSFWScoreBean { |
||||
if (!isSDKInit) throw RuntimeException( |
||||
"SDK未初始化,请初始化后使用。方式一:如果您的模型文件存放在Assets下并名称必须为:nsfw.tflite,请直接引用[implementation 'com.zwy.nsfw:nsfw_initializer:1.3.7']可免去初始化过程(模型文件在demo中有存放)" + |
||||
"方式二:否则需要指定模型文件的路径,使用:'NSFWHelper.init(modelPath = \"模型文件的路径\")'进行初始化,两者任选其一即可(后者适用于如果模型置于Apk中导致Apk体积超出产品预算时,可将模型文件后台下载至用户手机中后指定路径进行手动初始化)" |
||||
) |
||||
SystemClock.uptimeMillis().let { startTime -> |
||||
//缩放位图时是否应使用双线性过滤。如果这是正确的,则在缩放时将使用双线性滤波,从而以较差的性能为代价具有更好的图像质量。如果这是错误的,则使用最近邻居缩放,这将使图像质量较差但速度更快。推荐的默认设置是将滤镜设置为“ true”,因为双线性滤镜的成本通常很小,并且改善的图像质量非常重要 |
||||
ByteArrayOutputStream().let { stream -> |
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream) |
||||
stream.close() |
||||
convertBitmapToByteBuffer( |
||||
Bitmap.createScaledBitmap( |
||||
bitmap, |
||||
256, |
||||
256, |
||||
true |
||||
) |
||||
).let { timeConsumingToLoadData -> |
||||
// out |
||||
Array(1) { FloatArray(2) }.apply { |
||||
mInterpreter.run(imgData, this) |
||||
|
||||
DecimalFormat("0.000").let { |
||||
return NSFWScoreBean( |
||||
it.format(this[0][1]).toFloat(), |
||||
it.format(this[0][0]).toFloat(), |
||||
timeConsumingToLoadData, |
||||
SystemClock.uptimeMillis() - startTime |
||||
).also { |
||||
log("扫描完成 -> $it") |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
} |
||||
} |
||||
|
||||
@Throws(Exception::class) |
||||
private fun initInterpreterByFile(modelFile: File, isOpenGPU: Boolean = false): Interpreter { |
||||
return Interpreter( |
||||
modelFile, |
||||
Interpreter.Options().also { |
||||
it.setNumThreads(4) |
||||
if (isOpenGPU) it.addDelegate(GpuDelegate()) |
||||
it.setAllowBufferHandleOutput(true) |
||||
it.setAllowFp16PrecisionForFp32(true) |
||||
}) |
||||
} |
||||
|
||||
@Throws(Exception::class) |
||||
private fun initInterpreterByMappedByteBuffer( |
||||
modelMappedByteBuffer: MappedByteBuffer, |
||||
isOpenGPU: Boolean = false |
||||
): Interpreter { |
||||
return Interpreter( |
||||
modelMappedByteBuffer, |
||||
Interpreter.Options().also { |
||||
it.setNumThreads(4) |
||||
if (isOpenGPU) it.addDelegate(GpuDelegate()) |
||||
it.setAllowBufferHandleOutput(true) |
||||
it.setAllowFp16PrecisionForFp32(true) |
||||
}) |
||||
} |
||||
|
||||
private fun loadModelFile(context: Context): MappedByteBuffer? { |
||||
try { |
||||
context.assets.openFd("nsfw.tflite").let { fileDescriptor -> |
||||
return FileInputStream(fileDescriptor.fileDescriptor).channel.map( |
||||
FileChannel.MapMode.READ_ONLY, |
||||
fileDescriptor.startOffset, |
||||
fileDescriptor.declaredLength |
||||
) |
||||
} |
||||
} catch (e: Exception) { |
||||
return null |
||||
} |
||||
} |
||||
|
||||
} |
@ -1,43 +0,0 @@ |
||||
package com.zwy.nsfw |
||||
|
||||
import android.graphics.Bitmap |
||||
import android.graphics.BitmapFactory |
||||
import android.util.Log |
||||
import java.io.File |
||||
|
||||
@JvmField |
||||
var isOpenLog = true |
||||
|
||||
fun Any.log(content: String) { |
||||
if (isOpenLog) |
||||
Log.d("NSFW", content) |
||||
} |
||||
|
||||
fun Any.logE(content: String) { |
||||
if (isOpenLog) |
||||
Log.e("NSFW", content) |
||||
} |
||||
|
||||
/** |
||||
* [nsfwScore]不适宜度 |
||||
* [sfwScore]适宜度 |
||||
* [TimeConsumingToLoadData]装载数据耗时 |
||||
* [TimeConsumingToScanData]扫描数据耗时 |
||||
*/ |
||||
data class NSFWScoreBean( |
||||
val nsfwScore: Float, |
||||
val sfwScore: Float, |
||||
val timeConsumingToLoadData: Long, |
||||
val timeConsumingToScanData: Long |
||||
) { |
||||
override fun toString(): String { |
||||
return "nsfwScore:$nsfwScore, sfwScore:$sfwScore, TimeConsumingToLoadData:$timeConsumingToLoadData ms, TimeConsumingToScanData=$timeConsumingToScanData ms)" |
||||
} |
||||
} |
||||
|
||||
fun Bitmap.getNSFWScore() = NSFWHelper.getNSFWScore(this) |
||||
fun File.getNSFWScore() = try { |
||||
BitmapFactory.decodeFile(this.path) |
||||
} catch (e: Exception) { |
||||
throw RuntimeException("File2Bitmap过程失败,请确认文件是否存在或是否为图片") |
||||
}.getNSFWScore() |
@ -0,0 +1,340 @@ |
||||
package io.github.devzwy.nsfw |
||||
|
||||
import android.app.Application |
||||
import android.graphics.Bitmap |
||||
import android.graphics.BitmapFactory |
||||
import android.graphics.Color |
||||
import android.os.SystemClock |
||||
import android.util.Log |
||||
import kotlinx.coroutines.* |
||||
import org.tensorflow.lite.Interpreter |
||||
import org.tensorflow.lite.gpu.GpuDelegate |
||||
import java.io.ByteArrayOutputStream |
||||
import java.io.File |
||||
import java.io.FileInputStream |
||||
import java.io.FileNotFoundException |
||||
import java.lang.Exception |
||||
import java.nio.ByteBuffer |
||||
import java.nio.ByteOrder |
||||
import java.nio.channels.FileChannel |
||||
import java.text.DecimalFormat |
||||
|
||||
object NSFWHelper { |
||||
|
||||
/*为空时表示未初始化SDK*/ |
||||
private var nsfwApplication: Application? = null |
||||
|
||||
/*扫描器*/ |
||||
private lateinit var mInterpreter: Interpreter |
||||
|
||||
/*数据宽*/ |
||||
private val INPUT_WIDTH = 224 |
||||
|
||||
/*数据高*/ |
||||
private val INPUT_HEIGHT = 224 |
||||
|
||||
/*日志输出控制*/ |
||||
private var isEnableLog = false |
||||
|
||||
|
||||
/** |
||||
* NSFW初始化函数 内部日志默认关闭,调试环境可使用openDebugLog()开启日志 |
||||
* [application] 建议传入application,传入activity可能会有内存泄漏 |
||||
* [modelPath] 模型文件路径,为空时将默认从Assets下读取 |
||||
* [isOpenGPU] 是否开启GPU扫描加速,部分机型兼容不友好的可关闭。默认开启 |
||||
* [numThreads] 扫描数据时内部分配的线程 默认4 |
||||
* [onInitSuccess] 初始化成功的回调 |
||||
* [onInitError] 初始化失败的回调,携带一个string |
||||
*/ |
||||
fun initHelper( |
||||
context: Application, |
||||
modelPath: String? = null, |
||||
isOpenGPU: Boolean = true, |
||||
numThreads: Int = 4, |
||||
onInitSuccess: (() -> Unit)? = null, |
||||
onInitError: ((String) -> Unit)? = null |
||||
) { |
||||
|
||||
nsfwApplication?.let { |
||||
|
||||
logD("NSFWHelper已初始化,自动跳过本次初始化!") |
||||
onInitError?.let { it1 -> it1("请勿重复初始化") } |
||||
|
||||
return |
||||
} |
||||
|
||||
|
||||
nsfwApplication = context |
||||
|
||||
getInterpreterOptions(isOpenGPU, numThreads).let { options -> |
||||
|
||||
if (modelPath.isNullOrEmpty()) { |
||||
logD("未传入模型路径,尝试从Assets下读取'nsfw.tflite'模型文件") |
||||
//指定模型为空时默认寻找assets目录下名称为nsfw.tflite的模型 |
||||
try { |
||||
mInterpreter = Interpreter( |
||||
nsfwApplication!!.assets.openFd("nsfw.tflite") |
||||
.let { fileDescriptor -> |
||||
FileInputStream(fileDescriptor.fileDescriptor).channel.map( |
||||
FileChannel.MapMode.READ_ONLY, |
||||
fileDescriptor.startOffset, |
||||
fileDescriptor.declaredLength |
||||
) |
||||
}, options |
||||
) |
||||
} catch (e: Exception) { |
||||
|
||||
nsfwApplication = null |
||||
|
||||
logE("未从Assets下成功读取'nsfw.tflite'模型") |
||||
onInitError?.let { it("未从Assets下成功读取'nsfw.tflite'模型") } |
||||
|
||||
|
||||
return |
||||
} |
||||
|
||||
logD("从Assets下加载模型文件成功!") |
||||
|
||||
} else { |
||||
|
||||
logD("尝试从传入的模型路径读取模型") |
||||
|
||||
//指定路径下寻找模型文件进行初始化 |
||||
try { |
||||
modelPath.let { |
||||
File(it).let { modelFile -> |
||||
modelFile.exists().assetBoolean({ |
||||
mInterpreter = Interpreter( |
||||
modelFile, |
||||
options |
||||
) |
||||
}, { |
||||
throw FileNotFoundException("未找到模型文件") |
||||
}) |
||||
} |
||||
} |
||||
|
||||
logD("模型加载成功!") |
||||
|
||||
} catch (e: Exception) { |
||||
|
||||
nsfwApplication = null |
||||
|
||||
logE("模型配置错误,读取失败") |
||||
onInitError?.let { it("未能正确读取到模型文件 '${modelPath}'") } |
||||
|
||||
|
||||
return |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
logD("NSFWHelper初始化成功!${if (isOpenGPU) "GPU加速已成功开启" else "GPU加速未开启"}") |
||||
onInitSuccess?.let { it() } |
||||
|
||||
} |
||||
|
||||
/** |
||||
* 开启日志 |
||||
*/ |
||||
fun openDebugLog() { |
||||
isEnableLog = true |
||||
} |
||||
|
||||
private fun logD(content: String) { |
||||
if (isEnableLog) Log.d(javaClass.name, content) |
||||
} |
||||
|
||||
private fun logE(content: String) { |
||||
if (isEnableLog) Log.e(javaClass.name, content) |
||||
} |
||||
|
||||
|
||||
private fun getInterpreterOptions(openGPU: Boolean, numThreads: Int): Interpreter.Options { |
||||
return Interpreter.Options().also { |
||||
it.setNumThreads(numThreads) |
||||
if (openGPU) { |
||||
it.addDelegate(GpuDelegate()) |
||||
/*CPU转到GPU可以直接读取或直接写入数据到GPU中的硬件缓冲区并绕过可避免的memory copies*/ |
||||
it.setAllowBufferHandleOutput(true) |
||||
/*CPU转到GPU处理提升扫描速度*/ |
||||
it.setAllowFp16PrecisionForFp32(true) |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 同步扫描文件NSFW数值 |
||||
*/ |
||||
fun getNSFWScore(file: File): NSFWScoreBean { |
||||
|
||||
nsfwApplication?.let { |
||||
return getNSFWScore(BitmapFactory.decodeFile(file.path)) |
||||
} |
||||
|
||||
throw NSFWUnInitException() |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 异步扫描文件NSFW数值 |
||||
*/ |
||||
fun getNSFWScore(file: File, onResult: ((NSFWScoreBean) -> Unit)) { |
||||
|
||||
if (nsfwApplication == null) { |
||||
throw NSFWUnInitException() |
||||
} |
||||
|
||||
GlobalScope.launch(Dispatchers.IO) { |
||||
getNSFWScore(BitmapFactory.decodeFile(file.path)).let { result -> |
||||
withContext(Dispatchers.Main) { |
||||
onResult(result) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 同步扫描文件NSFW数值 |
||||
*/ |
||||
fun getNSFWScore(filePath: String): NSFWScoreBean { |
||||
|
||||
nsfwApplication?.let { |
||||
return getNSFWScore(BitmapFactory.decodeFile(filePath)) |
||||
} |
||||
throw NSFWUnInitException() |
||||
|
||||
} |
||||
|
||||
|
||||
/** |
||||
* 异步扫描文件NSFW数值 |
||||
*/ |
||||
fun getNSFWScore(filePath: String, onResult: ((NSFWScoreBean) -> Unit)) { |
||||
|
||||
if (nsfwApplication == null) { |
||||
throw NSFWUnInitException() |
||||
} |
||||
|
||||
GlobalScope.launch(Dispatchers.IO) { |
||||
getNSFWScore(BitmapFactory.decodeFile(filePath)).let { result -> |
||||
withContext(Dispatchers.Main) { |
||||
onResult(result) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 同步扫描bitmap |
||||
*/ |
||||
fun getNSFWScore(bitmap: Bitmap): NSFWScoreBean { |
||||
|
||||
nsfwApplication?.let { |
||||
SystemClock.uptimeMillis().let { startTime -> |
||||
//缩放位图时是否应使用双线性过滤。如果这是正确的,则在缩放时将使用双线性滤波,从而以较差的性能为代价具有更好的图像质量。如果这是错误的,则使用最近邻居缩放,这将使图像质量较差但速度更快。推荐的默认设置是将滤镜设置为“ true”,因为双线性滤镜的成本通常很小,并且改善的图像质量非常重要 |
||||
ByteArrayOutputStream().let { stream -> |
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream) |
||||
stream.close() |
||||
convertBitmapToByteBuffer( |
||||
Bitmap.createScaledBitmap( |
||||
bitmap, |
||||
256, |
||||
256, |
||||
true |
||||
) |
||||
).let { result -> |
||||
// out |
||||
Array(1) { FloatArray(2) }.apply { |
||||
synchronized(this@NSFWHelper) { |
||||
mInterpreter.run(result.imgData, this) |
||||
|
||||
DecimalFormat("0.000").let { |
||||
return NSFWScoreBean( |
||||
it.format(this[0][1]).toFloat(), |
||||
it.format(this[0][0]).toFloat(), |
||||
result.exceTime, |
||||
SystemClock.uptimeMillis() - startTime |
||||
).also { |
||||
logD("扫描完成(${result}) -> $it") |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
} |
||||
} |
||||
throw NSFWUnInitException() |
||||
|
||||
} |
||||
|
||||
/** |
||||
* 异步扫描文件NSFW数值 |
||||
*/ |
||||
fun getNSFWScore(bitmap: Bitmap, onResult: ((NSFWScoreBean) -> Unit)) { |
||||
// # 根据路径获取图片 Image.open(path) |
||||
// # 判断图片的像素格式是否为RGB,如果不是RGB则转换为RGB(24位彩色图像,每个像素用24个bit表示,分别表示红色、绿色和蓝色三个通道) |
||||
// # 重制图片大小为256*256并使用 官方解释:若要调整大小,请对所有可能影响输出值的像素使用线性插值计算输出像素值。对于其他变换,使用输入图像中2x2环境上的线性插值。 |
||||
// # 对resize的结果转换为io流并保存为JPEG格式 |
||||
// # Convert to 64-bit floating point.asType float32 定义变量存储转换后的32位float图片数据 |
||||
// # 获取图片的宽高,截取 224*224大小 x从16位开始,取到16+224位置 y亦是如此 |
||||
// # 将取的数值转换位float32 |
||||
// # 将每一个颜色值*255 |
||||
// # 将每一个颜色减去一定的阈值 104.... |
||||
// # [[[127.64.-18]]] 转换为 [[[[127.64.-18]]]] |
||||
// # 使用index关键字喂入模型 |
||||
// # 删除所有单维度的条目 |
||||
// # 输出扫描结果 |
||||
if (nsfwApplication == null) { |
||||
logE("未初始化") |
||||
throw NSFWUnInitException() |
||||
} |
||||
|
||||
GlobalScope.launch(Dispatchers.IO) { |
||||
getNSFWScore(bitmap).let { result -> |
||||
withContext(Dispatchers.Main) { |
||||
onResult(result) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 装载扫描数据 |
||||
*/ |
||||
private fun convertBitmapToByteBuffer(bitmap_: Bitmap): CovertBitmapResultBean { |
||||
|
||||
ByteBuffer.allocateDirect(1 * INPUT_WIDTH * INPUT_HEIGHT * 3 * 4).let { imgData -> |
||||
|
||||
imgData.order(ByteOrder.LITTLE_ENDIAN) |
||||
|
||||
SystemClock.uptimeMillis().let { startTime -> |
||||
imgData.rewind() |
||||
IntArray(INPUT_WIDTH * INPUT_HEIGHT).let { |
||||
//把每个像素的颜色值转为int 存入intValues |
||||
bitmap_.getPixels( |
||||
it, |
||||
0, |
||||
INPUT_WIDTH, |
||||
Math.max((bitmap_.height - INPUT_HEIGHT) / 2, 0), |
||||
Math.max((bitmap_.width - INPUT_WIDTH) / 2, 0), |
||||
INPUT_WIDTH, |
||||
INPUT_HEIGHT |
||||
) |
||||
for (color in it) { |
||||
imgData.putFloat((Color.blue(color) - 104).toFloat()) |
||||
imgData.putFloat((Color.green(color) - 117).toFloat()) |
||||
imgData.putFloat((Color.red(color) - 123).toFloat()) |
||||
} |
||||
} |
||||
return CovertBitmapResultBean(imgData, SystemClock.uptimeMillis() - startTime) |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,29 @@ |
||||
package io.github.devzwy.nsfw |
||||
|
||||
import java.lang.Exception |
||||
import java.nio.ByteBuffer |
||||
|
||||
/** |
||||
* [nsfwScore]不适宜度 |
||||
* [sfwScore]适宜度 |
||||
* [TimeConsumingToLoadData]装载数据耗时 |
||||
* [TimeConsumingToScanData]扫描数据耗时 |
||||
*/ |
||||
data class NSFWScoreBean( |
||||
val nsfwScore: Float, |
||||
val sfwScore: Float, |
||||
val timeConsumingToLoadData: Long, |
||||
val timeConsumingToScanData: Long |
||||
) { |
||||
override fun toString(): String { |
||||
return "nsfwScore:$nsfwScore, sfwScore:$sfwScore, TimeConsumingToLoadData:$timeConsumingToLoadData ms, TimeConsumingToScanData=$timeConsumingToScanData ms)" |
||||
} |
||||
} |
||||
|
||||
fun Boolean.assetBoolean(onTrue: ()-> Unit,onFalse: ()-> Unit){ |
||||
if (this) onTrue() else onFalse() |
||||
} |
||||
|
||||
class NSFWUnInitException:Exception("请调用NSFWHelper.init(...)函数后再试!") |
||||
|
||||
data class CovertBitmapResultBean(val imgData: ByteBuffer,val exceTime:Long) |
@ -1 +0,0 @@ |
||||
/build |
@ -1,148 +0,0 @@ |
||||
apply plugin: 'com.android.library' |
||||
apply plugin: 'kotlin-android' |
||||
apply plugin: 'kotlin-android-extensions' |
||||
apply plugin: 'com.github.dcendents.android-maven' |
||||
apply plugin: 'com.jfrog.bintray' |
||||
apply plugin: 'org.jetbrains.dokka' |
||||
android { |
||||
compileSdkVersion 30 |
||||
buildToolsVersion "30.0.2" |
||||
|
||||
defaultConfig { |
||||
minSdkVersion 21 |
||||
targetSdkVersion 30 |
||||
versionCode 139 |
||||
versionName "$libVersion" |
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" |
||||
consumerProguardFiles "consumer-rules.pro" |
||||
} |
||||
|
||||
buildTypes { |
||||
release { |
||||
minifyEnabled false |
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' |
||||
} |
||||
} |
||||
|
||||
sourceSets { |
||||
main { |
||||
java { |
||||
include '**/*.java' |
||||
include '**/*.kt' |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
dependencies { |
||||
implementation fileTree(dir: "libs", include: ["*.jar"]) |
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" |
||||
implementation 'androidx.core:core-ktx:1.3.2' |
||||
implementation 'androidx.appcompat:appcompat:1.2.0' |
||||
api project(path: ':nsfw') |
||||
testImplementation 'junit:junit:4.12' |
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2' |
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' |
||||
|
||||
} |
||||
|
||||
//group = "com.zwy.nsfw" |
||||
//publish { |
||||
// userOrg = 'devzwy' |
||||
// repoName = 'maven' |
||||
// artifactId = 'nsfw_initializer' |
||||
// desc = 'android端离线鉴黄库快速初始化库' |
||||
// website = 'https://github.com/devzwy' |
||||
//} |
||||
// |
||||
//tasks.withType(Javadoc) {//防止编码问题 |
||||
// options.addStringOption('Xdoclint:none', '-quiet') |
||||
// options.addStringOption('encoding', 'UTF-8') |
||||
// options.addStringOption('charSet', 'UTF-8') |
||||
//} |
||||
|
||||
version = "$libVersion" |
||||
def siteUrl = 'https://github.com/devzwy/open_nsfw_android' // 项目的主页(可以写自己的库的GitHub地址) |
||||
def gitUrl = 'https://github.com/devzwy/open_nsfw_android.git' // Git仓库的url 这个是说明,可随便填 |
||||
group = "com.zwy.nsfw" // (**慎重填写**)这里是groupId ,必须填写 一般填你唯一的包名,对应com.squareup.okhttp3:okhttp:3.4.1中的com.squareup.okhttp3部分 |
||||
|
||||
install { |
||||
repositories.mavenInstaller { |
||||
pom { |
||||
project { |
||||
packaging 'aar' |
||||
name 'nsfw_initializer' //项目名字 |
||||
url siteUrl |
||||
licenses { |
||||
license { |
||||
name = 'The Apache Software License, Version 2.0' |
||||
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' |
||||
} |
||||
} |
||||
developers { |
||||
developer { |
||||
id 'z' //填写开发者的一些基本信息 |
||||
name 'Jason' //填写开发者的一些基本信息 |
||||
email 'dev_zwy@aliyun.com' //填写开发者的一些基本信息 |
||||
} |
||||
} |
||||
scm { |
||||
connection gitUrl |
||||
developerConnection gitUrl |
||||
url siteUrl |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
task generateSourcesJar(type: Jar) { |
||||
group = 'jar' |
||||
from android.sourceSets.main.java.srcDirs |
||||
classifier = 'sources' |
||||
} |
||||
|
||||
task sourcesJar(type: Jar) { |
||||
from android.sourceSets.main.java.srcDirs |
||||
classifier = 'sources' |
||||
} |
||||
task javadoc(type: Javadoc) { |
||||
source = android.sourceSets.main.java.srcDirs |
||||
options.encoding = "UTF-8" |
||||
classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) |
||||
} |
||||
task javadocJar(type: Jar, dependsOn: javadoc) { |
||||
classifier = 'javadoc' |
||||
from javadoc.destinationDir |
||||
} |
||||
task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) { |
||||
outputFormat = 'javadoc' |
||||
outputDirectory = javadoc.destinationDir |
||||
} |
||||
task generateJavadoc(type: Jar, dependsOn: dokkaJavadoc) { |
||||
group = 'jar' |
||||
classifier = 'javadoc' |
||||
from javadoc.destinationDir |
||||
} |
||||
|
||||
artifacts { |
||||
archives generateJavadoc //javadocJar |
||||
archives generateSourcesJar //sourcesJar |
||||
} |
||||
|
||||
Properties properties = new Properties() |
||||
properties.load(project.rootProject.file('local.properties').newDataInputStream()) |
||||
bintray { |
||||
user = properties.getProperty("bintray.user") //读取 local.properties 文件里面的 bintray.user |
||||
key = properties.getProperty("bintray.apikey") //读取 local.properties 文件里面的 bintray.apikey |
||||
configurations = ['archives'] |
||||
pkg { |
||||
repo = "maven" //(**慎重填写**)这里填写在bintray中自己新建仓库的名字 |
||||
name = "nsfw_initializer" //(**慎重填写**)发布到JCenter上的项目名字,必须填写,对应com.squareup.okhttp3:okhttp:3.4.1中的okhttp |
||||
websiteUrl = siteUrl |
||||
vcsUrl = gitUrl |
||||
licenses = ["Apache-2.0"] |
||||
publish = true |
||||
desc = 'android端离线鉴黄库快速初始化库' |
||||
} |
||||
} |
@ -1,21 +0,0 @@ |
||||
# Add project specific ProGuard rules here. |
||||
# You can control the set of applied configuration files using the |
||||
# proguardFiles setting in build.gradle. |
||||
# |
||||
# For more details, see |
||||
# http://developer.android.com/guide/developing/tools/proguard.html |
||||
|
||||
# If your project uses WebView with JS, uncomment the following |
||||
# and specify the fully qualified class name to the JavaScript interface |
||||
# class: |
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview { |
||||
# public *; |
||||
#} |
||||
|
||||
# Uncomment this to preserve the line number information for |
||||
# debugging stack traces. |
||||
#-keepattributes SourceFile,LineNumberTable |
||||
|
||||
# If you keep the line number information, uncomment this to |
||||
# hide the original source file name. |
||||
#-renamesourcefileattribute SourceFile |
@ -1,24 +0,0 @@ |
||||
package com.zwy.nsfw_initializer |
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry |
||||
import androidx.test.ext.junit.runners.AndroidJUnit4 |
||||
|
||||
import org.junit.Test |
||||
import org.junit.runner.RunWith |
||||
|
||||
import org.junit.Assert.* |
||||
|
||||
/** |
||||
* 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("zwy.nsfw.initializer.test", appContext.packageName) |
||||
} |
||||
} |
@ -1,11 +0,0 @@ |
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
||||
package="com.zwy.nsfw_initializer"> |
||||
|
||||
<application> |
||||
<provider |
||||
android:name="com.zwy.nsfw_initializer.NSFWInitProvider" |
||||
android:authorities="${applicationId}.nsfwinitprovider" |
||||
android:enabled="true" |
||||
android:exported="false" /> |
||||
</application> |
||||
</manifest> |
@ -1,45 +0,0 @@ |
||||
package com.zwy.nsfw_initializer |
||||
|
||||
import android.content.ContentProvider |
||||
import android.content.ContentValues |
||||
import android.database.Cursor |
||||
import android.net.Uri |
||||
import com.zwy.nsfw.NSFWHelper |
||||
|
||||
class NSFWInitProvider : ContentProvider() { |
||||
override fun insert(uri: Uri, values: ContentValues?): Uri? { |
||||
return null |
||||
} |
||||
|
||||
override fun query( |
||||
uri: Uri, |
||||
projection: Array<out String>?, |
||||
selection: String?, |
||||
selectionArgs: Array<out String>?, |
||||
sortOrder: String? |
||||
): Cursor? { |
||||
return null |
||||
} |
||||
|
||||
override fun onCreate(): Boolean { |
||||
NSFWHelper.init(requireNotNull(context),null) |
||||
return true |
||||
} |
||||
|
||||
override fun update( |
||||
uri: Uri, |
||||
values: ContentValues?, |
||||
selection: String?, |
||||
selectionArgs: Array<out String>? |
||||
): Int { |
||||
return 0 |
||||
} |
||||
|
||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int { |
||||
return 0 |
||||
} |
||||
|
||||
override fun getType(uri: Uri): String? { |
||||
return null |
||||
} |
||||
} |
@ -1,17 +0,0 @@ |
||||
package com.zwy.nsfw_initializer |
||||
|
||||
import org.junit.Test |
||||
|
||||
import org.junit.Assert.* |
||||
|
||||
/** |
||||
* 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) |
||||
} |
||||
} |
@ -1,3 +1,2 @@ |
||||
include ':nsfw_initializer' |
||||
include ':nsfw' |
||||
include ':app' |
||||
|
Loading…
Reference in new issue