diff --git a/lib_face/libs/arcsoft_face.jar b/lib_face/libs/arcsoft_face.jar index b2e827c..c95cd9d 100644 Binary files a/lib_face/libs/arcsoft_face.jar and b/lib_face/libs/arcsoft_face.jar differ diff --git a/lib_face/libs/armeabi-v7a/libarcsoft_face.so b/lib_face/libs/armeabi-v7a/libarcsoft_face.so index e37f157..4adb029 100644 Binary files a/lib_face/libs/armeabi-v7a/libarcsoft_face.so and b/lib_face/libs/armeabi-v7a/libarcsoft_face.so differ diff --git a/lib_face/libs/armeabi-v7a/libarcsoft_face_engine.so b/lib_face/libs/armeabi-v7a/libarcsoft_face_engine.so index 9a6e74c..4a0a59d 100644 Binary files a/lib_face/libs/armeabi-v7a/libarcsoft_face_engine.so and b/lib_face/libs/armeabi-v7a/libarcsoft_face_engine.so differ diff --git a/lib_face/src/main/java/com/sw/plate/utils/arcface/ConfigUtil.java b/lib_face/src/main/java/com/sw/plate/utils/arcface/ConfigUtil.java index a874581..df12861 100644 --- a/lib_face/src/main/java/com/sw/plate/utils/arcface/ConfigUtil.java +++ b/lib_face/src/main/java/com/sw/plate/utils/arcface/ConfigUtil.java @@ -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和业务代码 - *

