You've already forked FrameTour-BE
Compare commits
3 Commits
841c89af04
...
0a57eeaeef
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a57eeaeef | |||
| fb637bc7db | |||
| ca2b812574 |
@@ -129,6 +129,21 @@ public class SourceRepository {
|
||||
sourcesMap.forEach((key, value) -> {
|
||||
value.removeIf(item -> !value.getFirst().equals(item));
|
||||
});
|
||||
} else {
|
||||
// 找出重复的templatePlaceholder,修改对应的sourceMap[key]中的list顺序为倒序
|
||||
Map<String, Long> placeholderCounts = templatePlaceholder.stream()
|
||||
.collect(Collectors.groupingBy(placeholder -> placeholder, Collectors.counting()));
|
||||
|
||||
placeholderCounts.entrySet().stream()
|
||||
.filter(entry -> entry.getValue() > 1)
|
||||
.map(Map.Entry::getKey)
|
||||
.forEach(duplicatePlaceholder -> {
|
||||
// 对于重复的占位符,找到对应的设备ID并倒序sourceMap中的列表
|
||||
if (sourcesMap.containsKey(duplicatePlaceholder)) {
|
||||
List<SourceEntity> sourceList = sourcesMap.get(duplicatePlaceholder);
|
||||
java.util.Collections.reverse(sourceList);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
boolean hasPPlaceholder = templatePlaceholder.stream().anyMatch(placeholder -> placeholder.startsWith("P"));
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -518,29 +518,92 @@ public class TaskFaceServiceImpl implements TaskFaceService {
|
||||
if (limitPhoto == null || limitPhoto <= 0) {
|
||||
List<Long> deviceSampleIds = deviceSamples.stream()
|
||||
.map(FaceSampleEntity::getId)
|
||||
.collect(Collectors.toList());
|
||||
.toList();
|
||||
resultIds.addAll(deviceSampleIds);
|
||||
log.debug("设备照片限制:设备ID={}, 无限制,保留{}张照片",
|
||||
deviceId, deviceSampleIds.size());
|
||||
} else {
|
||||
// 取前N张
|
||||
List<FaceSampleEntity> limitedSamples = deviceSamples.stream()
|
||||
.limit(limitPhoto)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<Long> limitedIds = limitedSamples.stream()
|
||||
.map(FaceSampleEntity::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
resultIds.addAll(limitedIds);
|
||||
log.debug("设备照片限制:设备ID={}, 限制={}张, 原始{}张,最终{}张",
|
||||
deviceId, limitPhoto, deviceSamples.size(), limitedIds.size());
|
||||
// 根据样本数量与限制的关系进行不同处理
|
||||
if (deviceSamples.size() > (limitPhoto + 2)) {
|
||||
// 样本数大于(limit_photo+2)时,去掉首尾
|
||||
List<Long> limitedIds = processDeviceSamples(deviceSamples, limitPhoto, true);
|
||||
resultIds.addAll(limitedIds);
|
||||
log.debug("设备照片限制:设备ID={}, 限制={}张, 原始{}张,去首尾后最终{}张",
|
||||
deviceId, limitPhoto, deviceSamples.size(), limitedIds.size());
|
||||
} else if (deviceSamples.size() > (limitPhoto + 1)) {
|
||||
// 样本数大于(limit_photo+1)时,去掉尾部
|
||||
List<Long> limitedIds = processDeviceSamples(deviceSamples, limitPhoto, false);
|
||||
resultIds.addAll(limitedIds);
|
||||
log.debug("设备照片限制:设备ID={}, 限制={}张, 原始{}张,去尾部后最终{}张",
|
||||
deviceId, limitPhoto, deviceSamples.size(), limitedIds.size());
|
||||
} else if (deviceSamples.size() > limitPhoto) {
|
||||
// 样本数大于limit_photo但小于等于(limit_photo+1),取前N张
|
||||
List<Long> limitedIds = deviceSamples.stream()
|
||||
.limit(limitPhoto)
|
||||
.map(FaceSampleEntity::getId)
|
||||
.toList();
|
||||
resultIds.addAll(limitedIds);
|
||||
log.debug("设备照片限制:设备ID={}, 限制={}张, 原始{}张,取前{}张",
|
||||
deviceId, limitPhoto, deviceSamples.size(), limitedIds.size());
|
||||
} else {
|
||||
// 样本数小于等于limit_photo,不做操作
|
||||
List<Long> limitedIds = deviceSamples.stream()
|
||||
.map(FaceSampleEntity::getId)
|
||||
.toList();
|
||||
resultIds.addAll(limitedIds);
|
||||
log.debug("设备照片限制:设备ID={}, 限制={}张, 原始{}张,无需筛选,保留全部",
|
||||
deviceId, limitPhoto, deviceSamples.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.info("设备照片数量限制筛选:原始样本数量={}, 筛选后数量={}",
|
||||
log.info("设备照片数量限制筛选:原始样本数量={}, 筛选后数量={}",
|
||||
acceptedSampleIds.size(), resultIds.size());
|
||||
|
||||
|
||||
return resultIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理设备样本,根据参数决定是否去掉首尾
|
||||
*
|
||||
* @param deviceSamples 设备样本列表
|
||||
* @param limitPhoto 限制数量
|
||||
* @param removeBoth 是否去掉首尾,true去掉首尾,false只去掉尾部
|
||||
* @return 处理后的样本ID列表
|
||||
*/
|
||||
private List<Long> processDeviceSamples(List<FaceSampleEntity> deviceSamples, int limitPhoto, boolean removeBoth) {
|
||||
// 创建原始排序的索引映射,用于后续恢复排序
|
||||
Map<Long, Integer> originalIndexMap = new HashMap<>();
|
||||
for (int i = 0; i < deviceSamples.size(); i++) {
|
||||
originalIndexMap.put(deviceSamples.get(i).getId(), i);
|
||||
}
|
||||
|
||||
// 按创建时间排序
|
||||
List<FaceSampleEntity> sortedByCreateTime = deviceSamples.stream()
|
||||
.sorted(Comparator.comparing(FaceSampleEntity::getCreateAt))
|
||||
.toList();
|
||||
|
||||
// 根据参数决定去掉首尾还是只去掉尾部
|
||||
List<FaceSampleEntity> filteredSamples;
|
||||
if (removeBoth && sortedByCreateTime.size() > 2) {
|
||||
// 去掉首尾
|
||||
filteredSamples = sortedByCreateTime.subList(1, sortedByCreateTime.size() - 1);
|
||||
} else if (!removeBoth && sortedByCreateTime.size() > 1) {
|
||||
// 只去掉尾部(最新的)
|
||||
filteredSamples = sortedByCreateTime.subList(0, sortedByCreateTime.size() - 1);
|
||||
} else {
|
||||
// 样本数量不足,无法去掉
|
||||
filteredSamples = sortedByCreateTime;
|
||||
}
|
||||
|
||||
// 恢复原排序并取限制数量
|
||||
List<FaceSampleEntity> finalSamples = filteredSamples.stream()
|
||||
.sorted(Comparator.comparing(sample -> originalIndexMap.get(sample.getId())))
|
||||
.limit(limitPhoto)
|
||||
.toList();
|
||||
|
||||
return finalSamples.stream()
|
||||
.map(FaceSampleEntity::getId)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user