人脸识别项目总结
前言
這篇博客是記錄如何將已有人臉識別算法的前提下,一步步搭建人臉識別系統,找人臉識別算法不用往下看了
作為一水碩,研究生兩年就做了一個半成品的人臉識別演示項目,突然想寫點什么給以后的自己看看。也許未來自己會嘲笑現在寫的爛代碼,也許不會,也許未來會把這段經歷忘掉。人臉識別伴隨著我的研究生3年,也許這篇文章寫著寫著就成了我研究生的總結了。
自己當前狀態
2015年,研究生正式入學,我以為我的生活是不停地搞學術,立刻我發現自己對學術沒興趣,我以為我的生活是參與一個精美的項目中進行coding,結果是不斷地寫demo做演示。我和我本科入學時犯了同樣的錯誤看待問題,不全面,把事物看的太極端了,正如大學不是全是天堂,研究生也不是全是學術和項目。剛入學時,想著臥槽自己怎么也是個高級人才了,得學點高級的知識,什么機器學習,數據挖掘,圖像處理都好好涉獵一下,但無奈沒有鉆研的決心與信心,自己又沒啥天賦,到現在也只是知道名詞,上網百度百度調用調用開源庫。我們導師的人臉識別項目組,我可能是最后一屆,整體項目已經過渡到另一個老師下面了。研一時不停地上課,被同學拉著參加兩個比賽,順帶出差做演示;研二渾渾噩噩,看論文,出差,寫demo,做演示;研三找工作、寫論文,寫項目;兩年半,就這么平平淡淡過來了,并不出彩,但也沒混日子。
項目前身
被分到人臉識別組時,人臉識別組已經火過一波了。當時,我們導師從外面搞來了一整套系統,這套系統是一個成熟而完整的產品,有多成熟而完整?這套系統支持sqlserver、 mysql 、oracle等數據庫,安裝包有近400M,有不錯的GUI界面,以windows 服務作為提供后臺服務,支持C#和C++調用接口,從1:1人臉相似度比較到1:N的黑名單搜索,從靜態圖片搜索到視頻流實時跟蹤都支持,印象中還有一些我看不懂的功能。我所做的項目也是模仿的這套系統。這套系統的缺點也很明顯,那就是嚴格的加密,每次運行時都需要一個加密狗插在電腦上,每兩個月需通過專門的人員更新加密狗,它做了什么?如何做的?對于我們幾乎是黑盒。當時這套系統就是我們組的命根子,研一研二時的演示我們組就主要就靠它,每次要演示都要擔心加密狗是否過期了。
我無法回憶起當時的是什么促成了我想自己動手實現一個人臉識別的項目,也許是厭煩了整天拿著那套系統出去演示還要說是我們自己的技術,也許是當時出現了seetaface這樣一個開源的技術,也許是師兄的鼓勵,也許是閑的蛋疼自己想找點事做,也許是必然。
在對那整套人臉識別的二次開發中,一次有客戶要求能夠從遠程調用人臉識別接口,我詢問師兄 ,師兄說寫過。打開師兄封裝的代碼,封裝得也太簡單了,不就是WCF嗎?本科我也學過。我得做些什么,C#不是可以查看引用dll的所有類嗎?好!我把所有的類都自己二次封裝一遍,以后遠程調用就更加靈活了,然而最終并沒有用上。這次封裝直加深了我對這套系統的認識,算是對未來自己構建人臉識別系統打下基礎。
研一暑假時,導師來了一個行駛證識別項目,手機拍攝的照片,各種角度和光照的都有,導師讓我和師兄試試,師兄和我不知道其中的難度,便開始了嘗試。那個暑假我在師兄的指導下完成了opencv的配置;嘗試opencv各種自帶函數:膨脹收縮、放射變換、高斯模糊、霍夫變換、灰度均衡化等;使用tesseract進OCR,最終做出了針對某一張照片的識別,理所當然項目沒成功。
研二時,外面的人臉識別技術依舊火熱,seetaface出現,聽說效果不錯,LFW能到97.1%,和現有的這套系統一對比,效果真的不錯,好多沒有檢測到的人臉都檢測出來了,如果我能照著寫一套支持視頻流的演示系統,那就不用受加密狗的約束了,說干就干,花了大概20天一套基于opencv的原始系統被我寫出來了。期間人臉組也添加一個師弟,師弟是很厲害,初中學編程,能自己寫外掛,ACM拿過獎。我讓師弟給我的系統加一個線程并行,他花了半天加了一個線程池任務隊列,這個機制我一直到研三才弄懂,但后來師弟到別的項目組了,可能覺得我們這邊只有他一個16級的,亦或是在這邊沒人指導。后來老師又搞了一個人臉識別算法,這次不是一個系統,直接以庫的形式給我們,但仍然需要加密狗,因為加密狗的緣故,它不能斷點調試,開發很痛苦,不過有了前面seetaface的經驗,我很快又完成了一個系統。研二寒假時,導師的合作人有個人臉開閘機的項目,需要做出演示系統來,為這個項目拖到了過年的前一天才回家。同時,我們導師手下的另一個老師L,接手了此前一個與外面合作的動態人臉識別的項目,項目搞得很大,有政府部門和好幾家公司,但我們這邊寫代碼的只有三人,我、L老師的一個學生P和公司的一個人X,此時,我帶起了我真正意義上的師弟。項目是基于我的系統,幾輪測試下來,不太理想,但還是讓我們測試了一段時間。期間,我們對代碼進行修正,但bug仍不段出現,內存泄漏,程序莫名崩潰,無法斷點調試。項目一直持續到17年的7月,最終不了了之。
17年的9月下旬時,此時我已研三了,L老師又有一個項目,要把以前的C/S架構改成B/S架構。我答應下來后端開發的任務,從github上找了一個http服務器魔改一下,前后斷斷續續花了一個月,完成了任務,后來將項目給了P。時至今日,2018年的元旦,估計P仍在我的基礎上改著程序。
人臉識別詳情
一、人臉識別SDK
所謂人臉識別接口有三個:
| 1 | 人臉檢測 face detection | DetectFace | 圖像 | 人臉位置 | |
| 2 | 人臉特征抽取 feature extraction | Extract | 圖像,人臉位置 | 特征數組 | 將人臉圖像變成大小固定的特征數組 |
| 3 | 特征相似度比較feature comparation | Compare | 特征數組,特征 | 相似度 | 比較人臉特征的相似度 |
有的人臉識別接口會存在第1.5步 :檢測人臉關鍵點,眼睛鼻子嘴之類的,無需在意。
二、人臉識別系統
1基本功能
一個初級人臉識別系統需要的基本功能如下:
| 1 | 人員注冊 | Register | 人臉圖像,人員信息 | ———— |
| 2 | 人員搜索 | Search | 人臉圖像,相似度閾值,最大候選數 | 可能相似的人員 |
- 人員注冊
人員注冊會用到如下數據結構,最關鍵的是face_feature,它由人臉識別SDK的前兩步得到:
- 人員搜索
人員搜索會面臨輸入圖像中存在多個人臉的情況,這里首先討論單個人臉的情況。
首先確定搜索的返回結構Hitrecord,如下:
struct Hitrecord{float threshold;//搜索時設定的閾值//image search_face_image;//搜索的人臉圖片string search_face_image_path;//搜索的人臉圖片//`hit_detials`中的`HitDetial`是按`score`降序排列的array<HitDetial> hit_detials;//候選的人員結果time occur_time;//發生時間 } struct HitrecordDetail{float score;//相似度int person_id; }在搜索之前所有的注冊人員信息都載入內存了,整個搜索過程如下:
- 利用SDK的前兩步得到搜索的人臉特征feature
- 將feature與每個已經注冊的人員的face_feature依次進行Compare得到相似度,剔除掉小于相似度閾值的結果,保留最大的幾個結果。
對于輸入中存在多個人臉的情況,只需分開處理即可
2數據庫模塊-DataAgine 工程
以上的基本功能并沒有考慮數據存儲的問題,當然我寫過沒有數據庫的版本,直接將注冊的人員庫序列化到文件中,而不保留命中的記錄,這樣做很省事,但只能演示,客戶肯定是要查看最終的命中紀錄的,也就是以Hitrecord為基礎的擴展數據。所以必須考慮存儲的問題。
最終我用了mysql數據庫,原因很簡單,mysql很容易安裝,下載解壓,輸幾個命令就行了。
根據基本功能根據基本功能我寫了如下幾個表:
CREATE DATABASE `frsdb`; USE `frsdb`;create table `frsdb`.`person` (`id` int(11) AUTO_INCREMENT,`name` nvarchar(50) NULL,`gender` char(1) NULL,`face_image_path` varchar(200) NOT NULL,`feature_data` LongBlob NOT NULL,PRIMARY KEY (`id`),UNIQUE KEY `id` (`id`) )ENGINE=InnoDB DEFAULT CHARSET=utf8;create table `frsdb`.`hitrecord` (`id` int(11) AUTO_INCREMENT,`search_face_image_path` varchar(200) NOT NULL,`threshold` float NOT NULL,`occur_time` datetime NOT NULL,PRIMARY KEY (`id`),UNIQUE KEY `id` (`id`) )ENGINE=InnoDB DEFAULT CHARSET=utf8;create table `frsdb`.`hitrecord_detail` (`id` int(11) AUTO_INCREMENT,`hitrecord_id` int(11) NOT NULL,`person_id` int(11) NOT NULL, `rank` int(11) NOT NULL,`score` float NOT NULL,PRIMARY KEY (`id`),UNIQUE KEY `id` (`id`) )ENGINE=InnoDB DEFAULT CHARSET=utf8;create view `frsdb`.`hitalert` as select hit.id,hit.search_face_image_path,hit.threshold,hit.occur_time,detail.id as detail_id,detail.rank,detail.score,person.id as person_id,person.name as person_name,person.gender as person_gender,person.face_image_path as person_face_image_path,FROM (`frsdb`.`hitrecord_detail` as detailleft join `frsdb`.`hitrecord` as hit on detail.hitrecord_id=hit.id) left join `frsdb`.`person` as person on detail.person_id = person.id;為了最后的方便查看命中記錄,我創建了視圖hitalert。
我用了動軟的代碼生成器,自動生成了Model,DLL,BLL,所謂的三層架構。動軟Model,DLL,BLL三層分布在不同的項目中,我嫌項目太多,直接將他們合為一個DataAgine 項目,只是Model,DLL,BLL放在了三個不同的文件夾中。
3基本實現
完備的人臉識別演示系統,無非就是不斷從視頻中抽畫面幀,進行人員搜索。
現在我手里有一個對外提供C語言接口的windows人臉識別SDK,圖片是以char數組的形式傳入的,它可以檢測人臉 提取人臉的特征(固定大小的數組),比較特征間的相似度。我要實現什么?注冊、搜索、與視頻流進行對接,實現實時監控。
我的內心活動:圖片什么的,用opencv就好了,貌似opencv也支持視頻流的讀取,等等,得有界面啊,opencv貌似只能用C++啊,C++ windows GUI開發下有qt和MFC,臥槽都不熟,我只會C# GUI,拖拽控件就行了。 C#和C++怎么交互啊。經過一番尋覓,最終我確定了用托管C++來實現,也就是“CLI/C++”,它可以同時使用C++和C#的庫,C#也可以直接引用托管C++寫的庫,perfect! 就是它了,主體實現就用它了。
話外音:其實主體用托管C++,不一定是最好的選擇,C#一樣可以處理圖片和視頻流的庫,托管C++語法需要慢慢熟悉,有坑踩。托管C++(C#)的數組類型,與C++的數組類型轉換就很煩,stackoverflow解決了。托管C++(C#)的Image類型到 opencv 的Mat類型也煩,github 找了個opencvsharp解決了。
基本功能中的Register和Search 就放一個類中,就叫PersonDataset,人臉識別的接口也得托管C++用封裝一下,以防C#調用(事實上并沒有發生),還得有一個負責視處理頻流的類,就參考opencv叫Capture,每個Capture就對應一個PersonDataset對象。
class PersonDataset{array<Person> persons;//存放注冊的用戶int LoadAllPerson();//載入人員庫int Register(Image im, String name,String gender..)//注冊//搜索,接受圖片,相似度閾值,每張人臉返回的最大候選數目,存在數據庫操作array<Hitrecord> Search(Image im,float thresh,int maxCandinateNum); } Class Capture{PersonDataset *personDataset;Callback OnHit(array<Hitrecord> hits);//每次命中時的回掉函數,C#中用事件Callback OnGrab(Image im);//每次獲取一幀畫面時的回掉函數,用于展示,讓一個控件顯示im//循環搜索線程Thread searchThread{while(runing){獲取一幀畫面frame;hits=personDataset->Search(frame,..);OnHit(hits);//調用回調}}//循環顯示線程Thread showThread{while(runing){獲取一幀畫面frame;Sleep(30);//休眠30msOnGrab(frame);}}int Start(String videoAddress){開啟視頻流;showThread.start();searchThread.start();} }由于使用了多線程Capture,在讀取畫面時,互斥必不可少。Capture需要與一個PersonDataset相關聯。Capture 靠回調函數返回結果。開始時,Start開啟了兩個線程一個用來顯示畫面,一個用來返回命中結果。
以上都是一個后臺邏輯的實現,采用的都是托管C++,還需要一個界面。界面用的是winform,C#直接引用DataAngine的所編譯的庫,其他就是接收視頻地址的TextBox,開始停止,查詢用的Button,顯示查詢結果的DatagridView,顯示監控畫面的PictureBox,做的很是丑陋。
4系統改進
opencv 處理的視頻流用的是VideoCapture類有bug,會花屏,每次開啟關閉視頻流會內存泄漏,后來改用了vlc,在github上搜vlc opencv windows 找到個例子直接用了 。
為了提高精度,L老師想出了利用海康攝像頭自帶人臉跟蹤的功能,一個人從出現到消失在鏡頭中海康攝像頭會記錄這個人的幾張照片。提高精度就靠這幾張照片,假設有5張,首先對這5張照片都搜索,返回:
{{a1,b1,c1,d1,e1},{a2,b2,c2,d2,e2},{a3,b3,c3,d3,e3},{a4,b4,c4,d4,e4},{a5,b5,c5,d5,e5}}a b c d e 的分數為降序。
- 返回這五張照片中分數最高的那個
- 返回a 比b 相差最大的那個a,這個貌似結果好一點
- 返回a 中多數的那個
三、從C/S到B/S
從C/S到B/S,客戶需要自己添加設備信息(device),人員庫類別也有多種(person 表添加一個type字段就可以),還需要一個表將設備與人員庫象關聯(surveillance_task)
create table `frsdb`.`device` (`id` int(11) AUTO_INCREMENT, `name` nvarchar(50) NOT NULL,`video_address` nvarchar(200) NOT NULLPRIMARY KEY (`id`),UNIQUE KEY `id` (`id`) )ENGINE=InnoDB DEFAULT CHARSET=utf8;create table `frsdb`.`surveillance_task` (`id` int(11) AUTO_INCREMENT,`name` nvarchar(50) NOT NULL,`person_type` int(11) NOT NULL,`device_id` int(11) NOT NULL,PRIMARY KEY (`id`),UNIQUE KEY `id` (`id`) )ENGINE=InnoDB DEFAULT CHARSET=utf8;每次開始需要指定一個surveillance_task的id來載入指定的人員,監控指定的設備。
不得不說程序員都是樂觀的,當時我從來沒有做過這樣的項目,沒寫過網站項目。完全不知道怎么返回數據,思考了半天,我覺得和http服務器有點像,客戶端(瀏覽器)請求一個地址,我給它返回一個數據,于是在gitihub找了個C#的http服務器,直接魔改了。當然其中過程有些曲折。原來的服務器只能返回靜態文件,后來改成了可以根據不同的地址和參數接受返回不同的(json)數據,期間我接觸了restful 風格的設計,于是就有了以下的設計:
獲得id 為1的設備 GET http://localhost:8080/v1/device/1/update 添加設備 GET http://localhost:8080/v1/device/更新id 為1的設備 POST http://localhost:8080/v1/device/1/update 刪除id 為1的設備 POST http://localhost:8080/v1/device/1/delete至此傳統的 服務–客戶端 已經走通,還有個難題,那就是如何實時更新命中的數據(hitrecord)到客戶端?我查了一下可以用websocket,沒錯又是在github上找了一個C#的websocket 庫– fleck,但我不是直接用,而是將其最主要的通信功能摳出來,fleck原來是通過回掉函數進行通信,一旦有連接到達就發送數據,通信完畢就直接關閉websocket了?,F在我需要改成:只要客戶端不關閉,我就一直向客戶端發送數據的邏輯,同時如果有別的客戶端連接了,我就主動關閉。沒錯這只是一個偽B/S,因為實時更新的websocket只能有一個客戶端。
FaceAngine的改動并不大,只需要在OnHit中使用websocket向客戶端發送數據就可以,OnGrab沒有作用了。
總結
自學。
總結
- 上一篇: [小技巧] ArrayList与Link
- 下一篇: 推荐一个不错的 Chrome 插件,百变