去年下半年开始从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(); }