項(xiàng)目背景
在人類社會的快速發(fā)展中,科技正深刻地影響著各行各業(yè),其中人工智能技術(shù)尤為突出。特別是人臉識別技術(shù),因其高效、直觀和便捷的特性,在金融、安防、醫(yī)療、零售等多個領(lǐng)域得到了廣泛應(yīng)用。隨著技術(shù)的逐步成熟,人臉識別正在從基礎(chǔ)的身份驗(yàn)證功能,拓展到更深層次的場景需求中,例如行為分析、個性化服務(wù)以及高效管理等。
本項(xiàng)目的背景基于當(dāng)下對智能化、高效化需求的日益增長。傳統(tǒng)身份認(rèn)證方式(如卡片、密碼)存在安全性低、易被遺忘或偽造的問題,已無法滿足某些場景對高安全性與無感化體驗(yàn)的雙重要求。為此,本項(xiàng)目旨在開發(fā)一個服務(wù)端人臉識別系統(tǒng),專注于實(shí)時、高效的人臉檢測與識別能力,為目標(biāo)行業(yè)(如智能安防、醫(yī)療管理或企業(yè)考勤)提供技術(shù)支持和服務(wù)優(yōu)化。
環(huán)境
服務(wù)器:wsl Ubuntu 22.04.3
開發(fā)語言(框架):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放入服務(wù)器/usr/local/arcsoft_lib目錄
引擎池
引擎池的優(yōu)勢:
(1)為了避免頻繁創(chuàng)建和銷毀引擎實(shí)例帶來的開銷
(2)支持高并發(fā)任務(wù)處理,減少系統(tǒng)瓶頸
(3)減少初始化時間,提高系統(tǒng)響應(yīng)速度。
引擎池注意事項(xiàng):
(1)根據(jù)系統(tǒng)的并發(fā)需求、可用資源(如內(nèi)存和CPU)以及引擎實(shí)例的開銷,合理設(shè)置池的最小值和最大值,避免浪費(fèi)系統(tǒng)資源。
(2)每次使用完畢,需及時正確歸還引擎,避免資源被長期占用。
1、創(chuàng)建AFaceEngine繼承FaceEngine,用isCore來標(biāo)識核心引擎,因?yàn)槿四樧缘侥膫€引擎,識別就必須用哪個引擎,核心引擎即用于注冊與識別。
(1)核心引擎:本文使用核心引擎來做人臉注冊和比對,人臉注冊時,使用哪個引擎注冊的,人臉的特征數(shù)據(jù)就保存在哪個引擎中,注冊人臉不適于隨機(jī)使用引擎實(shí)例注冊,否則識別時拿到的引擎若沒有注冊人臉,就會導(dǎo)致人臉識別不到的情況,本文示例核心引擎僅一個,如果需要多個核心引擎可根據(jù)本文邏輯自信擴(kuò)展,注意多個核心引擎注冊人臉時,每個核心引擎都需要注冊。
(2)非核心引擎:非核心引擎可用于做一些與識別和注冊無關(guān)的操作,例如人臉檢測、人臉屬性檢測、特征值提取、圖像質(zhì)量檢測、活體檢測等操作。
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,用于創(chuàng)建引擎。
創(chuàng)建引擎時,創(chuàng)建一個核心引擎(可根據(jù)業(yè)務(wù)量控制核心引擎數(shù)量)與多個普通引擎,并激活與初始化所有引擎,核心引擎用于注冊、識別,普通引擎用于人臉檢測、圖像質(zhì)量檢測、活體檢測、屬性識別、特征提取等操作
@Override
public PooledObject<AFaceEngine> makeObject() {
AFaceEngine faceEngine = new AFaceEngine(true,"/usr/local/arcsoft_lib"); // 創(chuàng)建核心對象
faceEngine.activeOnline(appId,sdkKey,activeKey);
faceEngine.setLivenessParam(0.5f, 0.7f);
if (!coreObjectCreated) {
//因?yàn)楹诵囊鎯H做注冊與識別,為了節(jié)省資源,故此只初始化最近本識別檢測功能
faceEngine.init(coreEngineConfiguration);
coreObjectCreated = true;
System.out.println("Created core FaceEngine.");
} else {
//非核心引擎要做人臉各項(xiàng)功能,故單獨(dú)初始化所有功能
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"); // 創(chuàng)建核心對象
faceEngine.activeOnline(appId,sdkKey,activeKey);
faceEngine.setLivenessParam(0.5f, 0.7f);
if (!coreObjectCreated) {
//因?yàn)楹诵囊鎯H做注冊與識別,為了節(jié)省資源,故此只初始化最近本識別檢測功能
faceEngine.init(coreEngineConfiguration);
coreObjectCreated = true;
System.out.println("Created core FaceEngine.");
} else {
//非核心引擎要做人臉各項(xiàng)功能,故單獨(dú)初始化所有功能
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、創(chuàng)建引擎池CustomFaceEnginePool,用于管理引擎,自定義borrowCoreObject方法,用于借用核心引擎,并設(shè)置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 {
// 不是核心對象,立即歸還并繼續(xù)等待
this.returnObject(coreObject);
}
Thread.sleep(100);
}
throw new Exception("Timed out waiting for core object.");
}
為了避免引擎被借出未被正確歸還,需設(shè)計(jì)一個最大借出時間,超時后系統(tǒng)強(qiáng)制歸還引擎
記錄借出時間:
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;
}
檢查借出引擎是否超時,如果超時強(qiáng)制歸還:
/**
* 監(jiān)控借出的對象,檢測超時
*/
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) {
// 超時強(qiáng)制歸還
AFaceEngine engine = entry.getKey();
System.err.println("Engine timed out, forcing return: " + engine);
borrowedObjects.remove(engine); // 從記錄中移除
super.returnObject(engine); // 強(qiáng)制歸還到池中
}
}
} 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);
// 啟動監(jiān)控線程
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.");
}
/**
* 監(jiān)控借出的對象,檢測超時
*/
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) {
// 超時強(qiáng)制歸還
AFaceEngine engine = entry.getKey();
System.err.println("Engine timed out, forcing return: " + engine);
borrowedObjects.remove(engine); // 從記錄中移除
super.returnObject(engine); // 強(qiáng)制歸還到池中
}
}
} catch (Exception e) {
System.err.println("Error in monitor thread: " + e.getMessage());
}
}
}
}
引擎準(zhǔn)備完成,接下來就是使用引擎進(jìn)行各項(xiàng)人臉操作,本示例實(shí)現(xiàn)了人臉注冊、人臉識別、人臉屬性檢測三個功能,其余擴(kuò)展功能都可根據(jù)本文提供的代碼進(jìn)行擴(kuò)展,本文提供的各項(xiàng)人臉操作均為原子性接口,可根據(jù)實(shí)際需求自由組合。
引擎操作類
1、引擎初始化,根據(jù)文檔配置引擎的各項(xiàng)功能與參數(shù),調(diào)用引擎池進(jìn)行初始化操作
public void init() {
try {
//引擎配置
EngineConfiguration engineConfiguration = new EngineConfiguration();
EngineConfiguration coreEngineConfiguration = new EngineConfiguration();
//設(shè)置檢測模式為image
engineConfiguration.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE);
coreEngineConfiguration.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE);
//設(shè)置人臉角度為全角度
engineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_ALL_OUT);
coreEngineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_ALL_OUT);
//設(shè)置可檢測最大人臉數(shù)
engineConfiguration.setDetectFaceMaxNum(1);
coreEngineConfiguration.setDetectFaceMaxNum(1);
//設(shè)置人臉識別的模型,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);
//圖像質(zhì)量檢測
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、加載數(shù)據(jù)庫中所有人臉到引擎
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),借用核心引擎進(jìn)行l(wèi)oad face,將數(shù)據(jù)控中所有人臉注冊到核心引擎中,使用完畢后立即調(diào)用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、人臉圖像質(zhì)量檢測,有些圖像雖然可檢測到人臉,但因?yàn)閳D像質(zhì)量較差,無法提取到有效的人臉特征值,所以,提取特征值之前,建議檢測圖像質(zhì)量,僅對達(dá)標(biāo)的圖像進(jìn)行特征值提取,提高效率。
//圖像質(zhì)量檢測
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("圖像質(zhì)量檢測 res:{}", imageQualityCode);
log.info("圖像質(zhì)量檢測分?jǐn)?shù):{}", 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活體檢測,需要前端傳入雙目攝像頭數(shù)據(jù),包括RGB圖像與IR圖像,對IR圖像進(jìn)行人臉檢測、活體檢測,得到活體結(jié)果后需要判斷與RGB圖像人臉位置的重合度用于初步確認(rèn)兩張圖像為同一張人臉(未實(shí)現(xiàn))。
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、移除人臉,此處應(yīng)增加數(shù)據(jù)庫的移除操作
public int removeFace(int searchId) throws Exception {
AFaceEngine faceEngine = facePool.borrowCoreObject(5000);
int i = faceEngine.removeFaceFeature(searchId);
facePool.returnObject(faceEngine);
return i;
}
9、更新人臉,此處應(yīng)新增數(shù)據(jù)的更新操作
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("人臉?biāo)阉?res:{}", +searchCode);
log.info("人臉?biāo)阉?sim:{}", +searchResult.getMaxSimilar());
facePool.returnObject(faceEngine);
return searchResult;
}
注意:人臉識別需傳入CompareModel識別模式,CompareModel.LIFE_PHOTO為生活照識別,即通用的人臉識別;CompareModel.ID_PHOTO為身份證照片識別,針對身份證照片進(jìn)行了特殊優(yōu)化,用于人證比對。
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表示此特征值將用于識別操作。
核心的引擎操作已經(jīng)完成了,接下來就可以自由組合進(jìn)行業(yè)務(wù)上的處理了
人臉注冊(效果圖)

人臉識別(效果圖)

人臉屬性檢測(效果圖)

項(xiàng)目地址:https://gitee.com/wanmaomao/arc-face-for-linux