您好,登錄后才能下訂單哦!
本篇內容主要講解“android基于虹軟的人臉識別+測溫+道閘項目的實現方法”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“android基于虹軟的人臉識別+測溫+道閘項目的實現方法”吧!
平臺為Android平臺,采用kotlin+java混編 虹軟SDK版本為最新的4.0可以戴口罩識別 終端攝像頭采用雙目攝像頭模組IR活體識別 掃碼頭、測溫頭、身份證讀卡器皆為本公司設備,就不一一介紹了
人臉識別通過后自動測溫,然后向后臺上傳溫度和人員信息,后臺判斷溫度是否異常,并且保存人員通行記錄
人臉注冊: 人臉注冊采用另一種終端和小程序注冊兩種方式,這里只說小程序。 用戶使用小程序采集人臉照片上傳至服務器-->人臉終端起服務定時向服務端請求終端沒有注冊過的人臉-->終端拿到人臉照片之后注冊至本地。另外定時請求需要刪除和更改的人臉信息,然后本地做刪除更改操作。(直接同步人臉照片而不是特征值是因為虹軟目前沒有小程序的人臉識別sdk)
開門條件 以人臉識別+測溫、刷身份證+測溫、刷健康碼+測溫為開門條件。 本文主要講解人臉+測溫
PullDataServerHelper 拉取人臉信息幫助類,實現了拿到信息之后注冊人臉、刪除人臉、更改信息的操作
DataSyncService 數據同步服務,此類為server,主要功能是定時調用PullDataServerHelper做網絡請求
facedb包 此包中為數據庫操作相關文件,本項目數據操作使用greendao,不了解的可以了解一下,非常好用。
項目的一些東西就先說這么多,文章最后會附上源碼,接下來著重講一些虹軟SDK的使用
1.sdk的激活
SDK為一次激活永久使用,不可多次激活,本文使用在線激活的方式,后端錄入終端綁定激活碼,app帶著終端唯一標識向后端請求激活碼。 激活之前先判斷是否已經激活,沒有激活才繼續激活操作,下面為代碼:
fun Active() { //獲取激活文件 val activeFileInfo = ActiveFileInfo() val code = FaceEngine.getActiveFileInfo(mContext, activeFileInfo) if (code == ErrorInfo.MOK) { //已經激活 isActive.value = true return } else { //未激活 讀取本地存儲的激活碼 var sdkKey = readString( mContext, Constants.APP_SDK_KEY ) var appId = readString( mContext, Constants.APP_ID_KEY ) var activeKey = readString( mContext, Constants.APP_ACTIVE_KEY ) if (sdkKey.isNullOrEmpty()) { //本地無激活碼 從網絡獲取 getSdkInfo() } else { val code1 = FaceEngine.activeOnline( mContext, activeKey, appId, sdkKey ) if (code1 == ErrorInfo.MOK) { isActive.value = true return } else { getSdkInfo() } } } } private fun getSdkInfo() { RetrofitManager.getInstance().createReq(ApiServer::class.java) .getSdkInfo(AppUtils.getMac()) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(object : BaseObserver<SdkInfoResult>() { override fun onSuccees(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) val code1 = FaceEngine.activeOnline( mContext, data.data.activeKey, data.data.appId, data.data.sdkKey ) if (code1 == ErrorInfo.MOK) { isActive.value = true return } else { isActive.value = false } } } override fun onFailure(message: String?) { isActive.value = false } }) }
2、sdk初始化 初始化的各個屬性官方文檔都有詳細講解,這里就不贅述了
public void init() { Context context = CustomApplication.Companion.getMContext(); FaceServer.getInstance().init(context); ftEngine = new FaceEngine(); int ftEngineMask = FaceEngine.ASF_FACE_DETECT | FaceEngine.ASF_MASK_DETECT; int ftCode = ftEngine.init(context, DetectMode.ASF_DETECT_MODE_VIDEO, DetectFaceOrientPriority.ASF_OP_90_ONLY, FaceConfig.RECOGNIZE_MAX_DETECT_FACENUM, ftEngineMask); ftInitCode.postValue(ftCode); frEngine = new FaceEngine(); int frEngineMask = FaceEngine.ASF_FACE_RECOGNITION; if (FaceConfig.ENABLE_FACE_QUALITY_DETECT) { frEngineMask |= FaceEngine.ASF_IMAGEQUALITY; } int frCode = frEngine.init(context, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_90_ONLY, 10, frEngineMask); frInitCode.postValue(frCode); //啟用活體檢測時,才初始化活體引擎 int flCode = -1; if (FaceConfig.ENABLE_LIVENESS) { flEngine = new FaceEngine(); int flEngineMask = (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); LivenessParam livenessParam = new LivenessParam(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; } }
人臉注冊
public FaceEntity registerJpeg(Context context, FaceImageResult.DataBean data) throws RegisterFailedException { if (faceRegisterInfoList != null && faceRegisterInfoList.size() >= MAX_REGISTER_FACE_COUNT) { Log.e(TAG, "registerJpeg: registered face count limited " + faceRegisterInfoList.size()); // 已達注冊上限,超過該值會影響識別率 throw new RegisterFailedException("registered face count limited"); } Bitmap bitmap = 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); int code = ArcSoftImageUtil.bitmapToImageData(bitmap, imageData, ArcSoftImageFormat.BGR24); if (code != ArcSoftImageUtilError.CODE_SUCCESS) { throw new RuntimeException("bitmapToImageData failed, code is " + code); } return registerBgr24(context, imageData, bitmap.getWidth(), bitmap.getHeight(), data); } /** * 用于注冊照片人臉 * * @param context 上下文對象 * @param bgr24 bgr24數據 * @param width bgr24寬度 * @param height bgr24高度 * @param name 保存的名字,若為空則使用時間戳 * @return 注冊成功后的人臉信息 */ public FaceEntity registerBgr24(Context context, byte[] bgr24, int width, int height, String name,String idCard) { if (faceEngine == null || context == null || bgr24 == null || width % 4 != 0 || bgr24.length != width * height * 3) { Log.e(TAG, "registerBgr24: invalid params"); return null; } //人臉檢測 List<FaceInfo> faceInfoList = new ArrayList<>(); int code; 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 = new ArrayList<>(); faceEngine.getMask(maskInfoList); if (!maskInfoList.isEmpty()) { int isMask = maskInfoList.get(0).getMask(); if (isMask == MaskInfo.WORN) { /* * 注冊照要求不戴口罩 */ Log.e(TAG, "registerBgr24: maskInfo is worn"); return null; } } } FaceFeature faceFeature = new FaceFeature(); /* * 特征提取,注冊人臉時參數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); } String userName = name == null ? String.valueOf(System.currentTimeMillis()) : name; //保存注冊結果(注冊圖、特征數據) if (code == ErrorInfo.MOK) { //為了美觀,擴大rect截取注冊圖 Rect cropRect = getBestRect(width, height, faceInfoList.get(0).getRect()); if (cropRect == null) { Log.e(TAG, "registerBgr24: cropRect is null"); return null; } cropRect.left &= ~3; cropRect.top &= ~3; cropRect.right &= ~3; cropRect.bottom &= ~3; String imgPath = getImagePath(userName); // 創建一個頭像的Bitmap,存放旋轉結果圖 Bitmap headBmp = getHeadImage(bgr24, width, height, faceInfoList.get(0).getOrient(), cropRect, ArcSoftImageFormat.BGR24); try { FileOutputStream fos = new FileOutputStream(imgPath); headBmp.compress(Bitmap.CompressFormat.JPEG, 100, fos); fos.close(); } catch (IOException e) { e.printStackTrace(); return null; } // 內存中的數據同步 if (faceRegisterInfoList == null) { faceRegisterInfoList = new ArrayList<>(); } FaceEntity faceEntity = new FaceEntity(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()) { long faceId = GreendaoUtils.Companion.getGreendaoUtils().insert(faceEntity); faceEntity.setFaceId(faceId); }else { faceEntities.get(0).setFeatureData(faceFeature.getFeatureData()); GreendaoUtils.Companion.getGreendaoUtils().update(faceEntities.get(0)); } } else { long faceId = GreendaoUtils.Companion.getGreendaoUtils().insert(faceEntity); faceEntity.setFaceId(faceId); } faceRegisterInfoList.add(faceEntity); return faceEntity; } else { Log.e(TAG, "registerBgr24: extract face feature failed, code is " + code); return null; } } else { Log.e(TAG, "registerBgr24: no face detected, code is " + code); return null; } }
人臉搜索
/** * 在特征庫中搜索 * * @param faceFeature 傳入特征數據 * @return 比對結果 */ public CompareResult getTopOfFaceLib(FaceFeature faceFeature) { if (faceEngine == null || faceFeature == null || faceRegisterInfoList == null || faceRegisterInfoList.isEmpty()) { return null; } long start = System.currentTimeMillis(); FaceFeature tempFaceFeature = new FaceFeature(); FaceSimilar faceSimilar = new FaceSimilar(); float maxSimilar = 0; int maxSimilarIndex = -1; int code = ErrorInfo.MOK; synchronized (searchLock) { for (int i = 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) { return new CompareResult(faceRegisterInfoList.get(maxSimilarIndex), maxSimilar, code, System.currentTimeMillis() - start); } return null; }
測溫頭我們使用usb連接測溫頭,采用簡單的usb 模擬鍵盤的方式,測溫頭測到溫度模擬鍵盤輸入到終端的文本框中,代碼監聽鍵盤輸入讀取溫度。當然也可以通過串口連接測溫頭,主動發指令操作測溫頭測溫,這里我采用的是模擬鍵盤的方式。
public class ReadTemperatureHelper { private StringBuffer mStringBufferResult; //掃描內容 private boolean mCaps; private boolean isCtrl;//大小寫 private OnReadSuccessListener onReadSuccessListener; public ReadTemperatureHelper(OnReadSuccessListener onReadSuccessListener) { this.onReadSuccessListener = onReadSuccessListener; mStringBufferResult = new StringBuffer(); } /** * 事件解析 * * @param event */ public void analysisKeyEvent(KeyEvent event) { int keyCode = event.getKeyCode(); //判斷字母大小寫 checkLetterStatus(event); checkInputEnt(event); if (event.getAction() == KeyEvent.ACTION_DOWN) { char aChar = getInputCode(event); if (aChar != 0) { mStringBufferResult.append(aChar); } Log.i("123123", "keyCode:" + keyCode); if (keyCode == KeyEvent.KEYCODE_ENTER) { //回車鍵 返回 Log.i("123123", "dispatchKeyEvent:" + mStringBufferResult.toString()); String s = mStringBufferResult.toString(); // int i = s.lastIndexOf(":"); // String substring = s.substring(i); // String[] s1 = substring.split(" "); Log.i("123123", "體溫為:" + s); onReadSuccessListener.onReadSuccess(s.trim()); mStringBufferResult.setLength(0); } } } /** * ctrl */ private void checkInputEnt(KeyEvent event) { if (event.getKeyCode() == KeyEvent.KEYCODE_CTRL_LEFT) { if (event.getAction() == KeyEvent.ACTION_DOWN) { isCtrl = true; } else { isCtrl = false; } } } /** * shift鍵 * * @param keyEvent */ private void checkLetterStatus(KeyEvent keyEvent) { int keyCode = 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; } } } /** * 獲取掃描內容 * * @param keyEvent * @return */ private char getInputCode(KeyEvent keyEvent) { char aChar; int keyCode = 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);// } else if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) { //數字 if (mCaps)//是否按住了shift鍵 { //按住了 需要將數字轉換為對應的字符 switch (keyCode) { case KeyEvent.KEYCODE_0: aChar = ')'; break; case KeyEvent.KEYCODE_1: aChar = '!'; break; case KeyEvent.KEYCODE_2: aChar = '@'; break; case KeyEvent.KEYCODE_3: aChar = '#'; break; case KeyEvent.KEYCODE_4: aChar = '$'; break; case KeyEvent.KEYCODE_5: aChar = '%'; break; case KeyEvent.KEYCODE_6: aChar = '^'; break; case KeyEvent.KEYCODE_7: aChar = '&'; break; case KeyEvent.KEYCODE_8: aChar = '*'; break; case KeyEvent.KEYCODE_9: aChar = '('; break; default: aChar = ' '; break; } } else { aChar = (char) ('0' + keyCode - KeyEvent.KEYCODE_0); } } else { //其他符號 switch (keyCode) { case KeyEvent.KEYCODE_PERIOD: aChar = '.'; break; case KeyEvent.KEYCODE_MINUS: aChar = mCaps ? '_' : '-'; break; case KeyEvent.KEYCODE_SLASH: aChar = '/'; break; case KeyEvent.KEYCODE_STAR: aChar = '*'; break; case KeyEvent.KEYCODE_POUND: aChar = '#'; break; case KeyEvent.KEYCODE_SEMICOLON: aChar = mCaps ? ':' : ';'; break; case KeyEvent.KEYCODE_AT: aChar = '@'; break; case KeyEvent.KEYCODE_BACKSLASH: aChar = mCaps ? '|' : '\\'; break; default: aChar = ' '; break; } } return aChar; } public interface OnReadSuccessListener { void onReadSuccess(String temperature); } }
在activity的dispatchKeyEvent方法,監聽鍵盤輸入事件
override fun dispatchKeyEvent(event: KeyEvent?): Boolean { if (isReadTemp) { read.analysisKeyEvent(event) if (event!!.keyCode == KeyEvent.KEYCODE_ENTER) { return true } } return super.dispatchKeyEvent(event) }
public void openG() { String status = "1"; try { FileOutputStream fos = new FileOutputStream("/sys/exgpio/relay1"); fos.write(status.getBytes()); fos.close(); } catch (Exception e) { e.printStackTrace(); } String status1 = "0"; SystemClock.sleep(200); try { FileOutputStream fos = new FileOutputStream("/sys/exgpio/relay1"); fos.write(status1.getBytes()); fos.close(); } catch (Exception e) { e.printStackTrace(); } }
本項目的開發和使用中也遇到了很多問題,我認為比較值得注意的有兩個 1、室外復雜環境下,存在人臉識別久久不通過問題 這個問題不是偶發的問題,經過反復測試,室外較為昏暗的光線下,因為開啟了ir紅外活體檢測,存在熱源光不足導致活體檢測不通過
2、室外環境導致測溫不準確 這個問題是紅外測溫技術原理導致的,因為室外溫度過高或者過低無法保證測溫準確率,或者測不到溫度。目前沒有解決方案,后期會測量整個人臉框這以區域每個點的溫度,作一定補償取平均值。
上述簡單的羅列了一些核心的代碼塊,后面附源碼,源碼中有詳細的業務代碼,包含讀身份證和掃碼,因為身份證讀卡器是公司產品,與其它的身份證讀卡器讀卡sdk不一樣,所以刪除了讀卡sdk,業務代碼保留。
讀到身份證后回去后臺驗證此人健康碼狀態,然后確定是否開門
讀取健康碼使用串口讀取,代碼里有寫,讀到健康碼后,去后臺驗證此健康碼狀態確認是否開門
因為測試需要,所以健康碼部分代碼注釋掉了,項目中隨機給的溫度以便測試
到此,相信大家對“android基于虹軟的人臉識別+測溫+道閘項目的實現方法”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。