feat(face): 升级虹软SDK到5.0及完善人脸识别逻辑

- FaceHelper中集成独立口罩检测引擎maskEngine并调用口罩检测接口
- FaceEngine初始化中增加口罩检测引擎初始化及状态管理
- FaceServer注册人脸时打印特征信息日志,便于调试
- CompareResult新增similarPass字段,标记识别是否通过
- FaceApi新增针对人脸数据的增删查接口,方便管理
- FaceDatabase数据库版本升级至2,支持人脸特征库重建迁移
- FaceDao新增多用户查询、人脸计数及临时用户数据删除接口
- FaceEntity新增userId、userFaceId、member标记及更新时间字段及相关方法
- 优化人脸识别失败重试机制,连续失败后回调识别失败结果
- 优化识别回调逻辑,成功与失败结果均及时通知观察者
- 调整配置获取逻辑,禁用人脸活体检测时关闭相关初始化
- 精简并修正部分冗余代码与日志输出,提升代码清晰度与可维护性
This commit is contained in:
mazengfei
2026-07-15 09:17:23 +08:00
parent 5a7b090c02
commit 16dba32eb7
18 changed files with 281 additions and 110 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -88,14 +88,8 @@ public class ConfigUtil {
* 默认相机分辨率
*/
// private static final String DEFAULT_PREVIEW_SIZE = "1280x720";
// private static final String DEFAULT_PREVIEW_SIZE = "1080x720";
// private static final String DEFAULT_PREVIEW_SIZE = "720x1080";
private static final String DEFAULT_PREVIEW_SIZE = "720x1280";
// private static final String DEFAULT_PREVIEW_SIZE = "608x456";
// private static final String DEFAULT_PREVIEW_SIZE = "1024x768";
// private static final String DEFAULT_PREVIEW_SIZE = "800x600";
// private static final String DEFAULT_PREVIEW_SIZE = "640x480";
// private static final String DEFAULT_PREVIEW_SIZE = "400x640";
/**
* 获取String类型的preference
@@ -244,7 +238,7 @@ public class ConfigUtil {
/**
* TODO: 该Demo基于单人脸识别实现,若想使用多人脸识别,请将 return true 改成 return getBoolean,并修改相关配置项的preference.xml和业务代码
* <p>
*
* 获取识别界面是否保留最大人脸
*
* @param context 上下文
@@ -349,7 +343,7 @@ public class ConfigUtil {
return Float.parseFloat(getString(context, R.string.preference_ir_liveness_threshold, String.valueOf(RECOMMEND_IR_LIVENESS_THRESHOLD)));
}
public static float getLivenessFqThreshold(Context context) {
public static float getLivenessFqThreshold(Context context){
return Float.parseFloat(getString(context, R.string.preference_liveness_fq_threshold, String.valueOf(RECOMMEND_LIVENESS_FQ_THRESHOLD)));
}
@@ -27,7 +27,6 @@ public class ErrorCodeUtil {
}
return "unknown error";
}
/**
* 将ArcSoftImageUtil错误码转换为对应的错误码常量名,便于理解
* TODO:目前每次都遍历,如果使用频繁,建议将Field缓存处理,避免每次都反射
@@ -1,6 +1,7 @@
package com.sw.plate.utils.arcface;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import com.arcsoft.face.FaceEngine;
@@ -31,25 +32,21 @@ 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();
FaceDao faceDao = getFaceDao();
// 档口机业务:首页(index==1)全量同步前清空旧特征库,避免特征重复堆积
if (index == 1) {
faceDao.deleteAll();
faceDao.resetId();
}
List<Long> insertIdList = faceDao.insert(list);
Log.d(TAG, "updateFaceData: count = " + insertIdList.size());
faceDao.insert(list);
List<FaceEntity> queryList = faceDao.getAllFaces();
Log.d(TAG, "updateFaceData: queryList.size = " + queryList.size());
}
public void updateFaceData2(int index, List<FaceEntity> list) {
Log.d(TAG, "updateFaceData: index = " + index + ", listSize = " + list.size());
FaceDao faceDao = FaceDatabase.getInstance(App.getContext()).faceDao();
if (index == 0) {
faceDao.deleteAll();
faceDao.resetId();
}
faceDao.insert(list);
public void clearFaceData() {
FaceDao faceDao = getFaceDao();
faceDao.deleteAll();
faceDao.resetId();
}
/**
@@ -92,4 +89,35 @@ public class FaceApi {
// List<FacePreviewInfo> facePreviewInfoList = recognizeViewModel.onPreviewFrame(nv21, true);
return null;
}
public void deleteByUserName(String userName) {
getFaceDao().deleteFaceById(userName);
}
public Long insert(FaceEntity entity) {
if (entity == null) {
return 0L;
}
return getFaceDao().insert(entity);
}
public FaceEntity queryByUserName(String userName) {
if (TextUtils.isEmpty(userName)) {
return null;
}
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();
}
}
@@ -77,7 +77,7 @@ public class FaceRectTransformer {
rect.bottom *= verticalRatio;
Rect newRect = new Rect();
// L.e("cameraDisplayOrientation " + cameraDisplayOrientation + " === " + cameraId);
L.e("cameraDisplayOrientation " + cameraDisplayOrientation + " === " + cameraId);
switch (cameraDisplayOrientation) {
case 0:
if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
@@ -2,7 +2,6 @@ package com.sw.plate.utils.arcface;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
@@ -47,6 +46,10 @@ public class FaceRectView extends View {
}
}
public int getRectColor() {
return paint.getColor();
}
public void clearFaceInfo() {
drawInfoList.clear();
postInvalidate();
@@ -216,7 +219,6 @@ public class FaceRectView extends View {
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(faceRectThickness);
paint.setColor(drawInfo.getColor());
// paint.setColor(Color.parseColor("#FF0000"));
paint.setAntiAlias(true);
Path mPath = new Path();
@@ -27,7 +27,7 @@ public class FileUtil {
}
public static boolean saveDataToFile(byte[] data, File file, boolean append) {
if (data == null) {
if (data == null){
return false;
}
File parentFile = file.getParentFile();
@@ -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;
@@ -350,11 +350,11 @@ public class FaceHelper implements FaceListener {
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;
@@ -430,7 +430,7 @@ public class FaceHelper implements FaceListener {
if (!contained) {
RecognizeInfo recognizeInfo = recognizeInfoMap.remove(key);
if (recognizeInfo != null) {
recognizeCallback.onNoticeChanged("leave");
recognizeCallback.onNoticeChanged("");
// 人脸离开时,通知特征提取线程,避免一直等待活体结果
synchronized (recognizeInfo.getWaitLock()) {
recognizeInfo.getWaitLock().notifyAll();
@@ -660,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);
}
}
@@ -847,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));
}
@@ -1065,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;
@@ -1108,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;
@@ -1133,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);
}
@@ -2,7 +2,6 @@ package com.sw.plate.utils.arcface.face.constants;
/**
* 人脸识别中可能出现的状态
*
* @author
*/
public @interface RequestFeatureStatus {
@@ -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;
@@ -169,7 +169,6 @@ public class RecognizeConfiguration {
this.enableImageQuality = val;
return this;
}
public Builder enableFaceAreaLimit(boolean val) {
this.enableFaceAreaLimit = val;
return this;
@@ -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();
}
}
}
@@ -73,7 +73,7 @@ public interface FaceDao {
Long insert(FaceEntity faceEntity);
@Insert(onConflict = OnConflictStrategy.IGNORE)
List<Long> insert(List<FaceEntity> items);
void insert(List<FaceEntity> items);
/**
* 获取已注册的人脸数
@@ -91,4 +91,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);
}
@@ -52,12 +52,39 @@ public class FaceEntity implements Parcelable {
/**
* 会员编号
*/
@ColumnInfo(name = "cardNo")
@ColumnInfo(name = "card_no")
private String cardNo;
@Ignore
private int trackId;//人脸追踪ID
@ColumnInfo(name = "user_idd")
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;
@@ -65,6 +92,7 @@ public class FaceEntity implements Parcelable {
registerTime = System.currentTimeMillis();
}
@Ignore
public FaceEntity(FaceEntity faceEntity) {
this.faceId = faceEntity.faceId;
this.userName = faceEntity.userName;
@@ -73,9 +101,14 @@ public class FaceEntity implements Parcelable {
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();
@@ -84,6 +117,10 @@ public class FaceEntity implements Parcelable {
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>() {
@@ -162,7 +199,37 @@ public class FaceEntity implements Parcelable {
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() {
@@ -178,6 +245,10 @@ public class FaceEntity implements Parcelable {
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
@@ -195,12 +266,16 @@ public class FaceEntity implements Parcelable {
TextUtils.equals(this.imagePath, that.imagePath) &&
Arrays.equals(featureData, that.featureData) &&
TextUtils.equals(this.userType, that.userType) &&
TextUtils.equals(this.cardNo, that.cardNo);
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, cardNo);
int result = Objects.hash(faceId, registerTime, userName, imagePath, userType, cardNo, userId, userFaceId, member, faceUpdateTimestamp);
result = 31 * result + Arrays.hashCode(featureData);
return result;
}
@@ -33,6 +33,7 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
@@ -60,6 +61,10 @@ public class FaceServer {
faceRegisterInfoList = new ArrayList<>();
}
public FaceEngine getFaceEngine() {
return faceEngine;
}
public static FaceServer getInstance() {
if (faceServer == null) {
synchronized (FaceServer.class) {
@@ -123,16 +128,16 @@ public class FaceServer {
*/
public void initFaceList(final Context context, FaceEngine faceEngine, final OnInitFinishedCallback onInitFinishedCallback, boolean recognize) {
Disposable disposable = Observable.create((ObservableOnSubscribe<Integer>) emitter -> {
if (recognize) {
List<FaceEntity> faceEntityList = FaceDatabase.getInstance(context).faceDao().getAllFaces();
registerFaceFeatureInfoListFromDb(faceEngine, faceEntityList);
emitter.onNext(faceEntityList.size());
} else {
faceRegisterInfoList = FaceDatabase.getInstance(context).faceDao().getAllFaces();
emitter.onNext(faceRegisterInfoList == null ? 0 : faceRegisterInfoList.size());
}
emitter.onComplete();
}).subscribeOn(Schedulers.io())
if (recognize) {
List<FaceEntity> faceEntityList = FaceDatabase.getInstance(context).faceDao().getAllFaces();
registerFaceFeatureInfoListFromDb(faceEngine, faceEntityList);
emitter.onNext(faceEntityList.size());
} else {
faceRegisterInfoList = FaceDatabase.getInstance(context).faceDao().getAllFaces();
emitter.onNext(faceRegisterInfoList == null ? 0 : faceRegisterInfoList.size());
}
emitter.onComplete();
}).subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(size -> {
@@ -164,6 +169,7 @@ public class FaceServer {
}
@SuppressLint("CheckResult")
public synchronized int clearAllFaces() {
if (faceRegisterInfoList != null) {
@@ -340,6 +346,7 @@ public class FaceServer {
*/
public void registerFaceFeatureInfoFromDb(FaceEntity faceEntity, FaceEngine faceEngine) {
if (faceEntity != null && faceEngine != null) {
Log.i(TAG, "registerFaceFeature:" + faceEntity.getFaceId()+"==="+ Arrays.toString(faceEntity.getFeatureData()));
FaceFeatureInfo faceFeatureInfo = new FaceFeatureInfo((int) faceEntity.getFaceId(), faceEntity.getFeatureData());
int res = faceEngine.registerFaceFeature(faceFeatureInfo);
Log.i(TAG, "registerFaceFeature:" + res);
@@ -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;
@@ -160,10 +164,6 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
private MutableLiveData<CompareResult> recognizeUserId = new MutableLiveData<>();
/**
* 检测ir活体前,是否需要更新faceData
*/
private boolean needUpdateFaceData;
/**
* 当前活体检测的检测类型
*/
@@ -270,6 +270,7 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
*/
public void init(PreviewConfig previewConfig1) {
Context context = App.getContext();
// 档口机业务:支持外部传入 PreviewConfig(指定 RGB/IR 摄像头 id 及旋转角度),未传时走默认配置
if (previewConfig1 != null) {
previewConfig = previewConfig1;
} else {
@@ -281,9 +282,11 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
Integer.parseInt(ConfigUtil.getIrCameraAdditionalRotation(context))
);
}
// 填入在设置界面设置好的配置信息
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);
@@ -299,22 +302,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) {
@@ -324,16 +327,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);
}
@@ -341,8 +341,10 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
}
public void addFace(FaceEntity faceEntity) {
if (frEngine != null)
if (frEngine != null) {
Log.e("performSync", "addFace=" + faceEntity.getRegisterTime());
FaceServer.getInstance().registerFaceFeatureInfoFromDb(faceEntity, frEngine);
}
}
public void refreshFaceList() {
@@ -356,19 +358,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);
}
}
}
@@ -400,6 +408,7 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
/**
* 重置人脸识别状态:清空上次识别结果及 FaceHelper 内部的 recognizeInfoMap
* 使下一帧进入时能重新触发识别流程。
* 适用场景:短时间内再次识别、点击重试按钮等需要重新开始识别的时机。
*/
public void resetFaceState() {
// 清空粘性 LiveData,防止旧结果被重新投递给 observer
@@ -468,7 +477,7 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
.ftEngine(ftEngine)
.frEngine(frEngine)
.flEngine(flEngine)
.needUpdateFaceData(needUpdateFaceData)
.maskEngine(maskEngine)
.frQueueSize(maxDetectFaceNum)
.flQueueSize(maxDetectFaceNum)
.previewSize(previewSize)
@@ -484,13 +493,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);
}
recognizeUserId.postValue(compareResult);
boolean isAdded = false;
List<CompareResult> compareResults = compareResultList.getValue();
if (compareResults != null && !compareResults.isEmpty()) {
@@ -512,6 +528,8 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
getFaceItemEventMutableLiveData().postValue(new FaceItemEvent(compareResults.size() - 1, EventType.INSERTED));
}
}
} else {
recognizeUserId.postValue(compareResult);
}
});
}
@@ -599,14 +617,9 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
if (recognizeStatus != null) {
if (recognizeStatus == RequestFeatureStatus.FAILED) {
color = RecognizeColor.COLOR_FAILED;
}else if (recognizeStatus == RequestFeatureStatus.SUCCEED) {
}
if (recognizeStatus == RequestFeatureStatus.SUCCEED) {
color = RecognizeColor.COLOR_SUCCESS;
} else if (recognizeStatus == RequestFeatureStatus.TO_RETRY) {
color = RecognizeColor.COLOR_UNKNOWN;
//需要重试
// FaceEntity faceEntity = new FaceEntity("2",null, null);
// CompareResult result = new CompareResult(faceEntity, 0.0f);
// recognizeUserId.postValue(result);
}
}
if (liveness != null && liveness == LivenessInfo.NOT_ALIVE) {