android基于虹软的人脸识别+测温+道闸项目的实现方法

本篇内容主要讲解“android基于虹软的人脸识别+测温+道闸项目的实现方法”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“android基于虹软的人脸识别+测温+道闸项目的实现方法”吧!

软硬件环境

平台为Android平台,采用kotlin+java混编 虹软SDK版本为最新的4.0可以戴口罩识别 终端摄像头采用双目摄像头模组IR活体识别 扫码头、测温头、身份证读卡器皆为本公司设备,就不一一介绍了

UI界面和机器展示

android基于虹软的人脸识别+测温+道闸项目的实现方法

使用说明

人脸识别通过后自动测温,然后向后台上传温度和人员信息,后台判断温度是否异常,并且保存人员通行记录

项目总体流程

人脸注册: 人脸注册采用另一种终端和小程序注册两种方式,这里只说小程序。 用户使用小程序采集人脸照片上传至服务器-->人脸终端起服务定时向服务端请求终端没有注册过的人脸-->终端拿到人脸照片之后注册至本地。另外定时请求需要删除和更改的人脸信息,然后本地做删除更改操作。(直接同步人脸照片而不是特征值是因为虹软目前没有小程序的人脸识别sdk)

开门条件 以人脸识别+测温、刷身份证+测温、刷健康码+测温为开门条件。 本文主要讲解人脸+测温

项目主要类介绍

PullDataServerHelper 拉取人脸信息帮助类,实现了拿到信息之后注册人脸、删除人脸、更改信息的操作

DataSyncService 数据同步服务,此类为server,主要功能是定时调用PullDataServerHelper做网络请求

facedb包 此包中为数据库操作相关文件,本项目数据操作使用greendao,不了解的可以了解一下,非常好用。

项目的一些东西就先说这么多,文章最后会附上源码,接下来着重讲一些虹软SDK的使用

人脸识别部分(核心代码)

1.sdk的激活

SDK为一次激活永久使用,不可多次激活,本文使用在线激活的方式,后端录入终端绑定激活码,app带着终端唯一标识向后端请求激活码。 激活之前先判断是否已经激活,没有激活才继续激活操作,下面为代码:

funActive(){
//获取激活文件
valactiveFileInfo=ActiveFileInfo()
valcode=FaceEngine.getActiveFileInfo(mContext,activeFileInfo)
if(code==ErrorInfo.MOK){
//已经激活
isActive.value=true
return
}else{
//未激活读取本地存储的激活码
varsdkKey=readString(
mContext,
Constants.APP_SDK_KEY
)
varappId=readString(
mContext,
Constants.APP_ID_KEY
)
varactiveKey=readString(
mContext,
Constants.APP_ACTIVE_KEY
)
if(sdkKey.isNullOrEmpty()){
//本地无激活码从网络获取
getSdkInfo()
}else{
valcode1=FaceEngine.activeOnline(
mContext,
activeKey,
appId,
sdkKey
)
if(code1==ErrorInfo.MOK){
isActive.value=true
return
}else{
getSdkInfo()
}
}
}
}


privatefungetSdkInfo(){
RetrofitManager.getInstance().createReq(ApiServer::class.java)
.getSdkInfo(AppUtils.getMac())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object:BaseObserver<SdkInfoResult>(){
overridefunonSuccees(data:SdkInfoResult){
if(data.code==200&&null!=data.data){
write(mContext,Constants.APP_SDK_KEY,data.data.SdkKey)
write(mContext,Constants.APP_ID_KEY,data.data.AppId)
write(mContext,Constants.APP_ACTIVE_KEY,data.data.ActiveKey)
valcode1=FaceEngine.activeOnline(
mContext,
data.data.activeKey,
data.data.appId,
data.data.sdkKey
)
if(code1==ErrorInfo.MOK){
isActive.value=true
return
}else{
isActive.value=false
}
}
}
overridefunonFailure(message:String?){
isActive.value=false
}

})


}

2、sdk初始化 初始化的各个属性官方文档都有详细讲解,这里就不赘述了

