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