修改模式切换流程

This commit is contained in:
mazengfei
2025-11-10 18:55:21 +08:00
parent 4abc855b36
commit b4087e435e
15 changed files with 421 additions and 136 deletions
Binary file not shown.
@@ -9,10 +9,12 @@ import timber.log.Timber
class MyApp : App() { class MyApp : App() {
companion object { companion object {
const val DEBUG: Boolean = true const val DEBUG: Boolean = true
var instance: MyApp?=null
} }
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
instance = this
Timber.plant(Timber.DebugTree()) Timber.plant(Timber.DebugTree())
var deviceId = AppUtil.getUDID(this) var deviceId = AppUtil.getUDID(this)
Timber.d("UDID = ${AppUtil.getUDID(this)}") Timber.d("UDID = ${AppUtil.getUDID(this)}")
@@ -3,12 +3,17 @@ package com.sw.dualscreen.activity
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Typeface import android.graphics.Typeface
import android.net.Uri import android.net.Uri
import android.view.View
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.Toast import android.widget.Toast
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.camera.view.PreviewView
import androidx.core.net.toUri
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import com.example.utils.FloatBase64Utils import com.example.utils.FloatBase64Utils
@@ -19,7 +24,9 @@ import com.sw.dualscreen.adapter.GridSpacingItemDecoration
import com.sw.dualscreen.adapter.dpToPx import com.sw.dualscreen.adapter.dpToPx
import com.sw.dualscreen.databinding.ActivityFoodCollectionBinding import com.sw.dualscreen.databinding.ActivityFoodCollectionBinding
import com.sw.dualscreen.databinding.ItemSearchFoodInfoBinding import com.sw.dualscreen.databinding.ItemSearchFoodInfoBinding
import com.sw.dualscreen.databinding.LayoutCameraPreviewBinding
import com.sw.dualscreen.ext.clickWithDebounce import com.sw.dualscreen.ext.clickWithDebounce
import com.sw.dualscreen.ext.dp
import com.sw.dualscreen.model.response.FoodInfo import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.objbox.Food import com.sw.dualscreen.objbox.Food
import com.sw.dualscreen.objbox.FoodCollectionBean import com.sw.dualscreen.objbox.FoodCollectionBean
@@ -28,6 +35,7 @@ import com.sw.dualscreen.objbox.ObjectBox
import com.sw.dualscreen.utils.BitmapCropper import com.sw.dualscreen.utils.BitmapCropper
import com.sw.dualscreen.utils.BitmapSaver import com.sw.dualscreen.utils.BitmapSaver
import com.sw.dualscreen.utils.CameraHelper import com.sw.dualscreen.utils.CameraHelper
import com.sw.dualscreen.utils.CameraUtils
import com.sw.dualscreen.utils.Debouncer import com.sw.dualscreen.utils.Debouncer
import com.sw.dualscreen.utils.ImageUtil import com.sw.dualscreen.utils.ImageUtil
import com.sw.dualscreen.viewmodel.BaseViewModel import com.sw.dualscreen.viewmodel.BaseViewModel
@@ -46,17 +54,19 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
private val viewModel by viewModels<UserViewModel>() private val viewModel by viewModels<UserViewModel>()
private var selectedFoodId: String? = "" private var selectedFoodId: String? = ""
private var selectedFoodName: String? = "" private var selectedFoodName: String? = ""
private val NO_MEAN_RGB = floatArrayOf(0.0f, 0.0f, 0.0f)
private val NO_STD_RGB = floatArrayOf(1.0f, 1.0f, 1.0f)
private var module: Module? = null
private var box: Box<Food>? = null private var box: Box<Food>? = null
private val foodCollectionList: MutableList<FoodCollectionBean> = mutableListOf() private val foodCollectionList: MutableList<FoodCollectionBean> = mutableListOf()
private lateinit var cameraHelper: CameraHelper // private lateinit var cameraHelper: CameraHelper
private lateinit var foodAdapter: GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding> private lateinit var foodAdapter: GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding>
private val foodList = mutableListOf<FoodInfo>() // 适配器内部维护的数据列表
private val debouncer = Debouncer(2000) private val debouncer = Debouncer(2000)
private lateinit var previewView: PreviewView
private val cameraUtils: CameraUtils by lazy {
CameraUtils(this)
}
private val collectionAdapter: FoodCollectionAdapter by lazy { private val collectionAdapter: FoodCollectionAdapter by lazy {
FoodCollectionAdapter(foodCollectionList).apply { FoodCollectionAdapter(foodCollectionList).apply {
@@ -67,9 +77,15 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
// } // }
// } // }
addOnItemChildClickListener(R.id.ivDelete) { _, _, position -> addOnItemChildClickListener(R.id.ivDelete) { _, _, position ->
list.removeAt(position) // list.removeAt(position)
collectionAdapter.notifyItemRemoved(position) // collectionAdapter.notifyItemRemoved(position)
collectionAdapter.notifyItemRangeChanged(position, list.size) // collectionAdapter.notifyItemRangeChanged(position, list.size)
foodCollectionList[position].let {
it.bitmap = null
it.isShowCamera = true
it.isFinish = false
}
notifyItemChanged(position)
} }
} }
} }
@@ -82,54 +98,77 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
return ActivityFoodCollectionBinding.inflate(layoutInflater) return ActivityFoodCollectionBinding.inflate(layoutInflater)
} }
private fun cameraCallback(uri:Uri){ private val cameraCallback: (Uri) -> Unit = { uri ->
val index = foodCollectionList.indexOfFirst { it.bitmap == null } val index = foodCollectionList.indexOfFirst { it.bitmap == null }
if (index == -1) { if (index == -1) {
ToastUtils.showToast("每次只允许保存6条数据") ToastUtils.showToast("每次只允许保存6条数据")
return rerurn@ cameraCallback
} }
ImageUtil.uriToBitmap(this, uri)?.let { bitmap -> ImageUtil.uriToBitmap(this, uri)?.let { bitmap ->
val cropBitmap = BitmapCropper.cropCenter( // val cropBitmap = BitmapCropper.cropCenter(
original = bitmap, // original = bitmap,
targetWidth = 1300, targetHeight = 900, // targetWidth = 1300, targetHeight = 900,
//offsetX = 30, offsetY = 100 // //offsetX = 30, offsetY = 100
) // )
val file = BitmapSaver.saveToAppFilesDir( val file = BitmapSaver.saveToAppFilesDir(
cropBitmap, this, "IMG_CROP_${System.currentTimeMillis()}.jpg" bitmap, this, "IMG_CROP_${System.currentTimeMillis()}.jpg"
) )
Timber.d("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}") Timber.d("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}")
foodCollectionList[index].let { foodCollectionList[index].let {
it.bitmap = cropBitmap it.bitmap = bitmap
it.isShowCamera = false it.isShowCamera = false
it.imageUri = file?.toUri()
} }
collectionAdapter.notifyItemChanged(index) collectionAdapter.notifyItemChanged(index)
} }
} }
override fun initialize() {
// 初始化 CameraHelper @SuppressLint("NotifyDataSetChanged")
cameraHelper = CameraHelper( private fun takePhoto() {
context = this, val count = foodCollectionList.count { it.bitmap != null }
caller = this, if (count == 6) {
authority = "${packageName}.fileprovider" ToastUtils.showToast("每次只允许保存6条数据")
) { uri, path -> return
//if (foodCollectionList.size < 6) {
// val insertIndex = if (foodCollectionList.isEmpty()) 0 else foodCollectionList.size - 1
// foodCollectionList.add(insertIndex, FoodCollectionBean(imageUri = uri))
//} else {
// foodCollectionList[foodCollectionList.size - 1] = FoodCollectionBean(imageUri = uri)
//}
//collectionAdapter.notifyDataSetChanged()
cameraCallback(uri)
} }
cameraUtils.takePhoto(cameraCallback)
}
override fun initialize() {
cameraUtils.initCamera()
val previewBinding =
LayoutCameraPreviewBinding.inflate(layoutInflater, binding.flCameraPreview)
previewView = previewBinding.previewView.also {
it.updateLayoutParams {
width = 180.dp
height = 180.dp
}
}
cameraUtils.setPreviewController(previewView)
// 初始化 CameraHelper
// cameraHelper = CameraHelper(
// context = this,
// caller = this,
// authority = "${packageName}.fileprovider"
// ) { uri, path ->
// //if (foodCollectionList.size < 6) {
// // val insertIndex = if (foodCollectionList.isEmpty()) 0 else foodCollectionList.size - 1
// // foodCollectionList.add(insertIndex, FoodCollectionBean(imageUri = uri))
// //} else {
// // foodCollectionList[foodCollectionList.size - 1] = FoodCollectionBean(imageUri = uri)
// //}
// //collectionAdapter.notifyDataSetChanged()
// cameraCallback(uri)
// }
binding.ivBack.setOnClickListener { binding.ivBack.setOnClickListener {
val intent = Intent(this, MainActivity::class.java) val intent = Intent(this, MainActivity::class.java)
startActivity(intent) startActivity(intent)
finish() finish()
} }
repeat(6) {
foodCollectionList.add(FoodCollectionBean(isShowCamera = true)) foodCollectionList.add(FoodCollectionBean(isShowCamera = true))
}
binding.rvFoodCollection.let { binding.rvFoodCollection.let {
it.layoutManager = GridLayoutManager(this, 3, GridLayoutManager.VERTICAL, false) it.layoutManager = GridLayoutManager(this, 3, GridLayoutManager.VERTICAL, false)
it.adapter = collectionAdapter it.adapter = collectionAdapter
@@ -164,15 +203,17 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
} }
binding.btnCollectedGoods.setOnClickListener { binding.btnCollectedGoods.setOnClickListener {
startActivity(Intent(this, CollectedDataActivity::class.java))
} }
binding.btnTakePhoto.clickWithDebounce { binding.btnTakePhoto.clickWithDebounce {
binding.btnTakePhoto.text = "拍照"
val count = foodCollectionList.count { it.bitmap != null } val count = foodCollectionList.count { it.bitmap != null }
if (count == 6) { if (count == 6) {
ToastUtils.showToast("每次只允许保存6条数据") ToastUtils.showToast("每次只允许保存6条数据")
return@clickWithDebounce return@clickWithDebounce
} }
cameraHelper.openCamera() // cameraHelper.openCamera()
takePhoto()
} }
binding.btnClearData.setOnClickListener { clearData() } binding.btnClearData.setOnClickListener { clearData() }
foodAdapter = createAdapter() foodAdapter = createAdapter()
@@ -192,10 +233,10 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
//Loading.show(this) //Loading.show(this)
showWaitingDialog("加载中……") showWaitingDialog("加载中……")
Thread { Thread {
foodCollectionList.forEachIndexed { index, it -> foodCollectionList
.filter { it.bitmap != null }
if (!it.isShowCamera) .forEachIndexed { index, it ->
image2VectorTask(it.imageUri!!, index) image2VectorTask(it, index)
} }
runOnUiThread { runOnUiThread {
window.decorView.postDelayed({ window.decorView.postDelayed({
@@ -206,45 +247,34 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
}.start() }.start()
} }
private fun image2VectorTask(imageUri: Uri, position: Int) { private fun initBox() {
if (module == null) {
module =
Module.load(FoodModule.copyAssetToCache(this, "best_embedding_model_mobile.pt"))
}
if (box == null) { if (box == null) {
box = ObjectBox.boxStore.boxFor(Food::class) box = ObjectBox.boxStore.boxFor(Food::class)
} }
ImageUtil.uriToBitmap(this, imageUri)?.let { bitmap -> }
//val bitmap2 = BitmapCropper.cropCenter(bitmap!!, 500, 500)
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!!.forward(IValue.from(inputTensor)).toTensor()
val base64Str = FloatBase64Utils.floatArrayToBase64(outputTensor.dataAsFloatArray) private fun image2VectorTask(item: FoodCollectionBean, position: Int) {
initBox()
val imageVector = FoodModule.bitmap2FloatArray(item.bitmap!!)
val base64Str = FloatBase64Utils.floatArrayToBase64(imageVector)
Timber.tag("mzf1").e(base64Str) Timber.tag("mzf1").e(base64Str)
val imageVector = outputTensor.dataAsFloatArray // viewModel.postImageData(
box?.put(Food(name = selectedFoodName, foodIdx = 0, foodVector = imageVector)) // context,
// foodId = selectedFoodId.toString(),
// foodName = selectedFoodName.toString(),
// foodVector = base64Str,
// uri = item.imageUri!!
// )
box?.put(Food(name = checkedItem!!.foodName, foodIdx = 0, foodVector = imageVector))
foodCollectionList[position].let { foodCollectionList[position].let {
it.imageVector = imageVector it.imageVector = imageVector
it.isFinish = true it.isFinish = true
Timber
viewModel.postImageData(
context,
foodId = selectedFoodId.toString(),
foodName = selectedFoodName.toString(),
foodVector = base64Str,
uri = imageUri
)
} }
runOnUiThread { runOnUiThread {
collectionAdapter.notifyItemChanged(position) collectionAdapter.notifyItemChanged(position)
} }
Timber.tag("registerDataChange").e("box.all.size=${box?.all?.size}")
}
} }
@@ -258,6 +288,8 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
super.registerDataChange() super.registerDataChange()
lifecycleScope.launch { lifecycleScope.launch {
viewModel.searchFoodInfoList.collect { viewModel.searchFoodInfoList.collect {
// foodList.clear()
// foodList.addAll(it)
foodAdapter.updateData(it) foodAdapter.updateData(it)
} }
} }
@@ -294,7 +326,9 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
} }
) )
} }
private var clickIndex = -1 private var clickIndex = -1
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
private fun clearData() { private fun clearData() {
foodCollectionList.forEach { foodCollectionList.forEach {
@@ -306,9 +340,27 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
clickIndex = -1 clickIndex = -1
binding.editFoodName.setText("") binding.editFoodName.setText("")
foodAdapter.updateData(mutableListOf()) // foodList.clear()
foodAdapter.notifyDataSetChanged() // foodAdapter.updateData(mutableListOf())
// foodAdapter.notifyDataSetChanged()
//loadEmptyView() //loadEmptyView()
} }
override fun onResume() {
super.onResume()
cameraUtils.bind()
binding.llCameraFlag.run {
visibility = View.VISIBLE
postDelayed({
visibility = View.GONE
}, 3000)
}
}
override fun onPause() {
super.onPause()
cameraUtils.unbind()
binding.llCameraFlag.visibility = View.VISIBLE
}
} }
@@ -235,15 +235,15 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
ImageUtil.uriToBitmap(this, photoUri)?.let { bitmap -> ImageUtil.uriToBitmap(this, photoUri)?.let { bitmap ->
Timber.d("registerDataChange photoUri 拿到bitmap") Timber.d("registerDataChange photoUri 拿到bitmap")
val bmp = BitmapCropper.cropCenter(bitmap, 1300, 900) // val bmp = BitmapCropper.cropCenter(bitmap, 1300, 900)
Timber.d("registerDataChange photoUri bitmap裁剪完成") Timber.d("registerDataChange photoUri bitmap裁剪完成")
val file = BitmapSaver.saveToAppFilesDir( val file = BitmapSaver.saveToAppFilesDir(
bmp, bitmap,
this, this,
"IMG_CROP_${System.currentTimeMillis()}.jpg" "IMG_CROP_${System.currentTimeMillis()}.jpg"
) )
Timber.d("registerDataChange photoUri bitmap保存文件路径:${file?.absolutePath}") Timber.d("registerDataChange photoUri bitmap保存文件路径:${file?.absolutePath}")
val resultList = FoodModule.queryFood(bmp) val resultList = FoodModule.queryFood(bitmap)
Timber.d("registerDataChange photoUri 拿到识别数据") Timber.d("registerDataChange photoUri 拿到识别数据")
val foodName = resultList.joinToString(separator = ",") val foodName = resultList.joinToString(separator = ",")
Timber.d("registerDataChange photoUri 识别数据名称:$foodName") Timber.d("registerDataChange photoUri 识别数据名称:$foodName")
@@ -33,7 +33,8 @@ class FoodCollectionAdapter (var list: MutableList<FoodCollectionBean>) :
setImageResource(R.drawable.ic_camera256) setImageResource(R.drawable.ic_camera256)
} else { } else {
scaleType = ImageView.ScaleType.FIT_CENTER scaleType = ImageView.ScaleType.FIT_CENTER
setImageURI(it.imageUri) //setImageURI(it.imageUri)
setImageBitmap(it.bitmap)
} }
} }
} }
@@ -13,7 +13,7 @@ import com.sw.dualscreen.ext.hideKeyboard
abstract class BaseDialog( abstract class BaseDialog(
context: Context, context: Context,
var defWidth: Int = 400.dp, var defWidth: Int = 400.dp,
var defHeight: Int = 300.dp var defHeight: Int = 260.dp
) : Dialog(context, R.style.DialogTheme) { ) : Dialog(context, R.style.DialogTheme) {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -1,10 +1,13 @@
package com.sw.dualscreen.objbox package com.sw.dualscreen.objbox
import com.sw.dualscreen.MyApp
import com.sw.dualscreen.utils.AssetsTool
import android.content.Context import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.net.Uri
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.dualscreen.utils.AssetsTool import com.sw.dualscreen.utils.ImageUtil
import io.objectbox.Box import io.objectbox.Box
import io.objectbox.kotlin.boxFor import io.objectbox.kotlin.boxFor
import io.objectbox.query.Query import io.objectbox.query.Query
@@ -19,34 +22,64 @@ import java.io.InputStream
object FoodModule { object FoodModule {
private const val THRESHOLD = 0.8
private lateinit var module_mobile: Module private lateinit var module_mobile: Module
private lateinit var box: Box<Food> private lateinit var box: Box<Food>
private lateinit var embeddingsList: List<List<Float>> private lateinit var embeddingsList: List<List<Float>>
private lateinit var labelsList: IntArray private lateinit var labelsList: IntArray
private lateinit var classInfo: FoodClassInfo private lateinit var classInfo: FoodClassInfo
private val NO_MEAN_RGB = floatArrayOf(0.0f, 0.0f, 0.0f) val NO_MEAN_RGB = floatArrayOf(0.0f, 0.0f, 0.0f)
private val NO_STD_RGB = floatArrayOf(1.0f, 1.0f, 1.0f) val NO_STD_RGB = floatArrayOf(1.0f, 1.0f, 1.0f)
val DEFAULT_FOOD_INDEX = -1
fun init(context: Context) { fun init(context: Context) {
Thread {
module_mobile = Module.load(copyAssetToCache(context, "best_embedding_model_mobile.pt")) module_mobile = Module.load(copyAssetToCache(context, "best_embedding_model_mobile.pt"))
box = ObjectBox.boxStore.boxFor(Food::class) box = ObjectBox.boxStore.boxFor(Food::class)
// if (box.all.isNotEmpty()) { //if (box.all.isNotEmpty()) {
// box.removeAll() // box.removeAll()
// } //}
// if (box.all.isEmpty()) { //if (box.all.isEmpty()) {
// initFoodData(context) // initDefFoodData(context)
// } //}
}.start()
} }
fun queryFood(bitmap: Bitmap, queryCount: Int = 15): List<String> { fun uri2FloatArray(uri: Uri): FloatArray? {
return MyApp.instance?.let { context ->
ImageUtil.uriToBitmap(context, uri)?.let {
bitmap2FloatArray(it)
}
}
}
fun bitmap2FloatArray(bitmap: Bitmap): FloatArray {
val inputTensor = TensorImageUtils.bitmapToFloat32Tensor( val inputTensor = TensorImageUtils.bitmapToFloat32Tensor(
bitmap, bitmap,
NO_MEAN_RGB, // [0.485, 0.456, 0.406] TORCHVISION_NORM_MEAN_RGB 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 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
}
fun queryFood(uri: Uri, queryCount: Int = 15): List<String>? {
return uri2FloatArray(uri)?.let {
queryFood(it, queryCount)
}
}
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 val floatArray = outputTensor.dataAsFloatArray
if (bitmap.isRecycled.not()) {
bitmap.recycle()
}
return queryFood(floatArray, queryCount) return queryFood(floatArray, queryCount)
} }
@@ -56,24 +89,35 @@ object FoodModule {
//查询比较分数 //查询比较分数
// 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 map = mutableMapOf<String, Int>()
query.findIdsWithScores().forEach { 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("registerDataChange").d("queryFood数据:${box.get(it.id).name}===score=${it.score}")
}
// Timber.tag("FoodModule").d("queryFood数据:${nameScoreList.toJsonString()}")
idScoreList.filter { it.score < 0.20 }.forEach {
val food = box.get(it.id) val food = box.get(it.id)
Timber.d("${food.name}|${food.foodIdx}|${it.score}")
if (1 - it.score >= THRESHOLD) {
//FoodQueryResult(id = it.id, name = food.name, foodIdx = food.foodIdx, score = it.score) //FoodQueryResult(id = it.id, name = food.name, foodIdx = food.foodIdx, score = it.score)
food.name?.let { key -> food.name?.let { key ->
val count = map[key] ?: 0 val count = map[key] ?: 0
map.put(key, count + 1) map[key] = 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
} }
data class IdNameScore(
val id:Long,
val name:String,
val score: Double
)
private fun initFoodData(context: Context) { fun initDefFoodData(context: Context, action:()-> Unit={}) {
if (box.all.isNotEmpty()) { val count = box.all.count { it.foodIdx == DEFAULT_FOOD_INDEX }
if (count > 0) {
return return
} }
val embeddingsJson = AssetsTool.readAssetsFile(context, "data/embeddings.json") val embeddingsJson = AssetsTool.readAssetsFile(context, "data/embeddings.json")
@@ -91,8 +135,9 @@ object FoodModule {
val classIdx = labelsList[index] val classIdx = labelsList[index]
val foodName = foodMap["$classIdx"] val foodName = foodMap["$classIdx"]
val array = floatList.toFloatArray() val array = floatList.toFloatArray()
box.put(Food(name = foodName, foodVector = array, foodIdx = index)) box.put(Food(name = foodName, foodVector = array, foodIdx = DEFAULT_FOOD_INDEX))
} }
action()
} }
@@ -29,7 +29,8 @@ object BitmapSaver {
format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG, format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG,
quality: Int = 100 quality: Int = 100
): File? { ): File? {
val dir = context.getExternalFilesDir(null) //val dir = context.getExternalFilesDir(null)
val dir = context.cacheDir
return saveBitmap(bitmap, File(dir, fileName), format, quality) return saveBitmap(bitmap, File(dir, fileName), format, quality)
} }
@@ -0,0 +1,81 @@
package com.sw.dualscreen.utils
import android.net.Uri
import androidx.activity.ComponentActivity
import androidx.camera.core.CameraSelector
import androidx.camera.view.CameraController
import androidx.camera.view.LifecycleCameraController
import androidx.camera.view.PreviewView
class CameraUtils(private var activity: ComponentActivity) {
private var cameraController: LifecycleCameraController? = null
private var photoCaptureHelper: PhotoCaptureHelper? = null
// private var isCameraReady = false
fun takePhoto(callback: (Uri) -> Unit) {
cameraController?.let {
if (photoCaptureHelper == null) {
initCaptureHelper()
}
}
photoCaptureHelper?.let {
it.addSuccessCallback(callback)
it.bindCameraCallback {
bind()
}
it.takePhoto()
}
}
private fun initCaptureHelper() {
photoCaptureHelper = PhotoCaptureHelper(
context = activity,
cameraController = cameraController!!,
onSuccess = {},
onError = { msg ->
//toast(msg)
}
)
}
fun setPreviewController(previewView: PreviewView?) {
if (previewView?.controller == null) {
previewView?.controller = cameraController
}
}
fun initCamera() {
if (cameraController == null) {
cameraController = LifecycleCameraController(activity).apply {
// 必须设置有效的用例
setEnabledUseCases(
CameraController.IMAGE_CAPTURE
// or CameraController.VIDEO_CAPTURE
)
cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
}
bind()
}
// if (isCameraReady.not()) {
// try {
// cameraController!!.initializationFuture.addListener({
// isCameraReady = true
// Timber.d("Camera initialized successfully")
// }, ContextCompat.getMainExecutor(this))
// } catch (e: Exception) {
// Timber.d("Camera initialized error = ${e.message}")
// }
// }
}
fun bind() {
cameraController?.bindToLifecycle(activity)
}
fun unbind() {
cameraController?.unbind()
}
}
@@ -0,0 +1,92 @@
package com.sw.dualscreen.utils
import android.content.Context
import android.net.Uri
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCaptureException
import androidx.camera.view.CameraController
import androidx.core.content.ContextCompat
import timber.log.Timber
import java.io.File
/**
* 拍照工具类
* @param context Context 上下文
* @param cameraController CameraController 相机控制器
* @param onSuccess (Uri) -> Unit 拍照成功回调
* @param onError (String) -> Unit 拍照失败回调
*/
class PhotoCaptureHelper(
private val context: Context,
private val cameraController: CameraController,
private val onSuccess: (Uri) -> Unit = {},
private val onError: (String) -> Unit = {}
) {
private val callbackList: MutableList<(Uri) -> Unit> = mutableListOf()
fun addSuccessCallback(callback:(Uri) -> Unit) {
if (callbackList.contains(callback).not()) {
callbackList.add(callback)
}
}
private var bindCamera:(()->Unit)?=null
fun bindCameraCallback(callback:()->Unit) {
this.bindCamera = callback
}
/**
* 拍照方法
* @param fileNamePrefix 文件名前缀,默认为"IMG_"
* @param fileExtension 文件扩展名,默认为".jpg"
*/
fun takePhoto(
fileNamePrefix: String = "IMG_",
fileExtension: String = ".jpg"
) {
Timber.d("开始拍照采集")
try {
val executor = ContextCompat.getMainExecutor(context)
val cacheDir = context.cacheDir
val photoFile = File.createTempFile(
"${fileNamePrefix}${System.currentTimeMillis()}",
fileExtension,
cacheDir
)
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
cameraController.takePicture(
outputOptions,
executor,
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
val photoUri = outputFileResults.savedUri ?: Uri.fromFile(photoFile)
Timber.d("照片保存成功: $photoUri")
onSuccess(photoUri)
callbackList.forEach {
it(photoUri)
}
}
override fun onError(exception: ImageCaptureException) {
val errorMsg = "拍照失败: ${exception.message}"
if (errorMsg.contains("Not bound to a valid Camera")) {
if (bindCamera != null) {
bindCamera?.invoke()
//takePhoto()
}
}
Timber.e(exception, errorMsg)
onError(errorMsg)
}
}
)
} catch (e: Exception) {
val errorMsg = "创建临时文件失败: ${e.message}"
Timber.e(e, errorMsg)
onError(errorMsg)
}
}
}
@@ -65,6 +65,7 @@ abstract class BaseViewModel() : ViewModel() {
fun parseEquipmentInfo(equipmentInfo: EquipmentInfo) { fun parseEquipmentInfo(equipmentInfo: EquipmentInfo) {
GlobalData.appBaseUrl = equipmentInfo.appPackageUrl!! GlobalData.appBaseUrl = equipmentInfo.appPackageUrl!!
GlobalData.appBaseUrl="http://192.168.1.210/gateway/local"
GlobalData.sdkKey = equipmentInfo.arcsoftSdkKey!! GlobalData.sdkKey = equipmentInfo.arcsoftSdkKey!!
GlobalData.appId = equipmentInfo.arcsoftAppId!! GlobalData.appId = equipmentInfo.arcsoftAppId!!
// GlobalData.activeKey = equipmentInfo.arcsoftActiveKey!! // GlobalData.activeKey = equipmentInfo.arcsoftActiveKey!!
@@ -7,12 +7,12 @@
android:layout_height="match_parent"> android:layout_height="match_parent">
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="68dp" android:layout_height="88dp"
android:layout_marginTop="24dp"> android:layout_marginTop="24dp">
<ImageView <ImageView
android:id="@+id/ivBack" android:id="@+id/ivBack"
android:layout_width="50dp" android:layout_width="68dp"
android:layout_height="68dp" android:layout_height="68dp"
android:padding="10dp" android:padding="10dp"
android:src="@drawable/ic_back" /> android:src="@drawable/ic_back" />
@@ -8,14 +8,15 @@
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="68dp" android:layout_height="88dp"
android:layout_marginTop="24dp"> android:layout_marginTop="24dp">
<ImageView <ImageView
android:id="@+id/ivBack" android:id="@+id/ivBack"
android:layout_width="50dp" android:layout_width="68dp"
android:layout_height="68dp" android:layout_height="68dp"
android:padding="10dp" android:padding="15dp"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_back" /> android:src="@drawable/ic_back" />
<TextView <TextView
@@ -31,7 +32,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:layout_marginBottom="10dp"> android:layout_marginBottom="24dp">
<FrameLayout <FrameLayout
android:id="@+id/flPreview" android:id="@+id/flPreview"
@@ -54,6 +55,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center" android:layout_gravity="center"
android:gravity="center" android:gravity="center"
android:background="@color/white"
android:orientation="vertical" android:orientation="vertical"
android:visibility="visible"> android:visibility="visible">
@@ -79,32 +81,32 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginStart="20dp" android:layout_marginStart="20dp"
android:orientation="vertical" android:orientation="horizontal"
android:gravity="center_vertical"> android:gravity="center_vertical">
<Button <Button
android:id="@+id/btnTakePhoto" android:id="@+id/btnTakePhoto"
android:layout_width="120dp" android:layout_width="140dp"
android:layout_height="wrap_content" android:layout_height="80dp"
android:gravity="center" android:gravity="center"
android:text="拍照" android:text="开始拍照"
android:textColor="#ffffff" android:textColor="#ffffff"
android:textSize="18sp" /> android:textSize="18sp" />
<Button <Button
android:id="@+id/btnClearData" android:id="@+id/btnClearData"
android:layout_width="120dp" android:layout_width="120dp"
android:layout_height="wrap_content" android:layout_height="80dp"
android:gravity="center" android:gravity="center"
android:text="清除" android:text="清除"
android:layout_marginVertical="5dp" android:layout_marginHorizontal="10dp"
android:textColor="#ffffff" android:textColor="#ffffff"
android:textSize="18sp" /> android:textSize="18sp" />
<Button <Button
android:id="@+id/btnCollectedGoods" android:id="@+id/btnCollectedGoods"
android:layout_width="wrap_content" android:layout_width="220dp"
android:layout_height="wrap_content" android:layout_height="80dp"
android:gravity="center" android:gravity="center"
android:text="查看已采集菜品" android:text="已采集菜品"
android:textColor="#ffffff" android:textColor="#ffffff"
android:textSize="18sp" /> android:textSize="18sp" />
@@ -125,9 +127,9 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="66dp"
android:layout_marginLeft="24dp" android:layout_marginLeft="24dp"
android:layout_marginTop="10dp" android:layout_marginTop="24dp"
android:layout_marginRight="24dp" android:layout_marginRight="24dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal">
@@ -141,11 +143,11 @@
android:gravity="center_vertical" android:gravity="center_vertical"
android:hint="输入菜品名称" android:hint="输入菜品名称"
android:imeOptions="actionSearch" android:imeOptions="actionSearch"
android:paddingStart="5dp" android:paddingStart="15dp"
android:paddingEnd="5dp" android:paddingEnd="15dp"
android:singleLine="true" android:singleLine="true"
android:textColor="@color/black" android:textColor="@color/black"
android:textSize="18sp" /> android:textSize="26sp" />
<Button <Button
android:id="@+id/btnFoodSearch" android:id="@+id/btnFoodSearch"
@@ -154,7 +156,7 @@
android:layout_marginLeft="12dp" android:layout_marginLeft="12dp"
android:text="菜品检索" android:text="菜品检索"
android:textColor="#ffffff" android:textColor="#ffffff"
android:textSize="16sp" /> android:textSize="26sp" />
</LinearLayout> </LinearLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
@@ -171,9 +173,9 @@
android:layout_width="260dp" android:layout_width="260dp"
android:layout_height="60dp" android:layout_height="60dp"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_marginVertical="10dp" android:layout_marginVertical="24dp"
android:text="保存" android:text="保存"
android:textColor="#ffffff" android:textColor="#ffffff"
android:textSize="20sp" /> android:textSize="26sp" />
</LinearLayout> </LinearLayout>
+9 -9
View File
@@ -2,15 +2,15 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="400dp" android:layout_width="400dp"
android:layout_height="200dp" android:layout_height="260dp"
android:gravity="center" android:gravity="center"
android:orientation="vertical" android:orientation="vertical"
android:background="@drawable/bg_white_radius12" android:background="@drawable/bg_white_radius12"
tools:ignore="HardcodedText"> tools:ignore="HardcodedText">
<ImageView <ImageView
android:layout_width="30dp" android:layout_width="50dp"
android:layout_height="30dp" android:layout_height="50dp"
android:src="@drawable/ic_tip_warn" android:src="@drawable/ic_tip_warn"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
@@ -19,11 +19,11 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="30dp" android:layout_marginHorizontal="30dp"
android:layout_marginVertical="20dp" android:layout_marginVertical="30dp"
android:gravity="center" android:gravity="center"
tools:text="存在未收货的物品,请确认是否放弃收货,若放弃则数据不会保存?" tools:text="存在未收货的物品,请确认是否放弃收货,若放弃则数据不会保存?"
android:textColor="#ff141428" android:textColor="#ff141428"
android:textSize="18sp" /> android:textSize="20sp" />
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -33,8 +33,8 @@
<TextView <TextView
android:id="@+id/btnCancel" android:id="@+id/btnCancel"
android:layout_width="100dp" android:layout_width="120dp"
android:layout_height="40dp" android:layout_height="50dp"
android:background="@drawable/bg_white_stroke_blue_ripple" android:background="@drawable/bg_white_stroke_blue_ripple"
android:text="取消" android:text="取消"
android:gravity="center" android:gravity="center"
@@ -43,8 +43,8 @@
<TextView <TextView
android:id="@+id/btnConfirm" android:id="@+id/btnConfirm"
android:layout_width="100dp" android:layout_width="120dp"
android:layout_height="40dp" android:layout_height="50dp"
android:layout_marginStart="30dp" android:layout_marginStart="30dp"
android:background="@drawable/bg_blue_ripple" android:background="@drawable/bg_blue_ripple"
android:text="确认" android:text="确认"
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="180dp"
android:layout_height="180dp"
android:layout_gravity="center"/>
</merge>