publicvoidinit(){
Contextcontext=CustomApplication.Companion.getMContext();
FaceServer.getInstance().init(context);

ftEngine=newFaceEngine();
intftEngineMask=FaceEngine.ASF_FACE_DETECT|FaceEngine.ASF_MASK_DETECT;
intftCode=ftEngine.init(context,DetectMode.ASF_DETECT_MODE_VIDEO,DetectFaceOrientPriority.ASF_OP_90_ONLY,FaceConfig.RECOGNIZE_MAX_DETECT_FACENUM,ftEngineMask);
ftInitCode.postValue(ftCode);

frEngine=newFaceEngine();
intfrEngineMask=FaceEngine.ASF_FACE_RECOGNITION;
if(FaceConfig.ENABLE_FACE_QUALITY_DETECT){
frEngineMask|=FaceEngine.ASF_IMAGEQUALITY;
}
intfrCode=frEngine.init(context,DetectMode.ASF_DETECT_MODE_IMAGE,DetectFaceOrientPriority.ASF_OP_90_ONLY,
10,frEngineMask);
frInitCode.postValue(frCode);

//启用活体检测时,才初始化活体引擎
intflCode=-1;
if(FaceConfig.ENABLE_LIVENESS){
flEngine=newFaceEngine();
intflEngineMask=(livenessType==LivenessType.RGB?FaceEngine.ASF_LIVENESS:(FaceEngine.ASF_IR_LIVENESS|FaceEngine.ASF_FACE_DETECT));
if(needUpdateFaceData){
flEngineMask|=FaceEngine.ASF_UPDATE_FACEDATA;
}
flCode=flEngine.init(context,DetectMode.ASF_DETECT_MODE_IMAGE,
DetectFaceOrientPriority.ASF_OP_90_ONLY,FaceConfig.RECOGNIZE_MAX_DETECT_FACENUM,flEngineMask);
flInitCode.postValue(flCode);
LivenessParamlivenessParam=newLivenessParam(FaceConfig.RECOMMEND_RGB_LIVENESS_THRESHOLD,FaceConfig.RECOMMEND_IR_LIVENESS_THRESHOLD);
flEngine.setLivenessParam(livenessParam);
}

if(ftCode==ErrorInfo.MOK&&frCode==ErrorInfo.MOK&&flCode==ErrorInfo.MOK){
Constants.isInitEnt=true;
}

}

人脸注册

publicFaceEntityregisterJpeg(Contextcontext,FaceImageResult.DataBeandata)throwsRegisterFailedException{
if(faceRegisterInfoList!=null&&faceRegisterInfoList.size()>=MAX_REGISTER_FACE_COUNT){
Log.e(TAG,"registerJpeg:registeredfacecountlimited"+faceRegisterInfoList.size());
//已达注册上限,超过该值会影响识别率
thrownewRegisterFailedException("registeredfacecountlimited");
}
Bitmapbitmap=ImageUtil.jpegToScaledBitmap(Base64.decode(data.getImage(),Base64.DEFAULT),ImageUtil.DEFAULT_MAX_WIDTH,ImageUtil.DEFAULT_MAX_HEIGHT);
bitmap=ArcSoftImageUtil.getAlignedBitmap(bitmap,true);
byte[]imageData=ArcSoftImageUtil.createImageData(bitmap.getWidth(),bitmap.getHeight(),ArcSoftImageFormat.BGR24);
intcode=ArcSoftImageUtil.bitmapToImageData(bitmap,imageData,ArcSoftImageFormat.BGR24);
if(code!=ArcSoftImageUtilError.CODE_SUCCESS){
thrownewRuntimeException("bitmapToImageDatafailed,codeis"+code);
}
returnregisterBgr24(context,imageData,bitmap.getWidth(),bitmap.getHeight(),data);
}

