项目背景
在人类社会的快速发展中,科技正深刻地影响着各行各业,其中人工智能技术尤为突出。特别是人脸识别技术,因其高效、直观和便捷的特性,在金融、安防、医疗、零售等多个领域得到了广泛应用。随着技术的逐步成熟,人脸识别正在从基础的身份验证功能,拓展到更深层次的场景需求中,例如行为分析、个性化服务以及高效管理等。
本项目的背景基于当下对智能化、高效化需求的日益增长。传统身份认证方式(如卡片、密码)存在安全性低、易被遗忘或伪造的问题,已无法满足某些场景对高安全性与无感化体验的双重要求。为此,本项目旨在开发一个服务端人脸识别系统,专注于实时、高效的人脸检测与识别能力,为目标行业(如智能安防、医疗管理或企业考勤)提供技术支持和服务优化。
环境
服务器:wsl Ubuntu 22.04.3
开发语言(框架):java SpringBoot
SDK版本:Linux Pro
集成
1、下载sdk
2、将arcsoft-sdk-face-server-1.0.0.0.jar 放入工程lib文件夹
3、引入jar包
<dependency>
<groupId>arcsoft-sdk-face-server</groupId>
<artifactId>arcsoft-sdk-face-server</artifactId>
<version>1.0.0.0</version>
<scope>system</scope>
<systemPath>${pom.basedir}/lib/arcsoft-sdk-face-server-1.0.0.0.jar</systemPath>
</dependency>
4、将libarcsoft_face.so、libarcsoft_face_engine.so、libarcsoft_face_engine_jni.so放入服务器/usr/local/arcsoft_lib目录
引擎池
引擎池的优势:
(1)为了避免频繁创建和销毁引擎实例带来的开销
(2)支持高并发任务处理,减少系统瓶颈
(3)减少初始化时间,提高系统响应速度。
引擎池注意事项:
(1)根据系统的并发需求、可用资源(如内存和CPU)以及引擎实例的开销,合理设置池的最小值和最大值,避免浪费系统资源。
(2)每次使用完毕,需及时正确归还引擎,避免资源被长期占用。
1、创建AFaceEngine继承FaceEngine,用isCore来标识核心引擎,因为人脸注册到哪个引擎,识别就必须用哪个引擎,核心引擎即用于注册与识别。
(1)核心引擎:本文使用核心引擎来做人脸注册和比对,人脸注册时,使用哪个引擎注册的,人脸的特征数据就保存在哪个引擎中,注册人脸不适于随机使用引擎实例注册,否则识别时拿到的引擎若没有注册人脸,就会导致人脸识别不到的情况,本文示例核心引擎仅一个,如果需要多个核心引擎可根据本文逻辑自信扩展,注意多个核心引擎注册人脸时,每个核心引擎都需要注册。
(2)非核心引擎:非核心引擎可用于做一些与识别和注册无关的操作,例如人脸检测、人脸属性检测、特征值提取、图像质量检测、活体检测等操作。
public class AFaceEngine extends FaceEngine {
private boolean isCore;
public AFaceEngine(Boolean isCore,String libPath){
super(libPath);
this.isCore = isCore;
}
public AFaceEngine(Boolean isCore,String libPath,String appId, String sdkKey, String activeKey){
super(libPath);
this.isCore = isCore;
}
public boolean isCore() {
return isCore;
}
}
2、引擎工厂FaceEngineFactory,用于创建引擎。
创建引擎时,创建一个核心引擎(可根据业务量控制核心引擎数量)与多个普通引擎,并激活与初始化所有引擎,核心引擎用于注册、识别,普通引擎用于人脸检测、图像质量检测、活体检测、属性识别、特征提取等操作
@Override
public PooledObject<AFaceEngine> makeObject() {
AFaceEngine faceEngine = new AFaceEngine(true,"/usr/local/arcsoft_lib"); // 创建核心对象
faceEngine.activeOnline(appId,sdkKey,activeKey);
faceEngine.setLivenessParam(0.5f, 0.7f);
if (!coreObjectCreated) {
//因为核心引擎仅做注册与识别,为了节省资源,故此只初始化最近本识别检测功能
faceEngine.init(coreEngineConfiguration);
coreObjectCreated = true;
System.out.println("Created core FaceEngine.");
} else {
//非核心引擎要做人脸各项功能,故单独初始化所有功能
faceEngine.init(engineConfiguration);
System.out.println("Created regular FaceEngine.");
}
return new DefaultPooledObject<>(faceEngine);
}
完整的FaceEngineFactory引擎工厂
class FaceEngineFactory implements PooledObjectFactory<AFaceEngine> {
private boolean coreObjectCreated = false;
private EngineConfiguration engineConfiguration;
private EngineConfiguration coreEngineConfiguration;
private String appId;
private String sdkKey;
private String activeKey;
public FaceEngineFactory(EngineConfiguration engineConfiguration, EngineConfiguration coreEngineConfiguration, String appId, String sdkKey, String activeKey){
super();
this.engineConfiguration = engineConfiguration;
this.coreEngineConfiguration = coreEngineConfiguration;
this.appId = appId;
this.sdkKey = sdkKey;
this.activeKey = activeKey;
}
@Override
public PooledObject<AFaceEngine> makeObject() {
AFaceEngine faceEngine = new AFaceEngine(true,"/usr/local/arcsoft_lib"); // 创建核心对象
faceEngine.activeOnline(appId,sdkKey,activeKey);
faceEngine.setLivenessParam(0.5f, 0.7f);
if (!coreObjectCreated) {
//因为核心引擎仅做注册与识别,为了节省资源,故此只初始化最近本识别检测功能
faceEngine.init(coreEngineConfiguration);
coreObjectCreated = true;
System.out.println("Created core FaceEngine.");
} else {
//非核心引擎要做人脸各项功能,故单独初始化所有功能
faceEngine.init(engineConfiguration);
System.out.println("Created regular FaceEngine.");
}
return new DefaultPooledObject<>(faceEngine);
}
@Override
public void destroyObject(PooledObject<AFaceEngine> p) {
p.getObject().unInit();
}
@Override
public boolean validateObject(PooledObject<AFaceEngine> p) {
return true;
}
@Override
public void activateObject(PooledObject<AFaceEngine> p) {
System.out.println("Activating FaceEngine.");
}
@Override
public void passivateObject(PooledObject<AFaceEngine> p) {
System.out.println("Passivating FaceEngine.");
}
3、创建引擎池CustomFaceEnginePool,用于管理引擎,自定义borrowCoreObject方法,用于借用核心引擎,并设置maxWaitMillis超时时间。
// 阻塞等待核心对象归还
public AFaceEngine borrowCoreObject(long maxWaitMillis) throws Exception {
AFaceEngine coreObject = null;
long startTime = System.currentTimeMillis();
// 不断尝试从池中借用对象
while (System.currentTimeMillis() - startTime < maxWaitMillis) {
coreObject = this.borrowObject();
if (coreObject.isCore()) {
return coreObject; // 找到核心对象,返回
} else {
// 不是核心对象,立即归还并继续等待
this.returnObject(coreObject);
}
Thread.sleep(100);
}
throw new Exception("Timed out waiting for core object.");
}
为了避免引擎被借出未被正确归还,需设计一个最大借出时间,超时后系统强制归还引擎
记录借出时间:
private final Map<AFaceEngine, Long> borrowedObjects = new ConcurrentHashMap<>();
private static final long MAX_BORROW_TIME = 10000; // 最大借出时间 10s
public AFaceEngine borrowObject() throws Exception {
AFaceEngine engine = super.borrowObject();
// 记录借出时间
borrowedObjects.put(engine, System.currentTimeMillis());
return engine;
}
检查借出引擎是否超时,如果超时强制归还:
/**
* 监控借出的对象,检测超时
*/
private void monitorBorrowedObjects() {
while (true) {
try {
Thread.sleep(1000); // 每秒扫描一次
long currentTime = System.currentTimeMillis();
for (Map.Entry<AFaceEngine, Long> entry : borrowedObjects.entrySet()) {
long borrowedTime = currentTime - entry.getValue();
if (borrowedTime > MAX_BORROW_TIME) {
// 超时强制归还
AFaceEngine engine = entry.getKey();
System.err.println("Engine timed out, forcing return: " + engine);
borrowedObjects.remove(engine); // 从记录中移除
super.returnObject(engine); // 强制归还到池中
}
}
} catch (Exception e) {
System.err.println("Error in monitor thread: " + e.getMessage());
}
}
}
完整的引擎池CustomFaceEnginePool代码:
public class CustomFaceEnginePool extends GenericObjectPool<AFaceEngine> {
// 记录借出对象及其借出时间
private final Map<AFaceEngine, Long> borrowedObjects = new ConcurrentHashMap<>();
private static final long MAX_BORROW_TIME = 10000; // 最大借出时间 10s
public CustomFaceEnginePool(PooledObjectFactory<AFaceEngine> factory, GenericObjectPoolConfig<AFaceEngine> config) {
super(factory, config);
// 启动监控线程
new Thread(this::monitorBorrowedObjects).start();
}
@Override
public AFaceEngine borrowObject() throws Exception {
AFaceEngine engine = super.borrowObject();
// 记录借出时间
borrowedObjects.put(engine, System.currentTimeMillis());
return engine;
}
@Override
public void returnObject(AFaceEngine obj) {
// 移除借出记录
borrowedObjects.remove(obj);
super.returnObject(obj);
}
/**
* 阻塞等待核心对象归还
*/
public AFaceEngine borrowCoreObject(long maxWaitMillis) throws Exception {
AFaceEngine coreObject = null;
long startTime = System.currentTimeMillis();
while (System.currentTimeMillis() - startTime < maxWaitMillis) {
coreObject = this.borrowObject();
if (coreObject.isCore()) {
borrowedObjects.put(coreObject, System.currentTimeMillis());
return coreObject;
} else {
this.returnObject(coreObject);
}
Thread.sleep(100);
}
throw new Exception("Timed out waiting for core object.");
}
/**
* 监控借出的对象,检测超时
*/
private void monitorBorrowedObjects() {
while (true) {
try {
Thread.sleep(1000); // 每秒扫描一次
long currentTime = System.currentTimeMillis();
for (Map.Entry<AFaceEngine, Long> entry : borrowedObjects.entrySet()) {
long borrowedTime = currentTime - entry.getValue();
if (borrowedTime > MAX_BORROW_TIME) {
// 超时强制归还
AFaceEngine engine = entry.getKey();
System.err.println("Engine timed out, forcing return: " + engine);
borrowedObjects.remove(engine); // 从记录中移除
super.returnObject(engine); // 强制归还到池中
}
}
} catch (Exception e) {
System.err.println("Error in monitor thread: " + e.getMessage());
}
}
}
}
引擎准备完成,接下来就是使用引擎进行各项人脸操作,本示例实现了人脸注册、人脸识别、人脸属性检测三个功能,其余扩展功能都可根据本文提供的代码进行扩展,本文提供的各项人脸操作均为原子性接口,可根据实际需求自由组合。
引擎操作类
1、引擎初始化,根据文档配置引擎的各项功能与参数,调用引擎池进行初始化操作
public void init() {
try {
//引擎配置
EngineConfiguration engineConfiguration = new EngineConfiguration();
EngineConfiguration coreEngineConfiguration = new EngineConfiguration();
//设置检测模式为image
engineConfiguration.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE);
coreEngineConfiguration.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE);
//设置人脸角度为全角度
engineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_ALL_OUT);
coreEngineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_ALL_OUT);
//设置可检测最大人脸数
engineConfiguration.setDetectFaceMaxNum(1);
coreEngineConfiguration.setDetectFaceMaxNum(1);
//设置人脸识别的模型,ASF_REC_MIDDLE:中等模型,ASF_REC_LARGE:大模型
engineConfiguration.setFaceModel(FaceModel.ASF_REC_MIDDLE);
coreEngineConfiguration.setFaceModel(FaceModel.ASF_REC_MIDDLE);
//功能配置
FunctionConfiguration functionConfiguration = new FunctionConfiguration();
//年龄检测
functionConfiguration.setSupportAge(true);
//人脸检测
functionConfiguration.setSupportFaceDetect(true);
//人脸识别
functionConfiguration.setSupportFaceRecognition(true);
//性别检测
functionConfiguration.setSupportGender(true);
//活体检测
functionConfiguration.setSupportLiveness(true);
//ir活体检测
functionConfiguration.setSupportIRLiveness(true);
//图像质量检测
functionConfiguration.setSupportImageQuality(true);
//口罩检测
functionConfiguration.setSupportMaskDetect(true);
engineConfiguration.setFunctionConfiguration(functionConfiguration);
GenericObjectPoolConfig<AFaceEngine> config = new GenericObjectPoolConfig<>();
config.setMaxTotal(5);
facePool = new CustomFaceEnginePool(new FaceEngineFactory(engineConfiguration,coreEngineConfiguration,appId,sdkKey,activeKey), config);
} catch (Exception e) {
e.printStackTrace();
}
}
2、加载数据库中所有人脸到引擎
public void loadAllFace(List<FaceFeatureInfo> faceFeatureInfoList) throws Exception {
AFaceEngine faceEngine = facePool.borrowCoreObject(5000);
int i = faceEngine.registerFaceFeature(faceFeatureInfoList);
log.info("load all face count:{}", +faceFeatureInfoList.size());
log.info("load all face res:{}", +i);
facePool.returnObject(faceEngine);
}
注意:此处使用的是facePool.borrowCoreObject(5000),借用核心引擎进行load face,将数据控中所有人脸注册到核心引擎中,使用完毕后立即调用facePool.returnObject(faceEngine);归还核心引擎。
3、人脸检测(普通引擎)
//人脸检测
public ArrayList<FaceInfo> detectFaces(ImageInfo imageInfo) throws Exception {
ArrayList<FaceInfo> faceInfos = new ArrayList<>();
AFaceEngine faceEngine = facePool.borrowObject();
int detectFacesCode = faceEngine.detectFaces(imageInfo, faceInfos);
log.info("人脸属性检测 res:{}", detectFacesCode);
facePool.returnObject(faceEngine);
return faceInfos;
}
4、人脸图像质量检测,有些图像虽然可检测到人脸,但因为图像质量较差,无法提取到有效的人脸特征值,所以,提取特征值之前,建议检测图像质量,仅对达标的图像进行特征值提取,提高效率。
//图像质量检测
public float imageQuality(ImageInfo imageInfo, FaceInfo faceInfo) throws Exception {
ImageQuality imageQuality = new ImageQuality();
AFaceEngine faceEngine = facePool.borrowObject();
int imageQualityCode =faceEngine.imageQualityDetect(imageInfo, faceInfo, 0, imageQuality);
facePool.returnObject(faceEngine);
log.info("图像质量检测 res:{}", imageQualityCode);
log.info("图像质量检测分数:{}", imageQuality.getFaceQuality());
return imageQuality.getFaceQuality();
}
5、人脸属性检测
public FaceAttributesResponse faceAttributes(ImageInfo imageInfo, FaceInfo faceInfo) throws Exception {
FaceAttributesResponse faceAttributesResponse = new FaceAttributesResponse();
FunctionConfiguration configuration = new FunctionConfiguration();
configuration.setSupportAge(true);
configuration.setSupportGender(true);
configuration.setSupportLiveness(true);
configuration.setSupportMaskDetect(true);
ArrayList<FaceInfo> faceInfos = new ArrayList<>();
faceInfos.add(faceInfo);
AFaceEngine faceEngine = facePool.borrowObject();
int faceAttributesCode = faceEngine.process(imageInfo, faceInfos, configuration);
log.info("图像属性处理errorCode:{}", faceAttributesCode);
//性别检测
List<GenderInfo> genderInfoList = new ArrayList<GenderInfo>();
int genderCode = faceEngine.getGender(genderInfoList);
log.info("性别 res:{}", +genderCode);
log.info("性别:{}", +genderInfoList.get(0).getGender());
faceAttributesResponse.setSex(genderInfoList.get(0).getGender() == 0?"男":"女");
//年龄检测
List<AgeInfo> ageInfoList = new ArrayList<AgeInfo>();
int ageCode = faceEngine.getAge(ageInfoList);
log.info("年龄 res:{}", +ageCode);
log.info("年龄:{}", ageInfoList.get(0).getAge());
faceAttributesResponse.setAge(String.valueOf(ageInfoList.get(0).getAge()));
//rgb活体检测
List<LivenessInfo> livenessInfoList = new ArrayList<LivenessInfo>();
int livenCode = faceEngine.getLiveness(livenessInfoList);
log.info("RGB活体 res:{}", +livenCode);
log.info("活体:{}", livenessInfoList.get(0).getLiveness());
faceAttributesResponse.setLive(livenessInfoList.get(0).getLiveness()==0?"非真人":"真人");
//口罩检测
List<MaskInfo> maskInfoList = new ArrayList<MaskInfo>();
int maskCode = faceEngine.getMask(maskInfoList);
log.info("口罩 res:{}", +maskCode);
log.info("口罩:{}", +maskInfoList.get(0).getMask());
faceAttributesResponse.setMask(maskInfoList.get(0).getMask()==0?"无口罩":"有口罩");
facePool.returnObject(faceEngine);
return faceAttributesResponse;
}
6、IR活体检测,需要前端传入双目摄像头数据,包括RGB图像与IR图像,对IR图像进行人脸检测、活体检测,得到活体结果后需要判断与RGB图像人脸位置的重合度用于初步确认两张图像为同一张人脸(未实现)。
public int irLivingDetect(ImageInfo irImg) throws Exception {
AFaceEngine faceEngine = facePool.borrowObject();
//IR属性处理
List<FaceInfo> faceInfoListGray = new ArrayList<FaceInfo>();
int irFaceDetectCode = faceEngine.detectFaces(irImg, faceInfoListGray);
log.info("ir图像人脸检测 res:{}", +irFaceDetectCode);
FunctionConfiguration configuration2 = new FunctionConfiguration();
configuration2.setSupportIRLiveness(true);
int irLivingCode = faceEngine.processIr(irImg, faceInfoListGray, configuration2);
log.info("ir活体检测 res:{}", +irLivingCode);
//IR活体检测
List<IrLivenessInfo> irLivenessInfo = new ArrayList<>();
int errorCode = faceEngine.getLivenessIr(irLivenessInfo);
log.info("获取ir活体检测 res:{}", +errorCode);
facePool.returnObject(faceEngine);
return irLivenessInfo.get(0).getLiveness();
}
7、注册人脸,此操作需使用核心引擎
public int registerFace(FaceFeatureInfo faceFeatureInfo) throws Exception {
AFaceEngine faceEngine = facePool.borrowCoreObject(5000);
int i = faceEngine.registerFaceFeature(faceFeatureInfo);
facePool.returnObject(faceEngine);
return i;
}
8、移除人脸,此处应增加数据库的移除操作
public int removeFace(int searchId) throws Exception {
AFaceEngine faceEngine = facePool.borrowCoreObject(5000);
int i = faceEngine.removeFaceFeature(searchId);
facePool.returnObject(faceEngine);
return i;
}
9、更新人脸,此处应新增数据的更新操作
public int updateFace(FaceFeatureInfo faceFeatureInfo) throws Exception {
AFaceEngine faceEngine = facePool.borrowCoreObject(5000);
int i = faceEngine.updateFaceFeature(faceFeatureInfo);
facePool.returnObject(faceEngine);
return i;
}
10、人脸识别(搜索),此操作需使用核心引擎
public SearchResult searchFace(FaceFeature faceFeature) throws Exception {
AFaceEngine faceEngine = facePool.borrowCoreObject(5000);
FaceSearchCount faceSearchCount = new FaceSearchCount();
faceEngine.getFaceCount(faceSearchCount);
log.info("引擎库face count:{}", +faceSearchCount.getCount());
SearchResult searchResult = new SearchResult();
int searchCode = faceEngine.searchFaceFeature(faceFeature, CompareModel.LIFE_PHOTO, searchResult);
log.info("人脸搜索 res:{}", +searchCode);
log.info("人脸搜索 sim:{}", +searchResult.getMaxSimilar());
facePool.returnObject(faceEngine);
return searchResult;
}
注意:人脸识别需传入CompareModel识别模式,CompareModel.LIFE_PHOTO为生活照识别,即通用的人脸识别;CompareModel.ID_PHOTO为身份证照片识别,针对身份证照片进行了特殊优化,用于人证比对。
11、提取特征值
public FaceFeature extractFaceFeature(ImageInfo imageInfo, FaceInfo faceInfo, ExtractType type) throws Exception {
AFaceEngine faceEngine = facePool.borrowObject();
FaceFeature faceFeature = new FaceFeature();
int extractCode = faceEngine.extractFaceFeature(imageInfo, faceInfo, type, 0, faceFeature);
log.info("特征提取 res:{}", extractCode);
facePool.returnObject(faceEngine);
return faceFeature;
}
注意:特征值提取需要传入ExtractType,ExtractType.REGISTER表示此特征值将用于注册操作,ExtractType.RECOGNIZE表示此特征值将用于识别操作。
核心的引擎操作已经完成了,接下来就可以自由组合进行业务上的处理了