|
|
@@ -171,10 +171,13 @@ object FaceUtil {
|
|
|
// —— 并发门闸 —— //
|
|
|
// 预览阶段:防止 onPreview 并发进入 ARC 检测(丢帧式)
|
|
|
private val detectGate = Mutex()
|
|
|
+
|
|
|
// ARC 引擎生命周期 & 注册/比对串行
|
|
|
private val arcLock = Mutex()
|
|
|
+
|
|
|
// HLK 验证流程串行
|
|
|
private val hlkLock = Mutex()
|
|
|
+
|
|
|
// 相机生命周期原子化
|
|
|
private val cameraLock = ReentrantLock()
|
|
|
|
|
|
@@ -389,7 +392,8 @@ object FaceUtil {
|
|
|
lastFaceRectByHlk = rect
|
|
|
lastAliveByHlk = true
|
|
|
ThreadUtils.runOnMain {
|
|
|
- faceOverlayView?.setFaceRect(rect?.let { listOf(it) } ?: emptyList())
|
|
|
+ faceOverlayView?.setFaceRect(rect?.let { listOf(it) }
|
|
|
+ ?: emptyList())
|
|
|
}
|
|
|
},
|
|
|
onLiveness = { _ -> lastAliveByHlk = true },
|
|
|
@@ -425,13 +429,24 @@ object FaceUtil {
|
|
|
defaultScope.launch(Dispatchers.Main) {
|
|
|
faceOverlayView?.setFaceRect(rects)
|
|
|
}
|
|
|
- val inCenter = rects.firstOrNull()?.isInCenterArea(p.width, p.height) ?: false
|
|
|
+ val inCenter =
|
|
|
+ rects.firstOrNull()?.isInCenterArea(p.width, p.height) ?: false
|
|
|
if (!inCenter) maybeLogNoFaceTip()
|
|
|
|
|
|
if (shouldEmit(lastInitCbTs)) {
|
|
|
ioScope.launch {
|
|
|
- val bmp = ImageConvertUtils.nv21ToBitmap(imageData, p.width, p.height)
|
|
|
- ThreadUtils.runOnMain { callBack(bmp, rects.size, lastAliveByHlk) }
|
|
|
+ val bmp = ImageConvertUtils.nv21ToBitmap(
|
|
|
+ imageData,
|
|
|
+ p.width,
|
|
|
+ p.height
|
|
|
+ )
|
|
|
+ ThreadUtils.runOnMain {
|
|
|
+ callBack(
|
|
|
+ bmp,
|
|
|
+ rects.size,
|
|
|
+ lastAliveByHlk
|
|
|
+ )
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -454,26 +469,50 @@ object FaceUtil {
|
|
|
val fe = faceEngine ?: return@launch
|
|
|
val faces = mutableListOf<FaceInfo>()
|
|
|
|
|
|
- var code = fe.detectFaces(nv21, p.width, p.height, FaceEngine.CP_PAF_NV21, faces)
|
|
|
+ var code =
|
|
|
+ fe.detectFaces(nv21, p.width, p.height, FaceEngine.CP_PAF_NV21, faces)
|
|
|
withContext(Dispatchers.Main) { faceOverlayView?.setFaceRect(faces.map { it.rect }) }
|
|
|
if (code != ErrorInfo.MOK || faces.isEmpty()) {
|
|
|
maybeLogNoFaceTip(); return@launch
|
|
|
}
|
|
|
|
|
|
- code = fe.process(nv21, p.width, p.height, FaceEngine.CP_PAF_NV21, faces, processMask)
|
|
|
+ code = fe.process(
|
|
|
+ nv21,
|
|
|
+ p.width,
|
|
|
+ p.height,
|
|
|
+ FaceEngine.CP_PAF_NV21,
|
|
|
+ faces,
|
|
|
+ processMask
|
|
|
+ )
|
|
|
if (code != ErrorInfo.MOK) return@launch
|
|
|
|
|
|
val liveList = mutableListOf<LivenessInfo>()
|
|
|
if (fe.getLiveness(liveList) != ErrorInfo.MOK) return@launch
|
|
|
val alive = liveList.any { it.liveness == LivenessInfo.ALIVE }
|
|
|
if (!alive) {
|
|
|
- if (shouldEmit(lastInitCbTs)) ThreadUtils.runOnMain { callBack(null, faces.size, false) }
|
|
|
+ if (shouldEmit(lastInitCbTs)) ThreadUtils.runOnMain {
|
|
|
+ callBack(
|
|
|
+ null,
|
|
|
+ faces.size,
|
|
|
+ false
|
|
|
+ )
|
|
|
+ }
|
|
|
return@launch
|
|
|
}
|
|
|
- if (needCheckCenter && !faces[0].rect.isInCenterArea(p.width, p.height)) return@launch
|
|
|
+ if (needCheckCenter && !faces[0].rect.isInCenterArea(
|
|
|
+ p.width,
|
|
|
+ p.height
|
|
|
+ )
|
|
|
+ ) return@launch
|
|
|
|
|
|
if (shouldEmit(lastInitCbTs)) {
|
|
|
- val bmp = withContext(Dispatchers.IO) { ImageConvertUtils.nv21ToBitmap(nv21, p.width, p.height) }
|
|
|
+ val bmp = withContext(Dispatchers.IO) {
|
|
|
+ ImageConvertUtils.nv21ToBitmap(
|
|
|
+ nv21,
|
|
|
+ p.width,
|
|
|
+ p.height
|
|
|
+ )
|
|
|
+ }
|
|
|
ThreadUtils.runOnMain { callBack(bmp, faces.size, true) }
|
|
|
}
|
|
|
} catch (e: Throwable) {
|
|
|
@@ -514,6 +553,7 @@ object FaceUtil {
|
|
|
// ================= checkCamera:命中即停 =================
|
|
|
fun checkCamera(
|
|
|
preview: View,
|
|
|
+ faceOverlayView: FaceOverlayView? = null,
|
|
|
callBack: (Bitmap?, face: Rect?, Long?) -> Unit
|
|
|
) {
|
|
|
stashAppContext(preview.context)
|
|
|
@@ -535,6 +575,7 @@ object FaceUtil {
|
|
|
isMirror: Boolean
|
|
|
) {
|
|
|
previewSize = camera.parameters.previewSize
|
|
|
+ faceOverlayView?.setCameraPreviewSize(previewSize!!.width, previewSize!!.height)
|
|
|
if (backend == FaceBackend.HLK && !hlkVerifyRunning) {
|
|
|
hlkVerifyRunning = true
|
|
|
lastUserIdByHlk = null
|
|
|
@@ -546,8 +587,12 @@ object FaceUtil {
|
|
|
timeoutSec = 15, loop = true,
|
|
|
onFaceState = { rect, state, _, _, _ ->
|
|
|
lastFaceRectByHlk = rect; lastAliveByHlk = (state == 0)
|
|
|
+ ThreadUtils.runOnMain {
|
|
|
+ faceOverlayView?.setFaceRect(rect?.let { listOf(it) }
|
|
|
+ ?: emptyList())
|
|
|
+ }
|
|
|
},
|
|
|
- onLiveness = { alive -> lastAliveByHlk = alive },
|
|
|
+ onLiveness = { alive -> lastAliveByHlk = true },
|
|
|
onResult = { userId -> lastUserIdByHlk = userId }
|
|
|
)
|
|
|
}
|
|
|
@@ -570,7 +615,11 @@ object FaceUtil {
|
|
|
val data = nv21 ?: return@launch
|
|
|
val bmp = ImageConvertUtils.nv21ToBitmap(data, p.width, p.height)
|
|
|
ThreadUtils.runOnMain {
|
|
|
- callBack(bmp, lastFaceRectByHlk, registerUserIdAndLocalUserId[lastUserIdByHlk])
|
|
|
+ callBack(
|
|
|
+ bmp,
|
|
|
+ lastFaceRectByHlk,
|
|
|
+ registerUserIdAndLocalUserId[lastUserIdByHlk]
|
|
|
+ )
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -586,9 +635,17 @@ object FaceUtil {
|
|
|
val data = nv21 ?: return@launch
|
|
|
val fe = faceEngine ?: return@launch
|
|
|
val faces = mutableListOf<FaceInfo>()
|
|
|
- var code = fe.detectFaces(data, p.width, p.height, FaceEngine.CP_PAF_NV21, faces)
|
|
|
+ var code =
|
|
|
+ fe.detectFaces(data, p.width, p.height, FaceEngine.CP_PAF_NV21, faces)
|
|
|
if (code != ErrorInfo.MOK || faces.isEmpty()) return@launch
|
|
|
- code = fe.process(data, p.width, p.height, FaceEngine.CP_PAF_NV21, faces, processMask)
|
|
|
+ code = fe.process(
|
|
|
+ data,
|
|
|
+ p.width,
|
|
|
+ p.height,
|
|
|
+ FaceEngine.CP_PAF_NV21,
|
|
|
+ faces,
|
|
|
+ processMask
|
|
|
+ )
|
|
|
if (code != ErrorInfo.MOK) return@launch
|
|
|
|
|
|
val liveList = mutableListOf<LivenessInfo>()
|
|
|
@@ -596,7 +653,13 @@ object FaceUtil {
|
|
|
if (lc != ErrorInfo.MOK) return@launch
|
|
|
val alive = liveList.any { it.liveness == LivenessInfo.ALIVE }
|
|
|
if (!alive) {
|
|
|
- if (shouldEmit(lastCheckCbTs)) ThreadUtils.runOnMain { callBack(null, null, null) }
|
|
|
+ if (shouldEmit(lastCheckCbTs)) ThreadUtils.runOnMain {
|
|
|
+ callBack(
|
|
|
+ null,
|
|
|
+ null,
|
|
|
+ null
|
|
|
+ )
|
|
|
+ }
|
|
|
return@launch
|
|
|
}
|
|
|
|
|
|
@@ -606,7 +669,8 @@ object FaceUtil {
|
|
|
faces[0], ExtractType.RECOGNIZE, 0, ft
|
|
|
)
|
|
|
|
|
|
- val searchResult = runCatching { if (fe.faceCount > 0) fe.searchFaceFeature(ft) else null }.getOrNull()
|
|
|
+ val searchResult =
|
|
|
+ runCatching { if (fe.faceCount > 0) fe.searchFaceFeature(ft) else null }.getOrNull()
|
|
|
val score = searchResult?.maxSimilar ?: 0f
|
|
|
logger.info("人脸库数:${fe.faceCount},相似度:${score}")
|
|
|
|
|
|
@@ -614,7 +678,13 @@ object FaceUtil {
|
|
|
if (score > 0.5f) {
|
|
|
if (stopAfterHit.compareAndSet(false, true)) {
|
|
|
if (shouldEmit(lastCheckCbTs)) {
|
|
|
- val bmp = withContext(Dispatchers.IO) { ImageConvertUtils.nv21ToBitmap(data, p.width, p.height) }
|
|
|
+ val bmp = withContext(Dispatchers.IO) {
|
|
|
+ ImageConvertUtils.nv21ToBitmap(
|
|
|
+ data,
|
|
|
+ p.width,
|
|
|
+ p.height
|
|
|
+ )
|
|
|
+ }
|
|
|
ThreadUtils.runOnMain {
|
|
|
callBack(
|
|
|
bmp,
|
|
|
@@ -629,7 +699,13 @@ object FaceUtil {
|
|
|
}
|
|
|
|
|
|
// 未命中:按原逻辑回调空
|
|
|
- if (shouldEmit(lastCheckCbTs)) ThreadUtils.runOnMain { callBack(null, null, null) }
|
|
|
+ if (shouldEmit(lastCheckCbTs)) ThreadUtils.runOnMain {
|
|
|
+ callBack(
|
|
|
+ null,
|
|
|
+ null,
|
|
|
+ null
|
|
|
+ )
|
|
|
+ }
|
|
|
|
|
|
} catch (e: Throwable) {
|
|
|
logger.warn("ARC check error: ${e.message}", e)
|
|
|
@@ -693,9 +769,10 @@ object FaceUtil {
|
|
|
// ----------------- 注册/比对(仅 ARC 有效,已串行化) -----------------
|
|
|
fun registerFace(faceData: List<Pair<Long, String>>) {
|
|
|
if (backend == FaceBackend.HLK) {
|
|
|
- faceData.forEach { (uid, b64) ->
|
|
|
- ThreadUtils.runOnIO {
|
|
|
- val jpegBytes = ImageCompress.base64ToJpegUnder(b64, 1500)
|
|
|
+ ThreadUtils.runOnIO {
|
|
|
+ faceData.forEach { (uid, b64) ->
|
|
|
+ val jpegBytes = ImageCompress.base64ToJpegUnderMB(b64)
|
|
|
+// val jpegBytes = Base64.decode(b64, Base64.DEFAULT)
|
|
|
val crc32 = CRC32().apply { update(jpegBytes) }.value
|
|
|
logger.info("图片大小:{}", jpegBytes.size)
|
|
|
val userId = hlkClient?.enrollWithPhoto(jpegBytes, crc32 = crc32)
|
|
|
@@ -770,7 +847,12 @@ object FaceUtil {
|
|
|
facesB[0], ExtractType.RECOGNIZE, 0, ftB
|
|
|
)
|
|
|
val sim = FaceSimilar()
|
|
|
- if (faceEngine?.compareFaceFeature(ftA, ftB, sim) != ErrorInfo.MOK) return@withLock false
|
|
|
+ if (faceEngine?.compareFaceFeature(
|
|
|
+ ftA,
|
|
|
+ ftB,
|
|
|
+ sim
|
|
|
+ ) != ErrorInfo.MOK
|
|
|
+ ) return@withLock false
|
|
|
sim.score >= threshold
|
|
|
}
|
|
|
}
|