基于虹软人脸识别,实现身份认证和自助发卡

  去年下半年开始从BS开发转战CS开发了,相继做了一些大大小小的项目。最近在做的一个人脸识别挺有意思,作为一个初学者我也是摸着石头过河。这个项目主要是通过摄像头获取视频帧,然后使用SDK提取视频帧和身份证照片的特征,使用特征进行比对,比对通过的话,可以通过发卡机写入信息至卡片并吐出这张卡片,用户拿着这张卡片进行后续操作。对于发卡机只需要把一些操作方法进行封装,通过串口发送命令就可以了,身份证信息可以通过读卡器进行获取,这里主要聊一聊进行人脸识别的业务集成,希望对你有所帮助。

一、流程图

基本流程如下图,用户在自助发卡机前选择发卡操作,这时自助机会打开摄像头,只需要把身份证放到身份证读卡器上即可,然后摄像头捕获到的人脸与身份证上的人脸进行相似度比对,如果比对通过则由发卡机写入信息并进行发卡操作。

二、申请并配置KEY

   我这里使用的是虹软视觉开发平台的SDK,首先注册开发者,然后新建应用,你会得到全新的APP_ID和SDK_KEY。个人认证用户每年共有100个设备可以免费激活,而且有效期是一年,一年之后需要给程序更换新的SDK。

然后我们拿到对应的APP_ID以及SDK_KEY之后,就可以下载开发包了,我这里选择V3.0版本的SDK,然后配置到程序中。

三、界面设计 

大致的界面是这个样子,很普通,左侧是视频,右边是身份证照片以及一些状态

四、初始化引擎 

开发时用到了三个引擎,

 第一个是图片模式下的人脸检测引擎

#region 图片引擎pImageEngine初始化
//初始化引擎
uint detectMode = DetectionMode.ASF_DETECT_MODE_IMAGE;
//检测脸部的角度优先值
int detectFaceOrientPriority = ASF_OrientPriority.ASF_OP_0_HIGHER_EXT;
//人脸在图片中所占比例,如果需要调整检测人脸尺寸请修改此值,有效数值为2-32
int detectFaceScaleVal = 16;
//最大需要检测的人脸个数
int detectFaceMaxNum = 5;
//引擎初始化时需要初始化的检测功能组合
int combinedMask = FaceEngineMask.ASF_FACE_DETECT | FaceEngineMask.ASF_FACERECOGNITION | FaceEngineMask.ASF_AGE | FaceEngineMask.ASF_GENDER | FaceEngineMask.ASF_FACE3DANGLE;
//初始化引擎,正常值为0,其他返回值请参考http://ai.arcsoft.com.cn/bbs/forum.php?mod=viewthread&tid=19&_dsign=dbad527e
retCode = ASFFunctions.ASFInitEngine(detectMode, detectFaceOrientPriority, detectFaceScaleVal, detectFaceMaxNum, combinedMask, ref pImageEngine);
if (retCode == 0)
{
    lbl_msg.Text=("图片引擎初始化成功!\n");
}
else
{
    lbl_msg.Text = (string.Format("图片引擎初始化失败!错误码为:{0}\n", retCode));
}
#endregion

第二个是视频模式下的人脸检测引擎

#region 初始化视频模式下人脸检测引擎
uint detectModeVideo = DetectionMode.ASF_DETECT_MODE_VIDEO;
int combinedMaskVideo = FaceEngineMask.ASF_FACE_DETECT | FaceEngineMask.ASF_FACERECOGNITION;
retCode = ASFFunctions.ASFInitEngine(detectModeVideo, detectFaceOrientPriority, detectFaceScaleVal, detectFaceMaxNum, combinedMaskVideo, ref pVideoEngine);
if (retCode == 0)
{
    lbl_msg.Text=("视频引擎初始化成功!\n");
}
else
{
    lbl_msg.Text = (string.Format("视频引擎初始化失败!错误码为:{0}\n", retCode));
}
#endregion

第三个是视频专用FR引擎,进行活体检测

#region 视频专用FR引擎
detectFaceMaxNum = 1;
combinedMask = FaceEngineMask.ASF_FACERECOGNITION | FaceEngineMask.ASF_FACE3DANGLE | FaceEngineMask.ASF_LIVENESS;
retCode = ASFFunctions.ASFInitEngine(detectMode, detectFaceOrientPriority, detectFaceScaleVal, detectFaceMaxNum, combinedMask, ref pVideoImageEngine);
Console.WriteLine("InitVideoEngine Result:" + retCode);

