946 lines
36 KiB
Kotlin
946 lines
36 KiB
Kotlin
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 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(
|
||
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: LoginByFaceActivity? = 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<String> = 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<String, Int>()
|
||
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<EquipmentUserInfo>) -> 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<FacePreviewInfo>?) {
|
||
if (rgbFaceRectTransformer != null) {
|
||
val rgbDrawInfoList: MutableList<DrawInfo>? = 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<FacePreviewInfo>? = 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
|
||
|
||
} |