From 2ee4d2d320634355c702083ae7ddb71bdeac7169 Mon Sep 17 00:00:00 2001 From: lvmeng <848755140@qq.com> Date: Thu, 5 Feb 2026 13:32:45 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BA=BA=E8=84=B8=E8=AF=86=E5=88=AB+=E9=87=87?= =?UTF-8?q?=E9=9B=86=E5=88=86=E5=BC=80=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../activity/LoginByFaceActivity.kt | 213 ++-- .../activity/LoginByFaceActivityBak.kt | 946 ++++++++++++++++++ .../com/sw/platecabinet/ext/TextViewExt.kt | 11 + .../main/res/layout/activity_login_face.xml | 40 + 4 files changed, 1146 insertions(+), 64 deletions(-) create mode 100644 app/src/main/java/com/sw/platecabinet/activity/LoginByFaceActivityBak.kt diff --git a/app/src/main/java/com/sw/platecabinet/activity/LoginByFaceActivity.kt b/app/src/main/java/com/sw/platecabinet/activity/LoginByFaceActivity.kt index 9b8df31..3c77d04 100644 --- a/app/src/main/java/com/sw/platecabinet/activity/LoginByFaceActivity.kt +++ b/app/src/main/java/com/sw/platecabinet/activity/LoginByFaceActivity.kt @@ -39,6 +39,9 @@ import com.sw.platecabinet.MyApp import com.sw.platecabinet.R import com.sw.platecabinet.databinding.ActivityLoginFaceBinding import com.sw.platecabinet.databinding.ItemTitleTimeBinding +import com.sw.platecabinet.ext.clickWithDebounce +import com.sw.platecabinet.ext.gone +import com.sw.platecabinet.ext.visible import com.sw.platecabinet.model.response.EquipmentUserInfo import com.sw.platecabinet.model.response.UserFaceModel import com.sw.platecabinet.utils.BitmapSaver @@ -148,7 +151,21 @@ class LoginByFaceActivity : BaseActivity(), soundMap[COLLECT_SUCCESS] = R.raw.collect_success soundMap[RECOGNIZE_SUCCESS] = R.raw.recognize_success soundMap[NO_PLATE_REMIND] = R.raw.no_plate_remind - SoundPoolUtil.getInstance().loadR(this, soundMap); + SoundPoolUtil.getInstance().loadR(this, soundMap) + + binding.btnCollect.clickWithDebounce { + //先重置,后采集 + resetRecognizeState() + //采集数据按钮 + retryFailCount = 0 + isCollectFace = true + collectFace() + } + + binding.btnRetry.clickWithDebounce { + //重新识别按钮 + resetRecognizeState() + } } @@ -187,6 +204,17 @@ class LoginByFaceActivity : BaseActivity(), private val regDebouncer = Debouncer(3000) private var registerHandler: RegisterCallbackHandler? = null + private fun isFitData(userFaceInfo: UserFaceInfo?): Boolean { + if (userFaceInfo == null) return false + val headBmp = userFaceInfo.headBmp + if (headBmp == null) return false + if (headBmp.width <= 200 || headBmp.height <= 200) { + loadLogInfo("collectFace,isFitData:拿到人脸提取特征数据照片过小-----------") + return false + } + return true + } + private fun initArcViewModel() { recognizeViewModel.setLiveType(livenessType) recognizeViewModel.prepareRegister() @@ -195,14 +223,10 @@ class LoginByFaceActivity : BaseActivity(), if (!isCollectFace) { return@setCallback } - if (this.userFaceInfo == null && userFaceInfo != null) { + if (this.userFaceInfo == null && isFitData(userFaceInfo)) { recognizeViewModel.updateRegisterStatus(RecognizeViewModel.REGISTER_STATUS_DONE) this.userFaceInfo = userFaceInfo - getFaceDataBlock { faceData, headBmp -> - isCollecting.set(false) - isCollectFace = false - sendFaceData(faceData, headBmp) - } + getFaceDataBlock() } } } @@ -247,11 +271,7 @@ class LoginByFaceActivity : BaseActivity(), recognizeViewModel.recognizeUserId.observe(this, Observer { result: CompareResult? -> loadLogInfo("collectFace,recognizeUserId observe userId = ${result?.faceEntity?.userName}") - if (faceUseSwitch) { - loadFaceRecognizeResult2(result) - return@Observer - } - loadFaceRecognizeResult(result) + loadFaceRecognizeResult3(result) }) recognizeViewModel.drawRectInfoText.observe(this, Observer { info -> @@ -288,6 +308,81 @@ class LoginByFaceActivity : BaseActivity(), private val openPlateDebouncer = Debouncer(5000) private val isCollecting = AtomicBoolean(false) + private fun loadFaceRecognizeResult3(result: CompareResult?) { + val similar = result?.similar ?: return + + binding.tvSimilarValue.text = "$similar,${retryFailCount}次" + loadLogInfo( + "collectFace,recognizeUserId 阈值 success=$faceSuccessThreshold, " + + "fail=$faceFailThreshold, similar=$similar, retry=$retryFailCount" + ) + if (similar >= faceSuccessThreshold) { + val userId = result.faceEntity?.userName + if (userId.isNullOrBlank()) { + loadLogInfo("collectFace,recognizeUserId 识别成功但 userId 为空,忽略") + return + } + + loadLogInfo( + "collectFace,recognizeUserId 识别成功 userId=$userId, time=${DateTimeUtils.getDateTimeString()}" + ) + + // 成功后必须重置状态 + retryFailCount = 0 + isCollecting.set(false) + isCollectFace = false + currentUserId = userId + + // 防抖,防止重复开闸 + openPlateDebouncer.debounce { + openPlate(userId) + } + return + } + + if (isCollectFace) { + loadLogInfo("collectFace,recognizeUserId 正在采集中,忽略失败结果 similar=$similar") + return + } + +// if (similar <= faceFailThreshold || retryFailCount >= maxFailCount) { + if (retryFailCount >= maxFailCount) { + loadLogInfo( + "collectFace,recognizeUserId 识别失败,开始采集 similar=$similar, " + + "retry=$retryFailCount, time=${DateTimeUtils.getDateTimeString()}" + ) + if (!isCollecting.compareAndSet(false, true)) { + loadLogInfo("collectFace 已在采集中,忽略重复触发") + return + } + + recognizeFail() + return + } + + retryFailCount++ + loadLogInfo("collectFace,recognizeUserId 中间态重试,retryFailCount=$retryFailCount") + } + + private fun recognizeFail() { + countDownTimer?.cancel() + binding.tvToInit.text = "" + binding.tvSimilarValue.text = "" + binding.llCollectButton.visible() + binding.btnCollect.run { + visible() + if (tag == "1") { + binding.btnRetry.gone() + text = "重新采集" + tag = "1" + } else { + binding.btnRetry.visible() + "新用户采集" + tag = "1" + } + } + } + /** * 根据相似度similar>设定阈值0.8作为识别成功判断, * 如果similar<0.3作为识别失败判断, @@ -352,38 +447,6 @@ class LoginByFaceActivity : BaseActivity(), loadLogInfo("collectFace,recognizeUserId 中间态重试,retryFailCount=$retryFailCount") } -// private fun loadFaceRecognizeResult2(result: CompareResult?) { -// -// if(isCollectFace){ -// return -// } -// -// val similar = result?.similar ?: 0f -// binding.tvSimilarValue.text = "$similar,${retryFailCount}次" -// loadLogInfo("collectFace,recognizeUserId 人脸识别配置阈值为:${faceSuccessThreshold},失败阈值为:${faceFailThreshold},相似度为:${similar},失败次数为:${retryFailCount}") -// if (similar > faceSuccessThreshold) { -// loadLogInfo("collectFace,recognizeUserId 相似度为:${similar},识别成功时间:${DateTimeUtils.getDateTimeString()}") -// //大 于识别阈值,作为识别成功判断 -// currentUserId = result?.faceEntity?.userName -// Debouncer(5000).debounce { openPlate(currentUserId) } -// return -// } -// loadLogInfo("collectFace,recognizeUserId 人脸识别相似度为:${similar},失败次数为:${retryFailCount}") -// if (similar < faceFailThreshold || retryFailCount >= maxFailCount) { -// //相似度低于识别错误阈值进行采集、识别失败次数大于等于最大失败次数,作为识别失败判断 -// if (isCollectFace.not()) { -// //非采集中 -// loadLogInfo("collectFace,recognizeUserId 未识别到人脸信息,相似度为:${similar},开始采集时间:${DateTimeUtils.getDateTimeString()}") -// isCollectFace = true -// collectFace() -// return -// } -// } -// //满足similar >= faceFailThreshold && similar <= successThreshold时进行重试 -// retryFailCount++ -// loadLogInfo("collectFace,recognizeUserId 人脸识别失败次数为:${retryFailCount}") -// } - /** * 根据相似度similar>设定阈值0.8作为识别是否成功来加载人脸识别结果 */ @@ -449,6 +512,9 @@ class LoginByFaceActivity : BaseActivity(), Debouncer(5000).debounce { //打开柜子 PlateUtils.open(this, item.equipmentBoxCode, false) + + countDownTimer?.cancel() + goInitActivity() } currentPlateInfo = item MyApp.plateList = items @@ -600,6 +666,9 @@ class LoginByFaceActivity : BaseActivity(), currentPlateInfo = null currentUserId = null userFaceInfo = null + binding.btnCollect.tag = "" + binding.tvSimilarValue.text = "" + retryFailCount = 0 } recognizeViewModel.clearLeftFace(facePreviewInfoList) } @@ -680,13 +749,13 @@ class LoginByFaceActivity : BaseActivity(), } } + /** * 临时用户id */ private var tempUserId: String = "" override fun onResume() { super.onResume() - registerHandler = RegisterCallbackHandler() try { if (isFirstOpen) { goInitActivity() @@ -695,26 +764,32 @@ class LoginByFaceActivity : BaseActivity(), }, 1000) return } - retryFailCount = 0 - binding.tvSimilarValue.text = "" - isCollecting.set(false) - isCollectFace = false - plateEnable = true - collectCount = 0 - tempUserId = "${System.currentTimeMillis()}" - resumeCamera() - userViewModel.resetUserInfo() - countDownTimer?.let { - it.cancel() - it.start() - } -// initFaceScheduler() -// checkFaceState() + resetRecognizeState() } catch (e: Exception) { e.printStackTrace() } } + /** + * 重置识别状态 + */ + private fun resetRecognizeState() { + binding.llCollectButton.gone() + retryFailCount = 0 + binding.tvSimilarValue.text = "" + isCollecting.set(false) + isCollectFace = false + plateEnable = true + collectCount = 0 + tempUserId = "${System.currentTimeMillis()}" + resumeCamera() + registerHandler = RegisterCallbackHandler() + countDownTimer?.let { + it.cancel() + it.start() + } + } + private var isFirstOpen = true protected override fun onPause() { @@ -742,7 +817,11 @@ class LoginByFaceActivity : BaseActivity(), } override fun onFinish() { - goInitActivity() + if (facePreviewInfoList.isNullOrEmpty()) { + goInitActivity() + } else { + recognizeFail() + } } } } @@ -813,16 +892,22 @@ class LoginByFaceActivity : BaseActivity(), } } - private fun getFaceDataBlock(block: (String, Bitmap) -> Unit) { - if (userFaceInfo == null || userFaceInfo?.faceFeature?.featureData == null) { + private fun getFaceDataBlock() { + if (userFaceInfo?.faceFeature?.featureData == null) { + loadLogInfo("collectFace,getFaceData: ----------7,拿到人脸提取特征数据为null-----------") return } val faceData: String? = Base64.encode(userFaceInfo?.faceFeature?.featureData) if (faceData.isNullOrBlank()) { + loadLogInfo("collectFace,getFaceData: ----------7,拿到人脸提取特征数据Base64编码为空") return } loadLogInfo("collectFace,getFaceData: ----------7,拿到人脸提取特征数据时间:${DateTimeUtils.getDateTimeString()}-----------") - block(faceData, userFaceInfo!!.headBmp) + val headBmp = userFaceInfo!!.headBmp + //block(faceData, headBmp) + isCollecting.set(false) + isCollectFace = false + sendFaceData(faceData, headBmp) } /** diff --git a/app/src/main/java/com/sw/platecabinet/activity/LoginByFaceActivityBak.kt b/app/src/main/java/com/sw/platecabinet/activity/LoginByFaceActivityBak.kt new file mode 100644 index 0000000..55ee620 --- /dev/null +++ b/app/src/main/java/com/sw/platecabinet/activity/LoginByFaceActivityBak.kt @@ -0,0 +1,946 @@ +//package com.sw.platecabinet.activity +// +//import android.Manifest +//import android.content.Intent +//import android.graphics.Bitmap +//import android.graphics.Point +//import android.hardware.Camera +//import android.os.Build +//import android.os.CountDownTimer +//import android.util.DisplayMetrics +//import android.view.View +//import android.view.ViewGroup +//import android.view.ViewTreeObserver +//import androidx.annotation.RequiresApi +//import androidx.lifecycle.Observer +//import com.arcsoft.face.ErrorInfo +//import com.google.gson.Gson +//import com.sw.inbound.utils.DateTimeUtils +//import com.sw.plate.utils.Base64 +//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.facedb.FaceDatabase +//import com.sw.plate.utils.arcface.facedb.entity.FaceEntity +//import com.sw.plate.utils.arcface.model.UserFaceInfo +//import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel +//import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel.REGISTER_STATUS_READY +//import com.sw.platecabinet.MyApp +//import com.sw.platecabinet.R +//import com.sw.platecabinet.databinding.ActivityLoginFaceBinding +//import com.sw.platecabinet.databinding.ItemTitleTimeBinding +//import com.sw.platecabinet.model.response.EquipmentUserInfo +//import com.sw.platecabinet.model.response.UserFaceModel +//import com.sw.platecabinet.utils.BitmapSaver +//import com.sw.platecabinet.utils.Debouncer +//import com.sw.platecabinet.utils.LogFileUtil +//import com.sw.platecabinet.utils.PermissionHelper +//import com.sw.platecabinet.utils.PlateUtils +//import com.sw.platecabinet.utils.RegisterCallbackHandler +//import com.sw.platecabinet.utils.SoundPoolUtil +//import com.sw.platecabinet.utils.SpTool +//import org.greenrobot.eventbus.EventBus +//import org.greenrobot.eventbus.Subscribe +//import org.greenrobot.eventbus.ThreadMode +//import timber.log.Timber +//import java.io.File +//import java.util.concurrent.atomic.AtomicBoolean +// +///** +// * 人脸识别 +// */ +//class LoginByFaceActivityBak : BaseActivity(), +// ViewTreeObserver.OnGlobalLayoutListener { +// // private val recognizeViewModel by viewModels() +// private var countDownTimer: CountDownTimer? = null +// +// private val CAMERA_PERMISSION_REQUEST_CODE = 100 +// private val REQUIRED_PERMISSIONS: Array = arrayOf( +// Manifest.permission.CAMERA, +// Manifest.permission.WRITE_EXTERNAL_STORAGE, +// Manifest.permission.READ_EXTERNAL_STORAGE +// ) +// +// override fun inflateViewBinding(): ActivityLoginFaceBinding { +// return ActivityLoginFaceBinding.inflate(layoutInflater) +// } +// +// override fun inflateTitleBinding(): ItemTitleTimeBinding { +// return binding.includeHeader +// } +// +// companion object { +// private const val TAG = "LoginByFaceActivity" +// private var instance: LoginByFaceActivityBak? = null +// +// private const val COLLECT_SUCCESS = "collect_success" +// private const val RECOGNIZE_SUCCESS = "recognize_success" +// private const val NO_PLATE_REMIND = "no_plate_remind"; +// +// fun goInitActivity() { +// instance?.goInitActivity() +// } +// } +// +//// private var logList: MutableList = mutableListOf() +//// private val logAdapter by lazy { LogAdapter(logList) } +// +// override fun initialize() { +// instance = this +// userViewModel.activeEngine() +//// addSocketListener() +// initCountTime() +// initArcViewModel() +// initArcView() +// openRectInfoDraw = true +// recognizeViewModel.setDrawRectInfoTextValue(true) +// //viewModel.generateToken() +// +//// binding.llToPwd.setOnClickListener { +//// val intent = Intent(this, LoginByPwdActivity::class.java) +//// startActivity(intent) +//// } +// +//// TaskManager.startTask() +// +// EventBus.getDefault().register(this) +// +//// binding.btnCollectFace.setOnClickListener { collectFace() } +// +// +//// binding.rvLogInfo.let { +//// it.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true) +//// it.adapter = logAdapter +//// } +//// +//// binding.btnLogState.let { +//// it.tag = false +//// it.setImageResource(R.drawable.ic_log_show) +//// it.setOnClickListener { v -> +//// binding.rvLogInfo.run { +//// val state = it.tag.toString().toBooleanStrictOrNull() ?: false +//// if (state) visible() else gone() +//// } +//// val state = it.tag.toString().toBooleanStrictOrNull() ?: false +//// it.tag = state +//// it.setImageResource( +//// if (state) R.drawable.ic_log_hide else R.drawable.ic_log_show +//// ) +//// } +//// } +// +// //开启人脸增量数据定时任务 +// startFaceTask { +// loadLogInfo("collectFace增量接口数据用户id:${Gson().toJson(it)}") +// } +// +// val soundMap = hashMapOf() +// soundMap[COLLECT_SUCCESS] = R.raw.collect_success +// soundMap[RECOGNIZE_SUCCESS] = R.raw.recognize_success +// soundMap[NO_PLATE_REMIND] = R.raw.no_plate_remind +// SoundPoolUtil.getInstance().loadR(this, soundMap); +// } +// +// +// private fun checkCameraPermission() { +// Timber.i("checkCameraPermission") +// val permissionHelper = +// PermissionHelper.with(this, REQUIRED_PERMISSIONS, CAMERA_PERMISSION_REQUEST_CODE) +// .onGranted { +// loadLogInfo("checkCameraPermission 权限已允许") +// // 权限已授予 +// recognizeViewModel.init() +// initRgbCamera() +// resumeCamera() +// } +// .build() +// registerPermissionHelper(permissionHelper) +// permissionHelper.checkAndRequest() +// } +// +// @Subscribe(threadMode = ThreadMode.MAIN) +// fun getFaceEntity(insertEntity: FaceEntity) { +// Timber.tag("performSync").e("-time=%s", insertEntity.registerTime) +// recognizeViewModel.addFace(insertEntity) +// Timber.tag("performSync over").e("-time=%s", insertEntity.registerTime) +// } +// +// override fun onLeftDoubleClick() { +// finish() +// } +// +// private var isRecognition = false +// private var rgbCameraHelper: DualCameraHelper? = null +// private var rgbFaceRectTransformer: FaceRectTransformer? = null +// private val livenessType = LivenessType.RGB +// private var openRectInfoDraw = false +// private val regDebouncer = Debouncer(3000) +// private var registerHandler: RegisterCallbackHandler? = null +// +// private fun initArcViewModel() { +// recognizeViewModel.setLiveType(livenessType) +// recognizeViewModel.prepareRegister() +// recognizeViewModel.setOnRegisterFinishedCallback { facePreviewInfo, userFaceInfo -> +// registerHandler?.setCallback { +// if (!isCollectFace) { +// return@setCallback +// } +// if (this.userFaceInfo == null && userFaceInfo != null) { +// recognizeViewModel.updateRegisterStatus(RecognizeViewModel.REGISTER_STATUS_DONE) +// this.userFaceInfo = userFaceInfo +// getFaceDataBlock { faceData, headBmp -> +// isCollecting.set(false) +// isCollectFace = false +// sendFaceData(faceData, headBmp) +// } +// } +// } +// } +// recognizeViewModel.ftInitCode.observe(this, Observer { ftInitCode: Int? -> +// if (ftInitCode != ErrorInfo.MOK) { +// val error: String? = getString( +// R.string.specific_engine_init_failed, "ftEngine", +// ftInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(ftInitCode!!) +// ) +// Timber.e("ftInitCode observe = $error") +// ToastUtils.showToast(error) +// } +// }) +// recognizeViewModel.frInitCode.observe(this, Observer { frInitCode: Int? -> +// if (frInitCode != ErrorInfo.MOK) { +// val error: String? = getString( +// R.string.specific_engine_init_failed, "frEngine", +// frInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(frInitCode!!) +// ) +// Timber.e("frInitCode observe = $error") +// ToastUtils.showToast(error) +// } +// }) +// recognizeViewModel.flInitCode.observe(this, Observer { flInitCode: Int? -> +// if (flInitCode != ErrorInfo.MOK) { +// val error: String? = getString( +// R.string.specific_engine_init_failed, "flEngine", +// flInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(flInitCode!!) +// ) +// Timber.e("flInitCode observe = $error") +// ToastUtils.showToast(error) +// } +// }) +// +// recognizeViewModel.recognizeConfiguration +// .observe(this, Observer { recognizeConfiguration: RecognizeConfiguration? -> +// Timber.i("recognizeConfiguration: observe = ${recognizeConfiguration.toString()}") +// }) +// recognizeViewModel.recognizeNotice.observe(this, Observer { notice: String? -> +// Timber.i("recognizeNotice observe notice = $notice") +// }) +// +// recognizeViewModel.recognizeUserId.observe(this, Observer { result: CompareResult? -> +// loadLogInfo("collectFace,recognizeUserId observe userId = ${result?.faceEntity?.userName}") +// if (faceUseSwitch) { +// loadFaceRecognizeResult2(result) +// return@Observer +// } +// loadFaceRecognizeResult(result) +// }) +// +// recognizeViewModel.drawRectInfoText.observe(this, Observer { info -> +// Timber.i("drawRectInfoText observe info = $info") +// }) +// } +// +// /** +// * 人脸识别开关,true-loadFaceRecognizeResult2,false-loadFaceRecognizeResult +// */ +// private var faceUseSwitch = true +// +// /** +// * 识别失败重试次数 +// */ +// private var retryFailCount = 0 +// +// /** +// * 识别失败最大次数 +// */ +// private val maxFailCount = 5 +// +// /** +// * 人脸失败阈值 +// */ +// private val faceFailThreshold = 0.3 +// +// /** +// * 配置阈值,默认值为0.8 +// */ +// private val faceSuccessThreshold by lazy { ConfigUtil.getRecognizeThreshold(this) } +// +// +// private val openPlateDebouncer = Debouncer(5000) +// private val isCollecting = AtomicBoolean(false) +// +// /** +// * 根据相似度similar>设定阈值0.8作为识别成功判断, +// * 如果similar<0.3作为识别失败判断, +// * 如果similar>=0.3&&similar<=0.8时,暂时不作为失败情况,再识别5次如果similar<0.8则作为识别失败处理 +// */ +// private fun loadFaceRecognizeResult2(result: CompareResult?) { +// // 无有效结果直接忽略 +// val similar = result?.similar ?: return +// +// binding.tvSimilarValue.text = "$similar,${retryFailCount}次" +// loadLogInfo( +// "collectFace,recognizeUserId 阈值 success=$faceSuccessThreshold, " + +// "fail=$faceFailThreshold, similar=$similar, retry=$retryFailCount" +// ) +// +// if (similar >= faceSuccessThreshold) { +// val userId = result.faceEntity?.userName +// if (userId.isNullOrBlank()) { +// loadLogInfo("collectFace,recognizeUserId 识别成功但 userId 为空,忽略") +// return +// } +// +// loadLogInfo( +// "collectFace,recognizeUserId 识别成功 userId=$userId, time=${DateTimeUtils.getDateTimeString()}" +// ) +// +// // 成功后必须重置状态 +// retryFailCount = 0 +// isCollecting.set(false) +// isCollectFace = false +// currentUserId = userId +// +// // 防抖,防止重复开闸 +// openPlateDebouncer.debounce { +// openPlate(userId) +// } +// return +// } +// +// if (isCollectFace) { +// loadLogInfo("collectFace,recognizeUserId 正在采集中,忽略失败结果 similar=$similar") +// return +// } +// +// if (similar <= faceFailThreshold || retryFailCount >= maxFailCount) { +// loadLogInfo( +// "collectFace,recognizeUserId 识别失败,开始采集 similar=$similar, " + +// "retry=$retryFailCount, time=${DateTimeUtils.getDateTimeString()}" +// ) +// if (!isCollecting.compareAndSet(false, true)) { +// loadLogInfo("collectFace 已在采集中,忽略重复触发") +// return +// } +// +// retryFailCount = 0 +// isCollectFace = true +// collectFace()//采集人脸 +// return +// } +// +// retryFailCount++ +// loadLogInfo("collectFace,recognizeUserId 中间态重试,retryFailCount=$retryFailCount") +// } +// +//// private fun loadFaceRecognizeResult2(result: CompareResult?) { +//// +//// if(isCollectFace){ +//// return +//// } +//// +//// val similar = result?.similar ?: 0f +//// binding.tvSimilarValue.text = "$similar,${retryFailCount}次" +//// loadLogInfo("collectFace,recognizeUserId 人脸识别配置阈值为:${faceSuccessThreshold},失败阈值为:${faceFailThreshold},相似度为:${similar},失败次数为:${retryFailCount}") +//// if (similar > faceSuccessThreshold) { +//// loadLogInfo("collectFace,recognizeUserId 相似度为:${similar},识别成功时间:${DateTimeUtils.getDateTimeString()}") +//// //大 于识别阈值,作为识别成功判断 +//// currentUserId = result?.faceEntity?.userName +//// Debouncer(5000).debounce { openPlate(currentUserId) } +//// return +//// } +//// loadLogInfo("collectFace,recognizeUserId 人脸识别相似度为:${similar},失败次数为:${retryFailCount}") +//// if (similar < faceFailThreshold || retryFailCount >= maxFailCount) { +//// //相似度低于识别错误阈值进行采集、识别失败次数大于等于最大失败次数,作为识别失败判断 +//// if (isCollectFace.not()) { +//// //非采集中 +//// loadLogInfo("collectFace,recognizeUserId 未识别到人脸信息,相似度为:${similar},开始采集时间:${DateTimeUtils.getDateTimeString()}") +//// isCollectFace = true +//// collectFace() +//// return +//// } +//// } +//// //满足similar >= faceFailThreshold && similar <= successThreshold时进行重试 +//// retryFailCount++ +//// loadLogInfo("collectFace,recognizeUserId 人脸识别失败次数为:${retryFailCount}") +//// } +// +// /** +// * 根据相似度similar>设定阈值0.8作为识别是否成功来加载人脸识别结果 +// */ +// private fun loadFaceRecognizeResult(result: CompareResult?) { +// val similar = result?.similar ?: 0f +// binding.tvSimilarValue.text = "$similar" +// val similarPass = result?.isSimilarPass ?: false +// if (similarPass.not()) { +// loadLogInfo("collectFace,recognizeUserId 未识别到人脸信息,开始采集") +// isCollectFace = true +// collectFace() +// return +// } +// currentUserId = result?.faceEntity?.userName +// if (tempUserId != currentUserId) { +// SoundPoolUtil.getInstance().play("success", 0) +// } +//// plateEnable = true +// Debouncer(5000).debounce { openPlate(currentUserId) } +// } +// +// /** +// * 查询餐盘数据 +// */ +// private fun getPlateData(block: (List) -> Unit) { +// MyApp.plateList?.let { +// block(it) +// } +// } +// +// private var currentPlateInfo: EquipmentUserInfo? = null +// private var plateEnable = false +// +// /** +// * 打开餐盘柜 +// */ +// private fun openPlate(userId: String?) { +// if (plateEnable.not()) return +// loadLogInfo("collectFace,openPlate: userId=$userId") +// getPlateData { items -> +// loadLogInfo("collectFace,openPlate: items:${Gson().toJson(items)}") +// // 未查询到当前用户,查询第一个未绑定的餐盘 +// val item = items.firstOrNull { it.plateNumber.isNullOrEmpty().not() } +// loadLogInfo("collectFace,openPlate: item2:${item}") +// if (item == null) { +// //暂无餐盘 +// loadLogInfo("collectFace,openPlate: 暂无餐盘,请联系管理人员,isCollectFace=$isCollectFace") +// //ToastUtils.showToast("暂无餐盘,请联系管理人员") +// SoundPoolUtil.getInstance().play(NO_PLATE_REMIND, 0) +// goInitActivity() +// return@getPlateData +// } +//// if (tempUserId != currentUserId) { +// SoundPoolUtil.getInstance().play(RECOGNIZE_SUCCESS, 0) +//// } else { +//// SoundPoolUtil.getInstance().play(COLLECT_SUCCESS, 0) +//// } +// loadLogInfo("collectFace,openPlate: item3:${item}") +// item.faceId = userId +// item.updateTime = DateTimeUtils.getDateTimeString() +// item.plateNumber = null +// plateEnable = false +// Debouncer(5000).debounce { +// //打开柜子 +// PlateUtils.open(this, item.equipmentBoxCode, false) +// } +// currentPlateInfo = item +// MyApp.plateList = items +// SpTool.savePlateData(items) +// } +// } +// +//// private val debouncer by lazy { Debouncer(3000) } +// +// private fun initArcView() { +// //在布局结束后才做初始化操作 +// binding.dualCameraTexturePreviewRgb.getViewTreeObserver().addOnGlobalLayoutListener(this) +// recognizeViewModel.getCompareResultList().getValue() +// } +// +// override fun onDestroy() { +// loadLogInfo("onDestroy") +// instance = null +// if (rgbCameraHelper != null) { +// rgbCameraHelper!!.release() +// rgbCameraHelper = null +// } +// +// recognizeViewModel.destroy() +// +// EventBus.getDefault().unregister(this) +// super.onDestroy() +// } +// +// +// /** +// * 调整View的宽高,使预览显示正常且采集框固定为380x380 +// * +// * @param rgbPreview RGB预览View +// * @param previewView 显示预览数据的view +// * @param faceRectView 画框的view (380x380) +// * @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() +// 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() +// } +// +// 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 +// ) { +// loadLogInfo("initRgbCamera Rgb onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation") +// runOnUiThread({ +// val previewSizeRgb = camera.getParameters().getPreviewSize() +// val layoutParams = adjustPreviewViewSize( +// binding.dualCameraTexturePreviewRgb, +// binding.dualCameraTexturePreviewRgb, binding.dualCameraFaceRectView, +// previewSizeRgb, displayOrientation, 0.6f +// ) +// loadLogInfo("initRgbCamera previewSizeRgb = ${previewSizeRgb.width}-${previewSizeRgb.height}") +// loadLogInfo("initRgbCamera layoutParams = ${layoutParams.width}-${layoutParams.height}") +// loadLogInfo( +// "initRgbCamera isMirror = ${isMirror}, isDrawRgbRectHorizontalMirror = ${ +// ConfigUtil.isDrawRgbRectHorizontalMirror( +// context +// ) +// }, isDrawRgbRectVerticalMirror = ${ +// ConfigUtil.isDrawRgbRectVerticalMirror( +// context +// ) +// }" +// ) +// rgbFaceRectTransformer = FaceRectTransformer( +// previewSizeRgb.width, +// previewSizeRgb.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() +// facePreviewInfoList = recognizeViewModel.onPreviewFrame(nv21, true) +// if (facePreviewInfoList != null && rgbFaceRectTransformer != null) { +// drawPreviewInfo(facePreviewInfoList) +// } +// if (facePreviewInfoList.isNullOrEmpty()) { +// currentPlateInfo = null +// currentUserId = null +// userFaceInfo = null +// } +// recognizeViewModel.clearLeftFace(facePreviewInfoList) +// } +// +// override fun onCameraClosed() { +// Timber.i("initRgbCamera onCameraClosed: ") +// } +// +// override fun onCameraError(e: java.lang.Exception) { +// Timber.i("initRgbCamera onCameraError: %s", e.message) +// e.printStackTrace() +// } +// +// override fun onCameraConfigurationChanged(cameraID: Int, displayOrientation: Int) { +// Timber.i("initRgbCamera onCameraConfigurationChanged: threadName = ${Thread.currentThread().name}") +// if (rgbFaceRectTransformer != null) { +// rgbFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation +// } +// Timber.i("initRgbCamera onCameraConfigurationChanged: $cameraID $displayOrientation") +// } +// } +// +// val previewConfig: PreviewConfig = recognizeViewModel.previewConfig +// rgbCameraHelper = DualCameraHelper.Builder() +// .previewViewSize( +// Point( +// binding.dualCameraTexturePreviewRgb.measuredWidth, +// binding.dualCameraTexturePreviewRgb.measuredHeight +// ) +// ) +// .rotation(windowManager.defaultDisplay.rotation) +// .additionalRotation(0) // 角度 +// .previewSize(recognizeViewModel.loadPreviewSize()) +// .specificCameraId(previewConfig.rgbCameraId) +// .isMirror(true) +// .previewOn(binding.dualCameraTexturePreviewRgb) +// .cameraListener(cameraListener) +// .build() +// rgbCameraHelper!!.init() +// rgbCameraHelper!!.start() +// } +// +// /** +// * 绘制RGB、IR画面的实时人脸信息 +// * +// * @param facePreviewInfoList RGB画面的实时人脸信息 +// */ +// private fun drawPreviewInfo(facePreviewInfoList: MutableList?) { +// if (rgbFaceRectTransformer != null) { +// val rgbDrawInfoList: MutableList? = recognizeViewModel.getDrawInfo( +// facePreviewInfoList, LivenessType.RGB, openRectInfoDraw +// ) +// // 识别成功 +// binding.dualCameraFaceRectView.drawRealtimeFaceInfo(rgbDrawInfoList) +// } +// } +// +// fun openRectInfoDraw(view: View?) { +// openRectInfoDraw = !openRectInfoDraw +// recognizeViewModel.setDrawRectInfoTextValue(openRectInfoDraw) +// } +// +// +// override fun onGlobalLayout() { +// loadLogInfo("onGlobalLayout") +// binding.dualCameraTexturePreviewRgb.getViewTreeObserver().removeOnGlobalLayoutListener(this) +// // 请求权限 +// checkCameraPermission() +// } +// +// private fun resumeCamera() { +// isRecognition = true +// if (rgbCameraHelper != null && rgbCameraHelper!!.isStopped) { +// loadLogInfo("collectFace,resumeCamera: ") +// rgbCameraHelper!!.start() +// } else { +// recognizeViewModel.onPreviewFrame(ByteArray(1382400), true) +// } +// } +// +// /** +// * 临时用户id +// */ +// private var tempUserId: String = "" +// override fun onResume() { +// super.onResume() +// registerHandler = RegisterCallbackHandler() +// try { +// if (isFirstOpen) { +// goInitActivity() +// binding.root.postDelayed({ +// isFirstOpen = false +// }, 1000) +// return +// } +// retryFailCount = 0 +// binding.tvSimilarValue.text = "" +// isCollecting.set(false) +// isCollectFace = false +// plateEnable = true +// collectCount = 0 +// tempUserId = "${System.currentTimeMillis()}" +// resumeCamera() +// userViewModel.resetUserInfo() +// countDownTimer?.let { +// it.cancel() +// it.start() +// } +//// initFaceScheduler() +//// checkFaceState() +// } catch (e: Exception) { +// e.printStackTrace() +// } +// } +// +// private var isFirstOpen = true +// +// protected override fun onPause() { +// pauseCamera() +// super.onPause() +// countDownTimer?.cancel() +// } +// +// private fun pauseCamera() { +// isRecognition = false +// recognizeViewModel.onPreviewFrame(ByteArray(1382400), true) +// } +// +// override fun handleLoginSuccess(equipmentUserInfo: EquipmentUserInfo, isAdmin: Boolean) { +// super.handleLoginSuccess(equipmentUserInfo, isAdmin) +//// goInitActivity() +// } +// +// private val totalTimeInMillis = 15 * 1000L +// fun initCountTime() { +// countDownTimer = object : CountDownTimer(totalTimeInMillis, 1000) { +// override fun onTick(millisUntilFinished: Long) { +// loadLogInfo("initCountTime onTick = ${(millisUntilFinished / 1000).toInt()}") +// binding.tvToInit.text = "${(millisUntilFinished / 1000).toInt() + 1}" +// } +// +// override fun onFinish() { +// goInitActivity() +// } +// } +// } +// +// fun goInitActivity() { +// val intent = Intent(context, InitActivity::class.java) +// startActivity(intent) +// } +// +// /** +// * 采集人脸 +// */ +// private fun collectFace() { +// loadLogInfo("collectFace: -----------1,开始采集人脸,开始时间:${DateTimeUtils.getDateTimeString()}----------") +// if (isFirstOpen) return +// loadLogInfo("collectFace: ----------2,isFirstOpen=false-----------") +// loadLogInfo("collectFace: ----------4,检测到人脸信息-----------") +// currentUserId = null +// userFaceInfo = null +// recognizeViewModel.updateRegisterStatus(REGISTER_STATUS_READY) +// loadLogInfo("collectFace: ----------5,人脸采集中-----------") +// } +// +// private val uploadFaceDebouncer = Debouncer(10000) +// +// /** +// * 改为上传人脸数据,不需要发生人脸数据到收银端 +// */ +// private fun sendFaceData(faceData: String, headBmp: Bitmap) { +// loadLogInfo("collectFace,sendFaceData: ----------11,发送人脸数据-----------") +// +// val imageFile = BitmapSaver.saveToAppFilesDir( +// headBmp, this, "IMG_CROP_${System.currentTimeMillis()}.jpg" +// ) +// loadLogInfo("collectFace,sendFaceData:${imageFile?.absolutePath}") +// if (imageFile == null) { +// ToastUtils.showToast("获取人脸照片失败") +// return +// } +// uploadFaceDebouncer.debounce { +// addUserFace(faceData, imageFile) +// } +//// // 给某个客户端发送 +//// val jsonObject = JSONObject().also { +//// it.put("type", "faceFeature") +//// it.put("content", faceData) +//// } +//// tcpClient?.send(jsonObject) +// } +// +// private fun addUserFace(faceData: String, imageFile: File) { +// settingViewModel.addUserFace(faceData, imageFile) { status, faceModel -> +// loadLogInfo("collectFace,saveFaceInfo: ----------12,人脸数据发送完成时间:${DateTimeUtils.getDateTimeString()},结果:$status-----------") +// if (status.not()) { +// ToastUtils.showToast("新增数据失败") +// loadLogInfo("collectFace,sendFaceData: ----------12,发送人脸数据失败,faceData=${faceData}") +// return@addUserFace +// } +// if (faceModel == null) { +// ToastUtils.showToast("新增数据成功,但未拿到接口数据") +// loadLogInfo("collectFace,sendFaceData: ----------12,发送人脸数据成功但接口返回为null,faceData=${faceData}") +// return@addUserFace +// } +// Thread { +// saveFaceInfo(faceModel) +// uploadFaceDebouncer.reset() +// }.start() +// } +// } +// +// private fun getFaceDataBlock(block: (String, Bitmap) -> Unit) { +// if (userFaceInfo == null || userFaceInfo?.faceFeature?.featureData == null) { +// return +// } +// val faceData: String? = Base64.encode(userFaceInfo?.faceFeature?.featureData) +// if (faceData.isNullOrBlank()) { +// return +// } +// loadLogInfo("collectFace,getFaceData: ----------7,拿到人脸提取特征数据时间:${DateTimeUtils.getDateTimeString()}-----------") +// block(faceData, userFaceInfo!!.headBmp) +// } +// +// /** +// * 加载日志 +// * @param msg 日志内容 +// */ +// private fun loadLogInfo(msg: String) { +// Timber.tag(TAG).d(msg) +// LogFileUtil.saveLog(msg) +// } +// +// private var facePreviewInfoList: MutableList? = null +// private var userFaceInfo: UserFaceInfo? = null +// +// private var currentUserId: String? = null +// +// private fun saveFaceInfo(faceModel: UserFaceModel) { +// try { +// loadLogInfo("collectFace,saveFaceInfo: ----------8,保存人脸取特征数据-----------") +// val faceDao = FaceDatabase.getInstance(this).faceDao() +// val faceEntity = FaceEntity( +// faceModel.userId, +// null, +// Base64.decode(faceModel.faceFeatureStr) +// ).also { +// //1-会员、2-临时用户 +// it.userType = if (faceModel.isMember) "1" else "2" +// } +// faceDao.insert(faceEntity) +// loadLogInfo("collectFace,saveFaceInfo: ----------10,人脸数据保存完成-----------") +// recognizeViewModel.refreshFaceList() +// loadLogInfo("collectFace,saveFaceInfo: ----------11,刷新人脸数据-----------") +// runOnUiThread { recognizeViewModel.onPreviewFrame(ByteArray(1382400), true) } +// loadLogInfo("---------------------------------") +// collectCount = 0 +// } catch (e: Exception) { +// e.printStackTrace() +// loadLogInfo("collectFace,saveFaceInfo: ----------13,人脸采集异常:${e.message}-----------") +// collectCount++ +// loadLogInfo("collectFace,saveFaceInfo: ----------14,${collectCount}次人脸采集异常:${e.message}-----------") +// ToastUtils.showToast("人脸采集异常:${e.message}") +// } +// } +// +// private var collectCount = 0 +// +//// private var tcpClient: TcpClient? = null +//// +//// private fun addSocketListener() { +////// val ipAddress = NetworkUtils.getIPAddress(true) +////// loadLogInfo( "addSocketListener,ipAddress: $ipAddress"); +//// tcpClient = TcpClient( +//// "192.168.1.95", +//// 5000, +//// GlobalData.deviceId, // clientId +//// 5000, // connectTimeoutMs +//// 10 * 1000, // heartbeatIntervalMs +//// 30 * 1000 // heartbeatTimeoutMs +//// ) +//// +//// tcpClient?.setListener(object : TcpClientListenerImpl() { +//// override fun onSendSuccess(json: JSONObject?) { +//// super.onSendSuccess(json) +//// loadLogInfo("collectFace,TcpClientListener发送成功:$json") +////// toast("发送成功") +//// } +//// +//// override fun onSendFailed(json: JSONObject?, e: java.lang.Exception?) { +//// super.onSendFailed(json, e) +//// ToastUtils.showToast("发送失败${e?.message}") +//// loadLogInfo("collectFace,TcpClientListener人脸数据发送失败:${e?.message}") +//// } +//// +//// override fun onMessage(json: JSONObject?) { +//// super.onMessage(json) +//// loadLogInfo("collectFace,TcpClientListener收到消息:$json") +//// val type = json?.getInt("type") +//// // 根据服务端消息类型处理删除新增临时用户人脸数据--------------------- +//// when (type) { +//// LanServer.TYPE_ADD_FACE_DATA -> { +//// //采集设备id自己生成,不接收服务端的,在本地只判断是否存在同一人脸数据 +//// loadLogInfo("collectFace,TcpClientListener收到消息,新增用户:采集设备id自己生成,不接收服务端的,在本地只判断是否存在同一人脸数据") +//// } +//// +//// LanServer.TYPE_CLEAR_FACE_DATA -> { +//// clearFaceData() +//// } +//// +//// else -> {} +//// } +//// } +//// }) +//// tcpClient?.start() +//// +//// try { +//// LanServer.getInstance().let { +//// it.setListener(object : LanServerListenerImpl() { +//// override fun onMessageReceived(clientId: String?, message: JSONObject?) { +//// super.onMessageReceived(clientId, message) +//// ToastUtils.showToast("收到消息clientId=$clientId") +//// } +//// }) +//// it.start() +//// } +//// } catch (e: Exception) { +//// e.printStackTrace() +//// } +//// } +// +// private fun clearFaceData() { +// Thread { +// val faceDao = FaceDatabase.getInstance(this).faceDao() +// faceDao.deleteTempUserFaceData() +// recognizeViewModel.refreshFaceList() +// loadLogInfo("collectFace,TcpClientListener收到消息,已删除并刷新人脸数据") +// }.start() +// } +// +// private var isCollectFace = false +// +//} \ No newline at end of file diff --git a/app/src/main/java/com/sw/platecabinet/ext/TextViewExt.kt b/app/src/main/java/com/sw/platecabinet/ext/TextViewExt.kt index 451199a..6347da0 100644 --- a/app/src/main/java/com/sw/platecabinet/ext/TextViewExt.kt +++ b/app/src/main/java/com/sw/platecabinet/ext/TextViewExt.kt @@ -69,4 +69,15 @@ fun View.clickWithCoroutines( lastClickTime = currentTime } } +} + +fun View.clickWithDebounce(delay: Long = 500, action: () -> Unit) { + var job: Job? = null + setOnClickListener { + job?.cancel() + job = CoroutineScope(Dispatchers.Main).launch { + delay(delay) + action() + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login_face.xml b/app/src/main/res/layout/activity_login_face.xml index ac9a973..91f4c90 100644 --- a/app/src/main/res/layout/activity_login_face.xml +++ b/app/src/main/res/layout/activity_login_face.xml @@ -105,6 +105,46 @@ android:textSize="36sp" tools:text="30" /> + + + + + + + +