This commit is contained in:
2025-11-13 10:43:36 +08:00
parent caaea3a480
commit 95e02d71de
8 changed files with 84 additions and 56 deletions
@@ -1,6 +1,5 @@
package com.sw.inbound.activity
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.text.SpannableStringBuilder
@@ -62,7 +61,7 @@ abstract class GoodsListActivity : ComponentActivity() {
public const val IS_NEW_RECEIPT = "isNewReceipt"
const val PAGE_SIZE = 10
private const val DELAY_TIME = 10 * 1000
private const val DELAY_TIME = 5 * 1000
}
var isReceiptPage: Boolean = false
@@ -127,11 +126,7 @@ abstract class GoodsListActivity : ComponentActivity() {
dropdownPopup?.showAsDropDown(v)
}
binding.tvPurchaseNo.setOnClickListener {
handleGoodsResult(
goodsList = mutableListOf(),
file = null,
name = null
)
handleGoodsResult()
}
binding.tvUserName.text = GlobalData.deviceId
binding.tvUserName.gone()
@@ -202,6 +197,9 @@ abstract class GoodsListActivity : ComponentActivity() {
fun recognizeFood() {
if (isShowRecognizeDialog.not()) {
if(goodsRecognizeDialog == null) {
isShowRecognizeDialog = true
}
return
}
Timber.tag(TAG).d("recognizeFood,takePhoto")
@@ -213,6 +211,7 @@ abstract class GoodsListActivity : ComponentActivity() {
} catch (e: Exception) {
Timber.tag(TAG).d("recognizeFood,takePhoto异常:${e.message}")
e.printStackTrace()
handleGoodsResult()
}
}
@@ -241,21 +240,21 @@ abstract class GoodsListActivity : ComponentActivity() {
"IMG_CROP_${System.currentTimeMillis()}.jpg"
)
Timber.d("${this.javaClass.simpleName}-searchFood-裁剪bitmap保存文件路径:${file?.absolutePath}")
val foodNameList = FoodModule.queryFood(newBmp)
//val foodList = FoodModule.queryFood(newBmp)
val foodList = FoodModule.getFoodScoreList(newBmp)
runOnUiThread {
if (foodNameList.isEmpty()) {
handleGoodsResult(
goodsList = mutableListOf(),
file = file,
name = null
)
if (foodList.isEmpty()) {
handleGoodsResult()
return@runOnUiThread
}
viewModel.recognizeGoodsList(foodNameList) { goodsList ->
viewModel.recognizeGoodsList(
//nameList = foodList,
scoreList = foodList,
) { goodsList ->
handleGoodsResult(
goodsList = if (goodsList.size == foodNameList.size) goodsList else mutableListOf(),
goodsList = if (goodsList.size == foodList.size) goodsList else mutableListOf(),
file = file,
name = foodNameList.joinToString(",")
name = foodList.joinToString(",")
)
}
}
@@ -269,9 +268,9 @@ abstract class GoodsListActivity : ComponentActivity() {
}
private fun handleGoodsResult(
goodsList: List<SearchGoodsInfo.Record>,
file: File?,
name: String?
goodsList: List<SearchGoodsInfo.Record> = mutableListOf(),
file: File?=null,
name: String?=null,
) {
runOnUiThread {
if (goodsRecognizeDialog?.isShowing == true) {
@@ -7,6 +7,7 @@ import coil.load
import com.chad.library.adapter4.BaseQuickAdapter
import com.chad.library.adapter4.viewholder.QuickViewHolder
import com.sw.inbound.databinding.ListItemRecognizeBinding
import com.sw.inbound.ext.toFormattedString
import com.sw.inbound.model.response.SearchGoodsInfo
import com.sw.inbound.utils.ext.dp
import com.sw.inbound.utils.ext.setShapeDrawable
@@ -26,7 +27,7 @@ class RecognizeAdapter(var list: MutableList<SearchGoodsInfo.Record>) :
override fun onBindViewHolder(holder: VH, position: Int, item: SearchGoodsInfo.Record?) {
holder.binding.let {
it.tvRecName.run {
text = item?.goodsName
text = if (item!!.score == 0) item.goodsName else "${item.goodsName}\n${((item.score)/100.0).toFormattedString(2)}%"
setShapeDrawable2(
solidColor = "#DEF5F5F5",
bottomRightRadius = 12.dp,
@@ -16,7 +16,7 @@ import com.sw.inbound.utils.ext.hideKeyboard
abstract class BaseDialog(
context: Context,
var defWidth: Int = 1500.dp,
var defHeight: Int = 900.dp
var defHeight: Int = 800.dp
) : Dialog(context, R.style.DialogTheme) {
override fun onCreate(savedInstanceState: Bundle?) {
@@ -102,12 +102,4 @@ class GoodsRecognizeDialog(
recognizeAdapter.stateView = it
}
}
override fun dismiss() {
super.dismiss()
activity?.run {
isShowRecognizeDialog = true
goodsRecognizeDialog = null
}
}
}
@@ -80,7 +80,8 @@ data class SearchGoodsInfo(
val unitVoList: List<UnitVo>? = listOf(),
@SerializedName("zjmCode")
val zjmCode: String? = "",
var isSelected: Boolean = false
var isSelected: Boolean = false,
var score: Int = 0
) : Parcelable {
val goodsNameStr: String
get() {
@@ -3,6 +3,7 @@ package com.sw.inbound.objbox
import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import android.util.SparseLongArray
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.sw.inbound.MyApp
@@ -60,7 +61,11 @@ object FoodModule {
NO_STD_RGB // [0.229, 0.224, 0.225] TORCHVISION_NORM_STD_RGB
)
val outputTensor = module_mobile.forward(IValue.from(inputTensor)).toTensor()
return outputTensor.dataAsFloatArray
val floatArray = outputTensor.dataAsFloatArray
if (bitmap.isRecycled.not()) {
bitmap.recycle()
}
return floatArray
}
fun queryFood(uri: Uri, queryCount: Int = 15): List<String>? {
@@ -69,40 +74,63 @@ object FoodModule {
}
}
/**
* 返回识别物品名称列表
*/
fun queryFood(bitmap: Bitmap, queryCount: Int = 15): List<String> {
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
)
val outputTensor = module_mobile.forward(IValue.from(inputTensor)).toTensor()
val floatArray = outputTensor.dataAsFloatArray
if (bitmap.isRecycled.not()) {
bitmap.recycle()
}
val floatArray = bitmap2FloatArray(bitmap)
return queryFood(floatArray, queryCount)
}
fun queryFood(floatArray: FloatArray, queryCount: Int = 15): List<String> {
/**
* 返回识别物品IdNameScore对象列表
*/
fun queryFoodNameScore(bitmap: Bitmap, queryCount: Int = 15): List<IdNameScore> {
val floatArray = bitmap2FloatArray(bitmap)
return queryFoodNameScore(floatArray, queryCount)
}
fun queryFoodNameScore(floatArray: FloatArray, queryCount: Int = 15): List<IdNameScore> {
val query: Query<Food> =
box.query(Food_.foodVector.nearestNeighbors(floatArray, queryCount)).build()
//查询比较分数
// val tempList = query.findWithScores().sortedBy { it.score }.map { "${it.get().name}|${it.get().foodIdx}|${it.score}" }
val map = mutableMapOf<String, Int>()
val idScoreList = query.findIdsWithScores()
val nameScoreList = mutableListOf<IdNameScore>()
idScoreList.forEach {
nameScoreList.add(IdNameScore(id = it.id, name = box.get(it.id).name?:"", score = it.score))
}
Timber.tag("FoodModule").d("queryFood数据:${nameScoreList.toJsonString()}")
idScoreList.filter { it.score < 0.05 }.forEach {
val food = box.get(it.id)
//FoodQueryResult(id = it.id, name = food.name, foodIdx = food.foodIdx, score = it.score)
food.name?.let { key ->
val count = map[key] ?: 0
map[key] = count + 1
}
return nameScoreList
}
fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = 15): List<IdNameScore> {
val floatArray = bitmap2FloatArray(bitmap)
val nameScoreList = queryFoodNameScore(floatArray, queryCount)
val maxScoreList = nameScoreList.groupBy { it.name }.map { (_, value) -> value.maxByOrNull { it.score }!! }.toMutableList()
val map = mutableMapOf<String, Int>()
nameScoreList.forEach {
val key = it.name
val count = map[key] ?: 0
map[key] = count + 1
}
val orderList = map.entries.sortedByDescending { it.value }.map { it.key }.toMutableList()
val firstFood = nameScoreList[0].name
orderList.remove(firstFood)
orderList.add(0, firstFood)
val sortedScoreList = maxScoreList.sortedWith(compareBy {
orderList.indexOf(it.name)
})
Timber.tag("FoodModule").d("getFoodScoreList数据:${sortedScoreList.toJsonString()}")
return sortedScoreList
}
fun queryFood(floatArray: FloatArray, queryCount: Int = 15): List<String> {
val map = mutableMapOf<String, Int>()
val nameScoreList = queryFoodNameScore(floatArray, queryCount)
nameScoreList.filter { it.score < 0.05 }.forEach {
val count = map[it.name] ?: 0
map[it.name] = count + 1
}
val list = map.entries.sortedByDescending { it.value }.map { it.key }
return list
@@ -10,6 +10,7 @@ import com.sw.inbound.model.response.EquipmentInfo
import com.sw.inbound.model.response.GoodsInfo
import com.sw.inbound.model.response.SearchGoodsInfo
import com.sw.inbound.network.LoadingState
import com.sw.inbound.objbox.FoodModule
import com.sw.inbound.repository.RemoteRepository
import com.sw.inbound.utils.ToastUtils
import kotlinx.coroutines.flow.MutableStateFlow
@@ -197,12 +198,18 @@ abstract class BaseViewModel(
}
fun recognizeGoodsList(
nameList: List<String>,
nameList: List<String>?=null,
scoreList: List<FoodModule.IdNameScore>?=null,
callback: (List<SearchGoodsInfo.Record>) -> Unit
) {
launch {
val response = repository.recognizeGoodsList(nameList)
if (nameList.isNullOrEmpty() && scoreList.isNullOrEmpty()) return@launch
val paramList = if (nameList.isNullOrEmpty()) scoreList!!.map { it.name } else nameList
val response = repository.recognizeGoodsList(paramList)
val list = if (parseResponse(response)) response.data?:listOf() else listOf()
scoreList?.forEachIndexed { index, item ->
list[index].score = ((1 - item.score) * 10000).toInt()
}
callback(list)
}
}
@@ -18,14 +18,14 @@
<TextView
android:id="@+id/tvRecName"
android:layout_width="340dp"
android:layout_height="85dp"
android:layout_height="86dp"
android:layout_gravity="bottom"
android:background="#DEF5F5F5"
android:textStyle="bold"
android:textColor="@color/black"
android:textSize="30sp"
android:gravity="center"
android:maxLines="1"
android:maxLines="2"
android:ellipsize="end"
tools:text="大白菜"/>