項目背景
在人類社會的快速發(fā)展中,科技正深刻地影響著各行各業(yè)咐扭,其中人工智能技術尤為突出芭挽。特別是人臉識別技術滑废,因其高效、直觀和便捷的特性袜爪,在金融蠕趁、安防、醫(yī)療辛馆、零售等多個領域得到了廣泛應用俺陋。隨著技術的逐步成熟,人臉識別正在從基礎的身份驗證功能昙篙,拓展到更深層次的場景需求中腊状,例如行為分析、個性化服務以及高效管理等苔可。
本項目的背景基于當下對智能化缴挖、高效化需求的日益增長。傳統(tǒng)身份認證方式(如卡片焚辅、密碼)存在安全性低映屋、易被遺忘或偽造的問題,已無法滿足某些場景對高安全性與無感化體驗的雙重要求同蜻。為此棚点,本項目旨在開發(fā)一個服務端人臉識別系統(tǒng),專注于實時湾蔓、高效的人臉檢測與識別能力乙濒,為目標行業(yè)(如智能安防、醫(yī)療管理或企業(yè)考勤)提供技術支持和服務優(yōu)化卵蛉。
環(huán)境
服務器: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放入服務器/usr/local/arcsoft_lib目錄
引擎池
引擎池的優(yōu)勢:
(1)為了避免頻繁創(chuàng)建和銷毀引擎實例帶來的開銷
(2)支持高并發(fā)任務處理亏掀,減少系統(tǒng)瓶頸
(3)減少初始化時間,提高系統(tǒng)響應速度泛释。
引擎池注意事項:
(1)根據(jù)系統(tǒng)的并發(fā)需求滤愕、可用資源(如內(nèi)存和CPU)以及引擎實例的開銷,合理設置池的最小值和最大值怜校,避免浪費系統(tǒng)資源间影。
(2)每次使用完畢,需及時正確歸還引擎茄茁,避免資源被長期占用魂贬。
1巩割、創(chuàng)建AFaceEngine繼承FaceEngine,用isCore來標識核心引擎付燥,因為人臉注冊到哪個引擎宣谈,識別就必須用哪個引擎,核心引擎即用于注冊與識別键科。
(1)核心引擎:本文使用核心引擎來做人臉注冊和比對闻丑,人臉注冊時,使用哪個引擎注冊的勋颖,人臉的特征數(shù)據(jù)就保存在哪個引擎中嗦嗡,注冊人臉不適于隨機使用引擎實例注冊,否則識別時拿到的引擎若沒有注冊人臉牙言,就會導致人臉識別不到的情況酸钦,本文示例核心引擎僅一個,如果需要多個核心引擎可根據(jù)本文邏輯自信擴展咱枉,注意多個核心引擎注冊人臉時卑硫,每個核心引擎都需要注冊。
(2)非核心引擎:非核心引擎可用于做一些與識別和注冊無關的操作蚕断,例如人臉檢測欢伏、人臉屬性檢測、特征值提取亿乳、圖像質(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è)務量控制核心引擎數(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) {
//因為核心引擎僅做注冊與識別挂滓,為了節(jié)省資源苦银,故此只初始化最近本識別檢測功能
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"); // 創(chuàng)建核心對象
faceEngine.activeOnline(appId,sdkKey,activeKey);
faceEngine.setLivenessParam(0.5f, 0.7f);
if (!coreObjectCreated) {
//因為核心引擎僅做注冊與識別,為了節(jié)省資源墓毒,故此只初始化最近本識別檢測功能
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亲怠、創(chuàng)建引擎池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 {
// 不是核心對象踪栋,立即歸還并繼續(xù)等待
this.returnObject(coreObject);
}
Thread.sleep(100);
}
throw new Exception("Timed out waiting for core object.");
}
為了避免引擎被借出未被正確歸還,需設計一個最大借出時間图毕,超時后系統(tǒ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;
}
檢查借出引擎是否超時,如果超時強制歸還:
/**
* 監(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) {
// 超時強制歸還
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);
// 啟動監(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) {
// 超時強制歸還
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());
}
}
}
}
引擎準備完成予颤,接下來就是使用引擎進行各項人臉操作囤官,本示例實現(xiàn)了人臉注冊、人臉識別蛤虐、人臉屬性檢測三個功能党饮,其余擴展功能都可根據(jù)本文提供的代碼進行擴展,本文提供的各項人臉操作均為原子性接口驳庭,可根據(jù)實際需求自由組合刑顺。
引擎操作類
1、引擎初始化饲常,根據(jù)文檔配置引擎的各項功能與參數(shù)蹲堂,調(diào)用引擎池進行初始化操作
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);
//設置可檢測最大人臉數(shù)
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);
//圖像質(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),借用核心引擎進行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ì)量檢測,有些圖像雖然可檢測到人臉枕磁,但因為圖像質(zhì)量較差渡蜻,無法提取到有效的人臉特征值,所以,提取特征值之前茸苇,建議檢測圖像質(zhì)量排苍,僅對達標的圖像進行特征值提取,提高效率学密。
//圖像質(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ì)量檢測分數(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圖像進行人臉檢測哭靖、活體檢測具垫,得到活體結果后需要判斷與RGB圖像人臉位置的重合度用于初步確認兩張圖像為同一張人臉(未實現(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筝蚕、移除人臉,此處應增加數(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铺坞、更新人臉起宽,此處應新增數(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("人臉搜索 res:{}", +searchCode);
log.info("人臉搜索 sim:{}", +searchResult.getMaxSimilar());
facePool.returnObject(faceEngine);
return searchResult;
}
注意:人臉識別需傳入CompareModel識別模式燎含,CompareModel.LIFE_PHOTO為生活照識別,即通用的人臉識別腿短;CompareModel.ID_PHOTO為身份證照片識別屏箍,針對身份證照片進行了特殊優(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)完成了,接下來就可以自由組合進行業(yè)務上的處理了