识别成功未提示及采集时间长问题优化

This commit is contained in:
2026-02-28 10:03:45 +08:00
parent e979322697
commit cc88dccaa6
5 changed files with 386 additions and 262 deletions
@@ -6,13 +6,14 @@ 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.core.view.isVisible
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import com.arcsoft.face.ErrorInfo
import com.google.gson.Gson
import com.sw.inbound.utils.DateTimeUtils
@@ -27,6 +28,7 @@ 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.constants.RecognizeColor
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
@@ -45,6 +47,8 @@ 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
import com.sw.platecabinet.utils.CountDownManager
import com.sw.platecabinet.utils.CountDownUtil
import com.sw.platecabinet.utils.Debouncer
import com.sw.platecabinet.utils.LogFileUtil
import com.sw.platecabinet.utils.PermissionHelper
@@ -57,6 +61,7 @@ import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import timber.log.Timber
import java.io.File
import java.util.Arrays
import java.util.concurrent.atomic.AtomicBoolean
/**
@@ -64,8 +69,6 @@ import java.util.concurrent.atomic.AtomicBoolean
*/
class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
ViewTreeObserver.OnGlobalLayoutListener {
// private val recognizeViewModel by viewModels<RecognizeViewModel>()
private var countDownTimer: CountDownTimer? = null
private val CAMERA_PERMISSION_REQUEST_CODE = 100
private val REQUIRED_PERMISSIONS: Array<String> = arrayOf(
@@ -101,47 +104,14 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
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)}")
@@ -159,6 +129,7 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
//采集数据按钮
retryFailCount = 0
isCollectFace = true
isCollecting.set(true)
collectFace()
}
@@ -202,6 +173,8 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
private val livenessType = LivenessType.RGB
private var openRectInfoDraw = false
private val regDebouncer = Debouncer(3000)
private val leftDebouncer = Debouncer(30*1000)
private var registerHandler: RegisterCallbackHandler? = null
private fun isFitData(userFaceInfo: UserFaceInfo?): Boolean {
@@ -220,7 +193,7 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
recognizeViewModel.prepareRegister()
recognizeViewModel.setOnRegisterFinishedCallback { facePreviewInfo, userFaceInfo ->
registerHandler?.setCallback {
if (!isCollectFace) {
if (!isCollectFace || !isCollecting.get()) {
return@setCallback
}
if (this.userFaceInfo == null && isFitData(userFaceInfo)) {
@@ -312,7 +285,6 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
*/
private val faceSuccessThreshold by lazy { ConfigUtil.getRecognizeThreshold(this) }
private val openPlateDebouncer = Debouncer(5000)
private val isCollecting = AtomicBoolean(false)
@@ -326,29 +298,11 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
)
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)
}
recognizeSuccess(userId)
return
}
if (isCollectFace) {
if (isCollectFace || isCollecting.get()) {
loadLogInfo("collectFace,recognizeUserId 正在采集中,忽略失败结果 similar=$similar")
return
}
@@ -372,8 +326,30 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
loadLogInfo("collectFace,recognizeUserId 中间态重试,retryFailCount=$retryFailCount")
}
private fun recognizeSuccess(userId: String?) {
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)
}
}
private fun recognizeFail() {
countDownTimer?.cancel()
CountDownManager.cancelCountDown()
binding.tvToInit.text = ""
binding.tvSimilarValue.text = ""
binding.llCollectButton.visible()
@@ -381,103 +357,9 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
binding.btnCollect.run {
visible()
text = "新用户采集"
// if (tag == "1") {
// binding.btnRetry.gone()
// text = "重新采集"
// tag = "1"
// } else {
// binding.btnRetry.visible()
// "新用户采集"
// tag = "1"
// }
}
}
/**
* 根据相似度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")
}
/**
* 根据相似度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) }
}
/**
* 查询餐盘数据
*/
@@ -524,7 +406,7 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
PlateUtils.open(this, item.equipmentBoxCode, false) {
goInitActivity()
}
countDownTimer?.cancel()
CountDownManager.cancelCountDown()
}
currentPlateInfo = item
MyApp.plateList = items
@@ -679,6 +561,10 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
binding.btnCollect.tag = ""
binding.tvSimilarValue.text = ""
retryFailCount = 0
if (binding.llCollectButton.isVisible) {
leftDebouncer.debounce { leftCountDown() }
}
}
recognizeViewModel.clearLeftFace(facePreviewInfoList)
}
@@ -750,15 +636,45 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
}
private fun resumeCamera() {
isRecognition = true
// isRecognition = true
if (rgbCameraHelper != null && rgbCameraHelper!!.isStopped) {
loadLogInfo("collectFace,resumeCamera: ")
isRecognition = true
rgbCameraHelper!!.start()
} else {
recognizeViewModel.onPreviewFrame(ByteArray(1382400), true)
//recognizeViewModel.onPreviewFrame(ByteArray(1382400), true)
recognizeViewModel.onPreviewFrame(emptyFrame, true)
binding.dualCameraFaceRectView.clearFaceInfo()
recognizeViewModel.clearLeftFace(facePreviewInfoList)
binding.root.postDelayed({
isRecognition = true
}, 500)
}
}
/**
* 构造符合分辨率的 NV21 格式全黑帧
* @param width 图像宽度(如 1920
* @param height 图像高度(如 1080
* @return 合法的全黑帧字节数组
*/
fun createBlackNV21Frame(width: Int, height: Int): ByteArray {
val frameSize = width * height
val nv21Data = ByteArray(frameSize * 3 / 2) // NV21 格式长度 = 宽×高×1.5
// Y 分量(亮度)设为 0(全黑),UV 分量设为 128(默认值)
// 1. 填充 Y 分量(前 frameSize 个字节)
Arrays.fill(nv21Data, 0, frameSize, 0.toByte())
// 2. 填充 UV 分量(后 frameSize/2 个字节)
Arrays.fill(nv21Data, frameSize, nv21Data.size, 128.toByte())
return nv21Data
}
// 调用示例:构造 1920×1080 的全黑帧(长度 1382400
val emptyFrame = createBlackNV21Frame(1920, 1080)
// 传递“空帧” recognizeViewModel.onPreviewFrame(emptyFrame, true)
/**
* 临时用户id
@@ -784,6 +700,7 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
* 重置识别状态
*/
private fun resetRecognizeState() {
binding.tvToInit.text = "15"
binding.llCollectButton.gone()
retryFailCount = 0
binding.tvSimilarValue.text = ""
@@ -794,10 +711,26 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
tempUserId = "${System.currentTimeMillis()}"
resumeCamera()
registerHandler = RegisterCallbackHandler()
countDownTimer?.let {
it.cancel()
it.start()
}
CountDownManager.startCountDown(
totalSeconds = 15,
onTick = { remaining ->
loadLogInfo("resetRecognizeState剩余时间:$remaining")
if (judgeRecognizeState()) return@startCountDown
loadLogInfo("initCountTime onTick = $remaining")
binding.tvToInit.text = "$remaining"
},
onFinish = {
loadLogInfo("resetRecognizeState倒计时结束")
if (judgeRecognizeState()) return@startCountDown
if (facePreviewInfoList.isNullOrEmpty()) {
goInitActivity()
} else {
loadLogInfo("recognizeFail---initCountTime")
recognizeFail()
}
}
)
}
private var isFirstOpen = true
@@ -805,12 +738,12 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
protected override fun onPause() {
pauseCamera()
super.onPause()
countDownTimer?.cancel()
CountDownManager.cancelCountDown()
}
private fun pauseCamera() {
isRecognition = false
recognizeViewModel.onPreviewFrame(ByteArray(1382400), true)
// recognizeViewModel.onPreviewFrame(emptyFrame, true)
}
override fun handleLoginSuccess(equipmentUserInfo: EquipmentUserInfo, isAdmin: Boolean) {
@@ -818,22 +751,17 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
// 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() {
if (facePreviewInfoList.isNullOrEmpty()) {
goInitActivity()
} else {
recognizeFail()
}
private fun judgeRecognizeState(): Boolean {
val rectColor = binding.dualCameraFaceRectView.rectColor
if (rectColor == RecognizeColor.COLOR_SUCCESS && currentUserId.isNullOrBlank().not() && facePreviewInfoList.isNullOrEmpty().not()) {
//人未离开,再次识别
loadLogInfo("userId=$currentUserId")
openPlateDebouncer.debounce {
openPlate(currentUserId)
}
return true
}
return false
}
fun goInitActivity() {
@@ -874,12 +802,6 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
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) {
@@ -950,7 +872,7 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
loadLogInfo("collectFace,saveFaceInfo: ----------10,人脸数据保存完成-----------")
recognizeViewModel.refreshFaceList()
loadLogInfo("collectFace,saveFaceInfo: ----------11,刷新人脸数据-----------")
runOnUiThread { recognizeViewModel.onPreviewFrame(ByteArray(1382400), true) }
runOnUiThread { recognizeViewModel.onPreviewFrame(emptyFrame, true) }
loadLogInfo("---------------------------------")
collectCount = 0
} catch (e: Exception) {
@@ -964,78 +886,25 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
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
private fun leftCountDown() {
CountDownUtil.startCountDown(total = 15, lifecycleScope = lifecycleScope, onStart = {
loadLogInfo("onPreview-----------leftCountDown-------------")
}, onTick = {
loadLogInfo("leftCountDown.onTick:$it")
if (facePreviewInfoList.isNullOrEmpty().not()) {
loadLogInfo("leftCountDown.onTick-cancel()")
CountDownUtil.cancelCountDown()
return@startCountDown
}
}, onFinish = {
if (facePreviewInfoList.isNullOrEmpty().not()) {
loadLogInfo("leftCountDown.onFinish-cancel()")
CountDownUtil.cancelCountDown()
return@startCountDown
}
CountDownUtil.cancelCountDown()
goInitActivity()
})
}
}