All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.gdface.facelog.FaceApiServiceManagement Maven / Gradle / Ivy

There is a newer version: 5.3.0
Show newest version
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