feat(api): 新增V2版本接口支持并集成至网络请求客户端

- 新增 ApiServiceV2 接口,支持人脸缓存、增量数据、设备配置等接口
- ApiClient 中新增 apiServiceV2 对象,复用 Retrofit 实例,调整超时时间至60秒
- DeviceInitActivity 改用 netViewModelV2 进行人脸缓存全量拉取及设备配置读取
- BaseActivity 新增对人脸增量同步接口调用,调用成功后刷新本地识别缓存
- BaseActivity 新增清空本地人脸库功能,异步清理数据库并刷新识别缓存
- FaceApi 增加清空本地人脸库接口,重写数据库操作逻辑
- 升级 lib_face 模块 Room 数据库版本,新增字段以支持多标识人脸实体扩展
- FaceEntity 实体扩展会员编号、用户ID、人脸ID、会员标识、更新时间等字段
- FaceDao 新增按人脸ID查询和删除接口,支持多重人脸数据操作
- 优化网络层 OkHttpClient 配置,简化拦截器写法及日志级别判断
- 升级 lib_face 模块支持 arm64-v8a 架构,增强兼容性
- 规范模块间依赖关系,统一版本管理及包路径声明
- 完善 CLAUDE.md 文档,补充项目架构、模块划分与开发流程说明
This commit is contained in:
mazengfei
2026-08-18 17:14:06 +08:00
parent 5b8db65fa2
commit d53ea8f798
41 changed files with 1786 additions and 258 deletions
@@ -2,6 +2,8 @@ package com.sw.plate.utils;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -30,18 +32,20 @@ public class ToastUtils {
* @param text the text
*/
public static void showToast(String text) {
Context context = App.getContext();
if (toast == null) {
View view = LayoutInflater.from(context).inflate(R.layout.toast_bg, null);
textCenterView = view.findViewById(R.id.toast_tv);
toast = new Toast(context);
toast.setGravity(Gravity.CENTER, 0, 20);
toast.setDuration(Toast.LENGTH_SHORT);
toast.setView(view);
}
new Handler(Looper.getMainLooper()).post(()->{
Context context = App.getContext();
if (toast == null) {
View view = LayoutInflater.from(context).inflate(R.layout.toast_bg, null);
textCenterView = view.findViewById(R.id.toast_tv);
toast = new Toast(context);
toast.setGravity(Gravity.CENTER, 0, 20);
toast.setDuration(Toast.LENGTH_SHORT);
toast.setView(view);
}
textCenterView.setText(text);
toast.show();
textCenterView.setText(text);
toast.show();
});
}
/**
@@ -87,10 +87,10 @@ public class ConfigUtil {
/**
* 默认相机分辨率
*/
private static final String DEFAULT_PREVIEW_SIZE = "1280x720";
// private static final String DEFAULT_PREVIEW_SIZE = "1280x720";
private static final String DEFAULT_PREVIEW_SIZE = "1024x768";
// private static final String DEFAULT_PREVIEW_SIZE = "400x640";
/**
* 获取String类型的preference
*
@@ -32,14 +32,20 @@ public class FaceApi {
*/
public void updateFaceData(int index, List<FaceEntity> list) {
Log.d(TAG, "updateFaceData: index = " + index + ", listSize = " + list.size());
FaceDao faceDao = FaceDatabase.getInstance(App.getContext()).faceDao();
if (index == 1) {
faceDao.deleteAll();
faceDao.resetId();
}
FaceDao faceDao = getFaceDao();
//if (index == 1) {
// faceDao.deleteAll();
// faceDao.resetId();
//}
faceDao.insert(list);
}
public void clearFaceData() {
FaceDao faceDao = getFaceDao();
faceDao.deleteAll();
faceDao.resetId();
}
/**
* 激活arcsoft 人脸
*
@@ -82,24 +88,51 @@ public class FaceApi {
}
public void deleteByUserName(String userName) {
FaceDao faceDao = FaceDatabase.getInstance(App.getContext()).faceDao();
faceDao.deleteFaceById(userName);
getFaceDao().deleteFaceById(userName);
}
/**
* 按 userFaceId 删除单条人脸记录
*/
public void deleteByUserFaceId(String userFaceId) {
getFaceDao().deleteFaceByUserFaceId(userFaceId);
}
/**
* 按 userFaceId 查询单条人脸记录
*/
public FaceEntity queryByUserFaceId(String userFaceId) {
if (userFaceId == null || userFaceId.isEmpty()) {
return null;
}
return getFaceDao().queryByUserFaceId(userFaceId);
}
public Long insert(FaceEntity entity) {
if (entity == null) {
return 0L;
}
FaceDao faceDao = FaceDatabase.getInstance(App.getContext()).faceDao();
return faceDao.insert(entity);
return getFaceDao().insert(entity);
}
public FaceEntity queryByUserName(String userName) {
if (TextUtils.isEmpty(userName)) {
return null;
}
FaceDao faceDao = FaceDatabase.getInstance(App.getContext()).faceDao();
return faceDao.queryByUserName(userName);
return getFaceDao().queryByUserName(userName);
}
public int queryFaceCount() {
return getFaceDao().getFaceCount();
}
public List<FaceEntity> queryAllByUserName(String userName) {
return getFaceDao().queryAllByUserName(userName);
}
public FaceDao getFaceDao() {
return FaceDatabase.getInstance(App.getContext()).faceDao();
}
}
@@ -46,6 +46,10 @@ public class FaceRectView extends View {
}
}
public int getRectColor() {
return paint.getColor();
}
public void clearFaceInfo() {
drawInfoList.clear();
postInvalidate();
@@ -5,6 +5,7 @@ import android.hardware.Camera;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
@@ -18,6 +19,7 @@ import com.arcsoft.face.ImageQualitySimilar;
import com.arcsoft.face.LivenessInfo;
import com.arcsoft.face.MaskInfo;
import com.arcsoft.face.enums.ExtractType;
import com.sw.plate.App;
import com.sw.plate.utils.L;
import com.sw.plate.utils.arcface.FaceRectTransformer;
import com.sw.plate.utils.arcface.face.constants.LivenessType;
@@ -37,7 +39,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
@@ -102,6 +103,10 @@ public class FaceHelper implements FaceListener {
* 活体检测引擎
*/
private FaceEngine flEngine;
/**
* 口罩检测引擎(5.0 独立引擎)
*/
private FaceEngine maskEngine;
private Camera.Size previewSize;
@@ -146,11 +151,6 @@ public class FaceHelper implements FaceListener {
*/
private boolean onlyDetectLiveness;
/**
* 是否需要更新faceData
*/
private boolean needUpdateFaceData;
/**
* 识别的配置项
*/
@@ -180,9 +180,9 @@ public class FaceHelper implements FaceListener {
}
private FaceHelper(Builder builder) {
needUpdateFaceData = builder.needUpdateFaceData;
onlyDetectLiveness = builder.onlyDetectLiveness;
ftEngine = builder.ftEngine;
maskEngine = builder.maskEngine;
trackedFaceCount = builder.trackedFaceCount;
previewSize = builder.previewSize;
frEngine = builder.frEngine;
@@ -341,12 +341,20 @@ public class FaceHelper implements FaceListener {
refreshTrackId(faceInfoList);
if (faceInfoList.isEmpty()) {
return facePreviewInfoList;
} else {
FaceInfo currentFaceInfo = faceInfoList.get(0);
// L.e("currentFaceInfo width=" + currentFaceInfo.getRect().width() +
// "---height" + currentFaceInfo.getRect().height());
if (currentFaceInfo.getRect().width() < 200) {//距离远(人脸小),不识别
clearLeftFace(facePreviewInfoList);
return facePreviewInfoList;
}
}
if (!onlyDetectLiveness) {
code = ftEngine.process(rgbNv21, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, faceInfoList,
if (!onlyDetectLiveness && maskEngine != null) {
code = maskEngine.process(rgbNv21, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, faceInfoList,
FaceEngine.ASF_MASK_DETECT);
if (code == ErrorInfo.MOK) {
code = ftEngine.getMask(maskInfoList);
code = maskEngine.getMask(maskInfoList);
if (code != ErrorInfo.MOK) {
onFail(new Exception("process getMask failed,code is " + code));
return facePreviewInfoList;
@@ -652,21 +660,52 @@ public class FaceHelper implements FaceListener {
});
}
private int failCount = 0;
private long startTime = 0;
private void searchFace(final FaceFeature faceFeature, final Integer trackId) {
CompareResult compareResult = FaceServer.getInstance().searchFaceFeature(faceFeature, frEngine);
if (compareResult == null || compareResult.getFaceEntity() == null) {
if (startTime == 0) {
failCount = 0;
startTime = System.currentTimeMillis();
}
if (System.currentTimeMillis() - startTime > 10*1000) {
failCount = 0;
startTime = System.currentTimeMillis();
}
failCount++;
float similar;
if (compareResult != null) {
similar = compareResult.getSimilar();
} else {
similar = 0f;
}
// new Handler(Looper.getMainLooper()).post(()-> {
// try {
// Toast.makeText(App.getContext(),"识别失败"+failCount+"次,similar="+similar, Toast.LENGTH_SHORT).show();
// } catch (Exception e) {
// e.printStackTrace();
// }
// });
Log.d(TAG, "collectFace,searchFace查询失败"+failCount+"次,similar="+similar);
if (failCount >= 2) {
recognizeCallback.onRecognized(null, LivenessInfo.UNKNOWN, false);
return;
}
retryRecognizeDelayed(trackId);
return;
}
compareResult.setTrackId(trackId);
boolean pass = compareResult.getSimilar() > recognizeConfiguration.getSimilarThreshold();
compareResult.setSimilarPass(pass);
Log.d(TAG, "collectFace,searchFace: pass="+pass+",similar="+compareResult.getSimilar()+",threshold="+recognizeConfiguration.getSimilarThreshold());
recognizeCallback.onRecognized(compareResult, getRecognizeInfo(recognizeInfoMap, trackId).getLiveness(), pass);
if (pass) {
setName(trackId, "识别通过");
noticeCurrentStatus("识别通过");
changeRecognizeStatus(trackId, RequestFeatureStatus.SUCCEED);
} else {
noticeCurrentStatus("未通过:NOT_REGISTERED");
noticeCurrentStatus("未通过:NOT_REGISTERED"+compareResult.getSimilar());
retryRecognizeDelayed(trackId);
}
}
@@ -839,18 +878,7 @@ public class FaceHelper implements FaceListener {
int fdCode = flEngine.detectFaces(nv21Data, width, height, format, faceInfoList);
boolean isFaceExists = isFaceExists(faceInfoList, faceInfo);
if (fdCode == ErrorInfo.MOK && isFaceExists) {
if (needUpdateFaceData) {
/*
* 若IR人脸框有偏移,则需要对IR的人脸数据进行updateFaceData处理,再将处理后的FaceInfo信息传输给活体检测接口
*/
flCode = flEngine.updateFaceData(nv21Data, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21,
new ArrayList<>(Collections.singletonList(faceInfo)));
if (flCode == ErrorInfo.MOK) {
flCode = flEngine.processIr(nv21Data, width, height, format, Arrays.asList(faceInfo), FaceEngine.ASF_IR_LIVENESS);
}
} else {
flCode = flEngine.processIr(nv21Data, width, height, format, Arrays.asList(faceInfo), FaceEngine.ASF_IR_LIVENESS);
}
flCode = flEngine.processIr(nv21Data, width, height, format, Arrays.asList(faceInfo), FaceEngine.ASF_IR_LIVENESS);
} else {
onFail(new Exception("ir detectFaces failed fdCode:" + fdCode + ",isFaceExists:" + isFaceExists));
}
@@ -1057,9 +1085,9 @@ public class FaceHelper implements FaceListener {
private FaceEngine ftEngine;
private FaceEngine frEngine;
private FaceEngine flEngine;
private FaceEngine maskEngine;
private Camera.Size previewSize;
private boolean onlyDetectLiveness;
private boolean needUpdateFaceData;
private RecognizeConfiguration recognizeConfiguration;
private RecognizeCallback recognizeCallback;
private IDualCameraFaceInfoTransformer dualCameraFaceInfoTransformer;
@@ -1100,6 +1128,11 @@ public class FaceHelper implements FaceListener {
return this;
}
public Builder maskEngine(FaceEngine val) {
maskEngine = val;
return this;
}
public Builder previewSize(Camera.Size val) {
previewSize = val;
return this;
@@ -1125,11 +1158,6 @@ public class FaceHelper implements FaceListener {
return this;
}
public Builder needUpdateFaceData(boolean val) {
needUpdateFaceData = val;
return this;
}
public FaceHelper build() {
return new FaceHelper(this);
}
@@ -10,6 +10,16 @@ public class CompareResult {
private int compareCode;
private long cost;
private boolean similarPass;
public void setSimilarPass(boolean similarPass) {
this.similarPass = similarPass;
}
public boolean isSimilarPass() {
return similarPass;
}
public CompareResult(FaceEntity faceEntity, float similar) {
this.faceEntity = faceEntity;
this.similar = similar;
@@ -9,7 +9,7 @@ import androidx.room.RoomDatabase;
import com.sw.plate.utils.arcface.facedb.dao.FaceDao;
import com.sw.plate.utils.arcface.facedb.entity.FaceEntity;
@Database(entities = {FaceEntity.class}, version = 1, exportSchema = false)
@Database(entities = {FaceEntity.class}, version = 2, exportSchema = false)
public abstract class FaceDatabase extends RoomDatabase {
public abstract FaceDao faceDao();
@@ -22,7 +22,10 @@ public abstract class FaceDatabase extends RoomDatabase {
faceDatabase = Room.databaseBuilder(context, FaceDatabase.class,
context.getDatabasePath("faceDB.db").getPath()
// context.getExternalFilesDir("database") + File.separator + "faceDB.db"
).build();
)
// 5.0 特征模型不兼容,升级时直接删除旧表重建
.fallbackToDestructiveMigration()
.build();
}
}
}
@@ -55,6 +55,18 @@ public interface FaceDao {
@Query("DELETE from face WHERE user_name = :userName")
int deleteFaceById(String userName);
/**
* 按 userFaceId 删除单条人脸记录
*/
@Query("DELETE from face WHERE user_face_id = :userFaceId")
int deleteFaceByUserFaceId(String userFaceId);
/**
* 按 userFaceId 查询单条人脸记录
*/
@Query("SELECT * FROM face WHERE user_face_id = :userFaceId LIMIT 1")
FaceEntity queryByUserFaceId(String userFaceId);
/**
* 删除所有已注册的人脸
*
@@ -91,4 +103,26 @@ public interface FaceDao {
@Query("SELECT * FROM face WHERE user_name = :userName limit 1")
FaceEntity queryByUserName(String userName);
/**
* 查询指定用户的所有人脸记录(同一用户可能存有多条特征数据)
*
* @param userName 用户ID
* @return 该用户所有人脸记录列表
*/
@Query("SELECT * FROM face WHERE user_name = :userName")
List<FaceEntity> queryAllByUserName(String userName);
/**
* @return 删除临时用户人脸
*/
@Query("DELETE from face WHERE user_type = :userType")
int deleteUserFaceData(int userType);
/**
* 查询用户人脸数
* @param userType 1-会员,2-临时用户
*/
@Query("SELECT COUNT(1) FROM face WHERE user_type = :userType")
int getFaceCountByUserType(int userType);
}
@@ -45,14 +45,46 @@ public class FaceEntity implements Parcelable {
@ColumnInfo(name = "register_time")
private long registerTime;
/**
* 用户类型:1-普通会员、2-临时用户、3-内部员工、或者其它待定类型
* 用户类型:1-普通会员、2-临时用户、或者其它待定类型
*/
@ColumnInfo(name = "user_type")
private String userType;
/**
* 会员编号
*/
@ColumnInfo(name = "card_no")
private String cardNo;
@Ignore
private int trackId;//人脸追踪ID
@ColumnInfo(name = "user_id")
private String userId;
@ColumnInfo(name = "user_face_id")
private String userFaceId;
@ColumnInfo(name = "member")
private boolean member;
@ColumnInfo(name = "face_update_timestamp")
private long faceUpdateTimestamp;
public FaceEntity() {
registerTime = System.currentTimeMillis();
}
@Ignore
public FaceEntity(String userName, byte[] featureData, String userType, String cardNo, String userId, String userFaceId, boolean member, long faceUpdateTimestamp) {
this.userName = userName;
this.featureData = featureData;
this.userType = userType;
this.cardNo = cardNo;
this.userId = userId;
this.userFaceId = userFaceId;
this.member = member;
this.faceUpdateTimestamp = faceUpdateTimestamp;
registerTime = System.currentTimeMillis();
}
@Ignore
public FaceEntity(String userName, String imagePath, byte[] featureData) {
this.userName = userName;
this.imagePath = imagePath;
@@ -60,21 +92,35 @@ public class FaceEntity implements Parcelable {
registerTime = System.currentTimeMillis();
}
@Ignore
public FaceEntity(FaceEntity faceEntity) {
this.faceId = faceEntity.faceId;
this.userName = faceEntity.userName;
this.imagePath = faceEntity.imagePath;
this.featureData = faceEntity.featureData;
this.registerTime = faceEntity.registerTime;
this.userType = faceEntity.getUserType();
this.cardNo = faceEntity.getCardNo();
this.userId = faceEntity.getUserId();
this.userFaceId = faceEntity.getUserFaceId();
this.member = faceEntity.isMember();
this.faceUpdateTimestamp = faceEntity.getFaceUpdateTimestamp();
}
@Ignore
protected FaceEntity(Parcel in) {
faceId = in.readLong();
registerTime = in.readLong();
userName = in.readString();
imagePath = in.readString();
featureData = in.createByteArray();
userType = in.readString();
cardNo = in.readString();
userId = in.readString();
userFaceId = in.readString();
member = in.readByte() != 0;
faceUpdateTimestamp = in.readLong();
}
public static final Creator<FaceEntity> CREATOR = new Creator<FaceEntity>() {
@@ -145,6 +191,46 @@ public class FaceEntity implements Parcelable {
this.userType = userType;
}
public String getCardNo() {
return cardNo;
}
public void setCardNo(String cardNo) {
this.cardNo = cardNo;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserFaceId() {
return userFaceId;
}
public void setUserFaceId(String userFaceId) {
this.userFaceId = userFaceId;
}
public boolean isMember() {
return member;
}
public void setMember(boolean member) {
this.member = member;
}
public long getFaceUpdateTimestamp() {
return faceUpdateTimestamp;
}
public void setFaceUpdateTimestamp(long faceUpdateTimestamp) {
this.faceUpdateTimestamp = faceUpdateTimestamp;
}
@Override
public int describeContents() {
return 0;
@@ -158,10 +244,13 @@ public class FaceEntity implements Parcelable {
dest.writeString(imagePath);
dest.writeByteArray(featureData);
dest.writeString(userType);
dest.writeString(cardNo);
dest.writeString(userId);
dest.writeString(userFaceId);
dest.writeByte((byte) (member ? 1 : 0));
dest.writeLong(faceUpdateTimestamp);
}
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -176,12 +265,17 @@ public class FaceEntity implements Parcelable {
TextUtils.equals(this.userName, that.userName) &&
TextUtils.equals(this.imagePath, that.imagePath) &&
Arrays.equals(featureData, that.featureData) &&
TextUtils.equals(this.userType, that.userType);
TextUtils.equals(this.userType, that.userType) &&
TextUtils.equals(this.cardNo, that.cardNo) &&
TextUtils.equals(this.userId, that.userId) &&
TextUtils.equals(this.userFaceId, that.userFaceId) &&
this.member == that.member &&
this.faceUpdateTimestamp == that.faceUpdateTimestamp;
}
@Override
public int hashCode() {
int result = Objects.hash(faceId, registerTime, userName, imagePath, userType);
int result = Objects.hash(faceId, registerTime, userName, imagePath, userType, cardNo, userId, userFaceId, member, faceUpdateTimestamp);
result = 31 * result + Arrays.hashCode(featureData);
return result;
}
@@ -12,7 +12,6 @@ import androidx.lifecycle.ViewModel;
import com.arcsoft.face.AgeInfo;
import com.arcsoft.face.ErrorInfo;
import com.arcsoft.face.FaceAttributeParam;
import com.arcsoft.face.FaceEngine;
import com.arcsoft.face.FaceInfo;
import com.arcsoft.face.GenderInfo;
@@ -132,13 +131,14 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
private MutableLiveData<Integer> ftInitCode = new MutableLiveData<>();
private MutableLiveData<Integer> frInitCode = new MutableLiveData<>();
private MutableLiveData<Integer> flInitCode = new MutableLiveData<>();
private MutableLiveData<Integer> maskInitCode = new MutableLiveData<>();
/**
* 人脸操作辅助类,推帧即可,内部会进行特征提取、识别
*/
private FaceHelper faceHelper;
/**
* VIDEO模式人脸检测引擎,用于预览帧人脸追踪及图像质量检测
* VIDEO模式人脸检测引擎,用于预览帧人脸追踪
*/
private FaceEngine ftEngine;
/**
@@ -149,6 +149,10 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
* IMAGE模式活体检测引擎,用于预览帧人脸活体检测
*/
private FaceEngine flEngine;
/**
* IMAGE模式口罩检测引擎(5.0 独立引擎)
*/
private FaceEngine maskEngine;
private PreviewConfig previewConfig;
@@ -158,12 +162,8 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
private MutableLiveData<String> drawRectInfoText = new MutableLiveData<>();
private MutableLiveData<String> recognizeUserId = new MutableLiveData<>();
private MutableLiveData<CompareResult> recognizeUserId = new MutableLiveData<>();
/**
* 检测ir活体前,是否需要更新faceData
*/
private boolean needUpdateFaceData;
/**
* 当前活体检测的检测类型
*/
@@ -280,6 +280,8 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
// 填入在设置界面设置好的配置信息
boolean enableLive = !ConfigUtil.getLivenessDetectType(context).equals(context.getString(R.string.value_liveness_type_disable));
enableLive = false;
boolean enableFaceQualityDetect = ConfigUtil.isEnableImageQualityDetect(context);
boolean enableFaceMoveLimit = ConfigUtil.isEnableFaceMoveLimit(context);
boolean enableFaceSizeLimit = ConfigUtil.isEnableFaceSizeLimit(context);
@@ -295,22 +297,22 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
.similarThreshold(ConfigUtil.getRecognizeThreshold(context))
.imageQualityNoMaskRecognizeThreshold(ConfigUtil.getImageQualityNoMaskRecognizeThreshold(context))
.imageQualityMaskRecognizeThreshold(ConfigUtil.getImageQualityMaskRecognizeThreshold(context))
.livenessParam(new LivenessParam(ConfigUtil.getRgbLivenessThreshold(context), ConfigUtil.getIrLivenessThreshold(context),
ConfigUtil.getLivenessFqThreshold(context)))
.livenessParam(new LivenessParam(ConfigUtil.getRgbLivenessThreshold(context), ConfigUtil.getIrLivenessThreshold(context)))
.build();
int cameraOffsetX = ConfigUtil.getDualCameraHorizontalOffset(context);
int cameraOffsetY = ConfigUtil.getDualCameraVerticalOffset(context);
needUpdateFaceData = (livenessType == LivenessType.IR && (cameraOffsetX != 0 || cameraOffsetY != 0));
// 人脸追踪引擎(VIDEO模式,仅检测)
ftEngine = new FaceEngine();
int ftEngineMask = FaceEngine.ASF_FACE_DETECT | FaceEngine.ASF_MASK_DETECT;
int ftEngineMask = FaceEngine.ASF_FACE_DETECT;
ftInitCode.postValue(ftEngine.init(context, DetectMode.ASF_DETECT_MODE_VIDEO, ConfigUtil.getFtOrient(context),
ConfigUtil.getRecognizeMaxDetectFaceNum(context), ftEngineMask));
FaceAttributeParam attributeParam = new FaceAttributeParam(
ConfigUtil.getRecognizeEyeOpenThreshold(context), ConfigUtil.getRecognizeMouthCloseThreshold(context),
ConfigUtil.getRecognizeWearGlassesThreshold(context));
ftEngine.setFaceAttributeParam(attributeParam);
// 口罩检测引擎(5.0 独立引擎)
maskEngine = new FaceEngine();
maskInitCode.postValue(maskEngine.init(context, DetectMode.ASF_DETECT_MODE_IMAGE,
DetectFaceOrientPriority.ASF_OP_ALL_OUT,
ConfigUtil.getRecognizeMaxDetectFaceNum(context), FaceEngine.ASF_MASK_DETECT));
// 特征提取引擎
frEngine = new FaceEngine();
int frEngineMask = FaceEngine.ASF_FACE_RECOGNITION;
if (enableFaceQualityDetect) {
@@ -320,16 +322,13 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
10, frEngineMask));
FaceServer.getInstance().initFaceList(context, frEngine, faceCount -> loadFaceList = true, true);
//启用活体检测时,才初始化活体引擎
// 启用活体检测时,才初始化活体引擎
if (enableLive) {
flEngine = new FaceEngine();
int flEngineMask = (livenessType == LivenessType.RGB ? FaceEngine.ASF_LIVENESS : (FaceEngine.ASF_IR_LIVENESS | FaceEngine.ASF_FACE_DETECT));
if (needUpdateFaceData) {
flEngineMask |= FaceEngine.ASF_UPDATE_FACEDATA;
}
flInitCode.postValue(flEngine.init(context, DetectMode.ASF_DETECT_MODE_IMAGE,
DetectFaceOrientPriority.ASF_OP_ALL_OUT, 10, flEngineMask));
LivenessParam livenessParam = new LivenessParam(ConfigUtil.getRgbLivenessThreshold(context), ConfigUtil.getIrLivenessThreshold(context), ConfigUtil.getLivenessFqThreshold(context));
LivenessParam livenessParam = new LivenessParam(ConfigUtil.getRgbLivenessThreshold(context), ConfigUtil.getIrLivenessThreshold(context));
flEngine.setLivenessParam(livenessParam);
}
@@ -354,19 +353,25 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
if (ftEngine != null) {
synchronized (ftEngine) {
int ftUnInitCode = ftEngine.unInit();
Log.i(TAG, "unInitEngine: " + ftUnInitCode);
Log.i(TAG, "unInitEngine ft: " + ftUnInitCode);
}
}
if (maskEngine != null) {
synchronized (maskEngine) {
int maskUnInitCode = maskEngine.unInit();
Log.i(TAG, "unInitEngine mask: " + maskUnInitCode);
}
}
if (frEngine != null) {
synchronized (frEngine) {
int frUnInitCode = frEngine.unInit();
Log.i(TAG, "unInitEngine: " + frUnInitCode);
Log.i(TAG, "unInitEngine fr: " + frUnInitCode);
}
}
if (flEngine != null) {
synchronized (flEngine) {
int flUnInitCode = flEngine.unInit();
Log.i(TAG, "unInitEngine: " + flUnInitCode);
Log.i(TAG, "unInitEngine fl: " + flUnInitCode);
}
}
}
@@ -395,6 +400,20 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
}
}
/**
* 重置人脸识别状态:清空上次识别结果及 FaceHelper 内部的 recognizeInfoMap
* 使下一帧进入时能重新触发识别流程。
* 适用场景:短时间内再次识别、点击重试按钮等需要重新开始识别的时机。
*/
public void resetFaceState() {
// 清空粘性 LiveData,防止旧结果被重新投递给 observer
recognizeUserId.postValue(null);
// 清空 FaceHelper 内部所有人脸状态,让 trackId 对应的状态回到 TO_RETRY
if (faceHelper != null) {
faceHelper.clearFacePreviewInfoList();
}
}
/**
* 释放操作
*/
@@ -453,7 +472,7 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
.ftEngine(ftEngine)
.frEngine(frEngine)
.flEngine(flEngine)
.needUpdateFaceData(needUpdateFaceData)
.maskEngine(maskEngine)
.frQueueSize(maxDetectFaceNum)
.flQueueSize(maxDetectFaceNum)
.previewSize(previewSize)
@@ -469,13 +488,20 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
}
}
private String getUserId(CompareResult result) {
if (result != null && result.getFaceEntity() != null) {
return result.getFaceEntity().getUserName();
}
return null;
}
@Override
public void onRecognized(CompareResult compareResult, Integer live, boolean similarPass) {
Disposable disposable = Observable.just(true).observeOn(AndroidSchedulers.mainThread()).subscribe(aBoolean -> {
// TODO: 2026/1/21 测试使用 Observable.just(similarPass)代替Observable.just(true)---
Observable.just(similarPass).observeOn(AndroidSchedulers.mainThread()).subscribe(aBoolean -> {
Log.d(TAG, "collectFace,onRecognized: similarPass=" + similarPass + ",live=" + live + ",userId=" + getUserId(compareResult));
if (similarPass) {
if (recognizeUserId != null) {
recognizeUserId.postValue(compareResult.getFaceEntity().getUserName());
}
recognizeUserId.postValue(compareResult);
boolean isAdded = false;
List<CompareResult> compareResults = compareResultList.getValue();
if (compareResults != null && !compareResults.isEmpty()) {
@@ -497,6 +523,8 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
getFaceItemEventMutableLiveData().postValue(new FaceItemEvent(compareResults.size() - 1, EventType.INSERTED));
}
}
} else {
recognizeUserId.postValue(compareResult);
}
});
}
@@ -540,7 +568,7 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
return recognizeNotice;
}
public MutableLiveData<String> getRecognizeUserId() {
public MutableLiveData<CompareResult> getRecognizeUserId() {
return recognizeUserId;
}
@@ -561,7 +589,7 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
}
}
private void updateRegisterStatus(int status) {
public void updateRegisterStatus(int status) {
registerStatus = status;
}
+3 -3
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:cardCornerRadius="5dp"
app:cardPreventCornerOverlap="true">
@@ -10,7 +10,7 @@
<TextView
android:id="@+id/toast_tv"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:maxLines="1"