/**
*用于注册照片人脸
*
*@paramcontext上下文对象
*@parambgr24bgr24数据
*@paramwidthbgr24宽度
*@paramheightbgr24高度
*@paramname保存的名字,若为空则使用时间戳
*@return注册成功后的人脸信息
*/
publicFaceEntityregisterBgr24(Contextcontext,byte[]bgr24,intwidth,intheight,Stringname,StringidCard){
if(faceEngine==null||context==null||bgr24==null||width%4!=0||bgr24.length!=width*height*3){
Log.e(TAG,"registerBgr24:invalidparams");
returnnull;
}
//人脸检测
List<FaceInfo>faceInfoList=newArrayList<>();
intcode;
synchronized(faceEngine){
code=faceEngine.detectFaces(bgr24,width,height,FaceEngine.CP_PAF_BGR24,faceInfoList);
}
if(code==ErrorInfo.MOK&&!faceInfoList.isEmpty()){
code=faceEngine.process(bgr24,width,height,FaceEngine.CP_PAF_BGR24,faceInfoList,
FaceEngine.ASF_MASK_DETECT);
if(code==ErrorInfo.MOK){
List<MaskInfo>maskInfoList=newArrayList<>();
faceEngine.getMask(maskInfoList);
if(!maskInfoList.isEmpty()){
intisMask=maskInfoList.get(0).getMask();
if(isMask==MaskInfo.WORN){
/*
*注册照要求不戴口罩
*/
Log.e(TAG,"registerBgr24:maskInfoisworn");
returnnull;
}
}
}

FaceFeaturefaceFeature=newFaceFeature();
/*
*特征提取,注册人脸时参数extractType值为ExtractType.REGISTER,参数mask的值为MaskInfo.NOT_WORN
*/
synchronized(faceEngine){
code=faceEngine.extractFaceFeature(bgr24,width,height,FaceEngine.CP_PAF_BGR24,faceInfoList.get(0),
ExtractType.REGISTER,MaskInfo.NOT_WORN,faceFeature);
}
StringuserName=name==null?String.valueOf(System.currentTimeMillis()):name;

//保存注册结果(注册图、特征数据)
if(code==ErrorInfo.MOK){
//为了美观,扩大rect截取注册图
RectcropRect=getBestRect(width,height,faceInfoList.get(0).getRect());
if(cropRect==null){
Log.e(TAG,"registerBgr24:cropRectisnull");
returnnull;
}

cropRect.left&=~3;
cropRect.top&=~3;
cropRect.right&=~3;
cropRect.bottom&=~3;

StringimgPath=getImagePath(userName);

//创建一个头像的Bitmap,存放旋转结果图
BitmapheadBmp=getHeadImage(bgr24,width,height,faceInfoList.get(0).getOrient(),cropRect,ArcSoftImageFormat.BGR24);

try{
FileOutputStreamfos=newFileOutputStream(imgPath);
headBmp.compress(Bitmap.CompressFormat.JPEG,100,fos);
fos.close();
}catch(IOExceptione){
e.printStackTrace();
returnnull;
}

//内存中的数据同步
if(faceRegisterInfoList==null){
faceRegisterInfoList=newArrayList<>();
}

FaceEntityfaceEntity=newFaceEntity(name,idCard,imgPath,faceFeature.getFeatureData(),0L);
//判断是否存在这个人,如果存在覆盖,否则新增(解决因重置人脸删除和注册同事进行问题)
if(faceRegisterInfoList.contains(faceEntity)){
faceRegisterInfoList.remove(faceEntity);
List<FaceEntity>faceEntities=GreendaoUtils.Companion.getGreendaoUtils().searchFaceForIdcard(idCard);
if(faceEntities==null||faceEntities.isEmpty()){
longfaceId=GreendaoUtils.Companion.getGreendaoUtils().insert(faceEntity);
faceEntity.setFaceId(faceId);
}else{
faceEntities.get(0).setFeatureData(faceFeature.getFeatureData());
GreendaoUtils.Companion.getGreendaoUtils().update(faceEntities.get(0));
}
}else{
longfaceId=GreendaoUtils.Companion.getGreendaoUtils().insert(faceEntity);
faceEntity.setFaceId(faceId);
}
faceRegisterInfoList.add(faceEntity);

returnfaceEntity;
}else{
Log.e(TAG,"registerBgr24:extractfacefeaturefailed,codeis"+code);
returnnull;
}
}else{
Log.e(TAG,"registerBgr24:nofacedetected,codeis"+code);
returnnull;
}
}

人脸搜索

