feat(face): 支持多种人脸搜索结果合并模式
All checks were successful
ZhenTu-BE/pipeline/head This commit looks good

- 新增face_select_post_mode配置,支持三种合并模式
    - 模式0:并集合并(默认),收集所有搜索结果样本ID
    - 模式1:交集合并,只保留所有结果中共有的样本ID
    - 模式2:直接使用用户选择的样本,跳过搜索过程
- 重构mergeSearchResults方法,增加mergeMode参数- 添加computeIntersection方法计算交集逻辑
- 添加createDirectResult方法处理模式2的直接结果
- 更新日志记录,便于追踪不同模式的执行情况
-保持向后兼容,旧调用方式默认使用并集模式
This commit is contained in:
2025-10-24 18:21:17 +08:00
parent fb637bc7db
commit 0a57eeaeef

View File

@@ -1133,33 +1133,47 @@ public class FaceServiceImpl implements FaceService {
throw new BaseException("人脸识别服务不可用,请稍后再试");
}
// 2. 对每个faceSample进行人脸搜索
List<SearchFaceRespVo> searchResults = new ArrayList<>();
for (FaceSampleEntity faceSample : faceSamples) {
try {
SearchFaceRespVo result = faceService.searchFace(faceBodyAdapter,
String.valueOf(face.getScenicId()),
faceSample.getFaceUrl(),
"自定义人脸匹配");
if (result != null) {
searchResults.add(result);
}
} catch (Exception e) {
log.warn("人脸样本搜索失败,faceSampleId={}, faceUrl={}",
faceSample.getId(), faceSample.getFaceUrl(), e);
// 继续处理其他样本,不中断整个流程
}
}
// 获取face_select_post_mode配置,默认为0(并集)
Integer faceSelectPostMode = scenicConfig != null ? scenicConfig.getInteger("face_select_post_mode", 0) : 0;
log.debug("face_select_post_mode配置值: {}", faceSelectPostMode);
if (searchResults.isEmpty()) {
log.warn("所有人脸样本搜索都失败,faceId={}, faceSampleIds={}", faceId, faceSampleIds);
throw new BaseException("人脸识别失败,请重试");
}
// 3. 整合多个搜索结果
SearchFaceRespVo mergedResult = mergeSearchResults(searchResults);
SearchFaceRespVo mergedResult;
// 4. 应用后置筛选逻辑
// 2. 根据face_select_post_mode决定搜索策略
if (Integer.valueOf(2).equals(faceSelectPostMode)) {
// 模式2:不搜索,直接使用用户选择的faceSampleIds
log.debug("使用模式2:直接使用用户选择的人脸样本,不进行搜索");
mergedResult = createDirectResult(faceSampleIds);
} else {
// 模式0(并集)和模式1(交集):需要进行搜索
// 2.1 对每个faceSample进行人脸搜索
List<SearchFaceRespVo> searchResults = new ArrayList<>();
for (FaceSampleEntity faceSample : faceSamples) {
try {
SearchFaceRespVo result = faceService.searchFace(faceBodyAdapter,
String.valueOf(face.getScenicId()),
faceSample.getFaceUrl(),
"自定义人脸匹配");
if (result != null) {
searchResults.add(result);
}
} catch (Exception e) {
log.warn("人脸样本搜索失败,faceSampleId={}, faceUrl={}",
faceSample.getId(), faceSample.getFaceUrl(), e);
// 继续处理其他样本,不中断整个流程
}
}
if (searchResults.isEmpty()) {
log.warn("所有人脸样本搜索都失败,faceId={}, faceSampleIds={}", faceId, faceSampleIds);
throw new BaseException("人脸识别失败,请重试");
}
// 2.2 根据模式整合多个搜索结果
mergedResult = mergeSearchResults(searchResults, faceSelectPostMode);
}
// 3. 应用后置筛选逻辑
if (mergedResult.getSampleListIds() != null && !mergedResult.getSampleListIds().isEmpty()) {
List<FaceSampleEntity> allFaceSampleList = faceSampleMapper.listByIds(mergedResult.getSampleListIds());
List<Long> filteredSampleIds = faceService.applySampleFilters(mergedResult.getSampleListIds(), allFaceSampleList, scenicConfig);
@@ -1217,22 +1231,33 @@ public class FaceServiceImpl implements FaceService {
}
/**
* 合并多个搜索结果
* 合并多个搜索结果(兼容老版本,默认使用并集模式)
*/
private SearchFaceRespVo mergeSearchResults(List<SearchFaceRespVo> searchResults) {
return mergeSearchResults(searchResults, 0);
}
/**
* 合并多个搜索结果
*
* @param searchResults 搜索结果列表
* @param mergeMode 合并模式:0-并集,1-交集
* @return 合并后的结果
*/
private SearchFaceRespVo mergeSearchResults(List<SearchFaceRespVo> searchResults, Integer mergeMode) {
SearchFaceRespVo mergedResult = new SearchFaceRespVo();
// 收集所有样本ID并去重
Set<Long> allSampleIds = new LinkedHashSet<>();
if (searchResults == null || searchResults.isEmpty()) {
return mergedResult;
}
List<String> allSearchJsons = new ArrayList<>();
float maxScore = 0f;
float maxFirstMatchRate = 0f;
boolean hasLowThreshold = false;
// 收集基础信息
for (SearchFaceRespVo result : searchResults) {
if (result.getSampleListIds() != null) {
allSampleIds.addAll(result.getSampleListIds());
}
if (result.getSearchResultJson() != null) {
allSearchJsons.add(result.getSearchResultJson());
}
@@ -1247,17 +1272,92 @@ public class FaceServiceImpl implements FaceService {
}
}
mergedResult.setSampleListIds(new ArrayList<>(allSampleIds));
// 根据合并模式处理样本ID
List<Long> finalSampleIds;
if (Integer.valueOf(1).equals(mergeMode)) {
// 模式1:交集 - 只保留所有搜索结果中都出现的样本ID
finalSampleIds = computeIntersection(searchResults);
log.debug("使用交集模式合并搜索结果,交集样本数: {}", finalSampleIds.size());
} else {
// 模式0:并集(默认) - 收集所有样本ID并去重
Set<Long> allSampleIds = new LinkedHashSet<>();
for (SearchFaceRespVo result : searchResults) {
if (result.getSampleListIds() != null) {
allSampleIds.addAll(result.getSampleListIds());
}
}
finalSampleIds = new ArrayList<>(allSampleIds);
log.debug("使用并集模式合并搜索结果,并集样本数: {}", finalSampleIds.size());
}
mergedResult.setSampleListIds(finalSampleIds);
mergedResult.setSearchResultJson(String.join("|", allSearchJsons));
mergedResult.setScore(maxScore);
mergedResult.setFirstMatchRate(maxFirstMatchRate);
mergedResult.setLowThreshold(hasLowThreshold);
log.debug("合并搜索结果完成,样本数: {}", allSampleIds.size());
log.debug("合并搜索结果完成,模式={}, 最终样本数: {}", mergeMode, finalSampleIds.size());
return mergedResult;
}
/**
* 计算多个搜索结果的交集
* 返回在所有搜索结果中都出现的样本ID
*/
private List<Long> computeIntersection(List<SearchFaceRespVo> searchResults) {
if (searchResults == null || searchResults.isEmpty()) {
return new ArrayList<>();
}
// 过滤掉空结果
List<List<Long>> validSampleLists = searchResults.stream()
.filter(result -> result.getSampleListIds() != null && !result.getSampleListIds().isEmpty())
.map(SearchFaceRespVo::getSampleListIds)
.toList();
if (validSampleLists.isEmpty()) {
return new ArrayList<>();
}
// 如果只有一个有效结果,直接返回
if (validSampleLists.size() == 1) {
return new ArrayList<>(validSampleLists.getFirst());
}
// 计算交集:从第一个列表开始,保留在所有其他列表中都出现的ID
Set<Long> intersection = new LinkedHashSet<>(validSampleLists.getFirst());
for (int i = 1; i < validSampleLists.size(); i++) {
intersection.retainAll(validSampleLists.get(i));
}
return new ArrayList<>(intersection);
}
/**
* 创建直接结果(模式2:不搜索,直接使用用户选择的faceSampleIds)
*
* @param faceSampleIds 用户选择的人脸样本ID列表
* @return 搜索结果对象
*/
private SearchFaceRespVo createDirectResult(List<Long> faceSampleIds) {
SearchFaceRespVo result = new SearchFaceRespVo();
// 直接使用用户选择的faceSampleIds作为结果
result.setSampleListIds(new ArrayList<>(faceSampleIds));
// 设置默认值
result.setScore(1.0f);
result.setFirstMatchRate(1.0f);
result.setLowThreshold(false);
result.setSearchResultJson("");
log.debug("创建直接结果,样本数: {}", faceSampleIds.size());
return result;
}
/**
* 记录自定义人脸匹配次数到Redis
*