android基于虹软的人脸识别+测温+道闸项目的实现方法
本篇内容主要讲解“android基于虹软的人脸识别+测温+道闸项目的实现方法”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“android基于虹软的人脸识别+测温+道闸项目的实现方法”吧!
软硬件环境
平台为Android平台,采用kotlin+java混编 虹软SDK版本为最新的4.0可以戴口罩识别 终端摄像头采用双目摄像头模组IR活体识别 扫码头、测温头、身份证读卡器皆为本公司设备,就不一一介绍了
UI界面和机器展示
使用说明
人脸识别通过后自动测温,然后向后台上传温度和人员信息,后台判断温度是否异常,并且保存人员通行记录
项目总体流程
人脸注册: 人脸注册采用另一种终端和小程序注册两种方式,这里只说小程序。 用户使用小程序采集人脸照片上传至服务器-->人脸终端起服务定时向服务端请求终端没有注册过的人脸-->终端拿到人脸照片之后注册至本地。另外定时请求需要删除和更改的人脸信息,然后本地做删除更改操作。(直接同步人脸照片而不是特征值是因为虹软目前没有小程序的人脸识别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基于虹软的人脸识别+测温+道闸项目的实现方法”有了更深的了解,不妨来实际操作一番吧!这里是恰卡编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!
推荐阅读
-
android(如何快速开发框架 小米note开发版MIUI,安卓6.0,怎么安装Xposed框架)
稳定版,你必须先根除。你上网搜索安卓可以叫别人s框架,对方可以把框架做成jar包,把这个jar包加载到项目目录的libs文件中使...
-
android(studio 虚拟机启动不了 android studio可以当模拟器用吗)
androidstudio可以当模拟器用吗?AmdCUP引导模拟器有点复杂。雷电模拟器上的抖音怎么登录不上?不是,闪电模拟调用...
-
从实践中学习手机抓包与数据分析(android 手机抓包app)
android手机抓包app?netcapture抓包精灵app(手机抓包工具)又名sslcapture,是什么专业的安卓手机抓...
-
android(studio全局搜索 android studio怎么看app界面)
androidstudio怎么看app界面?在设备桌面点击运用直接进入到App界面,就也可以参与其他你的操作了。android-...
-
怎么把android框架源代码拉到本地(android studio如何运行别人的源代码)
androidstudio如何运行别人的源代码?androidstudio点击刚建在列表中你选择导入module,导入即可在用...
-
android(studio2022年使用教程 怎么安装Android studio详细教程)
怎么安装Androidstudio详细教程?androidstudio中haxm直接安装的方法追加:1、简单的方法打开Andr...
-
怎么使用Android基准配置文件Baseline Profile方案提升启动速度
-
HTML5如何实现禁止android视频另存为
-
学java好还是学php好?
-
Android如何实现多点触控功能