/**
*在特征库中搜索
*
*@paramfaceFeature传入特征数据
*@return比对结果
*/
publicCompareResultgetTopOfFaceLib(FaceFeaturefaceFeature){
if(faceEngine==null||faceFeature==null||faceRegisterInfoList==null||faceRegisterInfoList.isEmpty()){
returnnull;
}
longstart=System.currentTimeMillis();
FaceFeaturetempFaceFeature=newFaceFeature();
FaceSimilarfaceSimilar=newFaceSimilar();
floatmaxSimilar=0;
intmaxSimilarIndex=-1;

intcode=ErrorInfo.MOK;

synchronized(searchLock){
for(inti=0;i<faceRegisterInfoList.size();i++){
tempFaceFeature.setFeatureData(faceRegisterInfoList.get(i).getFeatureData());
code=faceEngine.compareFaceFeature(faceFeature,tempFaceFeature,faceSimilar);
if(faceSimilar.getScore()>maxSimilar){
maxSimilar=faceSimilar.getScore();
maxSimilarIndex=i;
}
}
}
if(maxSimilarIndex!=-1){
returnnewCompareResult(faceRegisterInfoList.get(maxSimilarIndex),maxSimilar,code,System.currentTimeMillis()-start);
}
returnnull;
}

测温部分(核心代码)

测温头我们使用usb连接测温头,采用简单的usb 模拟键盘的方式,测温头测到温度模拟键盘输入到终端的文本框中,代码监听键盘输入读取温度。当然也可以通过串口连接测温头,主动发指令操作测温头测温,这里我采用的是模拟键盘的方式。

publicclassReadTemperatureHelper{

privateStringBuffermStringBufferResult;//扫描内容
privatebooleanmCaps;
privatebooleanisCtrl;//大小写
privateOnReadSuccessListeneronReadSuccessListener;


publicReadTemperatureHelper(OnReadSuccessListeneronReadSuccessListener){
this.onReadSuccessListener=onReadSuccessListener;
mStringBufferResult=newStringBuffer();
}


/**
*事件解析
*
*@paramevent
*/
publicvoidanalysisKeyEvent(KeyEventevent){
intkeyCode=event.getKeyCode();
//判断字母大小写
checkLetterStatus(event);
checkInputEnt(event);
if(event.getAction()==KeyEvent.ACTION_DOWN){
charaChar=getInputCode(event);
if(aChar!=0){
mStringBufferResult.append(aChar);
}
Log.i("123123","keyCode:"+keyCode);
if(keyCode==KeyEvent.KEYCODE_ENTER){
//回车键返回
Log.i("123123","dispatchKeyEvent:"+mStringBufferResult.toString());
Strings=mStringBufferResult.toString();
//inti=s.lastIndexOf(":");
//Stringsubstring=s.substring(i);
//String[]s1=substring.split("");
Log.i("123123","体温为:"+s);
onReadSuccessListener.onReadSuccess(s.trim());
mStringBufferResult.setLength(0);

}

}
}

/**
*ctrl
*/
privatevoidcheckInputEnt(KeyEventevent){

if(event.getKeyCode()==KeyEvent.KEYCODE_CTRL_LEFT){
if(event.getAction()==KeyEvent.ACTION_DOWN){
isCtrl=true;
}else{
isCtrl=false;
}
}

}

/**
*shift键
*
*@paramkeyEvent
*/
privatevoidcheckLetterStatus(KeyEventkeyEvent){
intkeyCode=keyEvent.getKeyCode();
if(keyCode==KeyEvent.KEYCODE_SHIFT_LEFT||keyCode==KeyEvent.KEYCODE_SHIFT_RIGHT){
if(keyEvent.getAction()==KeyEvent.ACTION_DOWN){
//按住shift键大写
mCaps=true;
}else{
//小写
mCaps=false;
}
}
}

/**
*获取扫描内容
*
*@paramkeyEvent
*@return
*/
privatechargetInputCode(KeyEventkeyEvent){
charaChar;
intkeyCode=keyEvent.getKeyCode();
Log.i("TAGKEYCODE",keyCode+"");
if(keyCode>=KeyEvent.KEYCODE_A&&keyCode<=keyEvent.KEYCODE_Z)//29<keycode<54
{
//字母

aChar=(char)((mCaps?'A':'a')+keyCode-KeyEvent.KEYCODE_A);//
}elseif(keyCode>=KeyEvent.KEYCODE_0&&keyCode<=KeyEvent.KEYCODE_9){
//数字
if(mCaps)//是否按住了shift键
{
//按住了需要将数字转换为对应的字符
switch(keyCode){
caseKeyEvent.KEYCODE_0:
aChar=')';
break;
caseKeyEvent.KEYCODE_1:
aChar='!';
break;
caseKeyEvent.KEYCODE_2:
aChar='@';
break;
caseKeyEvent.KEYCODE_3:
aChar='#';
break;
caseKeyEvent.KEYCODE_4:
aChar='$';
break;
caseKeyEvent.KEYCODE_5:
aChar='%';
break;
caseKeyEvent.KEYCODE_6:
aChar='^';
break;
caseKeyEvent.KEYCODE_7:
aChar='&';
break;
caseKeyEvent.KEYCODE_8:
aChar='*';
break;
caseKeyEvent.KEYCODE_9:
aChar='(';
break;
default:
aChar='';
break;
}
}else{
aChar=(char)('0'+keyCode-KeyEvent.KEYCODE_0);
}

}else{
//其他符号
switch(keyCode){
caseKeyEvent.KEYCODE_PERIOD:
aChar='.';
break;
caseKeyEvent.KEYCODE_MINUS:
aChar=mCaps?'_':'-';
break;
caseKeyEvent.KEYCODE_SLASH:
aChar='/';
break;
caseKeyEvent.KEYCODE_STAR:
aChar='*';
break;
caseKeyEvent.KEYCODE_POUND:
aChar='#';
break;
caseKeyEvent.KEYCODE_SEMICOLON:
aChar=mCaps?':':';';
break;
caseKeyEvent.KEYCODE_AT:
aChar='@';
break;
caseKeyEvent.KEYCODE_BACKSLASH:
aChar=mCaps?'|':'\\';
break;
default:
aChar='';
break;
}
}

returnaChar;
}


publicinterfaceOnReadSuccessListener{
voidonReadSuccess(Stringtemperature);
}


}

