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