feat(service): 实现自定义人脸匹配功能

- 新增 matchCustomFaceId 方法,实现自定义人脸匹配逻辑
- 优化 mergeSearchResults 方法,合并多个搜索结果
- 在 TaskFaceService 接口中添加 applySampleFilters 方法
- 在 TaskFaceServiceImpl 中实现 applySampleFilters 方法
This commit is contained in:
2025-09-15 16:04:41 +08:00
parent 8c37f2bf2f
commit ccddab37ea
3 changed files with 163 additions and 1 deletions

View File

@@ -69,9 +69,11 @@ import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -948,7 +950,151 @@ public class FaceServiceImpl implements FaceService {
@Override
public void matchCustomFaceId(Long faceId, List<Long> faceSampleIds) {
// 参数验证
if (faceId == null) {
throw new IllegalArgumentException("faceId 不能为空");
}
if (faceSampleIds == null || faceSampleIds.isEmpty()) {
throw new IllegalArgumentException("faceSampleIds 不能为空");
}
log.debug("开始自定义人脸匹配:faceId={}, faceSampleIds={}", faceId, faceSampleIds);
try {
// 1. 获取基础数据
FaceEntity face = faceRepository.getFace(faceId);
if (face == null) {
log.warn("人脸不存在,faceId: {}", faceId);
throw new BaseException("人脸不存在");
}
List<FaceSampleEntity> faceSamples = faceSampleMapper.listByIds(faceSampleIds);
if (faceSamples.isEmpty()) {
log.warn("未找到指定的人脸样本,faceSampleIds: {}", faceSampleIds);
throw new BaseException("未找到指定的人脸样本");
}
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(face.getScenicId());
IFaceBodyAdapter faceBodyAdapter = scenicService.getScenicFaceBodyAdapter(face.getScenicId());
if (faceBodyAdapter == null) {
log.error("无法获取人脸识别适配器,scenicId: {}", face.getScenicId());
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);
// 继续处理其他样本,不中断整个流程
}
}
if (searchResults.isEmpty()) {
log.warn("所有人脸样本搜索都失败,faceId={}, faceSampleIds={}", faceId, faceSampleIds);
throw new BaseException("人脸识别失败,请重试");
}
// 3. 整合多个搜索结果
SearchFaceRespVo mergedResult = mergeSearchResults(searchResults);
// 4. 应用后置筛选逻辑
if (mergedResult.getSampleListIds() != null && !mergedResult.getSampleListIds().isEmpty()) {
List<FaceSampleEntity> allFaceSampleList = faceSampleMapper.listByIds(mergedResult.getSampleListIds());
List<Long> filteredSampleIds = faceService.applySampleFilters(mergedResult.getSampleListIds(), allFaceSampleList, scenicConfig);
mergedResult.setSampleListIds(filteredSampleIds);
log.debug("应用后置筛选:原始样本数={}, 筛选后样本数={}", allFaceSampleList.size(), filteredSampleIds.size());
}
// 5. 更新人脸实体结果
updateFaceEntityResult(face, mergedResult, faceId);
// 6. 执行后续业务逻辑
List<Long> sampleListIds = mergedResult.getSampleListIds();
if (sampleListIds != null && !sampleListIds.isEmpty()) {
try {
List<MemberSourceEntity> memberSourceEntityList = processMemberSources(sampleListIds, face);
if (!memberSourceEntityList.isEmpty()) {
List<Long> freeSourceIds = processFreeSourceLogic(memberSourceEntityList, scenicConfig, false);
processBuyStatus(memberSourceEntityList, freeSourceIds, face.getMemberId(),
face.getScenicId(), faceId);
handleVideoRecreation(scenicConfig, memberSourceEntityList, faceId,
face.getMemberId(), sampleListIds, false);
sourceMapper.addRelations(memberSourceEntityList);
taskTaskService.autoCreateTaskByFaceId(face.getId());
log.info("自定义人脸匹配完成:faceId={}, 匹配样本数={}, 关联源文件数={}, 免费数={}",
faceId, sampleListIds.size(), memberSourceEntityList.size(), freeSourceIds.size());
}
} catch (Exception e) {
log.error("处理源文件关联失败,faceId={}", faceId, e);
// 源文件关联失败不影响主流程
}
} else {
log.warn("自定义人脸匹配无结果:faceId={}, faceSampleIds={}", faceId, faceSampleIds);
}
} catch (BaseException e) {
throw e;
} catch (Exception e) {
log.error("自定义人脸匹配处理异常,faceId={}, faceSampleIds={}", faceId, faceSampleIds, e);
throw new BaseException("自定义人脸匹配处理失败,请稍后重试");
}
}
/**
* 合并多个搜索结果
*/
private SearchFaceRespVo mergeSearchResults(List<SearchFaceRespVo> searchResults) {
SearchFaceRespVo mergedResult = new SearchFaceRespVo();
// 收集所有样本ID并去重
Set<Long> allSampleIds = new LinkedHashSet<>();
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());
}
if (result.getScore() > maxScore) {
maxScore = result.getScore();
}
if (result.getFirstMatchRate() > maxFirstMatchRate) {
maxFirstMatchRate = result.getFirstMatchRate();
}
if (result.isLowThreshold()) {
hasLowThreshold = true;
}
}
mergedResult.setSampleListIds(new ArrayList<>(allSampleIds));
mergedResult.setSearchResultJson(String.join("|", allSearchJsons));
mergedResult.setScore(maxScore);
mergedResult.setFirstMatchRate(maxFirstMatchRate);
mergedResult.setLowThreshold(hasLowThreshold);
log.debug("合并搜索结果完成,总样本数: {}", allSampleIds.size());
return mergedResult;
}
/**

View File

@@ -1,9 +1,13 @@
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.SearchFaceRespVo;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
public interface TaskFaceService {
SearchFaceRespVo searchFace(Long faceId);
@@ -13,4 +17,15 @@ public interface TaskFaceService {
boolean deleteFaceSample(Long scenicId, String dbName, String entityId);
boolean assureFaceDb(IFaceBodyAdapter faceBodyAdapter, String dbName);
/**
* 应用样本筛选逻辑
* @param acceptedSampleIds 已接受的样本ID列表
* @param allFaceSampleList 所有人脸样本实体列表
* @param scenicConfig 景区配置管理器
* @return 筛选后的样本ID列表
*/
List<Long> applySampleFilters(List<Long> acceptedSampleIds,
List<FaceSampleEntity> allFaceSampleList,
ScenicConfigManager scenicConfig);
}

View File

@@ -352,7 +352,8 @@ public class TaskFaceServiceImpl implements TaskFaceService {
* @param scenicConfig 景区配置,包含各种筛选策略的参数
* @return 筛选后的样本ID列表
*/
private List<Long> applySampleFilters(List<Long> acceptedSampleIds,
@Override
public List<Long> applySampleFilters(List<Long> acceptedSampleIds,
List<FaceSampleEntity> allFaceSampleList,
ScenicConfigManager scenicConfig) {
if (acceptedSampleIds == null || acceptedSampleIds.isEmpty()) {