You've already forked FrameTour-BE
feat(face): 支持多种人脸搜索结果合并模式
All checks were successful
ZhenTu-BE/pipeline/head This commit looks good
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:
@@ -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
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user