温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

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

发布时间:2021-07-09 17:35:30 来源:亿速云 阅读:461 作者:chen 栏目:编程语言

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

软硬件环境

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

UI界面和机器展示

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

使用说明

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

项目总体流程

人脸注册: 人脸注册采用另一种终端和小程序注册两种方式,这里只说小程序。 用户使用小程序采集人脸照片上传至服务器-->人脸终端起服务定时向服务端请求终端没有注册过的人脸-->终端拿到人脸照片之后注册至本地。另外定时请求需要删除和更改的人脸信息,然后本地做删除更改操作。(直接同步人脸照片而不是特征值是因为虹软目前没有小程序的人脸识别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基于虹软的人脸识别+测温+道闸项目的实现方法”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI