package com.sw.dualscreen.presentation import android.Manifest import android.app.Presentation import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.Outline import android.graphics.Point import android.hardware.Camera import android.os.Build import android.os.Bundle import android.view.Display import android.view.View import android.view.ViewGroup import android.view.ViewOutlineProvider import android.view.ViewTreeObserver import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat import androidx.core.view.updateLayoutParams import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope import com.arcsoft.face.ErrorInfo import com.sw.dualscreen.GlobalKey import com.sw.dualscreen.MyApp import com.sw.dualscreen.R import com.sw.dualscreen.activity.MainActivity import com.sw.dualscreen.databinding.PresentationMainScreenBinding import com.sw.dualscreen.ext.dp import com.sw.dualscreen.ext.format2String import com.sw.dualscreen.ext.gone import com.sw.dualscreen.ext.load import com.sw.dualscreen.ext.maskName import com.sw.dualscreen.ext.visible import com.sw.dualscreen.model.response.DinnerType import com.sw.dualscreen.model.response.FoodInfo import com.sw.dualscreen.model.response.UserEnergy import com.sw.dualscreen.model.response.UserNutrition import com.sw.dualscreen.utils.Debouncer import com.sw.dualscreen.utils.SPUtil import com.sw.dualscreen.viewmodel.UserViewModel import com.sw.plate.utils.LightManager import com.sw.plate.utils.ToastUtils import com.sw.plate.utils.arcface.ConfigUtil import com.sw.plate.utils.arcface.ErrorCodeUtil import com.sw.plate.utils.arcface.FaceRectTransformer import com.sw.plate.utils.arcface.FaceRectView import com.sw.plate.utils.arcface.FaceRectView.DrawInfo import com.sw.plate.utils.arcface.PreviewConfig import com.sw.plate.utils.arcface.camera.CameraListener import com.sw.plate.utils.arcface.camera.DualCameraHelper import com.sw.plate.utils.arcface.face.constants.LivenessType import com.sw.plate.utils.arcface.face.model.CompareResult import com.sw.plate.utils.arcface.face.model.FacePreviewInfo import com.sw.plate.utils.arcface.face.model.RecognizeConfiguration import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel import kotlinx.coroutines.flow.drop import kotlinx.coroutines.launch import timber.log.Timber import kotlin.math.max import kotlin.math.roundToInt class MainScreenPresentation( val activity: MainActivity, display: Display, val userViewModel: UserViewModel, val recognizeViewModel: RecognizeViewModel, private val onDismissListener: () -> Unit = {} ) : Presentation(activity, display), ViewTreeObserver.OnGlobalLayoutListener { companion object { const val TAG = "SubScreenPresentation" } private lateinit var binding: PresentationMainScreenBinding private lateinit var stepChangeCallback: (Int) -> Unit // 当前步骤 var currentStep: Int = 0 private var isGoStep1 = false;//防止持续调用 // 虹软人脸配置 ⬇ private var isRecognition = false private var rgbCameraHelper: DualCameraHelper? = null private var rgbFaceRectTransformer: FaceRectTransformer? = null private val livenessType = LivenessType.IR private var openRectInfoDraw = false private var irCameraHelper: DualCameraHelper? = null private var irFaceRectTransformer: FaceRectTransformer? = null private var detectWeight = 0.0 //识别菜品时的重量 var lastWeight = 0.0 // 上一次的计算热量结果 // private var userNutritionData: UserNutritionData? = null private var userNutrition: UserNutrition? = null private var dinnerTypeInfo: DinnerType? = null private val debouncer = Debouncer(500) private var recognitionTime: Long = 0L // 人脸识别时的时间 var recognitionWeight: Double = 0.0 // 人脸识别时的重量 private var lastFaceTrackId: Int = -1 // 上一次的人脸信息 var mealPickupMode: Int = 0 // 取餐模式 0 即放即取 1 余量取餐 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = PresentationMainScreenBinding.inflate(layoutInflater) setContentView(binding.root) mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0 window?.setBackgroundDrawableResource(android.R.color.white) initView() setupArcCamera() registerDataChange() } private var isFirstGetWeight = true fun updateWeight(weight: Double) { lastWeight = weight * 1000 // 将千克转成克 Timber.tag(TAG) .d("updateWeight lastWeight = $weight, currentStep = $currentStep, isGoStep1 = $isGoStep1") mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0 val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0) if (mealPickupMode == 1 && chargeMode == 0) { //余量计量+计费模式 if (isFirstGetWeight && lastWeight > 5) { recognitionWeight = lastWeight isFirstGetWeight = false } if (lastWeight > 5) { //余量计量,有重量的情况下不回到重新识别状态 return } isFirstGetWeight = true } if (currentStep == 2 && lastWeight <= 5 && isGoStep1) { //人脸识别过程中,秤上物品拿走 isGoStep1 = false activity.runOnUiThread { Timber.tag(TAG).e("切回step1") step1FoodRecognizing() } } else if (lastWeight <= 5) { isGoStep1 = false activity.runOnUiThread { Timber.tag(TAG).e("切回step1") step1FoodRecognizing() } } // if (currentStep != 3) { // return // } if (currentFood == null) { Timber.tag(TAG).e("updateWeight currentFood is null") return } //if (userNutritionData == null) { if (userNutrition == null) { Timber.tag(TAG).e("updateWeight userNutritionData is null") return } if (dinnerTypeInfo == null) { Timber.tag(TAG).e("updateWeight dinnerType is null") return } if (chargeMode == 1) { //不计费 if (mealPickupMode == 0) { //即放即取 calculateNutrition(lastWeight) } else { //余量计量 calculateNutrition(recognitionWeight - lastWeight) } } } fun setStepChangeCallback(callback: (Int) -> Unit) { stepChangeCallback = callback } override fun onGlobalLayout() { Timber.tag(TAG).d("onGlobalLayout") binding.dualCameraTexturePreviewRgb.getViewTreeObserver().removeOnGlobalLayoutListener(this) openCamera() } private fun openCamera() { Timber.tag(TAG).d("openCamera") if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED ) { ToastUtils.showToast("无摄像头权限") return } try { val cameraCount = Camera.getNumberOfCameras() if (cameraCount < 3) { ToastUtils.showToast("摄像头数量异常") return } recognizeViewModel.init( PreviewConfig( 2, 1, 90, 90 ) ) initRgbCamera() if (DualCameraHelper.hasDualCamera() && livenessType === LivenessType.IR) { initIrCamera() } } catch (e: Exception) { Timber.tag(TAG).e(e, "打开摄像头失败") } } /** * 启用虹软人脸失败 */ private fun setupArcCamera() { initArcViewModel() initArcView() openRectInfoDraw = true } private fun registerDataChange() { activity.lifecycleScope.launch { userViewModel.loadFaceResult.collect { needUpdate -> if (needUpdate) { recognizeViewModel.refreshFaceList() } } } // activity.lifecycleScope.launch { // userViewModel.nutritionData.collect { // Timber.tag(TAG).d("registerDataChange nutritionData = $it") // userNutritionData = it // if (it == null) return@collect //// step3ShowRecognizeResult() // //binding.nutritionInclude.tvUserName.text = it.userName.maskName() // //binding.nutritionInclude.tvRecommendHeat.text = "推荐热量:${it.recommendMin}-${it.recommendMax}" //// //updateWeight(lastWeight / 1000) // } // } activity.lifecycleScope.launch { userViewModel.dinnerTypeInfo.drop(1).collect { Timber.tag(TAG).d("registerDataChange dinnerTypeInfo = $it") dinnerTypeInfo = it } } } /** * 显示识别出的菜品信息 */ fun step3ShowRecognizeResult() { Timber.tag(TAG).d("step3") LightManager.closeRedLight() LightManager.openGreenLight() currentStep = 3 stepChangeCallback(currentStep) if (currentFood == null) { ToastUtils.showToast("选中物品为空") return } // updateFoodInfo(currentFood!!) //foodRecSuccess(currentFood!!) } private fun initView() { binding.ivRecImage.let { it.outlineProvider = object : ViewOutlineProvider() { override fun getOutline(view: View, outline: Outline) { outline.setRoundRect(0, 0, view.width, view.height, 12f.dp) } } it.clipToOutline = true } } override fun onDisplayRemoved() { super.onDisplayRemoved() onDismissListener() } /** * 加载待机页面 */ fun loadStandbyScreen() { binding.llStandbyPage.visible() } fun hideStandbyScreen() { binding.llStandbyPage.gone() } /** * 餐品识别成功,计费模式 */ fun loadBilledMode() { binding.calorieInclude.root.visible() binding.nutritionInclude.root.gone() binding.flCameraView.visible() binding.ivFaceRecMask.gone() binding.tvFaceTip.gone() binding.tvFoodRecPrompt.gone() binding.llPriceInfo.visible() //binding.ivRecImage.gone() //updateFoodInfo(foodInfo) // userNutritionData?.let { // Timber.tag(TAG).d("loadBilledMode:${GsonUtils.toJson(it)}") // } currentFood?.let { val specPrice = it.specPrice ?: 0.0 val vipPrice = it.vipPrice ?: 0.0 binding.tvNormalPrice.text = "${specPrice.format2String(2)} 元/份" binding.tvVipPrice.text = "${vipPrice.format2String(2)} 元/份" var calorie = getIntNutritionValue(it.calorie) calorie = max(calorie, 0) val fat = getIntNutritionValue(it.fat) val protein = getIntNutritionValue(it.protein) val carbohydrate = getIntNutritionValue(it.carbohydrate) val fatRate = getNutritionRate(fat, calorie) val proteinRate = getNutritionRate(protein, calorie) val carbohydrateRate = getNutritionRate(carbohydrate, calorie) binding.calorieInclude.run { tvCalorie.text = "$calorie" tvFatRate.text = "$fatRate%" tvProteinRate.text = "$proteinRate%" tvCarbohydrateRate.text = "$carbohydrateRate%" viewFatLine.updateLayoutParams { height = (fatRate / 100.0 * 84).roundToInt() * 1.dp } viewProteinLine.updateLayoutParams { height = (proteinRate / 100.0 * 84).roundToInt() * 1.dp } viewCarbohydrateLine.updateLayoutParams { height = (carbohydrateRate / 100.0 * 84).roundToInt() * 1.dp } } } } private fun getNutritionRate(value: Int, total: Int): Int { if (value == 0 || total == 0) return 0 return (100.0 * value / total).roundToInt() } private fun getIntNutritionValue(num: Double?): Int { return num?.roundToInt() ?: 0 } /** * 餐品识别成功,不计费模式 */ fun loadUnbilledMode() { binding.calorieInclude.root.gone() binding.nutritionInclude.root.visible() binding.flCameraView.visible() binding.ivFaceRecMask.gone() binding.tvFaceTip.gone() binding.tvFoodRecPrompt.gone() binding.llPriceInfo.gone() //binding.ivRecImage.gone() //updateFoodInfo(foodInfo) binding.nutritionInclude.tvUserName.text = userNutrition?.name.maskName() binding.nutritionInclude.tvRecommendHeat.text = "推荐热量:${userNutrition?.recommendCalorie.format2String(2)}kcal" //updateWeight(lastWeight / 1000) // ------------------------------------------------- // 删除userNutritionData,改为userNutrition calculateNutrition(recognitionWeight - lastWeight) } /** * 餐品识别中 */ fun step1FoodRecognizing() { hideStandbyScreen() activity.resetTouchTime() activity.resumeAnalysis() Timber.tag(TAG).d("step1") //if (abs(lastWeight.toInt()) <= 5) { // activity.runOnUiThread { // ToastUtils.showToast("秤上重量几乎为0,请确认") // } // return //} currentStep = 1 LightManager.closeGreenLight() LightManager.closeRedLight() dinnerTypeInfo = null currentFood = null // userNutritionData = null userNutrition = null recognitionWeight = 0.0 stepChangeCallback(currentStep) binding.calorieInclude.root.gone() binding.nutritionInclude.root.gone() binding.tvFoodName.text = "餐品识别中..." binding.tvFoodRecPrompt.visible() binding.flCameraView.visible() binding.ivRecImage.gone() binding.ivPreviewImage.visible() binding.ivFaceRecMask.gone() binding.tvFaceTip.gone() binding.llPriceInfo.gone() binding.flFace.gone() binding.flRecognizeIr.gone() pauseCamera() activity.clearFoodList() } // fun foodRecSuccess(foodInfo: FoodInfo) { // //识别逻辑完成根据是否计费显示不同页面 // val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0) // if (mode == 0) { // //计费 // loadBilledMode() // } else { // //不计费 // //userViewModel.getUserNutritionData( // // userId = userId, // // foodId = currentFood!!.foodId!! // //) // loadUnbilledMode() // } // } /** * 用户人脸识别中 */ fun step2FaceRecognizing(foodInfo: FoodInfo) { isGoStep1 = true Timber.tag(TAG).d("step2") currentStep = 2 LightManager.closeRedLight() binding.calorieInclude.root.gone() binding.nutritionInclude.root.gone() binding.tvFoodName.text = foodInfo.foodName binding.flFace.visible() binding.flRecognizeIr.visible() binding.flCameraView.visible() binding.ivFaceRecMask.visible() binding.tvFaceTip.visible() binding.tvFoodRecPrompt.visible() binding.llPriceInfo.gone() binding.ivRecImage.gone() binding.ivPreviewImage.gone() dinnerTypeInfo = null // userNutritionData = null userNutrition = null recognitionWeight = 0.0 stepChangeCallback(currentStep) currentFood = foodInfo userViewModel.getDinnerType() resumeCamera() } // 当前食物信息 private var currentFood: FoodInfo? = null fun updateFood(foodInfo: FoodInfo?) { Timber.tag(TAG).d("updateFood") currentFood = foodInfo if (foodInfo == null) { return } binding.tvFoodName.text = foodInfo.foodName binding.ivPreviewImage.gone() binding.ivRecImage.let { it.visible() val imageUrl = if (foodInfo.foodImg.isNullOrBlank()) foodInfo.photoUri else foodInfo.foodImg it.load(imageUrl) } val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0) if (mode == 0) { //计费,查询营养信息、价格信息------------------------ loadBilledMode() } else { //不计费 //开启人脸识别 if (currentStep == 1) { step2FaceRecognizing(foodInfo) } } //if (currentStep == 1) { // step2FaceRecognizing(foodInfo) //} else { // updateFoodInfo(foodInfo) //} } private fun updateFoodInfo(foodInfo: FoodInfo) { // binding.tvFoodName.text = foodInfo.foodName // binding.ivPreviewImage.gone() //binding.ivRecImage.visible() // if (foodInfo.photoUri != null) { // GlideUtils.loadRoundCornerImage( // context, // url = foodInfo.photoUri, // imageView = //binding.ivRecImage, // radius = 12 // ) // } else { // GlideUtils.loadRoundCornerImage( // context, // url = foodInfo.imgUrl, // imageView = //binding.ivRecImage, // radius = 12 // ) // } } private fun calculateNutrition(weight1: Double) { var weight = weight1 if (weight < 0) weight = 0.0 debouncer.debounce { Timber.tag(TAG) .d("calculateNutrition weight = $weight, recognitionWeight = $recognitionWeight") val dinnerType = dinnerTypeInfo!!.dinnerType!! // Timber.tag(TAG) // .d("calculateNutrition foodName = ${currentFood!!.foodName}, dinnerType = $dinnerType, userId = ${userNutritionData!!.userId}") val energy: UserEnergy = UserNutritionUtils.calculateNutrition2( currentFood!!, // userNutritionData!!, userNutrition!!, weight, dinnerType = dinnerType ) // Timber.tag(TAG).d("calculateNutrition calcResultInfo = $calcResultInfo") // val columnMax = UserNutritionUtils.getMaxInt(calcResultInfo) var columnMax = max(energy.grain, energy.fruitsVegetables) columnMax = max(columnMax, energy.meatEggs) activity.runOnUiThread { val totalKcal = energy.calorie val grain = energy.grain val fruitsVegetables = energy.fruitsVegetables val meatEggs = energy.meatEggs val include = binding.nutritionInclude // 设置热量--------------------------------------------------------------------- val calorieArray = doubleArrayOf( 0.0, userNutrition?.minCalorie?:0.0, userNutrition?.recommendCalorie?:0.0, ((userNutrition?.recommendCalorie?:0.0) + (userNutrition?.maxCalorie?:0.0))/2, userNutrition?.maxCalorie?:0.0, ) val calorieIndex = UserNutritionUtils.findCalorieIndex(energy.grain, calorieArray) include.customKcalColumn.setImageDrawable(true, calorieIndex) var maxKcal = userNutrition?.maxCalorie?:1.0 Timber.tag(TAG).d("maxKcal = $maxKcal") // 避免0作为被除数 if (maxKcal <= 0.0) { maxKcal = 1.0 } Timber.tag(TAG).d("maxKcal 1 = $maxKcal") include.totalKcalTv.text = totalKcal.format2String(2) var totalKcalHeight = (totalKcal / maxKcal).toFloat() if (totalKcalHeight == 0F) { include.divKcalView.visibility = View.VISIBLE } else { include.divKcalView.visibility = View.GONE totalKcalHeight += 0.1F } include.customKcalColumn.setCustomHeightPercent(totalKcalHeight, true) // 设置主食----------------------------------------------------------- val grainArray = UserNutritionUtils.getCalorieArray( left = userNutrition?.stapleFoodRecommend, mid = userNutrition?.stapleFoodNearExcess, right = userNutrition?.stapleFoodExcess ) val grainIndex = UserNutritionUtils.findCalorieIndex(energy.grain, grainArray) include.customFoodColumn.setImageDrawable(false, grainIndex) include.totalFoodTv.text = grain.format2String(2) var grainHeight = (grain / columnMax).toFloat() if (grainHeight == 0F) { include.divTotalFoodView.visibility = View.VISIBLE } else { include.divTotalFoodView.visibility = View.GONE grainHeight += 0.1F } include.customFoodColumn.setCustomHeightPercent(grainHeight, true) // 设置果蔬------------------------------------------------------------- val fruitsArray = UserNutritionUtils.getCalorieArray( left = userNutrition?.fruitsVegetablesRecommend, mid = userNutrition?.fruitsVegetablesNearExcess, right = userNutrition?.fruitsVegetablesExcess ) val fruitsIndex = UserNutritionUtils.findCalorieIndex(energy.fruitsVegetables, fruitsArray) include.customVegetableColumn.setImageDrawable(false, fruitsIndex) include.totalVegetableTv.text = fruitsVegetables.format2String(2) var fruitsHeight = (fruitsVegetables / columnMax).toFloat() if (fruitsHeight == 0F) { include.divVegetableView.visibility = View.VISIBLE } else { include.divVegetableView.visibility = View.GONE fruitsHeight += 0.1F } include.customVegetableColumn.setCustomHeightPercent(fruitsHeight, true) // 设置肉蛋----------------------------------------------------------- val meatEggsArray = UserNutritionUtils.getCalorieArray( left = userNutrition?.meatEggsRecommend, mid = userNutrition?.meatEggsNearExcess, right = userNutrition?.meatEggsExcess ) val meatEggsIndex = UserNutritionUtils.findCalorieIndex(energy.meatEggs, meatEggsArray) include.customMeatColumn.setImageDrawable(false, meatEggsIndex) include.totalMeatTv.text = meatEggs.format2String(2) var meatHeight = (meatEggs / columnMax).toFloat() if (meatHeight == 0F) { include.divMeatView.visibility = View.VISIBLE } else { include.divMeatView.visibility = View.GONE meatHeight += 0.1F } include.customMeatColumn.setCustomHeightPercent(meatHeight, true) } } } fun updateImage(bitmap: Bitmap) { activity.runOnUiThread { binding.ivPreviewImage.setImageBitmap(bitmap) } } /** * 余量取餐 */ fun updateMealPickupMode(mode: Int) { Timber.tag(TAG).d("updateMealPickupMode mode = $mode") mealPickupMode = mode step1FoodRecognizing() } override fun onStop() { if (rgbCameraHelper != null) { rgbCameraHelper!!.release() rgbCameraHelper = null } if (irCameraHelper != null) { irCameraHelper!!.release() irCameraHelper = null } recognizeViewModel.destroy() super.onStop() } fun resumeCamera() { Timber.tag(TAG).d("resumeCamera isRecognition = $isRecognition") isRecognition = true if (rgbCameraHelper != null && rgbCameraHelper!!.isStopped) { rgbCameraHelper!!.start() } } fun pauseCamera() { Timber.tag(TAG).d("pauseCamera isRecognition = $isRecognition") isRecognition = false recognizeViewModel.onPreviewFrame(ByteArray(1382400), true) } private fun initArcViewModel() { recognizeViewModel.setLiveType(livenessType) recognizeViewModel.ftInitCode.observe(activity, Observer { ftInitCode: Int? -> if (ftInitCode != ErrorInfo.MOK) { val error: String? = context.getString( R.string.specific_engine_init_failed, "ftEngine", ftInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(ftInitCode!!) ) Timber.tag(TAG).e("ftInitCode observe = $error") ToastUtils.showToast(error) } }) recognizeViewModel.frInitCode.observe(activity, Observer { frInitCode: Int? -> if (frInitCode != ErrorInfo.MOK) { val error: String? = context.getString( R.string.specific_engine_init_failed, "frEngine", frInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(frInitCode!!) ) Timber.tag(TAG).e("frInitCode observe = $error") ToastUtils.showToast(error) } }) recognizeViewModel.flInitCode.observe(activity, Observer { flInitCode: Int? -> if (flInitCode != ErrorInfo.MOK) { val error: String? = context.getString( R.string.specific_engine_init_failed, "flEngine", flInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(flInitCode!!) ) Timber.tag(TAG).e("flInitCode observe = $error") ToastUtils.showToast(error) } }) recognizeViewModel.recognizeConfiguration .observe(activity, Observer { recognizeConfiguration: RecognizeConfiguration? -> Timber.tag(TAG) .i("recognizeConfiguration: observe = ${recognizeConfiguration.toString()}") }) recognizeViewModel.recognizeNotice.observe(activity, Observer { notice: String? -> Timber.tag(TAG).i("recognizeNotice observe notice = $notice") }) recognizeViewModel.recognizeUserId.observe( activity, Observer { compareResult: CompareResult -> Timber.tag(TAG) .i("recognizeUserId observe compareResult = ${compareResult.trackId}, userId = ${compareResult.faceEntity.userName}") recognitionTime = System.currentTimeMillis() recognitionWeight = lastWeight lastFaceTrackId = compareResult.trackId val faceEntity = compareResult.faceEntity val userId = faceEntity.userName if (userId == null) return@Observer //ToastUtils.showToast("人脸识别成功,userId = $userId") if (currentFood == null) { currentFood = activity.checkedItem } if (currentFood == null) { activity.runOnUiThread { ToastUtils.showToast("请选择菜品") return@runOnUiThread } } step3ShowRecognizeResult() //根据接口数据更新热量数据-------------------------- userViewModel.getUserNutritionData(userId = userId) { nutrition -> activity.runOnUiThread { if (nutrition == null) { ToastUtils.showToast("未查询到营养数据") return@runOnUiThread } this@MainScreenPresentation.userNutrition = nutrition loadUnbilledMode() // ------------------------------- } } }) recognizeViewModel.drawRectInfoText.observe(activity, Observer { info -> Timber.tag(TAG).i("drawRectInfoText observe info = $info") }) } private fun initArcView() { //在布局结束后才做初始化操作 binding.dualCameraTexturePreviewRgb.getViewTreeObserver().addOnGlobalLayoutListener(this) recognizeViewModel.getCompareResultList().getValue() } /** * 调整View的宽高,使预览显示正常且采集框固定为 * * @param rgbPreview RGB预览View * @param previewView 显示预览数据的view * @param faceRectView 画框的view * @param previewSize 预览大小 * @param displayOrientation 相机旋转角度 * @param scale 缩放比例 * @return 调整后的LayoutParams */ private fun adjustPreviewViewSize( rgbPreview: View, previewView: View, faceRectView: FaceRectView, previewSize: Camera.Size, displayOrientation: Int, scale: Float ): ViewGroup.LayoutParams { // val layoutParams = previewView.layoutParams // val measuredWidth = previewView.measuredWidth // val measuredHeight = previewView.measuredHeight // var ratio = (previewSize.height.toFloat()) / previewSize.width.toFloat() // if (ratio > 1) { // ratio = 1 / ratio // } // if (displayOrientation % 180 == 0) { // layoutParams.width = measuredWidth // layoutParams.height = (measuredWidth * ratio).toInt() // } else { // layoutParams.height = measuredHeight // layoutParams.width = (measuredHeight * ratio).toInt() // } // if (scale < 1f) { // val rgbParam = rgbPreview.getLayoutParams() // layoutParams.width = (rgbParam.width * scale).toInt() // layoutParams.height = (rgbParam.height * scale).toInt() // } else { // layoutParams.width = (layoutParams.width * scale).toInt() // layoutParams.height = (layoutParams.height * scale).toInt() // } // // val metrics = DisplayMetrics() // activity.windowManager.defaultDisplay.getMetrics(metrics) // // if (layoutParams.width >= metrics.widthPixels) { // val viewRatio = layoutParams.width / (metrics.widthPixels.toFloat()) // layoutParams.width = (layoutParams.width / viewRatio).toInt() // layoutParams.height = (layoutParams.height / viewRatio).toInt() // } // if (layoutParams.height >= metrics.heightPixels) { // val viewRatio = layoutParams.height / (metrics.heightPixels.toFloat()) // layoutParams.width = (layoutParams.width / viewRatio).toInt() // layoutParams.height = (layoutParams.height / viewRatio).toInt() // } val layoutParams = previewView.layoutParams // layoutParams.width = (608.dp * 1.5).roundToInt() // layoutParams.height = (456.dp * 1.5).roundToInt() // layoutParams.width = (600.dp * 1.5).roundToInt() // layoutParams.height = (450.dp * 1.5).roundToInt() layoutParams.width = 1024 layoutParams.height = 768 previewView.setLayoutParams(layoutParams) faceRectView.setLayoutParams(layoutParams) return layoutParams } private fun initRgbCamera() { val cameraListener: CameraListener = object : CameraListener { override fun onCameraOpened( camera: Camera, cameraId: Int, displayOrientation: Int, isMirror: Boolean ) { Timber.tag(TAG) .d("initRgbCamera Rgb onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation") activity.runOnUiThread({ val previewSizeRgb = camera.getParameters().getPreviewSize() val layoutParams = adjustPreviewViewSize( binding.dualCameraTexturePreviewRgb, binding.dualCameraTexturePreviewRgb, binding.dualCameraFaceRectView, previewSizeRgb, displayOrientation, 0.6F ) Timber.tag(TAG) .d("initRgbCamera previewSizeRgb = ${previewSizeRgb.width}-${previewSizeRgb.height}") Timber.tag(TAG) .d("initRgbCamera layoutParams = ${layoutParams.width}-${layoutParams.height}") Timber.tag(TAG).d( "initRgbCamera isMirror = ${isMirror}, isDrawRgbRectHorizontalMirror = ${ ConfigUtil.isDrawRgbRectHorizontalMirror( context ) }, isDrawRgbRectVerticalMirror = ${ ConfigUtil.isDrawRgbRectVerticalMirror( context ) }" ) // 调整识别窗口位置 rgbFaceRectTransformer = FaceRectTransformer( // previewSizeRgb.width, // previewSizeRgb.height, layoutParams.width, layoutParams.height, layoutParams.width, layoutParams.height, 90, cameraId, isMirror, true, true ) recognizeViewModel.onRgbCameraOpened(camera) recognizeViewModel.setRgbFaceRectTransformer(rgbFaceRectTransformer) }) } @RequiresApi(api = Build.VERSION_CODES.Q) override fun onPreview(nv21: ByteArray?, camera: Camera?) { if (!isRecognition) { return } binding.dualCameraFaceRectView.clearFaceInfo() val facePreviewInfoList: MutableList? = recognizeViewModel.onPreviewFrame(nv21, true) if (facePreviewInfoList != null && rgbFaceRectTransformer != null) { drawPreviewInfo(facePreviewInfoList) } recognizeViewModel.clearLeftFace(facePreviewInfoList) } override fun onCameraClosed() { Timber.tag(TAG).i("initRgbCamera onCameraClosed: ") } override fun onCameraError(e: java.lang.Exception) { Timber.tag(TAG).i("initRgbCamera onCameraError: %s", e.message) e.printStackTrace() } override fun onCameraConfigurationChanged(cameraID: Int, displayOrientation: Int) { Timber.tag(TAG) .i("initRgbCamera onCameraConfigurationChanged: threadName = ${Thread.currentThread().name}") if (rgbFaceRectTransformer != null) { rgbFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation } Timber.tag(TAG) .i("initRgbCamera onCameraConfigurationChanged: $cameraID $displayOrientation") } } val measuredWidth = binding.dualCameraTexturePreviewRgb.measuredWidth val measuredHeight = binding.dualCameraTexturePreviewRgb.measuredHeight Timber.tag(TAG) .i("initRgbCamera measuredWidth=$measuredWidth,measuredHeight=$measuredHeight") val previewConfig: PreviewConfig = recognizeViewModel.previewConfig rgbCameraHelper = DualCameraHelper.Builder() .previewViewSize(Point(measuredWidth, measuredHeight)) .rotation(activity.windowManager.defaultDisplay.rotation) .additionalRotation(previewConfig.rgbAdditionalDisplayOrientation) // 角度 .previewSize(recognizeViewModel.loadPreviewSize()) .specificCameraId(previewConfig.rgbCameraId) .isMirror(true) .previewOn(binding.dualCameraTexturePreviewRgb) .cameraListener(cameraListener) .build() rgbCameraHelper!!.init() // rgbCameraHelper!!.start() } /** * 初始化红外相机,若活体检测类型是可见光活体检测或不启用活体,则不需要启用 */ private fun initIrCamera() { Timber.tag(TAG).d("initIrCamera: livenessType = $livenessType") if (livenessType === LivenessType.RGB) { return } val irCameraListener: CameraListener = object : CameraListener { override fun onCameraOpened( camera: Camera, cameraId: Int, displayOrientation: Int, isMirror: Boolean ) { Timber.tag(TAG) .d("initIrCamera IR onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation") val previewSizeIr = camera.getParameters().getPreviewSize() val layoutParams = adjustPreviewViewSize( binding.dualCameraTexturePreviewRgb, binding.dualCameraTexturePreviewIr, binding.dualCameraFaceRectViewIr, previewSizeIr, displayOrientation, 0.25f ) irFaceRectTransformer = FaceRectTransformer( // previewSizeIr.width, previewSizeIr.height, layoutParams.width, layoutParams.height, layoutParams.width, layoutParams.height, displayOrientation, cameraId, isMirror, ConfigUtil.isDrawIrRectHorizontalMirror(context), ConfigUtil.isDrawIrRectVerticalMirror(context) ) recognizeViewModel.onIrCameraOpened(camera) recognizeViewModel.setIrFaceRectTransformer(irFaceRectTransformer) } override fun onPreview(nv21: ByteArray?, camera: Camera?) { recognizeViewModel.refreshIrPreviewData(nv21) } override fun onCameraClosed() { Timber.tag(TAG).i("initIrCamera onCameraClosed: ") } override fun onCameraError(e: java.lang.Exception) { Timber.tag(TAG).i("initIrCamera onCameraError: ${e.message}") e.printStackTrace() } override fun onCameraConfigurationChanged( cameraID: Int, displayOrientation: Int ) { if (irFaceRectTransformer != null) { irFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation } Timber.tag(TAG) .i("initIrCamera onCameraConfigurationChanged: cameraID = $cameraID, displayOrientation = $displayOrientation") } } val previewConfig = recognizeViewModel.previewConfig irCameraHelper = DualCameraHelper.Builder() .previewViewSize( Point( binding.dualCameraTexturePreviewIr.measuredWidth, binding.dualCameraTexturePreviewIr.measuredHeight ) ) .rotation(activity.windowManager.defaultDisplay.rotation) .specificCameraId(previewConfig.irCameraId) .previewOn(binding.dualCameraTexturePreviewIr) .cameraListener(irCameraListener) .isMirror(true) .previewSize(recognizeViewModel.loadPreviewSize()) //相机预览大小设置,RGB与IR需使用相同大小 .additionalRotation(previewConfig.irAdditionalDisplayOrientation) //额外旋转角度 .build() irCameraHelper!!.init() try { irCameraHelper!!.start() } catch (e: RuntimeException) { ToastUtils.showToast(e.message + context.getString(R.string.camera_error_notice)) } } /** * 绘制RGB、IR画面的实时人脸信息 * * @param facePreviewInfoList RGB画面的实时人脸信息 */ private fun drawPreviewInfo(facePreviewInfoList: MutableList) { // Timber.tag(TAG).d("drawPreviewInfo facePreviewInfoList = ${facePreviewInfoList.size}, rgbFaceRectTransformer = ${rgbFaceRectTransformer != null}") if (rgbFaceRectTransformer != null) { val rgbDrawInfoList: MutableList? = recognizeViewModel.getDrawInfo( facePreviewInfoList, LivenessType.RGB, openRectInfoDraw ) // 识别成功 binding.dualCameraFaceRectView.drawRealtimeFaceInfo(rgbDrawInfoList) } val listIsEmpty = facePreviewInfoList.isEmpty() val listFirstTrackId = if (listIsEmpty.not()) facePreviewInfoList[0]!!.trackId else null Timber.tag(TAG) .d("listIsEmpty=$listIsEmpty,lastFaceTrackId=$lastFaceTrackId,listFirstTrackId=$listFirstTrackId") if (listIsEmpty || (lastFaceTrackId != listFirstTrackId)) { if (lastFaceTrackId != -1) { mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0 Timber.tag(TAG).i("$lastFaceTrackId 用户离开") lastFaceTrackId = -1 postUserData() if (mealPickupMode == 0) { activity.isAnalyzing = false step1FoodRecognizing() } else { step2FaceRecognizing(currentFood!!) } } } } fun postUserData() { Timber.tag(TAG).d("postUserData") if (userNutrition == null || currentFood == null) { Timber.tag(TAG) .d("postUserData userNutritionData = ${userNutrition == null}, currentFood = ${currentFood == null}") return } createOrder() } fun createOrder() { mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0 val eatWeight = if (mealPickupMode == 0) { //即放即取 recognitionWeight } else { recognitionWeight - lastWeight } //val userNutritionParam = UserNutritionParam( // userId = userNutritionData?.userId!!, // foodId = currentFood?.foodId!!, // faceTime = recognitionTime, // faceEndTime = System.currentTimeMillis(), // eatWeight = eatWeight,//lastWeight // foodWeight = lastWeight //) //userViewModel.postUserNutritionData(listOf(userNutritionParam)) activity.createOrder( foodInfo = currentFood!!, foodWeight = lastWeight.roundToInt(), eatWeight = eatWeight.roundToInt() ) { if (MyApp.DEBUG) { ToastUtils.showToast("订单已生成") } } } }