优化
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
package com.sw.dualscreen
|
||||
|
||||
class Test {
|
||||
|
||||
private var lastWeight = 0
|
||||
// private var currentWeight = 0
|
||||
|
||||
data class WeightRecord(
|
||||
var eatWeight: Int = 0,
|
||||
var deviceWeight: Int = 0,
|
||||
var lastWeight: Int = 0,
|
||||
var state: Boolean = false
|
||||
)
|
||||
|
||||
private val weightRecord by lazy { WeightRecord() }
|
||||
|
||||
fun main() {
|
||||
readWeight { weight ->
|
||||
if (weightRecord.state) {
|
||||
//数据已记录
|
||||
return@readWeight
|
||||
}
|
||||
if (weight == 0 || lastWeight == 0) {
|
||||
return@readWeight
|
||||
}
|
||||
if (weight < lastWeight) {
|
||||
weightRecord.let {
|
||||
it.deviceWeight = weight
|
||||
it.eatWeight = it.deviceWeight - it.lastWeight
|
||||
it.state = true
|
||||
}
|
||||
}
|
||||
lastWeight = weight
|
||||
}
|
||||
}
|
||||
|
||||
fun readWeight(block: (Int) -> Unit) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -884,7 +884,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
private fun uri2File(uri: Uri, block: (File?, FloatArray?) -> Unit) {
|
||||
ImageUtil.uriToBitmap(this, uri)?.let { bitmap ->
|
||||
val imageVector = try {
|
||||
FoodModule.bitmap2FloatArray(bitmap)
|
||||
FoodModule.bitmap2FloatArray(bitmap, false)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return@let
|
||||
|
||||
@@ -148,7 +148,7 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
||||
//// offsetX = 30, offsetY = 100
|
||||
// )
|
||||
val imageVector = try {
|
||||
FoodModule.bitmap2FloatArray(bitmap)
|
||||
FoodModule.bitmap2FloatArray(bitmap, false)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
ToastUtils.showToast("操作失败")
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
//package com.sw.dualscreen.adapter
|
||||
//
|
||||
//import android.content.Context
|
||||
//import android.view.LayoutInflater
|
||||
//import android.view.View
|
||||
//import android.view.ViewGroup
|
||||
//import android.widget.ImageView
|
||||
//import com.chad.library.adapter4.BaseQuickAdapter
|
||||
//import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||
//import com.sw.dualscreen.R
|
||||
//import com.sw.dualscreen.databinding.ListItemCollectedDataBinding
|
||||
//import com.sw.dualscreen.databinding.ListItemFoodCollectionBinding
|
||||
//import com.sw.dualscreen.objbox.CollectedFoodInfo
|
||||
//import com.sw.dualscreen.objbox.FoodCollectionBean
|
||||
//
|
||||
//class CollectedFoodAdapter (var list: MutableList<CollectedFoodInfo>) :
|
||||
// BaseQuickAdapter<CollectedFoodInfo, CollectedFoodAdapter.VH>(list) {
|
||||
//
|
||||
// inner class VH(var binding: ListItemCollectedDataBinding) : QuickViewHolder(binding.root)
|
||||
//
|
||||
// override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
|
||||
// val inflater = LayoutInflater.from(context)
|
||||
// val binding = ListItemCollectedDataBinding.inflate(inflater, parent, false)
|
||||
// return VH(binding)
|
||||
// }
|
||||
//
|
||||
// override fun onBindViewHolder(holder: VH, position: Int, item: CollectedFoodInfo?) {
|
||||
// holder.binding.tvFoodName.text = item?.foodName
|
||||
// holder.binding.divider.run {
|
||||
// visibility = if (position == list.size - 1) View.GONE else View.VISIBLE
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//}
|
||||
@@ -6,6 +6,8 @@ import android.view.ViewGroup
|
||||
import com.chad.library.adapter4.BaseQuickAdapter
|
||||
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||
import com.sw.dualscreen.databinding.ListItemCollectedFoodBinding
|
||||
import com.sw.dualscreen.ext.gone
|
||||
import com.sw.dualscreen.ext.visible
|
||||
import com.sw.dualscreen.objbox.CollectedFoodInfo
|
||||
|
||||
class CollectedFoodNewAdapter(var list: MutableList<CollectedFoodInfo>) :
|
||||
@@ -22,6 +24,9 @@ class CollectedFoodNewAdapter(var list: MutableList<CollectedFoodInfo>) :
|
||||
override fun onBindViewHolder(holder: VH, position: Int, item: CollectedFoodInfo?) {
|
||||
holder.binding.tvFoodName.text = item?.foodName
|
||||
holder.binding.tvCollectedNum.text = "已采集${item?.foodCount}"
|
||||
holder.binding.divider.run {
|
||||
if (position == list.size-1) gone() else visible()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,9 +4,11 @@ import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import androidx.core.graphics.scale
|
||||
import com.sw.dualscreen.MyApp
|
||||
import com.sw.dualscreen.utils.GsonUtils
|
||||
import com.sw.dualscreen.utils.ImageUtil
|
||||
import com.sw.plate.utils.ToastUtils
|
||||
import io.objectbox.Box
|
||||
import io.objectbox.kotlin.boxFor
|
||||
import org.pytorch.IValue
|
||||
@@ -23,25 +25,34 @@ object FoodModule {
|
||||
private const val THRESHOLD = 0.8
|
||||
|
||||
// private const val THRESHOLD = 0.0
|
||||
private lateinit var module: Module
|
||||
private lateinit var box: Box<Food>
|
||||
private var module: Module?=null
|
||||
private var box: Box<Food>?=null
|
||||
|
||||
// private lateinit var embeddingsList: List<List<Float>>
|
||||
// private lateinit var labelsList: IntArray
|
||||
// private lateinit var classInfo: FoodClassInfo
|
||||
val NO_MEAN_RGB = floatArrayOf(0.0f, 0.0f, 0.0f)
|
||||
val NO_STD_RGB = floatArrayOf(1.0f, 1.0f, 1.0f)
|
||||
// val DEFAULT_FOOD_INDEX = -1
|
||||
|
||||
// val DEFAULT_FOOD_INDEX = -1
|
||||
// 1. 定义你的模型固定输入尺寸 (根据你的tflite模型修改,比如224x224)
|
||||
private const val MODEL_INPUT_WIDTH = 224
|
||||
private const val MODEL_INPUT_HEIGHT = 224
|
||||
|
||||
@SuppressLint("SuspiciousIndentation")
|
||||
fun init(context: Context, block: () -> Unit = {}) {
|
||||
Thread {
|
||||
module = Module.load(copyAssetToCache(context, "best_embedding_model_mobile.pt"))
|
||||
val modelPath = copyAssetToCache(context, "best_embedding_model_mobile.pt")
|
||||
// ToastUtils.showToast("modelPath==null:${modelPath == null}")
|
||||
module = Module.load(modelPath)
|
||||
// ToastUtils.showToast("module==null:${module == null}")
|
||||
box = ObjectBox.boxStore.boxFor(Food::class)
|
||||
//初始化默认重新拉取数据,先清空本地数据
|
||||
// ObjectBox.boxStore.runInTx {
|
||||
if (box.all.isNotEmpty()) {
|
||||
box.removeAll()
|
||||
box?.run {
|
||||
if (all.isNotEmpty()) {
|
||||
removeAll()
|
||||
}
|
||||
}
|
||||
//if (box.all.isEmpty()) {
|
||||
// initDefFoodData(context)
|
||||
@@ -50,29 +61,46 @@ object FoodModule {
|
||||
}.start()
|
||||
}
|
||||
|
||||
fun uri2FloatArray(uri: Uri): FloatArray? {
|
||||
return MyApp.instance?.let { context ->
|
||||
ImageUtil.uriToBitmap(context, uri)?.let {
|
||||
bitmap2FloatArray(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun bitmap2FloatArray(bitmap: Bitmap): FloatArray {
|
||||
var tensorStartTime = System.currentTimeMillis()
|
||||
val inputTensor = TensorImageUtils.bitmapToFloat32Tensor(
|
||||
bitmap,
|
||||
NO_MEAN_RGB, // [0.485, 0.456, 0.406] TORCHVISION_NORM_MEAN_RGB
|
||||
NO_STD_RGB // [0.229, 0.224, 0.225] TORCHVISION_NORM_STD_RGB
|
||||
)
|
||||
Timber.tag(TAG).d("bitmap2FloatArray-inputTensor耗时:${System.currentTimeMillis() - tensorStartTime}")
|
||||
// if (module_mobile == null) {
|
||||
// init(App.getContext())
|
||||
// fun uri2FloatArray(uri: Uri): FloatArray? {
|
||||
// return MyApp.instance?.let { context ->
|
||||
// ImageUtil.uriToBitmap(context, uri)?.let {
|
||||
// bitmap2FloatArray(it)
|
||||
// }
|
||||
// }
|
||||
tensorStartTime = System.currentTimeMillis()
|
||||
val outputTensor = module.forward(IValue.from(inputTensor)).toTensor()
|
||||
Timber.tag(TAG).d("bitmap2FloatArray-toTensor耗时:${System.currentTimeMillis() - tensorStartTime}")
|
||||
return outputTensor.dataAsFloatArray
|
||||
// }
|
||||
|
||||
fun bitmap2FloatArray(originBitmap: Bitmap, isRecycle: Boolean): FloatArray? {
|
||||
var rgb565Bitmap: Bitmap? = null
|
||||
try {
|
||||
val scaledBitmap = originBitmap.scale(
|
||||
MODEL_INPUT_WIDTH,
|
||||
MODEL_INPUT_HEIGHT
|
||||
)
|
||||
if (isRecycle) {
|
||||
originBitmap.recycle()
|
||||
}
|
||||
rgb565Bitmap = scaledBitmap.copy(Bitmap.Config.RGB_565, false)
|
||||
scaledBitmap.recycle()
|
||||
val inputTensor = TensorImageUtils.bitmapToFloat32Tensor(
|
||||
rgb565Bitmap,
|
||||
NO_MEAN_RGB, // [0.485, 0.456, 0.406] TORCHVISION_NORM_MEAN_RGB
|
||||
NO_STD_RGB // [0.229, 0.224, 0.225] TORCHVISION_NORM_STD_RGB
|
||||
)
|
||||
if (module == null) {
|
||||
return null
|
||||
}
|
||||
val outputTensor = module?.forward(IValue.from(inputTensor))?.toTensor()
|
||||
return outputTensor?.dataAsFloatArray
|
||||
} catch (e: OutOfMemoryError) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
if (rgb565Bitmap != null && rgb565Bitmap.isRecycled.not()) {
|
||||
rgb565Bitmap.recycle()
|
||||
}
|
||||
//System.gc()
|
||||
//System.runFinalization()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// fun queryFood(uri: Uri, queryCount: Int = 15): List<String>? {
|
||||
@@ -97,22 +125,24 @@ object FoodModule {
|
||||
// return queryFoodNameScore(floatArray, queryCount)
|
||||
// }
|
||||
|
||||
fun queryFoodNameScore(floatArray: FloatArray, queryCount: Int = 15): List<IdNameScore> {
|
||||
fun queryFoodNameScore(floatArray: FloatArray?, queryCount: Int = 15): List<IdNameScore> {
|
||||
if (floatArray == null) return emptyList()
|
||||
val startTime = System.currentTimeMillis()
|
||||
Timber.tag(TAG).d("queryFoodNameScore-已采集向量总数:${box.all.filter { it.isDel.not() }.size}")
|
||||
box.store.runInTx { }
|
||||
val query = box.query()
|
||||
.equal(Food_.isDel, false)
|
||||
.and()
|
||||
.nearestNeighbors(Food_.foodVector, floatArray, queryCount)
|
||||
.build()
|
||||
Timber.tag(TAG)
|
||||
.d("queryFoodNameScore-已采集向量总数:${box?.all?.filter { it.isDel.not() }?.size}")
|
||||
//box.store.runInTx { }
|
||||
val query = box?.query()
|
||||
?.equal(Food_.isDel, false)
|
||||
?.and()
|
||||
?.nearestNeighbors(Food_.foodVector, floatArray, queryCount)
|
||||
?.build()
|
||||
//查询比较分数
|
||||
// val tempList = query.findWithScores().sortedBy { it.score }.map { "${it.get().name}|${it.get().foodIdx}|${it.score}" }
|
||||
// val idScoreList = query.findIdsWithScores()
|
||||
val objScoreList = query.findWithScores()
|
||||
val objScoreList = query?.findWithScores()
|
||||
Timber.tag(TAG).d("idScoreList:${GsonUtils.toJson(objScoreList)}")
|
||||
val nameScoreList = mutableListOf<IdNameScore>()
|
||||
objScoreList.forEach {
|
||||
objScoreList?.forEach {
|
||||
val food = it.get()
|
||||
nameScoreList.add(
|
||||
IdNameScore(
|
||||
@@ -123,14 +153,15 @@ object FoodModule {
|
||||
)
|
||||
}
|
||||
val nameScoreData = GsonUtils.toJson(nameScoreList)
|
||||
Timber.tag(TAG).d("queryFood,耗时:${System.currentTimeMillis() - startTime},数据:$nameScoreData")
|
||||
query.close()
|
||||
Timber.tag(TAG)
|
||||
.d("queryFood,耗时:${System.currentTimeMillis() - startTime},数据:$nameScoreData")
|
||||
query?.close()
|
||||
return nameScoreList
|
||||
}
|
||||
|
||||
fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = 15): List<IdNameScore> {
|
||||
val startTime = System.currentTimeMillis()
|
||||
val floatArray = bitmap2FloatArray(bitmap)
|
||||
val floatArray = bitmap2FloatArray(bitmap, false)
|
||||
Timber.tag(TAG).d("bitmap2FloatArray,耗时:${System.currentTimeMillis() - startTime}")
|
||||
|
||||
val nameScoreList = queryFoodNameScore(floatArray, queryCount)
|
||||
@@ -156,6 +187,9 @@ object FoodModule {
|
||||
val sortedScoreList = maxScoreList.sortedWith(compareBy {
|
||||
orderList.indexOf(it.name)
|
||||
})
|
||||
if (bitmap.isRecycled.not()) {
|
||||
bitmap.recycle()
|
||||
}
|
||||
//Timber.tag("FoodModule").d("getFoodScoreList数据:${sortedScoreList.toJsonString()}")
|
||||
return sortedScoreList
|
||||
}
|
||||
@@ -241,47 +275,75 @@ object FoodModule {
|
||||
*
|
||||
* 不可能反正SD
|
||||
*/
|
||||
fun copyAssetToCache(context: Context, fileName: String): String? {
|
||||
// 此app的缓存目录 --> 会默认在 cache目录...,可以自己去看看哦
|
||||
val cacheDir = context.cacheDir
|
||||
if (!cacheDir.exists()) {
|
||||
cacheDir.mkdirs() // TODO 如果没有缓存目录,就创建
|
||||
}
|
||||
val outPath = File(cacheDir, fileName) // TODO 创建输出的文件位置
|
||||
if (outPath.exists()) {
|
||||
outPath.delete() // TODO 如果该文件已经存在,就删掉
|
||||
}
|
||||
var `is`: InputStream? = null // 读取
|
||||
var fos: FileOutputStream? = null // 写入
|
||||
try {
|
||||
// 创建文件,如果创建成功,就返回true
|
||||
val res = outPath.createNewFile()
|
||||
if (res) {
|
||||
`is` = context.assets.open(fileName) // 拿到main/assets目录的输入流,用于读取字节
|
||||
fos = FileOutputStream(outPath) // 读取出来的字节最终写到outPath
|
||||
val buf = ByteArray(`is`.available()) // 缓存区
|
||||
var byteCount: Int
|
||||
// fun copyAssetToCache(context: Context, fileName: String): String? {
|
||||
// // 此app的缓存目录 --> 会默认在 cache目录...,可以自己去看看哦
|
||||
// val cacheDir = context.cacheDir
|
||||
// if (!cacheDir.exists()) {
|
||||
// cacheDir.mkdirs() // TODO 如果没有缓存目录,就创建
|
||||
// }
|
||||
// val outPath = File(cacheDir, fileName) // TODO 创建输出的文件位置
|
||||
// if (outPath.exists()) {
|
||||
// outPath.delete() // TODO 如果该文件已经存在,就删掉
|
||||
// }
|
||||
// var `is`: InputStream? = null // 读取
|
||||
// var fos: FileOutputStream? = null // 写入
|
||||
// try {
|
||||
// // 创建文件,如果创建成功,就返回true
|
||||
// val res = outPath.createNewFile()
|
||||
// if (res) {
|
||||
// `is` = context.assets.open(fileName) // 拿到main/assets目录的输入流,用于读取字节
|
||||
// fos = FileOutputStream(outPath) // 读取出来的字节最终写到outPath
|
||||
// val buf = ByteArray(`is`.available()) // 缓存区
|
||||
// var byteCount: Int
|
||||
//
|
||||
// // 开始循环读取
|
||||
// while ((`is`.read(buf).also { byteCount = it }) != -1) {
|
||||
// fos.write(buf, 0, byteCount)
|
||||
// }
|
||||
// return outPath.absolutePath
|
||||
// }
|
||||
// } catch (e: IOException) {
|
||||
// e.printStackTrace()
|
||||
// } finally {
|
||||
// try {
|
||||
// // TODO 一定要记得关闭资源,为了不去性能的磨损
|
||||
// fos!!.flush()
|
||||
// `is`!!.close()
|
||||
// fos.close()
|
||||
// } catch (e: IOException) {
|
||||
// e.printStackTrace()
|
||||
// }
|
||||
// }
|
||||
// return null
|
||||
// }
|
||||
|
||||
// 开始循环读取
|
||||
while ((`is`.read(buf).also { byteCount = it }) != -1) {
|
||||
fos.write(buf, 0, byteCount)
|
||||
}
|
||||
return outPath.absolutePath
|
||||
fun copyAssetToCache(context: Context, fileName: String): String? {
|
||||
val cacheFile = File(context.cacheDir, fileName)
|
||||
val buffer = ByteArray(8 * 1024)
|
||||
var inputStream: InputStream? = null
|
||||
var outputStream: FileOutputStream? = null
|
||||
|
||||
try {
|
||||
inputStream = context.assets.open(fileName)
|
||||
outputStream = FileOutputStream(cacheFile)
|
||||
var byteCount: Int
|
||||
while (inputStream.read(buffer).also { byteCount = it } != -1) {
|
||||
outputStream.write(buffer, 0, byteCount)
|
||||
}
|
||||
outputStream.channel.force(true) // 强制物理落盘,比flush更彻底
|
||||
return cacheFile.absolutePath
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
try {
|
||||
// TODO 一定要记得关闭资源,为了不去性能的磨损
|
||||
fos!!.flush()
|
||||
`is`!!.close()
|
||||
fos.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
Timber.tag("FoodModule").d("文件拷贝失败 fileName=$fileName, error=${e.message}")
|
||||
// 拷贝失败时删除残缺文件,避免下次读取到损坏文件
|
||||
if (cacheFile.exists()) {
|
||||
cacheFile.delete()
|
||||
}
|
||||
} finally {
|
||||
outputStream?.close()
|
||||
inputStream?.close()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user