百度人脸识别

This commit is contained in:
Jerry Yan 2025-04-05 14:48:21 +08:00
parent ab0f38cd97
commit 117a13cc2c
8 changed files with 478 additions and 3 deletions

12
pom.xml
View File

@ -70,6 +70,11 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -181,6 +186,13 @@
<artifactId>aliyun-java-sdk-facebody</artifactId>
<version>2.0.12</version>
</dependency>
<!-- 百度智能云人脸识别 -->
<dependency>
<groupId>com.baidu.aip</groupId>
<artifactId>java-sdk</artifactId>
<version>4.16.19</version>
</dependency>
</dependencies>
<build>

View File

@ -1,6 +1,7 @@
package com.ycwl.basic.facebody;
import com.ycwl.basic.facebody.adapter.AliFaceBodyAdapter;
import com.ycwl.basic.facebody.adapter.BceFaceBodyAdapter;
import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter;
import com.ycwl.basic.facebody.enums.FaceBodyAdapterType;
import com.ycwl.basic.facebody.exceptions.FaceBodyUnsupportedException;
@ -25,6 +26,8 @@ public class FaceBodyFactory {
switch (type) {
case ALI:
return new AliFaceBodyAdapter();
case BCE:
return new BceFaceBodyAdapter();
default:
throw new FaceBodyUnsupportedException("不支持的Adapter类型");
}

View File

@ -24,6 +24,7 @@ import com.ycwl.basic.ratelimiter.FixedRateLimiter;
import com.ycwl.basic.ratelimiter.IRateLimiter;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.Map;
@ -35,8 +36,9 @@ import java.util.stream.Collectors;
public class AliFaceBodyAdapter implements IFaceBodyAdapter {
private static final Map<String, IRateLimiter> addEntityLimiters = new ConcurrentHashMap<>();
private static final Map<String, IRateLimiter> addFaceLimiters = new ConcurrentHashMap<>();
private static final Map<String, IRateLimiter> searchFaceLimiters = new ConcurrentHashMap<>();
private static final Map<String, IRateLimiter> addDbLimiters = new ConcurrentHashMap<>();
private static final Map<String, IRateLimiter> listDbLimiters = new ConcurrentHashMap<>();
private static final Map<String, IRateLimiter> searchFaceLimiters = new ConcurrentHashMap<>();
private static final Map<String, IRateLimiter> deleteDbLimiters = new ConcurrentHashMap<>();
private static final Map<String, IRateLimiter> deleteEntityLimiters = new ConcurrentHashMap<>();
@ -74,6 +76,11 @@ public class AliFaceBodyAdapter implements IFaceBodyAdapter {
addFaceLimiters.put(config.getAccessKeyId(), new FixedRateLimiter(600, TimeUnit.MILLISECONDS));
}
return addFaceLimiters.get(config.getAccessKeyId());
case LIST_DB:
if (listDbLimiters.get(config.getAccessKeyId()) == null) {
listDbLimiters.put(config.getAccessKeyId(), new FixedRateLimiter(500, TimeUnit.MILLISECONDS));
}
return listDbLimiters.get(config.getAccessKeyId());
case SEARCH_FACE:
if (searchFaceLimiters.get(config.getAccessKeyId()) == null) {
searchFaceLimiters.put(config.getAccessKeyId(), new FixedRateLimiter(200, TimeUnit.MILLISECONDS));
@ -244,7 +251,9 @@ public class AliFaceBodyAdapter implements IFaceBodyAdapter {
} else {
listFaceEntitiesRequest.setLimit(200);
}
listFaceEntitiesRequest.setEntityIdPrefix(prefix);
if (StringUtils.isNotEmpty(prefix)) {
listFaceEntitiesRequest.setEntityIdPrefix(prefix);
}
try (ClientWrapper clientWrapper = getClient()) {
IAcsClient client = clientWrapper.getClient();
try {
@ -275,6 +284,7 @@ public class AliFaceBodyAdapter implements IFaceBodyAdapter {
SearchFaceResponse response = client.getAcsResponse(request);
List<SearchFaceResponse.Data.MatchListItem> matchList = response.getData().getMatchList();
if (matchList.isEmpty()) {
resp.setOriginalFaceScore(0f);
return resp;
}
SearchFaceResponse.Data.MatchListItem matchItem = matchList.get(0);
@ -326,6 +336,7 @@ public class AliFaceBodyAdapter implements IFaceBodyAdapter {
ADD_DB,
ADD_ENTITY,
ADD_FACE,
LIST_DB,
SEARCH_FACE,
DELETE_DB,
DELETE_ENTITY,

View File

@ -0,0 +1,377 @@
package com.ycwl.basic.facebody.adapter;
import com.baidu.aip.face.AipFace;
import com.ycwl.basic.facebody.entity.AddFaceResp;
import com.ycwl.basic.facebody.entity.AliFaceBodyConfig;
import com.ycwl.basic.facebody.entity.BceFaceBodyConfig;
import com.ycwl.basic.facebody.entity.SearchFaceResp;
import com.ycwl.basic.facebody.entity.SearchFaceResultItem;
import com.ycwl.basic.utils.ratelimiter.FixedRateLimiter;
import com.ycwl.basic.utils.ratelimiter.IRateLimiter;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONArray;
import org.json.JSONObject;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@Slf4j
public class BceFaceBodyAdapter implements IFaceBodyAdapter {
protected static final Map<String, AipFace> clients = new ConcurrentHashMap<>();
private static final Map<String, IRateLimiter> addEntityLimiters = new ConcurrentHashMap<>();
private static final Map<String, IRateLimiter> addFaceLimiters = new ConcurrentHashMap<>();
private static final Map<String, IRateLimiter> addDbLimiters = new ConcurrentHashMap<>();
private static final Map<String, IRateLimiter> listDbLimiters = new ConcurrentHashMap<>();
private static final Map<String, IRateLimiter> listFaceLimiters = new ConcurrentHashMap<>();
private static final Map<String, IRateLimiter> searchFaceLimiters = new ConcurrentHashMap<>();
private static final Map<String, IRateLimiter> deleteDbLimiters = new ConcurrentHashMap<>();
private static final Map<String, IRateLimiter> deleteEntityLimiters = new ConcurrentHashMap<>();
private static final Map<String, IRateLimiter> deleteFaceLimiters = new ConcurrentHashMap<>();
private BceFaceBodyConfig config;
public boolean setConfig(BceFaceBodyConfig config) {
this.config = config;
return true;
}
@Override
public boolean loadConfig(Map<String, String> _config) {
BceFaceBodyConfig config = new BceFaceBodyConfig();
config.setAppId(_config.get("appId"));
config.setApiKey(_config.get("apiKey"));
config.setSecretKey(_config.get("secretKey"));
config.setAddQps(Float.parseFloat(_config.get("addQps")));
config.setSearchQps(Float.parseFloat(_config.get("searchQps")));
this.config = config;
return true;
}
@Override
public boolean addFaceDb(String dbName) {
IRateLimiter addDbLimiter = getLimiter(LOCK_TYPE.ADD_DB);
try {
AipFace client = getClient();
HashMap<String, String> options = new HashMap<>();
try {
addDbLimiter.acquire();
} catch (InterruptedException ignored) {
}
JSONObject response = client.groupAdd(dbName, options);
if (response.getInt("error_code") == 0) {
return true;
} else {
log.warn("创建人脸库失败!{}", response);
return false;
}
} catch (Exception e) {
log.error("创建人脸库失败!", e);
return false;
}
}
@Override
public boolean deleteFaceDb(String dbName) {
IRateLimiter deleteDbLimiter = getLimiter(LOCK_TYPE.DELETE_DB);
try {
AipFace client = getClient();
HashMap<String, String> options = new HashMap<>();
try {
deleteDbLimiter.acquire();
} catch (InterruptedException ignored) {
}
JSONObject response = client.groupDelete(dbName, options);
if (response.getInt("error_code") == 0) {
return true;
} else {
log.warn("删除人脸库失败!{}", response);
return false;
}
} catch (Exception e) {
log.error("删除人脸库失败!", e);
return false;
}
}
@Override
public List<String> listFaceDb() {
IRateLimiter listDbLimiter = getLimiter(LOCK_TYPE.LIST_DB);
try {
AipFace client = getClient();
HashMap<String, String> options = new HashMap<>();
options.put("start", "0");
options.put("length", "1000");
try {
listDbLimiter.acquire();
} catch (InterruptedException ignored) {
}
JSONObject response = client.getGroupList(options);
if (response.getInt("error_code") == 0) {
JSONObject resultObj = response.getJSONObject("result");
if (resultObj != null) {
JSONArray data = resultObj.getJSONArray("group_id_list");
List<String> result = new ArrayList<>();
for (int i = 0; i < data.length(); i++) {
result.add(data.getString(i));
}
return result;
} else {
return Collections.emptyList();
}
} else {
log.warn("获取人脸库列表失败!{}", response);
return Collections.emptyList();
}
} catch (Exception e) {
log.error("获取人脸库列表失败!", e);
return Collections.emptyList();
}
}
@Override
public AddFaceResp addFace(String dbName, String entityId, String faceUrl, String extData) {
IRateLimiter addEntityLimiter = getLimiter(LOCK_TYPE.ADD_FACE);
try {
AipFace client = getClient();
HashMap<String, String> options = new HashMap<>();
options.put("user_info", extData);
options.put("quality_control", "LOW");
options.put("action_type", "REPLACE");
try {
addEntityLimiter.acquire();
} catch (InterruptedException ignored) {
}
JSONObject response = client.addUser(faceUrl, "URL", dbName, entityId, options);
if (response.getInt("error_code") == 0) {
AddFaceResp resp = new AddFaceResp();
resp.setScore(100f);
return resp;
} else {
log.warn("创建人脸失败!{}", response);
return null;
}
} catch (Exception e) {
log.error("创建人脸失败!", e);
return null;
}
}
@Override
public boolean deleteFace(String dbName, String entityId) {
IRateLimiter deleteFaceLimiter = getLimiter(LOCK_TYPE.DELETE_FACE);
try {
AipFace client = getClient();
HashMap<String, String> options = new HashMap<>();
List<String> tokenList = listUserFace(dbName, entityId);
AtomicInteger count = new AtomicInteger(0);
tokenList.forEach(faceToken -> {
try {
try {
deleteFaceLimiter.acquire();
} catch (InterruptedException ignored) {
}
JSONObject response = client.faceDelete(entityId, dbName, faceToken, options);
if (response.getInt("error_code") != 0) {
log.warn("删除人脸失败!{}", response);
} else {
count.incrementAndGet();
}
} catch (Exception e) {
log.error("删除人脸失败!", e);
}
});
return Integer.valueOf(count.get()).equals(tokenList.size());
} catch (Exception e) {
log.error("删除人脸失败!", e);
return false;
}
}
@Override
public List<String> listFace(String dbName, String prefix, Integer offset, Integer size) {
IRateLimiter listFaceLimiter = getLimiter(LOCK_TYPE.LIST_FACE);
try {
AipFace client = getClient();
HashMap<String, String> options = new HashMap<>();
options.put("start", offset == null ? "0" : offset.toString());
options.put("length", size == null ? "1000" : size.toString());
try {
listFaceLimiter.acquire();
} catch (InterruptedException ignored) {
}
JSONObject response = client.getGroupUsers(dbName, options);
if (response.getInt("error_code") == 0) {
JSONObject resultObj = response.getJSONObject("result");
if (resultObj != null) {
JSONArray data = resultObj.getJSONArray("user_id_list");
List<String> result = new ArrayList<>();
for (int i = 0; i < data.length(); i++) {
result.add(data.getString(i));
}
return result;
} else {
return Collections.emptyList();
}
} else {
log.warn("获取人脸列表失败!{}", response);
return Collections.emptyList();
}
} catch (Exception e) {
log.error("获取人脸列表失败!", e);
return Collections.emptyList();
}
}
public List<String> listUserFace(String dbName, String entityId) {
try {
AipFace client = getClient();
HashMap<String, String> options = new HashMap<>();
JSONObject response = client.faceGetlist(entityId, dbName, options);
if (response.getInt("error_code") == 0) {
JSONObject resultObj = response.getJSONObject("result");
if (resultObj != null) {
JSONArray faceList = resultObj.getJSONArray("face_list");
List<String> result = new ArrayList<>();
for (int i = 0; i < faceList.length(); i++) {
JSONObject jsonObject = faceList.getJSONObject(i);
result.add(jsonObject.getString("face_token"));
}
return result;
} else {
return Collections.emptyList();
}
} else {
log.warn("获取人脸列表失败!{}", response);
return Collections.emptyList();
}
} catch (Exception e) {
log.error("获取人脸列表失败!", e);
return Collections.emptyList();
}
}
@Override
public SearchFaceResp searchFace(String dbName, String faceUrl) {
IRateLimiter searchFaceLimiter = getLimiter(LOCK_TYPE.SEARCH_FACE);
SearchFaceResp resp = new SearchFaceResp();
try {
AipFace client = getClient();
HashMap<String, Object> options = new HashMap<>();
options.put("quality_control", "LOW");
options.put("max_user_num", "50");
try {
searchFaceLimiter.acquire();
} catch (InterruptedException ignored) {
}
JSONObject response = client.search(faceUrl, "URL", dbName, options);
if (response.getInt("error_code") == 0) {
resp.setOriginalFaceScore(100f);
JSONArray userList = response.getJSONArray("user_list");
List<SearchFaceResultItem> result = new ArrayList<>();
for (int i = 0; i < userList.length(); i++) {
JSONObject user = userList.getJSONObject(i);
SearchFaceResultItem item = new SearchFaceResultItem();
item.setDbName(dbName);
item.setFaceId(user.getString("user_id"));
item.setExtData(user.getString("user_info"));
item.setScore(user.getBigDecimal("score").divide(BigDecimal.valueOf(100), 6, RoundingMode.HALF_UP).floatValue());
result.add(item);
}
resp.setResult(result);
if (!result.isEmpty()) {
resp.setFirstMatchRate(result.get(0).getScore());
}
return resp;
} else {
resp.setOriginalFaceScore(0f);
return resp;
}
} catch (Exception e) {
log.error("搜索人脸失败!", e);
return null;
}
}
public AipFace getClient() {
if (clients.containsKey(config.getAppId())) {
return clients.get(config.getAppId());
}
synchronized (clients) {
if (clients.containsKey(config.getAppId())) {
return clients.get(config.getAppId());
}
AipFace client = new AipFace(config.getAppId(), config.getApiKey(), config.getSecretKey());
client.setConnectionTimeoutInMillis(5000);
client.setSocketTimeoutInMillis(60000);
clients.put(config.getAppId(), client);
return client;
}
}
private IRateLimiter getLimiter(LOCK_TYPE type) {
switch (type) {
case ADD_DB:
if (addDbLimiters.get(config.getAppId()) == null) {
addDbLimiters.put(config.getAppId(), new FixedRateLimiter(100, TimeUnit.MILLISECONDS));
}
return addDbLimiters.get(config.getAppId());
case ADD_FACE:
if (addFaceLimiters.get(config.getAppId()) == null) {
addFaceLimiters.put(config.getAppId(), new FixedRateLimiter(config.getAddQps()));
}
return addFaceLimiters.get(config.getAppId());
case LIST_DB:
if (listDbLimiters.get(config.getAppId()) == null) {
listDbLimiters.put(config.getAppId(), new FixedRateLimiter(100, TimeUnit.MILLISECONDS));
}
return listDbLimiters.get(config.getAppId());
case LIST_FACE:
if (listFaceLimiters.get(config.getAppId()) == null) {
listFaceLimiters.put(config.getAppId(), new FixedRateLimiter(100, TimeUnit.MILLISECONDS));
}
return listFaceLimiters.get(config.getAppId());
case SEARCH_FACE:
if (searchFaceLimiters.get(config.getAppId()) == null) {
searchFaceLimiters.put(config.getAppId(), new FixedRateLimiter(config.getSearchQps()));
}
return searchFaceLimiters.get(config.getAppId());
case DELETE_DB:
if (deleteDbLimiters.get(config.getAppId()) == null) {
deleteDbLimiters.put(config.getAppId(), new FixedRateLimiter(100, TimeUnit.MILLISECONDS));
}
return deleteDbLimiters.get(config.getAppId());
case DELETE_ENTITY:
if (deleteEntityLimiters.get(config.getAppId()) == null) {
deleteEntityLimiters.put(config.getAppId(), new FixedRateLimiter(100, TimeUnit.MILLISECONDS));
}
return deleteEntityLimiters.get(config.getAppId());
case DELETE_FACE:
if (deleteFaceLimiters.get(config.getAppId()) == null) {
deleteFaceLimiters.put(config.getAppId(), new FixedRateLimiter(100, TimeUnit.MILLISECONDS));
}
return deleteFaceLimiters.get(config.getAppId());
default:
return new FixedRateLimiter(500, TimeUnit.MILLISECONDS);
}
}
protected enum LOCK_TYPE {
ADD_DB,
ADD_FACE,
LIST_DB,
LIST_FACE,
SEARCH_FACE,
DELETE_DB,
DELETE_ENTITY,
DELETE_FACE,
}
}

View File

@ -0,0 +1,12 @@
package com.ycwl.basic.facebody.entity;
import lombok.Data;
@Data
public class BceFaceBodyConfig {
private String appId;
private String apiKey;
private String secretKey;
private float addQps = 2.0f;
private float searchQps = 2.0f;
}

View File

@ -4,7 +4,8 @@ import lombok.Getter;
@Getter
public enum FaceBodyAdapterType {
ALI("ALI")
ALI("ALI"),
BCE("BCE"),
;
private final String code;

View File

@ -9,6 +9,16 @@ public class FixedRateLimiter implements IRateLimiter {
private final Semaphore semaphore = new Semaphore(1);
private final ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(1);
public FixedRateLimiter(float maxRequestsPerSecond) {
int rate = Float.valueOf(1000000 / maxRequestsPerSecond).intValue();
scheduler.scheduleAtFixedRate(() -> {
if (semaphore.availablePermits() < 1) {
semaphore.release(1);
}
}, rate, rate, TimeUnit.NANOSECONDS);
}
public FixedRateLimiter(int rate, TimeUnit timeUnit) {
// 启动一个线程每0.5秒释放一个许可
scheduler.scheduleAtFixedRate(() -> {

View File

@ -0,0 +1,49 @@
package com.ycwl.basic.facebody.adapter;
import com.ycwl.basic.facebody.entity.BceFaceBodyConfig;
import org.junit.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
public class BceFaceBodyAdapterTest {
private BceFaceBodyAdapter getAdapter() {
BceFaceBodyAdapter adapter = new BceFaceBodyAdapter();
BceFaceBodyConfig config = new BceFaceBodyConfig();
config.setAppId("118363478");
config.setApiKey("3rXrDdU4cHZqLS8ICFSYZKse");
config.setSecretKey("zgGFehERZKYXEiRQpqWs9AYchLxzXzYa");
adapter.setConfig(config);
return adapter;
}
@Test
public void testDbCreate() {
BceFaceBodyAdapter adapter = getAdapter();
boolean b = adapter.addFaceDb("test");
assertTrue(b);
boolean b0 = adapter.assureFaceDb("test");
assertTrue(b0);
boolean b1 = adapter.deleteFaceDb("test");
assertTrue(b1);
boolean b2 = adapter.assureFaceDb("test");
assertTrue(b2);
boolean b3 = adapter.deleteFaceDb("test");
assertTrue(b3);
}
@Test
public void testAddFace() {
BceFaceBodyAdapter adapter = getAdapter();
adapter.assureFaceDb("test");
adapter.addFace("test", "test", "https://frametour-assets.oss-cn-shanghai.aliyuncs.com/user-faces/user-face/c925d970-216a-4eff-b699-cd047c0f9088.jpg", "test");
List<String> strings = adapter.listUserFace("test", "test");
assertFalse(strings.isEmpty());
adapter.deleteFace("test", "test");
List<String> stringList = adapter.listUserFace("test", "test");
assertTrue(stringList.isEmpty());
List<String> listFace = adapter.listFace("test", null, 0, 10);
assertTrue(listFace.isEmpty());
}
}