if (retCode == 0)
{
    lbl_msg.Text = ("视频专用FR引擎初始化成功!\n");
}
else
{
    lbl_msg.Text = (string.Format("视频专用FR引擎初始化失败!错误码为:{0}\n", retCode));
}
// 摄像头初始化
filterInfoCollection = new FilterInfoCollection(FilterCategory.VideoInputDevice);
lbl_msg.Text = (string.Format("摄像头初始化完成...\n"));
#endregion

  视频处理这里使用的是AForge.Video 视频处理类库,然后我们在电脑上接上USB摄像头,通过此类库就可以调用摄像头的开关了,那具体的人脸识别我们肯定要放在视频流渲染事件上了。

  我们首先将身份证放在身份证阅读器上,获取到身份信息,并把身份信息中的人脸照片拿出来,这时我们要用到pImageEngine图片引擎去从证件照中提取人脸特征值。然后我在能够读到身份证信息 和 视频信息都OK的情况下再去获取当前摄像头下的图片,

//得到当前摄像头下的图片
Bitmap bitmap = videoSource.GetCurrentVideoFrame();
//传入比对函数中进行比对
CompareImgWithIDImg(bitmap, e);

五、人脸比对

/// <summary>
/// 比对函数,将每一帧抓拍的照片和身份证照片进行比对
/// </summary>
/// <param name="bitmap"></param>
/// <param name="e"></param>
/// <returns></returns>
private bool CompareImgWithIDImg(Bitmap bitmap, PaintEventArgs e)
{
    recTimes--;
    if (bitmap == null)
    {
        return false;
    }
    Graphics g = e.Graphics;
    float offsetX = videoSource.Width * 1f / bitmap.Width;
    float offsetY = videoSource.Height * 1f / bitmap.Height;
    //检测人脸,得到Rect框
    ASF_MultiFaceInfo multiFaceInfo = FaceUtil.DetectFace(pVideoEngine, bitmap);
    //得到最大人脸
    ASF_SingleFaceInfo maxFace = FaceUtil.GetMaxFace(multiFaceInfo);
    //得到Rect
    MRECT rect = maxFace.faceRect;
    float x = rect.left * offsetX;
    float width = rect.right * offsetX - x;
    float y = rect.top * offsetY;
    float height = rect.bottom * offsetY - y;
    //根据Rect进行画框
    g.DrawRectangle(pen, x, y, width, height);
    //将上一帧检测结果显示到页面上
    g.DrawString(trackUnit.message, font, brush, x, y + 5);
    //保证只检测一帧,防止页面卡顿以及出现其他内存被占用情况
    if (isLock == false)
    {
        isLock = true;
        //异步处理提取特征值和比对,不然页面会比较卡
        ThreadPool.QueueUserWorkItem(new WaitCallback(delegate
        {
            if (rect.left != 0 && rect.right != 0 && rect.top != 0 && rect.bottom != 0)
            {
                try
                {
                    //提取人脸特征
                    IntPtr feature = FaceUtil.ExtractFeature(pVideoImageEngine, bitmap, maxFace);
                    float similarity = CompareTwoFeatures(feature, idCardHelper.idInfo.imageFeature);
                    this.similarity.Text = ("相似度为: " + similarity.ToString("P")); ; //显示在界面上
                    this.similarity.ForeColor = similarity > threshold ? Color.Green : Color.Red;
                    //得到比对结果
                    int result = (CompareTwoFeatures(feature, idCardHelper.idInfo.imageFeature) >= threshold) ? 1 : -1;
                    if (result > -1)
                    {
                        bool isLiveness = false;
                        ImageInfo imageInfo = ImageUtil.ReadBMP(bitmap); //调整图片数据
                        if (imageInfo == null)
                            return;
                        int retCode_Liveness = -1;
                        //RGB活体检测
                        ASF_LivenessInfo liveInfo = FaceUtil.LivenessInfo_RGB(pVideoImageEngine, imageInfo, multiFaceInfo, out retCode_Liveness);
                        //判断检测结果
                        if (retCode_Liveness == 0 && liveInfo.num > 0)
                        {
                            int isLive = MemoryUtil.PtrToStructure<int>(liveInfo.isLive);
                            isLiveness = (isLive == 1) ? true : false;
                        }
                        if (isLiveness)//活体检测成功
                        {
                            //存放当前人脸识别的相似度
                            idCardHelper.idInfo.similarity = similarity;
                            //记录下当前的摄像头的人脸抓拍照
                            idCardHelper.idInfo.capImage = bitmap;
                            //验证通过则不再是当前身份证,等待下一次身份证
                            idCardHelper.idInfo.isRight = false;
                            //在子线程中输出信息到messageBox
                            AppendText p = new AppendText(AddTextToMessBox);
                            lbl_msg.Invoke(p, "人脸验证成功,请取卡...\n");
                            pass = 1;
                            idCardHelper.idInfo.isPass = 1;
                            //将比对结果放到显示消息中,用于最新显示
                            trackUnit.message = string.Format("通过验证,相似度为{0}", similarity);
                            FileHelper.DeleteFile(m_strPath);   //删除验证过的本地文件
                            Thread.Sleep(1000);//延时1秒
                            this.IDPbox.Image = defaultImage;//照片恢复默认照片
                            trackUnit.message = "";//人脸识别框文字置空
                            setFormResultValue(true);
                        }
                        else
                        {
                            pass = 0;//标志未通过
                            trackUnit.message = "未通过,系统识别为照片";
                            AppendText p = new AppendText(AddTextToMessBox);
                            lbl_msg.Invoke(p, "抱歉,您未通过人脸验证...\n");
                            FileHelper.DeleteFile(m_strPath);//删除验证过的本地文件
                        }
                    }
                    else
                    {
                        pass = 0;//标志未通过
                        trackUnit.message = "未通过人脸验证";
                        AppendText p = new AppendText(AddTextToMessBox);
                        lbl_msg.Invoke(p, "抱歉,您未通过人脸验证...\n");
                        FileHelper.DeleteFile(m_strPath);//删除验证过的本地文件
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    FileHelper.DeleteFile(m_strPath);//删除验证过的本地文件
                }
                finally
                {
                    isLock = false;
                }
            }
            isLock = false;
        }));
    }
    return false;
}

/// <summary>
/// 比较两个特征值的相似度,返回相似度
/// </summary>
/// <param name="feature1"></param>
/// <param name="feature2"></param>
/// <returns></returns>
private float CompareTwoFeatures(IntPtr feature1, IntPtr feature2)
{
    float similarity = 0.0f;
    //调用人脸匹配方法,进行匹配
    ASFFunctions.ASFFaceFeatureCompare(pVideoImageEngine, feature1, feature2, ref similarity);
    return similarity;
}

我们这个时候是获取的视频中的图片,需要用到视频引擎pVideoEngine去从bitmap中检测人脸并获取最大的那张脸,并提取人脸特征值。获取到视频中和照片中两张人脸的特征值了,那接下来就是将两张照片交给虹软人脸比对算法获取相似度,我们可以设置一个阈值,超过90%我们就可以认定是同一个人,这个还是要在实际项目中去做权衡。

   这样我们就算是比对成功,可以进行后续业务了。但是还没完,我刷完身份证后,迅速用身份证对着摄像头,发现竟然也比对成功了,那如果这样的话,即使不是本人,别人从手机里面拿着照片就可以进行认证了,必然造成不安全性,于是我就把活体检测加上了,活体检测,顾名思义,就是看看是不是个大活人而非照片。

int retCode_Liveness = -1;
//RGB活体检测
ASF_LivenessInfo liveInfo = FaceUtil.LivenessInfo_RGB(pVideoImageEngine, imageInfo, multiFaceInfo, out retCode_Liveness);
//判断检测结果
if (retCode_Liveness == 0 && liveInfo.num > 0)
{
    int isLive = MemoryUtil.PtrToStructure<int>(liveInfo.isLive);
    isLiveness = (isLive == 1) ? true : false;
}
if (isLiveness)//活体检测成功

加上活体检测,即使是照片,就算相似度达到90%以上,我们也不会放过。

六、遇到的问题

  到这里基本功能都已经结束了,但是在多次的调试中发现,时不时就会来一次闪退,就是内存溢出。因为我的这个页面是单独弹出来的,这是一个子页面,之前关闭窗口的时候没有把引擎释放,所以导致每次初始化一个引擎大概需要50M左右的内存,迟早会出现内存溢出的情况。于是我就在这个子窗口关闭的时候,对这三个引擎进行释放,这个问题就最终解决了。

/// <summary>
/// 窗体关闭事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void IdentityVerify_FormClosed(object sender, FormClosedEventArgs e)
{
    //销毁引擎
    int retCode = ASFFunctions.ASFUninitEngine(pImageEngine);
    Console.WriteLine("UninitEngine pImageEngine Result:" + retCode);
    //销毁引擎
    retCode = ASFFunctions.ASFUninitEngine(pVideoEngine);
    Console.WriteLine("UninitEngine pVideoEngine Result:" + retCode);
    //销毁引擎
    retCode = ASFFunctions.ASFUninitEngine(pVideoImageEngine);
    Console.WriteLine("UninitEngine pVideoImageEngine Result:" + retCode);
    if (videoSource.IsRunning)
    {
        videoSource.SignalToStop(); //关闭摄像头
    }
    idCardHelper.CloseService();
    this.Dispose();
    this.Close();
    MemoryUtil.ClearMemory();
}

 GitHub已开源此Demo。

  

posted @ 2021-03-12 16:44  土伦  阅读(514)  评论(0编辑  收藏