+ * * 获取识别界面是否保留最大人脸 * * @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))); } diff --git a/lib_face/src/main/java/com/sw/plate/utils/arcface/ErrorCodeUtil.java b/lib_face/src/main/java/com/sw/plate/utils/arcface/ErrorCodeUtil.java index c8ba88a..591d4cb 100644 --- a/lib_face/src/main/java/com/sw/plate/utils/arcface/ErrorCodeUtil.java +++ b/lib_face/src/main/java/com/sw/plate/utils/arcface/ErrorCodeUtil.java @@ -27,7 +27,6 @@ public class ErrorCodeUtil { } return "unknown error"; } - /** * 将ArcSoftImageUtil错误码转换为对应的错误码常量名,便于理解 * TODO:目前每次都遍历,如果使用频繁,建议将Field缓存处理,避免每次都反射 diff --git a/lib_face/src/main/java/com/sw/plate/utils/arcface/FaceApi.java b/lib_face/src/main/java/com/sw/plate/utils/arcface/FaceApi.java index 4b3753e..bb2351a 100644 --- a/lib_face/src/main/java/com/sw/plate/utils/arcface/FaceApi.java +++ b/lib_face/src/main/java/com/sw/plate/utils/arcface/FaceApi.java @@ -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 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 insertIdList = faceDao.insert(list); - Log.d(TAG, "updateFaceData: count = " + insertIdList.size()); + faceDao.insert(list); List queryList = faceDao.getAllFaces(); Log.d(TAG, "updateFaceData: queryList.size = " + queryList.size()); } - public void updateFaceData2(int index, List 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 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 queryAllByUserName(String userName) { + return getFaceDao().queryAllByUserName(userName); + } + + public FaceDao getFaceDao() { + return FaceDatabase.getInstance(App.getContext()).faceDao(); + } + } diff --git a/lib_face/src/main/java/com/sw/plate/utils/arcface/FaceRectTransformer.java b/lib_face/src/main/java/com/sw/plate/utils/arcface/FaceRectTransformer.java index 7b34137..58c9d5d 100644 --- a/lib_face/src/main/java/com/sw/plate/utils/arcface/FaceRectTransformer.java +++ b/lib_face/src/main/java/com/sw/plate/utils/arcface/FaceRectTransformer.java @@ -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) { diff --git a/lib_face/src/main/java/com/sw/plate/utils/arcface/FaceRectView.java b/lib_face/src/main/java/com/sw/plate/utils/arcface/FaceRectView.java index c58ba6e..15c37d3 100644 --- a/lib_face/src/main/java/com/sw/plate/utils/arcface/FaceRectView.java +++ b/lib_face/src/main/java/com/sw/plate/utils/arcface/FaceRectView.java @@ -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(); diff --git a/lib_face/src/main/java/com/sw/plate/utils/arcface/FileUtil.java b/lib_face/src/main/java/com/sw/plate/utils/arcface/FileUtil.java index e0efc7e..977389a 100644 --- a/lib_face/src/main/java/com/sw/plate/utils/arcface/FileUtil.java +++ b/lib_face/src/main/java/com/sw/plate/utils/arcface/FileUtil.java @@ -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(); diff --git a/lib_face/src/main/java/com/sw/plate/utils/arcface/face/FaceHelper.java b/lib_face/src/main/java/com/sw/plate/utils/arcface/face/FaceHelper.java index 59f64eb..f23c24d 100644 --- a/lib_face/src/main/java/com/sw/plate/utils/arcface/face/FaceHelper.java +++ b/lib_face/src/main/java/com/sw/plate/utils/arcface/face/FaceHelper.java @@ -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); } diff --git a/lib_face/src/main/java/com/sw/plate/utils/arcface/face/constants/RequestFeatureStatus.java b/lib_face/src/main/java/com/sw/plate/utils/arcface/face/constants/RequestFeatureStatus.java index 08137d4..a63dc48 100644 --- a/lib_face/src/main/java/com/sw/plate/utils/arcface/face/constants/RequestFeatureStatus.java +++ b/lib_face/src/main/java/com/sw/plate/utils/arcface/face/constants/RequestFeatureStatus.java @@ -2,7 +2,6 @@ package com.sw.plate.utils.arcface.face.constants; /** * 人脸识别中可能出现的状态 - * * @author */ public @interface RequestFeatureStatus { diff --git a/lib_face/src/main/java/com/sw/plate/utils/arcface/face/model/CompareResult.java b/lib_face/src/main/java/com/sw/plate/utils/arcface/face/model/CompareResult.java index 2d33f8b..f78d7c2 100644 --- a/lib_face/src/main/java/com/sw/plate/utils/arcface/face/model/CompareResult.java +++ b/lib_face/src/main/java/com/sw/plate/utils/arcface/face/model/CompareResult.java @@ -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; diff --git a/lib_face/src/main/java/com/sw/plate/utils/arcface/face/model/RecognizeConfiguration.java b/lib_face/src/main/java/com/sw/plate/utils/arcface/face/model/RecognizeConfiguration.java index ee22a9b..4382eca 100644 --- a/lib_face/src/main/java/com/sw/plate/utils/arcface/face/model/RecognizeConfiguration.java +++ b/lib_face/src/main/java/com/sw/plate/utils/arcface/face/model/RecognizeConfiguration.java @@ -169,7 +169,6 @@ public class RecognizeConfiguration { this.enableImageQuality = val; return this; } - public Builder enableFaceAreaLimit(boolean val) { this.enableFaceAreaLimit = val; return this; diff --git a/lib_face/src/main/java/com/sw/plate/utils/arcface/facedb/FaceDatabase.java b/lib_face/src/main/java/com/sw/plate/utils/arcface/facedb/FaceDatabase.java index ddb985f..f8b5948 100644 --- a/lib_face/src/main/java/com/sw/plate/utils/arcface/facedb/FaceDatabase.java +++ b/lib_face/src/main/java/com/sw/plate/utils/arcface/facedb/FaceDatabase.java @@ -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(); } } } diff --git a/lib_face/src/main/java/com/sw/plate/utils/arcface/facedb/dao/FaceDao.java b/lib_face/src/main/java/com/sw/plate/utils/arcface/facedb/dao/FaceDao.java index 50091bf..2926276 100644 --- a/lib_face/src/main/java/com/sw/plate/utils/arcface/facedb/dao/FaceDao.java +++ b/lib_face/src/main/java/com/sw/plate/utils/arcface/facedb/dao/FaceDao.java @@ -73,7 +73,7 @@ public interface FaceDao { Long insert(FaceEntity faceEntity); @Insert(onConflict = OnConflictStrategy.IGNORE) - List insert(List items); + void insert(List 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 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); } diff --git a/lib_face/src/main/java/com/sw/plate/utils/arcface/facedb/entity/FaceEntity.java b/lib_face/src/main/java/com/sw/plate/utils/arcface/facedb/entity/FaceEntity.java index e0e4387..3dc95f5 100644 --- a/lib_face/src/main/java/com/sw/plate/utils/arcface/facedb/entity/FaceEntity.java +++ b/lib_face/src/main/java/com/sw/plate/utils/arcface/facedb/entity/FaceEntity.java @@ -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 CREATOR = new Creator() { @@ -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; } diff --git a/lib_face/src/main/java/com/sw/plate/utils/arcface/faceserver/FaceServer.java b/lib_face/src/main/java/com/sw/plate/utils/arcface/faceserver/FaceServer.java index da36a0e..c6fdfa1 100644 --- a/lib_face/src/main/java/com/sw/plate/utils/arcface/faceserver/FaceServer.java +++ b/lib_face/src/main/java/com/sw/plate/utils/arcface/faceserver/FaceServer.java @@ -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) emitter -> { - if (recognize) { - List 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 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); diff --git a/lib_face/src/main/java/com/sw/plate/utils/arcface/viewmodel/RecognizeViewModel.java b/lib_face/src/main/java/com/sw/plate/utils/arcface/viewmodel/RecognizeViewModel.java index ac55550..1317ddf 100644 --- a/lib_face/src/main/java/com/sw/plate/utils/arcface/viewmodel/RecognizeViewModel.java +++ b/lib_face/src/main/java/com/sw/plate/utils/arcface/viewmodel/RecognizeViewModel.java @@ -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 ftInitCode = new MutableLiveData<>(); private MutableLiveData frInitCode = new MutableLiveData<>(); private MutableLiveData flInitCode = new MutableLiveData<>(); + private MutableLiveData 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 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 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) {