net.gdface.facelog.FaceApiServiceManagement Maven / Gradle / Ivy
package net.gdface.facelog;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.commons.configuration2.HierarchicalConfiguration;
import org.apache.commons.configuration2.tree.ImmutableNode;
import com.google.common.base.Functions;
import com.google.common.base.Predicates;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.net.HostAndPort;
import net.gdface.facelog.DtalkServiceMenu.ExtractFeatureBaseFaceApi;
import net.gdface.facelog.common.FaceApiCmdAdapter;
import net.gdface.image.ImageErrorException;
import net.gdface.sdk.BaseFaceApiLocal;
import net.gdface.sdk.CapacityFieldConstant;
import net.gdface.sdk.ContextLoader;
import net.gdface.sdk.FaceApi;
import net.gdface.sdk.FaceApiContext;
import net.gdface.sdk.FseResult;
import net.gdface.sdk.NotFaceDetectedException;
import net.gdface.sdk.fse.FeatureSe;
import net.gdface.sdk.fse.thrift.FeatureSeThriftClient;
import net.gdface.sdk.thrift.FaceApiThriftClient;
import net.gdface.thrift.ClientFactory;
import net.gdface.utils.SimpleTypes;
import static com.google.common.base.Preconditions.*;
import static net.gdface.facelog.FeatureConfig.FEATURE_CONFIG;
import static com.google.common.base.MoreObjects.firstNonNull;
import static net.gdface.utils.ConditionChecks.checkTrue;
import static net.gdface.utils.ConditionChecks.checkNotNull;
/**
* FaceApi RPC服务管理
* @author guyadong
*
*/
class FaceApiServiceManagement implements ServiceConstant,CapacityFieldConstant {
private static class SingletonTimer{
private static final Timer TIMER = new Timer("FaceApiServiceManagement-timer",true);
}
private BiMap hosts = Maps.synchronizedBiMap(HashBiMap.create());
/** 不能访问的服务 */
private Map unreachableHosts = Maps.newConcurrentMap();
private Map faceApiInstances = Maps.newHashMap();
private Map fseInstances = Maps.newConcurrentMap();
private Map fseEngines = Maps.newConcurrentMap();
private Map config;
private final DtalkServiceTaskDispatcher dt;
private final RedisManagement rm;
private final DaoManagement dm;
/** 定时任务间隔(秒) */
private final long timerPeriod;
/** 默认相似度阀值 */
private float defaultSimThreshold;
FaceApiServiceManagement(DtalkServiceTaskDispatcher dt, RedisManagement rm, DaoManagement dm) {
this.dt = dt;
this.rm = rm;
this.dm = dm;
this.timerPeriod = 1000*CONFIG.getInt(FACEAPI_SERVICEMANAGEMENT_TIMERPERIODSEC,
DEFAULT_FACEAPI_SERVICEMANAGEMENT_TIMERPERIOD);
this.defaultSimThreshold = CONFIG.getFloat(FACEAPI_SERVICEMANAGEMENT_SIMTHRESHOLD,
DEFAULT_FACEAPI_SERVICEMANAGEMENT_SIMTHRESHOLD);
}
private boolean exists(String sdkVersion){
return faceApiInstances.containsKey(sdkVersion);
}
/**
* 初始化指定算法的RPC服务相关对象
* @param sdkVersion
* @param hostAndPort
* @return 初始化成功返回初始化对象数组,否则返回{@code null}
*/
private Object[] doInit(String sdkVersion,HostAndPort hostAndPort){
// 测试服务是否可连接
if(ClientFactory.testConnect(hostAndPort.getHost(),hostAndPort.getPort(), 0)){
Object[] initObjs = new Object[5];
FaceApiThriftClient faceapi = new FaceApiThriftClient(hostAndPort);
initObjs[0] = faceapi;
String sv = faceapi.sdkCapacity().get(C_SDK_VERSION);
// FaceApi服务的SDK版本号必须匹配
checkArgument(sdkVersion.equals(sv),
"MISMATCH sdkVersion%s VS %s with %s",
sdkVersion, sv, faceapi);
initObjs[1] = rm.sdkTaskQueueOf(TASK_FACEAPI_BASE, sdkVersion);
initObjs[2] = rm.sdkTaskQueueOf(TASK_FEATURE_BASE, sdkVersion);
logger.info("create FSE client:{} for {}",hostAndPort,sdkVersion);
FeatureSeThriftClient fse = new FeatureSeThriftClient(hostAndPort);
initObjs[3] = fse;
FseEngine fseEngine = new FseEngine(fse, sdkVersion);
try {
fseEngine.init();
} catch (Exception e) {
// 初始化过程中出错则继续循环
logger.error("{}:{}",e.getClass().getSimpleName(),e.getMessage());
return null;
}
// 初始化成功加入FSE 引擎表中
initObjs[4] = fseEngine;
return initObjs;
}
logger.warn("NOT CONNECTABLE FaceApi/FSE service:{}",hostAndPort);
return null;
}
private Object[] update(Object[] objs,String sdkVersion){
if(objs != null){
checkArgument(!Iterables.tryFind(Arrays.asList(objs), Predicates.isNull()).isPresent(),"objs has null elements");
if(sdkVersion == null){
sdkVersion = ((FaceApi) objs[0]).sdkCapacity().get(C_SDK_VERSION);
}
faceApiInstances.put(sdkVersion, (FaceApi) objs[0]);
// faceapi实例绑定到命令执行器,并将任务分发器注册到的FACEAPI任务队列
FaceApiCmdAdapter.INSTANCE.bindFaceApi((FaceApi) objs[0]);
ExtractFeatureBaseFaceApi.ADAPTER.bindFaceApi((FaceApi) objs[0]);
dt.register((String) objs[1]);
dt.register((String) objs[2]);
fseInstances.put(sdkVersion, (FeatureSe) objs[3]);
// 初始化成功加入FSE 引擎表中
fseEngines.put(sdkVersion,(FseEngine) objs[4]);
}
return objs;
}
/**
* 初始化指定算法的RPC服务相关对象
* 由{@link #doInit(String, HostAndPort)}执行初始化,初始化成功后再将对应的对象一个个保存到对应的map,
* 这样可以实现类似事务机制,确保所有初始化操作后统一保存到map,保证数据的一致性
* @param sdkVersion
* @param hostAndPort
* @return 初始化成功返回{@code true},否则返回{@code false}
*/
private synchronized boolean initService(String sdkVersion,HostAndPort hostAndPort){
Object[] objs = doInit(sdkVersion,hostAndPort);
if(objs != null){
try {
hosts.put(sdkVersion, hostAndPort);
} catch (IllegalArgumentException e) {
// hostAndPort 已经被另一个SDK VERSION占用
throw new IllegalStateException(
String.format("EXIST DUPLICATED HOST DEFINITION :%s %s",
hosts.inverse().get(hostAndPort),
hosts));
}
}
return update(objs,sdkVersion) != null;
}
/**
* 初始化指定算法相关对象
* @param local 本地算法实例
* @return 初始化成功返回初始化对象数组
*/
private Object[] doInit(BaseFaceApiLocal local){
Object[] initObjs = new Object[4];
String sdkVersion = local.sdkCapacity().get(C_SDK_VERSION);
BaseFaceApiLocal faceapi = local;
initObjs[0] = faceapi;
initObjs[1] = rm.sdkTaskQueueOf(TASK_FACEAPI_BASE, sdkVersion);
logger.info("get FSE instance for {}",sdkVersion);
FeatureSe fse =BaseFaceApiLocal.getFeatureSeInstance(local);
if(fse == null){
return null;
}
initObjs[2] = fse;
FseEngine fseEngine = new FseEngine(fse, sdkVersion);
try {
fseEngine.init();
} catch (Exception e) {
// 初始化过程中出错则继续循环
logger.error("{}:{}",e.getClass().getSimpleName(),e.getMessage());
return null;
}
// 初始化成功加入FSE 引擎表中
initObjs[3] = fseEngine;
return initObjs;
}
/**
* 通过 FaceApi 的应用上下文({@link ContextLoader})获取可用的本地 FaceApi 实例并初始化
*/
private void initLocalInstance(){
for(FaceApiContext context:ContextLoader.getInstance().CONTEXTS){
FaceApi instance = ContextLoader.ContextField.INSTANCE.from(context);
if(instance instanceof BaseFaceApiLocal){
Object[] objs = doInit( (BaseFaceApiLocal)instance);
update(objs, null);
}
}
}
/**
* 对象初始化
*/
void init(){
initLocalInstance();
// 加载所有 faceapi 服务配置
List> childs = CONFIG.childConfigurationsAt(PREFIX_FACEAPI_SERVICE);
for(HierarchicalConfiguration c:childs){
String sdkVersion = c.getRootElementName();
// 检查sdk_version是否有效
checkArgument(FEATURE_CONFIG.validateSdkVersion(sdkVersion),
"UNSUPPORTED SDK Version [%s]",sdkVersion);
HostAndPort hostAndPort = HostAndPort.fromParts(
c.getString("host",DEFAULT_FACEAPI_SERVICE_HOST),
c.getInt("port",DEFAULT_FACEAPI_SERVICE_PORT));
if(c.getBoolean("enable",Boolean.FALSE)){
if(!exists(sdkVersion) && !initService(sdkVersion,hostAndPort)){
unreachableHosts.put(sdkVersion, hostAndPort);
}
}
}
if(!unreachableHosts.isEmpty()){
// 启动定时任务尝试重新初始化连接不可访问的主机
SingletonTimer.TIMER.schedule(new UnreachableHostsReinitTask(), timerPeriod,timerPeriod);
}
config = Collections.unmodifiableMap(Maps.transformValues(hosts, Functions.toStringFunction()));
}
Map getConfig(){
return config;
}
/**
* 执行特征码内存搜索引擎(feature search engine)的 1:N特征搜索,识别照片中的人脸
* 尝试使用当前sdkVersion指定的算法实例对指定的人脸图像进行识别,返回对应的用户ID(fl_person表的主键)
* @param sdkVersion SDK版本号
* @param imageData 人脸图像数据(jpg,png,bmp)
* @param threshold 人脸特征比对的相似度阀值,为{@code null}使用默认值
* @return 如果识别成功返回用户ID,否则返回无效ID(-1)
* @throws FaceApiRuntimeException 识别时产生的异常
*/
int recognize(String sdkVersion, byte[] imageData, Float threshold) throws FaceApiRuntimeException{
checkTrue(faceApiInstances.containsKey(
checkNotNull(sdkVersion,FaceApiRuntimeException.class,"sdkVersion is null")),
FaceApiRuntimeException.class,
"UNSUPPORTED SDK_VERSION [%s]", sdkVersion);
try {
FseResult[] results = faceApiInstances.get(sdkVersion).searchFaces(imageData, null,
firstNonNull(threshold, defaultSimThreshold), 5);
for(FseResult r : results){
Integer personId = r.appIntId();
if(personId != null){
return personId;
}
}
} catch (NotFaceDetectedException e) {
throw new FaceApiRuntimeException(e);
} catch (ImageErrorException e) {
throw new FaceApiRuntimeException(e);
} catch (RuntimeException e) {
throw new FaceApiRuntimeException(e);
}
return -1;
}
/**
* 执行特征码内存搜索引擎(feature search engine)的 1:N特征搜索,识别照片中的人脸
* 尝试使用当前加载的所有算法实例对指定的人脸图像进行识别,返回对应的用户ID(fl_person表的主键)
* @param imageData 人脸图像数据(jpg,png,bmp)
* @param threshold 人脸特征比对的相似度阀值,为{@code null}使用默认值
* @return 如果识别成功返回用户ID,否则返回无效ID(-1)
* @throws FaceApiRuntimeException 识别时产生的异常
*/
int recognize(byte[] imageData,Float threshold) throws FaceApiRuntimeException{
checkTrue(!faceApiInstances.isEmpty(), FaceApiRuntimeException.class, "NOT AVAILABLE FaceApi INSTANCE");
int id = -1;
FaceApiRuntimeException last = null;
for(String sdkVersion : faceApiInstances.keySet()){
try {
if((id = recognize(sdkVersion,imageData,threshold)) > 0){
return id;
}
} catch (FaceApiRuntimeException e) {
last = e;
if(e.getCause() instanceof NotFaceDetectedException){
// 未检测到人脸则可以尝试下一个算法
continue;
} else if(SimpleTypes.isNetworkError(e.getCause())){
// 服务连接不上可以尝试下一个算法
continue;
}
logger.warn("recognize with {} ERROR {}:{}",sdkVersion,e.getClass().getSimpleName(),e.getMessage());
throw e;
}
}
if(last != null){
logger.warn("recognize ERROR {}:{}",last.getClass().getSimpleName(),last.getMessage());
throw last;
}
return id;
}
/**
* 识别照片中的人脸,识别后判断是否可以在指定设备通行
* @param imageData 人脸图像数据(jpg,png,bmp)
* @param threshold 人脸特征比对的相似度阀值,为{@code null}使用默认值
* @param deviceId 设备ID
* @param sdkVersion SDK版本号,为{@code null}时尝试所有算法
* @return 如果识别的用户允许在指定设备通过返回用户ID,否则返回无效ID(-1)
* @throws FaceApiRuntimeException 识别时产生的异常
* @see #recognize(byte[], Float)
*/
int permit(byte[] imageData, Float threshold, int deviceId, String sdkVersion) throws FaceApiRuntimeException{
int personId ;
if(sdkVersion == null){
personId = recognize(imageData,threshold);
}else{
personId = recognize(sdkVersion,imageData,threshold);
}
if(personId > 0){
if(!dm.daoIsPermit(personId,deviceId)){
return -1;
}
}
return personId;
}
/**
* 对不能访问主机尝试再初始化的定时任务
* @author guyadong
*
*/
private class UnreachableHostsReinitTask extends TimerTask{
@Override
public void run() {
for(Iterator> itor = unreachableHosts.entrySet().iterator();itor.hasNext();){
Entry entry = itor.next();
String sdkVersion = entry.getKey();
HostAndPort hostAndPort = entry.getValue();
if(initService(sdkVersion,hostAndPort)){
itor.remove();
}
}
// 终止定时任务
if(unreachableHosts.isEmpty()){
SingletonTimer.TIMER.cancel();
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy