|
|
@@ -0,0 +1,309 @@
|
|
|
+package com.yingyangfly.core.api.impl;
|
|
|
+
|
|
|
+import com.yingyangfly.core.bean.CreatePersonResult;
|
|
|
+import com.yingyangfly.core.bean.FaceSearchResult;
|
|
|
+import lombok.Data;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+import org.springframework.boot.context.properties.ConfigurationProperties;
|
|
|
+import com.tencentcloudapi.common.Credential;
|
|
|
+import com.tencentcloudapi.common.exception.TencentCloudSDKException;
|
|
|
+import com.tencentcloudapi.common.profile.ClientProfile;
|
|
|
+import com.tencentcloudapi.common.profile.HttpProfile;
|
|
|
+import com.tencentcloudapi.iai.v20200303.IaiClient;
|
|
|
+import com.tencentcloudapi.iai.v20200303.models.*;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import java.util.Arrays;
|
|
|
+
|
|
|
+@ConfigurationProperties(prefix = "tencent.cloudapi")
|
|
|
+@Component
|
|
|
+@Data
|
|
|
+@Slf4j
|
|
|
+public class FaceContrastServer {
|
|
|
+
|
|
|
+ private static final Logger logger = LoggerFactory.getLogger(FaceContrastServer.class);
|
|
|
+
|
|
|
+ private String secretId;
|
|
|
+
|
|
|
+ private String secretKey;
|
|
|
+
|
|
|
+ private String endpoint;
|
|
|
+
|
|
|
+ private String region;
|
|
|
+
|
|
|
+ private String groupId;
|
|
|
+
|
|
|
+ // 搜索配置
|
|
|
+ private static final Long MAX_FACE_NUM = 1L; // 最多检测人脸数
|
|
|
+ private static final Long MAX_PERSON_NUM = 1L; // 最多返回人员数
|
|
|
+ private static final Long QUALITY_CONTROL = 1L; // 质量控制等级 1:较高
|
|
|
+ private static final Long NEED_PERSON_INFO = 1L; // 是否需要返回人员信息
|
|
|
+ private static final float FACE_MATCH_THRESHOLD = 80.0f; // 人脸匹配阈值(0-100)
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 在人员库中搜索最相似的人员
|
|
|
+ * @param base64Image Base64格式的人脸图片
|
|
|
+ * @return FaceSearchResult 搜索结果
|
|
|
+ */
|
|
|
+ public FaceSearchResult searchPersonByFace(String base64Image) {
|
|
|
+ return searchPersonByFace(base64Image, new String[]{groupId});
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 在人员库中搜索最相似的人员
|
|
|
+ * @param base64Image Base64格式的人脸图片
|
|
|
+ * @param groupIds 要搜索的人员库ID数组
|
|
|
+ * @return FaceSearchResult 搜索结果
|
|
|
+ */
|
|
|
+ public FaceSearchResult searchPersonByFace(String base64Image, String[] groupIds) {
|
|
|
+ FaceSearchResult result = new FaceSearchResult();
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 1. 初始化客户端
|
|
|
+ Credential cred = new Credential(secretId, secretKey);
|
|
|
+ HttpProfile httpProfile = new HttpProfile();
|
|
|
+ httpProfile.setEndpoint(endpoint);
|
|
|
+ ClientProfile clientProfile = new ClientProfile();
|
|
|
+ clientProfile.setHttpProfile(httpProfile);
|
|
|
+ IaiClient client = new IaiClient(cred, region, clientProfile);
|
|
|
+
|
|
|
+ // 2. 构建搜索请求
|
|
|
+ SearchFacesRequest req = new SearchFacesRequest();
|
|
|
+
|
|
|
+ // 设置要搜索的人员库列表
|
|
|
+ req.setGroupIds(groupIds);
|
|
|
+
|
|
|
+ // 设置Base64图片
|
|
|
+ req.setImage(base64Image);
|
|
|
+
|
|
|
+ // 设置搜索参数
|
|
|
+ req.setMaxFaceNum(MAX_FACE_NUM);
|
|
|
+ req.setMaxPersonNum(MAX_PERSON_NUM);
|
|
|
+ req.setQualityControl(QUALITY_CONTROL);
|
|
|
+ req.setNeedPersonInfo(NEED_PERSON_INFO);
|
|
|
+ req.setFaceMatchThreshold(FACE_MATCH_THRESHOLD);
|
|
|
+
|
|
|
+ logger.info("开始人脸搜索,人员库: {}", Arrays.toString(groupIds));
|
|
|
+
|
|
|
+ // 3. 调用接口
|
|
|
+ SearchFacesResponse resp = client.SearchFaces(req);
|
|
|
+
|
|
|
+ // 4. 处理响应
|
|
|
+ processSearchResponse(resp, result);
|
|
|
+
|
|
|
+ } catch (TencentCloudSDKException e) {
|
|
|
+ logger.error("腾讯云人脸搜索失败: {}", e.getMessage(), e);
|
|
|
+ result.setSuccess(false);
|
|
|
+ result.setErrorCode(e.getErrorCode());
|
|
|
+ result.setErrorMessage(e.getMessage());
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("人脸搜索异常: {}", e.getMessage(), e);
|
|
|
+ result.setSuccess(false);
|
|
|
+ result.setErrorMessage("系统异常: " + e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理搜索结果
|
|
|
+ */
|
|
|
+ private void processSearchResponse(SearchFacesResponse resp, FaceSearchResult result) {
|
|
|
+ // 检查人脸检测结果
|
|
|
+ if (resp == null) {
|
|
|
+ result.setSuccess(false);
|
|
|
+ result.setErrorCode("NO_FACE_DETECTED");
|
|
|
+ result.setErrorMessage("未检测到人脸");
|
|
|
+ logger.warn("未在图片中检测到人脸");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查人脸质量
|
|
|
+ if (resp.getFaceModelVersion() != null) {
|
|
|
+ result.setFaceModelVersion(resp.getFaceModelVersion());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取人脸框信息
|
|
|
+ if (resp.getResults() != null && resp.getResults().length > 0) {
|
|
|
+ FaceRect rect = resp.getResults()[0].getFaceRect();
|
|
|
+ result.setFaceRect(rect);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查搜索结果
|
|
|
+ if (resp.getResults() == null || resp.getResults().length == 0) {
|
|
|
+ result.setSuccess(false);
|
|
|
+ result.setErrorCode("NO_MATCH_FOUND");
|
|
|
+ result.setErrorMessage("在人员库中未找到匹配的人员");
|
|
|
+ logger.info("在人员库中未找到匹配的人员");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取第一个(最相似)的结果
|
|
|
+ Result respResult = resp.getResults()[0];
|
|
|
+
|
|
|
+ // 设置结果
|
|
|
+ result.setSuccess(true);
|
|
|
+ result.setPersonId(respResult.getCandidates()[0].getPersonId());
|
|
|
+ result.setPersonName(respResult.getCandidates()[0].getPersonName());
|
|
|
+
|
|
|
+ // 腾讯云返回的分数是0-100,转换为0-1的小数
|
|
|
+ float similarity = respResult.getCandidates()[0].getScore() != null ? respResult.getCandidates()[0].getScore() / 100.0f : 0f;
|
|
|
+ result.setSimilarity(similarity);
|
|
|
+
|
|
|
+ // 设置人脸ID
|
|
|
+ if (respResult.getCandidates()[0].getFaceId() != null) {
|
|
|
+ result.setFaceId(respResult.getCandidates()[0].getFaceId());
|
|
|
+ }
|
|
|
+
|
|
|
+ logger.info("人脸搜索成功: 人员ID={}, 姓名={}, 相似度={}%",
|
|
|
+ respResult.getCandidates()[0].getPersonId(),
|
|
|
+ respResult.getCandidates()[0].getPersonName(),
|
|
|
+ String.format("%.2f", similarity * 100));
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建人员(注册人脸)
|
|
|
+ * @param personId 人员ID
|
|
|
+ * @param personName 人员姓名
|
|
|
+ * @param base64Image Base64人脸图片
|
|
|
+ * @return 创建结果
|
|
|
+ */
|
|
|
+ public CreatePersonResult createPerson(String personId, String personName, String base64Image) {
|
|
|
+ CreatePersonResult result = new CreatePersonResult();
|
|
|
+
|
|
|
+ // 1. 输入校验
|
|
|
+ if (!validateInput(personId, personName, base64Image, result)) {
|
|
|
+ return result; // 校验失败,直接返回
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 2. 初始化客户端 (与您的代码一致)
|
|
|
+ Credential cred = new Credential(secretId, secretKey);
|
|
|
+ HttpProfile httpProfile = new HttpProfile();
|
|
|
+ httpProfile.setEndpoint(endpoint);
|
|
|
+ ClientProfile clientProfile = new ClientProfile();
|
|
|
+ clientProfile.setHttpProfile(httpProfile);
|
|
|
+ IaiClient client = new IaiClient(cred, region, clientProfile);
|
|
|
+
|
|
|
+ // 3. 构建请求,并考虑添加防重复参数
|
|
|
+ CreatePersonRequest req = new CreatePersonRequest();
|
|
|
+ req.setGroupId(groupId);
|
|
|
+ req.setPersonId(personId);
|
|
|
+ req.setPersonName(personName);
|
|
|
+ req.setImage(base64Image);
|
|
|
+ req.setQualityControl(1L); // 启用图片质量控制
|
|
|
+ // 建议添加:防止同一人重复入库(根据业务选择级别,如2)
|
|
|
+ req.setUniquePersonControl(2L);
|
|
|
+ req.setNeedRotateDetection(1L);
|
|
|
+
|
|
|
+ // 4. 发送请求
|
|
|
+ CreatePersonResponse resp = client.CreatePerson(req);
|
|
|
+
|
|
|
+ // 5. 处理成功响应
|
|
|
+ result.setSuccess(true);
|
|
|
+ result.setFaceId(resp.getFaceId());
|
|
|
+ result.setFaceRect(resp.getFaceRect());
|
|
|
+ logger.info("创建人员成功: ID={}, 姓名={}, 人脸ID={}", personId, personName, resp.getFaceId());
|
|
|
+
|
|
|
+ } catch (TencentCloudSDKException e) { // 更精确地捕获SDK异常
|
|
|
+ logger.error("调用腾讯云SDK失败,错误码: {}, 错误信息: {}", e.getErrorCode(), e.getMessage(), e);
|
|
|
+ result.setSuccess(false);
|
|
|
+ result.setErrorMessage("系统服务调用异常: " + e.getErrorCode());
|
|
|
+ } catch (Exception e) { // 捕获其他未知异常
|
|
|
+ logger.error("创建人员时发生未知异常: {}", e.getMessage(), e);
|
|
|
+ result.setSuccess(false);
|
|
|
+ result.setErrorMessage("系统内部异常");
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 存在人员添加人脸
|
|
|
+ * @param personId
|
|
|
+ * @param imageBase64Array
|
|
|
+ * @param imageUrlArray
|
|
|
+ * @return
|
|
|
+ * @throws TencentCloudSDKException
|
|
|
+ */
|
|
|
+ public CreateFaceResponse addFace(String personId, String[] imageBase64Array,
|
|
|
+ String[] imageUrlArray) throws TencentCloudSDKException {
|
|
|
+ // 2. 初始化客户端 (与您的代码一致)
|
|
|
+ Credential cred = new Credential(secretId, secretKey);
|
|
|
+ HttpProfile httpProfile = new HttpProfile();
|
|
|
+ httpProfile.setEndpoint(endpoint);
|
|
|
+ ClientProfile clientProfile = new ClientProfile();
|
|
|
+ clientProfile.setHttpProfile(httpProfile);
|
|
|
+ IaiClient client = new IaiClient(cred, region, clientProfile);
|
|
|
+ CreateFaceRequest req = new CreateFaceRequest();
|
|
|
+
|
|
|
+ req.setPersonId(personId);
|
|
|
+
|
|
|
+ // 设置图片:Images.N 和 Urls.N 必须提供一个[citation:2]
|
|
|
+ if (imageUrlArray != null && imageUrlArray.length > 0) {
|
|
|
+ req.setUrls(imageUrlArray);
|
|
|
+ } else if (imageBase64Array != null && imageBase64Array.length > 0) {
|
|
|
+ req.setImages(imageBase64Array);
|
|
|
+ } else {
|
|
|
+ throw new IllegalArgumentException("必须提供Images或Urls其中之一");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 可选参数:人脸相似度阈值,只有超过此值的人脸才能添加成功,默认60[citation:2]
|
|
|
+ req.setFaceMatchThreshold(60.0F);
|
|
|
+ req.setQualityControl(4L);
|
|
|
+
|
|
|
+ CreateFaceResponse resp = client.CreateFace(req);
|
|
|
+ System.out.println("AddFace 成功,添加了 " + resp.getSucFaceNum() + " 张人脸。请求ID: " + resp.getRequestId());
|
|
|
+ return resp;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 删除人脸
|
|
|
+ * @param personId
|
|
|
+ * @param faceIds
|
|
|
+ * @return
|
|
|
+ * @throws TencentCloudSDKException
|
|
|
+ */
|
|
|
+ public DeleteFaceResponse deleteFaces(String personId, String[] faceIds)
|
|
|
+ throws TencentCloudSDKException {
|
|
|
+
|
|
|
+ // 2. 初始化客户端 (与您的代码一致)
|
|
|
+ Credential cred = new Credential(secretId, secretKey);
|
|
|
+ HttpProfile httpProfile = new HttpProfile();
|
|
|
+ httpProfile.setEndpoint(endpoint);
|
|
|
+ ClientProfile clientProfile = new ClientProfile();
|
|
|
+ clientProfile.setHttpProfile(httpProfile);
|
|
|
+ IaiClient client = new IaiClient(cred, region, clientProfile);
|
|
|
+ DeleteFaceRequest req = new DeleteFaceRequest();
|
|
|
+
|
|
|
+ // 设置必填参数
|
|
|
+ req.setPersonId(personId);
|
|
|
+ req.setFaceIds(faceIds);
|
|
|
+
|
|
|
+ // 调用删除人脸接口
|
|
|
+ DeleteFaceResponse resp = client.DeleteFace(req);
|
|
|
+ return resp;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private boolean validateInput(String personId, String personName, String base64Image, CreatePersonResult result) {
|
|
|
+ // 校验人员ID格式(只支持英文、数字、-%@#&_)
|
|
|
+ if (personId == null || !personId.matches("^[A-Za-z0-9\\-%@#&_]+$")) {
|
|
|
+ result.setSuccess(false);
|
|
|
+ result.setErrorMessage("人员ID格式非法,只支持英文、数字、-%@#&_");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ // 校验Base64图片大小(不超过5M)[citation:3]
|
|
|
+ if (base64Image == null || base64Image.length() > 5 * 1024 * 1024) {
|
|
|
+ result.setSuccess(false);
|
|
|
+ result.setErrorMessage("人脸图片数据不能为空且大小不能超过5M");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ // 可以添加更多校验...
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+}
|