修改模式切换流程
This commit is contained in:
Binary file not shown.
@@ -9,10 +9,12 @@ import timber.log.Timber
|
||||
class MyApp : App() {
|
||||
companion object {
|
||||
const val DEBUG: Boolean = true
|
||||
var instance: MyApp?=null
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
instance = this
|
||||
Timber.plant(Timber.DebugTree())
|
||||
var deviceId = AppUtil.getUDID(this)
|
||||
Timber.d("UDID = ${AppUtil.getUDID(this)}")
|
||||
|
||||
@@ -3,12 +3,17 @@ package com.sw.dualscreen.activity
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Typeface
|
||||
import android.net.Uri
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
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.recyclerview.widget.GridLayoutManager
|
||||
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.databinding.ActivityFoodCollectionBinding
|
||||
import com.sw.dualscreen.databinding.ItemSearchFoodInfoBinding
|
||||
import com.sw.dualscreen.databinding.LayoutCameraPreviewBinding
|
||||
import com.sw.dualscreen.ext.clickWithDebounce
|
||||
import com.sw.dualscreen.ext.dp
|
||||
import com.sw.dualscreen.model.response.FoodInfo
|
||||
import com.sw.dualscreen.objbox.Food
|
||||
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.BitmapSaver
|
||||
import com.sw.dualscreen.utils.CameraHelper
|
||||
import com.sw.dualscreen.utils.CameraUtils
|
||||
import com.sw.dualscreen.utils.Debouncer
|
||||
import com.sw.dualscreen.utils.ImageUtil
|
||||
import com.sw.dualscreen.viewmodel.BaseViewModel
|
||||
@@ -46,17 +54,19 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
|
||||
private val viewModel by viewModels<UserViewModel>()
|
||||
private var selectedFoodId: 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 val foodCollectionList: MutableList<FoodCollectionBean> = mutableListOf()
|
||||
|
||||
private lateinit var cameraHelper: CameraHelper
|
||||
// private lateinit var cameraHelper: CameraHelper
|
||||
|
||||
private lateinit var foodAdapter: GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding>
|
||||
private val foodList = mutableListOf<FoodInfo>() // 适配器内部维护的数据列表
|
||||
private val debouncer = Debouncer(2000)
|
||||
private lateinit var previewView: PreviewView
|
||||
private val cameraUtils: CameraUtils by lazy {
|
||||
CameraUtils(this)
|
||||
}
|
||||
|
||||
private val collectionAdapter: FoodCollectionAdapter by lazy {
|
||||
FoodCollectionAdapter(foodCollectionList).apply {
|
||||
@@ -67,9 +77,15 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
|
||||
// }
|
||||
// }
|
||||
addOnItemChildClickListener(R.id.ivDelete) { _, _, position ->
|
||||
list.removeAt(position)
|
||||
collectionAdapter.notifyItemRemoved(position)
|
||||
collectionAdapter.notifyItemRangeChanged(position, list.size)
|
||||
// list.removeAt(position)
|
||||
// collectionAdapter.notifyItemRemoved(position)
|
||||
// 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)
|
||||
}
|
||||
|
||||
private fun cameraCallback(uri:Uri){
|
||||
private val cameraCallback: (Uri) -> Unit = { uri ->
|
||||
val index = foodCollectionList.indexOfFirst { it.bitmap == null }
|
||||
if (index == -1) {
|
||||
ToastUtils.showToast("每次只允许保存6条数据")
|
||||
return
|
||||
rerurn@ cameraCallback
|
||||
}
|
||||
ImageUtil.uriToBitmap(this, uri)?.let { bitmap ->
|
||||
val cropBitmap = BitmapCropper.cropCenter(
|
||||
original = bitmap,
|
||||
targetWidth = 1300, targetHeight = 900,
|
||||
//offsetX = 30, offsetY = 100
|
||||
)
|
||||
// val cropBitmap = BitmapCropper.cropCenter(
|
||||
// original = bitmap,
|
||||
// targetWidth = 1300, targetHeight = 900,
|
||||
// //offsetX = 30, offsetY = 100
|
||||
// )
|
||||
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}")
|
||||
|
||||
foodCollectionList[index].let {
|
||||
it.bitmap = cropBitmap
|
||||
it.bitmap = bitmap
|
||||
it.isShowCamera = false
|
||||
it.imageUri = file?.toUri()
|
||||
}
|
||||
collectionAdapter.notifyItemChanged(index)
|
||||
}
|
||||
}
|
||||
override fun initialize() {
|
||||
|
||||
// 初始化 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)
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun takePhoto() {
|
||||
val count = foodCollectionList.count { it.bitmap != null }
|
||||
if (count == 6) {
|
||||
ToastUtils.showToast("每次只允许保存6条数据")
|
||||
return
|
||||
}
|
||||
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 {
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
startActivity(intent)
|
||||
finish()
|
||||
}
|
||||
foodCollectionList.add(FoodCollectionBean(isShowCamera = true))
|
||||
repeat(6) {
|
||||
foodCollectionList.add(FoodCollectionBean(isShowCamera = true))
|
||||
}
|
||||
binding.rvFoodCollection.let {
|
||||
it.layoutManager = GridLayoutManager(this, 3, GridLayoutManager.VERTICAL, false)
|
||||
it.adapter = collectionAdapter
|
||||
@@ -164,15 +203,17 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
|
||||
}
|
||||
|
||||
binding.btnCollectedGoods.setOnClickListener {
|
||||
|
||||
startActivity(Intent(this, CollectedDataActivity::class.java))
|
||||
}
|
||||
binding.btnTakePhoto.clickWithDebounce {
|
||||
binding.btnTakePhoto.text = "拍照"
|
||||
val count = foodCollectionList.count { it.bitmap != null }
|
||||
if (count == 6) {
|
||||
ToastUtils.showToast("每次只允许保存6条数据")
|
||||
return@clickWithDebounce
|
||||
}
|
||||
cameraHelper.openCamera()
|
||||
// cameraHelper.openCamera()
|
||||
takePhoto()
|
||||
}
|
||||
binding.btnClearData.setOnClickListener { clearData() }
|
||||
foodAdapter = createAdapter()
|
||||
@@ -192,11 +233,11 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
|
||||
//Loading.show(this)
|
||||
showWaitingDialog("加载中……")
|
||||
Thread {
|
||||
foodCollectionList.forEachIndexed { index, it ->
|
||||
|
||||
if (!it.isShowCamera)
|
||||
image2VectorTask(it.imageUri!!, index)
|
||||
}
|
||||
foodCollectionList
|
||||
.filter { it.bitmap != null }
|
||||
.forEachIndexed { index, it ->
|
||||
image2VectorTask(it, index)
|
||||
}
|
||||
runOnUiThread {
|
||||
window.decorView.postDelayed({
|
||||
//Loading.dismiss()
|
||||
@@ -206,44 +247,33 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun image2VectorTask(imageUri: Uri, position: Int) {
|
||||
if (module == null) {
|
||||
module =
|
||||
Module.load(FoodModule.copyAssetToCache(this, "best_embedding_model_mobile.pt"))
|
||||
}
|
||||
private fun initBox() {
|
||||
if (box == null) {
|
||||
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)
|
||||
Timber.tag("mzf1").e(base64Str)
|
||||
val imageVector = outputTensor.dataAsFloatArray
|
||||
box?.put(Food(name = selectedFoodName, foodIdx = 0, foodVector = imageVector))
|
||||
foodCollectionList[position].let {
|
||||
it.imageVector = imageVector
|
||||
it.isFinish = true
|
||||
private fun image2VectorTask(item: FoodCollectionBean, position: Int) {
|
||||
initBox()
|
||||
val imageVector = FoodModule.bitmap2FloatArray(item.bitmap!!)
|
||||
|
||||
Timber
|
||||
viewModel.postImageData(
|
||||
context,
|
||||
foodId = selectedFoodId.toString(),
|
||||
foodName = selectedFoodName.toString(),
|
||||
foodVector = base64Str,
|
||||
uri = imageUri
|
||||
)
|
||||
}
|
||||
runOnUiThread {
|
||||
collectionAdapter.notifyItemChanged(position)
|
||||
}
|
||||
Timber.tag("registerDataChange").e("box.all.size=${box?.all?.size}")
|
||||
val base64Str = FloatBase64Utils.floatArrayToBase64(imageVector)
|
||||
Timber.tag("mzf1").e(base64Str)
|
||||
// viewModel.postImageData(
|
||||
// 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 {
|
||||
it.imageVector = imageVector
|
||||
it.isFinish = true
|
||||
}
|
||||
|
||||
runOnUiThread {
|
||||
collectionAdapter.notifyItemChanged(position)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,6 +288,8 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
|
||||
super.registerDataChange()
|
||||
lifecycleScope.launch {
|
||||
viewModel.searchFoodInfoList.collect {
|
||||
// foodList.clear()
|
||||
// foodList.addAll(it)
|
||||
foodAdapter.updateData(it)
|
||||
}
|
||||
}
|
||||
@@ -294,7 +326,9 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private var clickIndex = -1
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun clearData() {
|
||||
foodCollectionList.forEach {
|
||||
@@ -306,9 +340,27 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
|
||||
|
||||
clickIndex = -1
|
||||
binding.editFoodName.setText("")
|
||||
foodAdapter.updateData(mutableListOf())
|
||||
foodAdapter.notifyDataSetChanged()
|
||||
// foodList.clear()
|
||||
// foodAdapter.updateData(mutableListOf())
|
||||
// foodAdapter.notifyDataSetChanged()
|
||||
//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 ->
|
||||
Timber.d("registerDataChange photoUri 拿到bitmap")
|
||||
val bmp = BitmapCropper.cropCenter(bitmap, 1300, 900)
|
||||
// val bmp = BitmapCropper.cropCenter(bitmap, 1300, 900)
|
||||
Timber.d("registerDataChange photoUri bitmap裁剪完成")
|
||||
val file = BitmapSaver.saveToAppFilesDir(
|
||||
bmp,
|
||||
bitmap,
|
||||
this,
|
||||
"IMG_CROP_${System.currentTimeMillis()}.jpg"
|
||||
)
|
||||
Timber.d("registerDataChange photoUri bitmap保存文件路径:${file?.absolutePath}")
|
||||
val resultList = FoodModule.queryFood(bmp)
|
||||
val resultList = FoodModule.queryFood(bitmap)
|
||||
Timber.d("registerDataChange photoUri 拿到识别数据")
|
||||
val foodName = resultList.joinToString(separator = ",")
|
||||
Timber.d("registerDataChange photoUri 识别数据名称:$foodName")
|
||||
|
||||
@@ -33,7 +33,8 @@ class FoodCollectionAdapter (var list: MutableList<FoodCollectionBean>) :
|
||||
setImageResource(R.drawable.ic_camera256)
|
||||
} else {
|
||||
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(
|
||||
context: Context,
|
||||
var defWidth: Int = 400.dp,
|
||||
var defHeight: Int = 300.dp
|
||||
var defHeight: Int = 260.dp
|
||||
) : Dialog(context, R.style.DialogTheme) {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package com.sw.dualscreen.objbox
|
||||
|
||||
import com.sw.dualscreen.MyApp
|
||||
import com.sw.dualscreen.utils.AssetsTool
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import com.google.gson.Gson
|
||||
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.kotlin.boxFor
|
||||
import io.objectbox.query.Query
|
||||
@@ -19,34 +22,64 @@ import java.io.InputStream
|
||||
|
||||
object FoodModule {
|
||||
|
||||
private const val THRESHOLD = 0.8
|
||||
private lateinit var module_mobile: Module
|
||||
private lateinit var box: Box<Food>
|
||||
private lateinit var embeddingsList: List<List<Float>>
|
||||
private lateinit var labelsList: IntArray
|
||||
private lateinit var classInfo: FoodClassInfo
|
||||
private 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_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
|
||||
|
||||
fun init(context: Context) {
|
||||
module_mobile = Module.load(copyAssetToCache(context, "best_embedding_model_mobile.pt"))
|
||||
box = ObjectBox.boxStore.boxFor(Food::class)
|
||||
// if (box.all.isNotEmpty()) {
|
||||
// box.removeAll()
|
||||
// }
|
||||
// if (box.all.isEmpty()) {
|
||||
// initFoodData(context)
|
||||
// }
|
||||
Thread {
|
||||
module_mobile = Module.load(copyAssetToCache(context, "best_embedding_model_mobile.pt"))
|
||||
box = ObjectBox.boxStore.boxFor(Food::class)
|
||||
//if (box.all.isNotEmpty()) {
|
||||
// box.removeAll()
|
||||
//}
|
||||
//if (box.all.isEmpty()) {
|
||||
// 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(
|
||||
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()
|
||||
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
|
||||
if (bitmap.isRecycled.not()) {
|
||||
bitmap.recycle()
|
||||
}
|
||||
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 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)
|
||||
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)
|
||||
food.name?.let { key ->
|
||||
val count = map[key] ?: 0
|
||||
map.put(key, count + 1)
|
||||
}
|
||||
//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
|
||||
}
|
||||
}
|
||||
val list = map.entries.sortedByDescending { it.value }.map { it.key }
|
||||
return list
|
||||
}
|
||||
|
||||
data class IdNameScore(
|
||||
val id:Long,
|
||||
val name:String,
|
||||
val score: Double
|
||||
)
|
||||
|
||||
private fun initFoodData(context: Context) {
|
||||
if (box.all.isNotEmpty()) {
|
||||
fun initDefFoodData(context: Context, action:()-> Unit={}) {
|
||||
val count = box.all.count { it.foodIdx == DEFAULT_FOOD_INDEX }
|
||||
if (count > 0) {
|
||||
return
|
||||
}
|
||||
val embeddingsJson = AssetsTool.readAssetsFile(context, "data/embeddings.json")
|
||||
@@ -91,8 +135,9 @@ object FoodModule {
|
||||
val classIdx = labelsList[index]
|
||||
val foodName = foodMap["$classIdx"]
|
||||
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,
|
||||
quality: Int = 100
|
||||
): File? {
|
||||
val dir = context.getExternalFilesDir(null)
|
||||
//val dir = context.getExternalFilesDir(null)
|
||||
val dir = context.cacheDir
|
||||
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) {
|
||||
GlobalData.appBaseUrl = equipmentInfo.appPackageUrl!!
|
||||
GlobalData.appBaseUrl="http://192.168.1.210/gateway/local"
|
||||
GlobalData.sdkKey = equipmentInfo.arcsoftSdkKey!!
|
||||
GlobalData.appId = equipmentInfo.arcsoftAppId!!
|
||||
// GlobalData.activeKey = equipmentInfo.arcsoftActiveKey!!
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
android:layout_height="match_parent">
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="68dp"
|
||||
android:layout_height="88dp"
|
||||
android:layout_marginTop="24dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivBack"
|
||||
android:layout_width="50dp"
|
||||
android:layout_width="68dp"
|
||||
android:layout_height="68dp"
|
||||
android:padding="10dp"
|
||||
android:src="@drawable/ic_back" />
|
||||
|
||||
@@ -8,14 +8,15 @@
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="68dp"
|
||||
android:layout_height="88dp"
|
||||
android:layout_marginTop="24dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivBack"
|
||||
android:layout_width="50dp"
|
||||
android:layout_width="68dp"
|
||||
android:layout_height="68dp"
|
||||
android:padding="10dp"
|
||||
android:padding="15dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:src="@drawable/ic_back" />
|
||||
|
||||
<TextView
|
||||
@@ -31,7 +32,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="10dp">
|
||||
android:layout_marginBottom="24dp">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/flPreview"
|
||||
@@ -54,6 +55,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:background="@color/white"
|
||||
android:orientation="vertical"
|
||||
android:visibility="visible">
|
||||
|
||||
@@ -79,32 +81,32 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="20dp"
|
||||
android:orientation="vertical"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
<Button
|
||||
android:id="@+id/btnTakePhoto"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="140dp"
|
||||
android:layout_height="80dp"
|
||||
android:gravity="center"
|
||||
android:text="拍照"
|
||||
android:text="开始拍照"
|
||||
android:textColor="#ffffff"
|
||||
android:textSize="18sp" />
|
||||
<Button
|
||||
android:id="@+id/btnClearData"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="80dp"
|
||||
android:gravity="center"
|
||||
android:text="清除"
|
||||
android:layout_marginVertical="5dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:textColor="#ffffff"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnCollectedGoods"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="220dp"
|
||||
android:layout_height="80dp"
|
||||
android:gravity="center"
|
||||
android:text="查看已采集菜品"
|
||||
android:text="已采集菜品"
|
||||
android:textColor="#ffffff"
|
||||
android:textSize="18sp" />
|
||||
|
||||
@@ -125,9 +127,9 @@
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="66dp"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginRight="24dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
@@ -141,11 +143,11 @@
|
||||
android:gravity="center_vertical"
|
||||
android:hint="输入菜品名称"
|
||||
android:imeOptions="actionSearch"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:paddingStart="15dp"
|
||||
android:paddingEnd="15dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="18sp" />
|
||||
android:textSize="26sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnFoodSearch"
|
||||
@@ -154,7 +156,7 @@
|
||||
android:layout_marginLeft="12dp"
|
||||
android:text="菜品检索"
|
||||
android:textColor="#ffffff"
|
||||
android:textSize="16sp" />
|
||||
android:textSize="26sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
@@ -171,9 +173,9 @@
|
||||
android:layout_width="260dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginVertical="10dp"
|
||||
android:layout_marginVertical="24dp"
|
||||
android:text="保存"
|
||||
android:textColor="#ffffff"
|
||||
android:textSize="20sp" />
|
||||
android:textSize="26sp" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -2,15 +2,15 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="400dp"
|
||||
android:layout_height="200dp"
|
||||
android:layout_height="260dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/bg_white_radius12"
|
||||
tools:ignore="HardcodedText">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:src="@drawable/ic_tip_warn"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="30dp"
|
||||
android:layout_marginVertical="20dp"
|
||||
android:layout_marginVertical="30dp"
|
||||
android:gravity="center"
|
||||
tools:text="存在未收货的物品,请确认是否放弃收货,若放弃则数据不会保存?"
|
||||
android:textColor="#ff141428"
|
||||
android:textSize="18sp" />
|
||||
android:textSize="20sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
@@ -33,8 +33,8 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnCancel"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="50dp"
|
||||
android:background="@drawable/bg_white_stroke_blue_ripple"
|
||||
android:text="取消"
|
||||
android:gravity="center"
|
||||
@@ -43,8 +43,8 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnConfirm"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginStart="30dp"
|
||||
android:background="@drawable/bg_blue_ripple"
|
||||
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>
|
||||
Reference in New Issue
Block a user