在activity的dispatchKeyEvent方法,监听键盘输入事件

overridefundispatchKeyEvent(event:KeyEvent?):Boolean{
if(isReadTemp){
read.analysisKeyEvent(event)
if(event!!.keyCode==KeyEvent.KEYCODE_ENTER){
returntrue
}
}
returnsuper.dispatchKeyEvent(event)

}

开门(继电器方式 核心代码)

publicvoidopenG(){
Stringstatus="1";
try{
FileOutputStreamfos=newFileOutputStream("/sys/exgpio/relay1");
fos.write(status.getBytes());
fos.close();
}catch(Exceptione){
e.printStackTrace();
}
Stringstatus1="0";
SystemClock.sleep(200);
try{
FileOutputStreamfos=newFileOutputStream("/sys/exgpio/relay1");
fos.write(status1.getBytes());
fos.close();
}catch(Exceptione){
e.printStackTrace();
}
}

常见问题

本项目的开发和使用中也遇到了很多问题,我认为比较值得注意的有两个 1、室外复杂环境下,存在人脸识别久久不通过问题 这个问题不是偶发的问题,经过反复测试,室外较为昏暗的光线下,因为开启了ir红外活体检测,存在热源光不足导致活体检测不通过

2、室外环境导致测温不准确 这个问题是红外测温技术原理导致的,因为室外温度过高或者过低无法保证测温准确率,或者测不到温度。目前没有解决方案,后期会测量整个人脸框这以区域每个点的温度,作一定补偿取平均值。

补充

上述简单的罗列了一些核心的代码块,后面附源码,源码中有详细的业务代码,包含读身份证和扫码,因为身份证读卡器是公司产品,与其它的身份证读卡器读卡sdk不一样,所以删除了读卡sdk,业务代码保留。

读到身份证后回去后台验证此人健康码状态,然后确定是否开门

读取健康码使用串口读取,代码里有写,读到健康码后,去后台验证此健康码状态确认是否开门

因为测试需要,所以健康码部分代码注释掉了,项目中随机给的温度以便测试

到此,相信大家对“android基于虹软的人脸识别+测温+道闸项目的实现方法”有了更深的了解,不妨来实际操作一番吧!这里是恰卡编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

发布于 2021-07-09 21:19:46
收藏
分享
海报
0 条评论
169
上一篇:c#为什么不能调试C++生成的DLL 下一篇:怎么用Python自动给抖音漂亮小姐姐视频点赞
目录

    推荐阅读

    0 条评论

    本站已关闭游客评论,请登录或者注册后再评论吧~

    忘记密码?

    图形验证码