refactor(face): 移除样本筛选轨迹功能及相关枚举

- 删除 FaceRecognitionFilterReason 枚举类
- 移除 SampleFilterTrace 类及其相关逻辑
- 简化样本筛选方法,去除轨迹记录功能- 更新 FaceServiceImpl 和 TaskFaceServiceImpl 中的调用逻辑
- 移除 SearchFaceRespVo 中的 filterTrace 字段- 清理无用的 import语句和相关代码引用
This commit is contained in:
2025-10-29 19:26:35 +08:00
parent b6bde4ad62
commit 745943fc23
8 changed files with 19 additions and 138 deletions

View File

@@ -1,7 +1,6 @@
package com.ycwl.basic.model.mobile.face;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ycwl.basic.model.pc.face.enums.FaceRecognitionFilterReason;
import lombok.Data;
import java.util.Date;

View File

@@ -19,10 +19,7 @@ public class FaceRecognitionUpdateReq {
*/
private List<Long> manualAcceptedSampleIds;
/**
* 用户主动排除的样本ID列表。
*/
private List<Long> manualRejectedSampleIds;
/**
* 是否强制重新走一次识别流程。

View File

@@ -1,22 +0,0 @@
package com.ycwl.basic.model.pc.face.enums;
import lombok.Getter;
/**
* 标识系统在过滤识别样本时的原因,便于前端渲染提示。
*/
@Getter
public enum FaceRecognitionFilterReason {
SCORE_BELOW_THRESHOLD("score_below_threshold", "置信度低于阈值"),
OUT_OF_TIME_RANGE("out_of_time_range", "超出景区时间范围限制"),
DEVICE_PHOTO_LIMIT("device_photo_limit", "超过设备照片数量限制"),
MANUAL_REJECTED("manual_rejected", "已被手动排除");
private final String code;
private final String description;
FaceRecognitionFilterReason(String code, String description) {
this.code = code;
this.description = description;
}
}

View File

@@ -1,34 +0,0 @@
package com.ycwl.basic.model.task.resp;
import com.ycwl.basic.model.pc.face.enums.FaceRecognitionFilterReason;
import lombok.Data;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 样本筛选的轨迹信息,记录最终样本集合及各过滤原因。
*/
@Data
public class SampleFilterTrace {
private List<Long> acceptedSampleIds;
private Map<Long, EnumSet<FaceRecognitionFilterReason>> filteredReasonMap = new HashMap<>();
public void addReason(Long sampleId, FaceRecognitionFilterReason reason) {
if (sampleId == null || reason == null) {
return;
}
filteredReasonMap.computeIfAbsent(sampleId, key -> EnumSet.noneOf(FaceRecognitionFilterReason.class))
.add(reason);
}
public Map<Long, EnumSet<FaceRecognitionFilterReason>> getFilteredReasonMap() {
if (filteredReasonMap == null) {
return Collections.emptyMap();
}
return filteredReasonMap;
}
}

View File

@@ -11,5 +11,4 @@ public class SearchFaceRespVo {
private String searchResultJson;
private Float firstMatchRate;
private boolean lowThreshold;
private SampleFilterTrace filterTrace;
}

View File

@@ -46,7 +46,7 @@ import com.ycwl.basic.model.pc.template.resp.TemplateRespVO;
import com.ycwl.basic.model.pc.video.entity.MemberVideoEntity;
import com.ycwl.basic.model.pc.video.entity.VideoEntity;
import com.ycwl.basic.model.repository.TaskUpdateResult;
import com.ycwl.basic.model.task.resp.SampleFilterTrace;
import com.ycwl.basic.model.task.resp.SearchFaceRespVo;
import com.ycwl.basic.repository.DeviceRepository;
import com.ycwl.basic.repository.FaceRepository;
@@ -1196,13 +1196,9 @@ public class FaceServiceImpl implements FaceService {
// 3. 应用后置筛选逻辑
if (mergedResult.getSampleListIds() != null && !mergedResult.getSampleListIds().isEmpty()) {
List<FaceSampleEntity> allFaceSampleList = faceSampleMapper.listByIds(mergedResult.getSampleListIds());
SampleFilterTrace filterTrace = faceService.applySampleFiltersWithTrace(
List<Long> filteredSampleIds = faceService.applySampleFilters(
mergedResult.getSampleListIds(), allFaceSampleList, scenicConfig);
List<Long> filteredSampleIds = filterTrace.getAcceptedSampleIds() == null
? Collections.emptyList()
: filterTrace.getAcceptedSampleIds();
mergedResult.setSampleListIds(filteredSampleIds);
mergedResult.setFilterTrace(filterTrace);
log.debug("应用后置筛选:原始样本数={}, 筛选后样本数={}",
allFaceSampleList.size(), filteredSampleIds.size());
}
@@ -1280,8 +1276,6 @@ public class FaceServiceImpl implements FaceService {
List<Long> currentAccepted = parseMatchSampleIds(face.getMatchSampleIds());
List<Long> manualAccepted = Optional.ofNullable(req.getManualAcceptedSampleIds()).orElse(Collections.emptyList());
List<Long> manualRejected = Optional.ofNullable(req.getManualRejectedSampleIds()).orElse(Collections.emptyList());
Set<Long> manualRejectedSet = new HashSet<>(manualRejected);
LinkedHashSet<Long> finalSampleSet = new LinkedHashSet<>();
manualAccepted.stream()
@@ -1289,10 +1283,9 @@ public class FaceServiceImpl implements FaceService {
.forEach(finalSampleSet::add);
currentAccepted.stream()
.filter(Objects::nonNull)
.filter(id -> !manualRejectedSet.contains(id))
.forEach(finalSampleSet::add);
boolean hasManualChange = !manualAccepted.isEmpty() || !manualRejectedSet.isEmpty();
boolean hasManualChange = !manualAccepted.isEmpty();
List<Long> finalSampleList = new ArrayList<>(finalSampleSet);
boolean needsUpdate = hasManualChange && !finalSampleList.equals(currentAccepted);

View File

@@ -3,7 +3,6 @@ package com.ycwl.basic.service.task;
import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter;
import com.ycwl.basic.integration.common.manager.ScenicConfigManager;
import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity;
import com.ycwl.basic.model.task.resp.SampleFilterTrace;
import com.ycwl.basic.model.task.resp.SearchFaceRespVo;
import org.springframework.web.multipart.MultipartFile;
@@ -29,16 +28,4 @@ public interface TaskFaceService {
List<Long> applySampleFilters(List<Long> acceptedSampleIds,
List<FaceSampleEntity> allFaceSampleList,
ScenicConfigManager scenicConfig);
/**
* 带过滤轨迹的样本筛选逻辑。
*
* @param acceptedSampleIds 已接受的样本ID列表
* @param allFaceSampleList 所有人脸样本实体列表
* @param scenicConfig 景区配置
* @return 包含最终样本及过滤原因的轨迹对象
*/
SampleFilterTrace applySampleFiltersWithTrace(List<Long> acceptedSampleIds,
List<FaceSampleEntity> allFaceSampleList,
ScenicConfigManager scenicConfig);
}

View File

@@ -23,7 +23,7 @@ import com.ycwl.basic.model.pc.device.entity.DeviceConfigEntity;
import com.ycwl.basic.model.pc.device.entity.DeviceEntity;
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
import com.ycwl.basic.model.pc.face.resp.FaceRespVO;
import com.ycwl.basic.model.pc.face.enums.FaceRecognitionFilterReason;
import com.ycwl.basic.model.pc.faceDetectLog.entity.FaceDetectLog;
import com.ycwl.basic.model.pc.faceDetectLog.resp.MatchLocalRecord;
import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity;
@@ -31,7 +31,7 @@ import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity;
import com.ycwl.basic.model.pc.source.entity.SourceEntity;
import com.ycwl.basic.model.task.resp.SearchFaceRespVo;
import com.ycwl.basic.model.task.resp.SampleFilterTrace;
import com.ycwl.basic.repository.DeviceRepository;
import com.ycwl.basic.repository.FaceRepository;
import com.ycwl.basic.repository.ScenicRepository;
@@ -271,18 +271,8 @@ public class TaskFaceServiceImpl implements TaskFaceService {
idIndexMap.put(allFaceSampleIds.get(i), i);
}
allFaceSampleList.sort(Comparator.comparing(sample -> idIndexMap.get(sample.getId())));
SampleFilterTrace filterTrace = applySampleFiltersWithTrace(acceptFaceSampleIds, allFaceSampleList, scenicConfig);
List<Long> finalAcceptedSampleIds = filterTrace.getAcceptedSampleIds() == null
? Collections.emptyList()
: filterTrace.getAcceptedSampleIds();
Set<Long> initialAcceptedSet = acceptFaceSampleIds == null
? Collections.emptySet()
: new HashSet<>(acceptFaceSampleIds);
for (Long sampleId : allFaceSampleIds) {
if (!finalAcceptedSampleIds.contains(sampleId) && !initialAcceptedSet.contains(sampleId)) {
filterTrace.addReason(sampleId, FaceRecognitionFilterReason.SCORE_BELOW_THRESHOLD);
}
}
List<Long> finalAcceptedSampleIds = applySampleFilters(acceptFaceSampleIds, allFaceSampleList, scenicConfig);
List<MatchLocalRecord> collect = new ArrayList<>();
for (SearchFaceResultItem item : records) {
MatchLocalRecord record = new MatchLocalRecord();
@@ -314,7 +304,6 @@ public class TaskFaceServiceImpl implements TaskFaceService {
}
respVo.setFirstMatchRate(response.getFirstMatchRate());
respVo.setSampleListIds(finalAcceptedSampleIds);
respVo.setFilterTrace(filterTrace);
return respVo;
} catch (Exception e) {
logEntity.setMatchRawResult("识别错误,错误为:["+e.getLocalizedMessage()+"]");
@@ -410,23 +399,11 @@ public class TaskFaceServiceImpl implements TaskFaceService {
public List<Long> applySampleFilters(List<Long> acceptedSampleIds,
List<FaceSampleEntity> allFaceSampleList,
ScenicConfigManager scenicConfig) {
SampleFilterTrace trace = applySampleFiltersWithTrace(acceptedSampleIds, allFaceSampleList, scenicConfig);
List<Long> result = trace.getAcceptedSampleIds();
return result == null ? Collections.emptyList() : result;
}
@Override
public SampleFilterTrace applySampleFiltersWithTrace(List<Long> acceptedSampleIds,
List<FaceSampleEntity> allFaceSampleList,
ScenicConfigManager scenicConfig) {
SampleFilterTrace trace = new SampleFilterTrace();
if (acceptedSampleIds == null || acceptedSampleIds.isEmpty()) {
trace.setAcceptedSampleIds(acceptedSampleIds == null ? Collections.emptyList() : new ArrayList<>(acceptedSampleIds));
return trace;
return acceptedSampleIds == null ? Collections.emptyList() : new ArrayList<>(acceptedSampleIds);
}
if (allFaceSampleList == null || allFaceSampleList.isEmpty()) {
trace.setAcceptedSampleIds(new ArrayList<>(acceptedSampleIds));
return trace;
return new ArrayList<>(acceptedSampleIds);
}
Map<Long, FaceSampleEntity> sampleMap = allFaceSampleList.stream()
@@ -437,29 +414,26 @@ public class TaskFaceServiceImpl implements TaskFaceService {
.collect(Collectors.toCollection(ArrayList::new));
if (workingList.isEmpty()) {
trace.setAcceptedSampleIds(Collections.emptyList());
return trace;
return Collections.emptyList();
}
Integer tourMinutes = scenicConfig != null ? scenicConfig.getInteger("tour_time") : null;
if (tourMinutes != null && tourMinutes > 0) {
workingList = filterSampleIdsByTimeRangeWithTrace(workingList, sampleMap, tourMinutes, trace);
workingList = filterSampleIdsByTimeRange(workingList, sampleMap, tourMinutes);
log.debug("应用时间范围筛选:游览时间限制={}分钟,过滤后数量={}", tourMinutes, workingList.size());
} else {
log.debug("时间范围逻辑:景区未设置游览时间限制");
}
workingList = applyDevicePhotoLimitWithTrace(workingList, sampleMap, trace);
trace.setAcceptedSampleIds(new ArrayList<>(workingList));
workingList = applyDevicePhotoLimit(workingList, sampleMap);
log.debug("样本筛选完成:原始数量={}, 最终数量={}", acceptedSampleIds.size(), workingList.size());
return trace;
return new ArrayList<>(workingList);
}
private List<Long> filterSampleIdsByTimeRangeWithTrace(List<Long> acceptedSampleIds,
private List<Long> filterSampleIdsByTimeRange(List<Long> acceptedSampleIds,
Map<Long, FaceSampleEntity> sampleMap,
int tourMinutes,
SampleFilterTrace trace) {
int tourMinutes) {
if (acceptedSampleIds == null || acceptedSampleIds.isEmpty()) {
return Collections.emptyList();
}
@@ -486,8 +460,6 @@ public class TaskFaceServiceImpl implements TaskFaceService {
Date createAt = sample.getCreateAt();
if (createAt.after(startDate) && createAt.before(endDate)) {
result.add(sampleId);
} else {
trace.addReason(sampleId, FaceRecognitionFilterReason.OUT_OF_TIME_RANGE);
}
}
@@ -496,9 +468,8 @@ public class TaskFaceServiceImpl implements TaskFaceService {
return result;
}
private List<Long> applyDevicePhotoLimitWithTrace(List<Long> acceptedSampleIds,
Map<Long, FaceSampleEntity> sampleMap,
SampleFilterTrace trace) {
private List<Long> applyDevicePhotoLimit(List<Long> acceptedSampleIds,
Map<Long, FaceSampleEntity> sampleMap) {
if (acceptedSampleIds == null || acceptedSampleIds.isEmpty()) {
return Collections.emptyList();
}
@@ -557,15 +528,6 @@ public class TaskFaceServiceImpl implements TaskFaceService {
}
retainedSampleIds.addAll(retainedForDevice);
if (trace != null) {
Set<Long> retainedSet = new HashSet<>(retainedForDevice);
for (Long sampleId : deviceSampleIds) {
if (!retainedSet.contains(sampleId)) {
trace.addReason(sampleId, FaceRecognitionFilterReason.DEVICE_PHOTO_LIMIT);
}
}
}
}
List<Long> resultIds = new ArrayList<>(acceptedSampleIds.size());