1 實(shí)驗(yàn)?zāi)康?/a>
????目前計(jì)算機(jī)視覺(jué)技術(shù)已經(jīng)比較成熟导帝,相關(guān)的開(kāi)源項(xiàng)目與算法很多,可以將這些開(kāi)源算法進(jìn)行整合,進(jìn)而做成一個(gè)小項(xiàng)目钦购,以供日后學(xué)習(xí)與研究。本實(shí)驗(yàn)主要將利用人臉識(shí)別開(kāi)源項(xiàng)目SeetaFace褂萧,結(jié)合使用OpenCV工具押桃,結(jié)合VS2017與Qt實(shí)現(xiàn)一個(gè)人臉識(shí)別的小項(xiàng)目。最后對(duì)實(shí)驗(yàn)系統(tǒng)進(jìn)行測(cè)試評(píng)估导犹。
2 相關(guān)知識(shí)與技術(shù)介紹
2.1 OpenCV簡(jiǎn)介
????OpenCV(Open Source Computer Vision Library)唱凯,是一個(gè)開(kāi)源的可以跨平臺(tái)運(yùn)行的計(jì)算機(jī)視覺(jué)庫(kù)羡忘,可以運(yùn)行在Linux、Windows磕昼、Android和Mac OS操作系統(tǒng)上卷雕。它輕量級(jí)而且高效,由一系列C函數(shù)和少量C++類(lèi)構(gòu)成票从,同時(shí)提供了Python漫雕、Ruby、MATLAB等語(yǔ)言的接口峰鄙,實(shí)現(xiàn)了圖像處理和計(jì)算機(jī)視覺(jué)方面的很多通用算法浸间。
????OpenCV的設(shè)計(jì)理念是所包含的函數(shù)能以最快的速度進(jìn)行編譯,之所以使用C代碼進(jìn)行編寫(xiě)吟榴,就是希望能夠利用多核處理器的優(yōu)勢(shì)達(dá)到最快的運(yùn)行速度魁蒜。它構(gòu)建了一個(gè)方便開(kāi)發(fā)人員使用的、簡(jiǎn)單易懂的計(jì)算機(jī)視覺(jué)框架吩翻,在這個(gè)基礎(chǔ)上兜看,開(kāi)發(fā)人員能都更方便的設(shè)計(jì)出更復(fù)雜的計(jì)算機(jī)視覺(jué)相關(guān)程序。OpenCV是由Intel發(fā)起的項(xiàng)目狭瞎,其中的源代碼都是開(kāi)源免費(fèi)的代碼铣减,因此可以用于科研人員的研究領(lǐng)域,也可以用于商業(yè)領(lǐng)域脚作。最新的版本OpenCV3.4.2已于2018年7月4日發(fā)布葫哗。
2.2 SeetaFace簡(jiǎn)介
????SeetaFace是由中科院山世光老師帶領(lǐng)的人臉識(shí)別研發(fā)組基于C++代碼研發(fā)的人臉識(shí)別算法。SeetaFace人臉識(shí)別引擎包括了搭建一套全自動(dòng)人臉識(shí)別系統(tǒng)所需的三個(gè)核心模塊球涛,即:人臉檢測(cè)模塊SeetaFace Detection劣针、面部特征點(diǎn)定位模塊SeetaFace Alignment以及人臉特征提取與比對(duì)模塊SeetaFace Identification。
????其中亿扁,SeetaFace Detection采用了一種結(jié)合傳統(tǒng)人造特征與多層感知機(jī)(MLP)的級(jí)聯(lián)結(jié)構(gòu)捺典,在FDDB上達(dá)到了84.4%的召回率(100個(gè)誤檢時(shí)),并可在單個(gè)i7 CPU上實(shí)時(shí)處理VGA分辨率的圖像从祝。
????面部特征點(diǎn)定位模塊SeetaFace Alignment通過(guò)級(jí)聯(lián)多個(gè)深度模型(棧式自編碼網(wǎng)絡(luò))來(lái)回歸5個(gè)關(guān)鍵特征點(diǎn)(兩眼中心襟己、鼻尖和兩個(gè)嘴角)的位置贮预,在AFLW數(shù)據(jù)庫(kù)上達(dá)到state-of-the-art的精度,定位速度在單個(gè)i7 CPU上超過(guò)200fps售葡。
????人臉識(shí)別模塊SeetaFace Identification采用一個(gè)9層的卷積神經(jīng)網(wǎng)絡(luò)(CNN)來(lái)提取人臉特征介却,在LFW數(shù)據(jù)庫(kù)上達(dá)到97.1%的精度(注:采用SeetaFace人臉檢測(cè)和SeetaFace面部特征點(diǎn)定位作為前端進(jìn)行全自動(dòng)識(shí)別的情況下),特征提取速度為每圖120ms(在單個(gè)i7 CPU上)所坯。
3 基于SeetaFace的人臉識(shí)別算法
????SeetaFace人臉識(shí)別引擎包括三個(gè)核心模塊,即:人臉檢測(cè)模塊SeetaFace Detection泻肯、面部特征點(diǎn)定位模塊SeetaFace Alignment以及人臉特征提取與比對(duì)模塊SeetaFace Identification惕医。下面對(duì)上述三個(gè)模塊的情況做簡(jiǎn)要介紹能岩。
3.1 人臉檢測(cè)模塊SeetaFace Detection
????該模塊基于一種結(jié)合經(jīng)典級(jí)聯(lián)結(jié)構(gòu)和多層神經(jīng)網(wǎng)絡(luò)的人臉檢測(cè)方法實(shí)現(xiàn)煌寇,其所采用的漏斗型級(jí)聯(lián)結(jié)構(gòu)(Funnel-Structured Cascade,F(xiàn)uSt)專(zhuān)門(mén)針對(duì)多姿態(tài)人臉檢測(cè)而設(shè)計(jì)逾雄,其中引入了由粗到精的設(shè)計(jì)理念阀溶,兼顧了速度和精度的平衡腻脏。如圖1所示,F(xiàn)uSt級(jí)聯(lián)結(jié)構(gòu)在頂部由多個(gè)針對(duì)不同姿態(tài)的快速LAB級(jí)聯(lián)分類(lèi)器構(gòu)成银锻,緊接著是若干個(gè)基于SURF特征的多層感知機(jī)(MLP)級(jí)聯(lián)結(jié)構(gòu)永品,最后由一個(gè)統(tǒng)一的MLP級(jí)聯(lián)結(jié)構(gòu)(同樣基于SURF特征)來(lái)處理所有姿態(tài)的候選窗口,整體上呈現(xiàn)出上寬下窄的漏斗形狀击纬。
????從上往下鼎姐,各個(gè)層次上的分類(lèi)器及其所采用的特征逐步變得復(fù)雜,從而可以保留人臉窗口并排除越來(lái)越難與人臉區(qū)分的非人臉候選窗口更振。?
????整個(gè)算法采用漏斗型炕桨,先采用計(jì)算量小的特征,快速過(guò)濾大量非人臉窗口(圖像滑窗)肯腕,然后采用復(fù)雜結(jié)構(gòu)逐層篩選人臉献宫。由圖2所示SeetaFace檢測(cè)效果圖看到在人臉局部遮擋的情況下也能很好的檢測(cè)到人臉區(qū)域。
3.2 面部特征點(diǎn)定位模塊SeetaFace Alignment
????面部特征點(diǎn)定位(人臉對(duì)齊)在人臉識(shí)別实撒、表情識(shí)別姊途、人臉動(dòng)畫(huà)合成等諸多人臉?lè)治鋈蝿?wù)中扮演著非常重要的角色。由于姿態(tài)奈惑、表情吭净、光照和遮擋等因素的影響睡汹,真實(shí)場(chǎng)景下的人臉對(duì)齊任務(wù)是一個(gè)非常困難的問(wèn)題肴甸。形式上,該問(wèn)題可以看作是從人臉表觀到人臉形狀的復(fù)雜非線性映射囚巴。為此原在,SeetaFace Alignment采用一種由粗到精的自編碼器網(wǎng)絡(luò)(Coarse-to-Fine Auto-encoder Networks,?CFAN)來(lái)求解這個(gè)復(fù)雜的非線性映射過(guò)程。如圖3所示彤叉,CFAN級(jí)聯(lián)了多級(jí)棧式自編碼器網(wǎng)絡(luò)庶柿,其中的每一級(jí)都刻畫(huà)從人臉表觀到人臉形狀的部分非線性映射。具體來(lái)說(shuō)秽浇,輸入一個(gè)人臉區(qū)域(由人臉檢測(cè)模塊得到)浮庐,第一級(jí)自編碼器網(wǎng)絡(luò)直接從該人臉的低分辨率版本中快速估計(jì)大致的人臉形狀S0。然后柬焕,提高輸入人臉圖像的分辨率审残,并抽取當(dāng)前人臉形狀S0(相應(yīng)提升分辨率)各特征點(diǎn)位置的局部特征,輸入到下一級(jí)自編碼器網(wǎng)絡(luò)來(lái)進(jìn)一步優(yōu)化人臉對(duì)齊結(jié)果斑举。以此類(lèi)推搅轿,通過(guò)級(jí)聯(lián)多個(gè)棧式自編碼器網(wǎng)絡(luò),在越來(lái)越高分辨率的人臉圖像上逐步優(yōu)化人臉對(duì)齊結(jié)果富玷。
????此次開(kāi)源的SeetaFace Alignment基于上述CFAN方法實(shí)現(xiàn)了5個(gè)面部關(guān)鍵特征點(diǎn)(兩眼中心璧坟,鼻尖和兩個(gè)嘴角)的精確定位既穆,訓(xùn)練集包括23,000余幅人臉圖像(標(biāo)注了5點(diǎn))。需要注意的是雀鹃,為加速之目的幻工,在基本不損失精度的情況下,開(kāi)源實(shí)現(xiàn)中將CFAN級(jí)聯(lián)的數(shù)目減少到了2級(jí)黎茎,從而可在單顆Intel i7-3770 (3.4 GHz CPU)上達(dá)到每個(gè)人臉5ms的處理速度(不包括人臉檢測(cè)時(shí)間)会钝。
圖4是本文通過(guò)SeetaFace Alignment分別對(duì)不同人員在不同狀態(tài)特征點(diǎn)定位得到的效果圖。測(cè)試中發(fā)現(xiàn)通過(guò)多次驗(yàn)證工三,在不同表情迁酸、不同面部偏轉(zhuǎn)角度、抬頭低頭俭正、臉部局部遮擋等情況下都能有效的定位奸鬓。且對(duì)于戴眼鏡測(cè)試者,也能很好的定位到特征區(qū)域掸读。
3.3人臉特征提取與比對(duì)模塊SeetaFace Identification
????人臉識(shí)別本質(zhì)上是要計(jì)算兩幅圖像中人臉的相似程度串远,其一為注冊(cè)階段(類(lèi)比人的相識(shí)過(guò)程)輸入系統(tǒng)的,另一幅為識(shí)別階段(即再見(jiàn)時(shí)的辨認(rèn)過(guò)程)的輸入儿惫。為此澡罚,如圖5所示,一套全自動(dòng)的人臉識(shí)別系統(tǒng)在完成前述的人臉檢測(cè)與人臉對(duì)齊兩個(gè)步驟之后肾请,即進(jìn)入第三個(gè)核心步驟:人臉特征提取和比對(duì)留搔。這個(gè)階段也是深度學(xué)習(xí)風(fēng)起云涌之后進(jìn)步最大的模塊,目前大多數(shù)優(yōu)秀的人臉識(shí)別算法均采用卷積神經(jīng)網(wǎng)絡(luò)(CNN)來(lái)學(xué)習(xí)特征提取器(即圖5中的函數(shù)F)铛铁。
????SeetaFace開(kāi)源的人臉特征提取模塊也是基于卷積神經(jīng)網(wǎng)絡(luò)的隔显。具體地說(shuō),是深度卷積神經(jīng)網(wǎng)絡(luò)VIPLFaceNet:一個(gè)包含7個(gè)卷積層與2個(gè)全連接層的DCNN饵逐。其直接修改自Hinton教授的學(xué)生Alex Krizhevsky等于2012年設(shè)計(jì)的AlexNet(即引爆CNN在視覺(jué)中廣泛應(yīng)用的網(wǎng)絡(luò))括眠。
????與開(kāi)源的SeetaFace Identification代碼一起發(fā)布的人臉識(shí)別模型是使用140萬(wàn)人臉圖像訓(xùn)練出來(lái)的,這些訓(xùn)練圖像來(lái)自于約1.6萬(wàn)人倍权,其中既有東方人也有西方人掷豺。人臉特征直接采用VIPLFaceNet FC2層的2048個(gè)結(jié)點(diǎn)的輸出,特征比對(duì)可簡(jiǎn)單采用Cosine計(jì)算相似度薄声,然后進(jìn)行閾值比較(驗(yàn)證應(yīng)用)或排序(識(shí)別應(yīng)用)即可当船。
????在LFW standard Image-Restricted測(cè)試協(xié)議下,使用SeetaFace Detection與SeetaFace Alignment檢測(cè)并對(duì)齊人臉奸柬,采用SeetaFace Identification進(jìn)行特征提取和比對(duì)生年,可以達(dá)到97.1%的識(shí)別正確率(請(qǐng)注意:這是系統(tǒng)全自動(dòng)運(yùn)行的結(jié)果,對(duì)少量不能檢到人臉的圖像廓奕,截取中間區(qū)域輸入人臉對(duì)齊模塊即可)抱婉。速度方面档叔,在單顆Intel i7-3770 CPU上,開(kāi)源代碼提取一張人臉之特征的時(shí)間約為120ms(不含人臉檢測(cè)和特征點(diǎn)定位時(shí)間)蒸绩。
? ? 以上關(guān)于SeetaFace的理論介紹基本來(lái)源于SeetaFace官方的說(shuō)明衙四,想詳細(xì)了解SeetaFace更多的理論信息,請(qǐng)參考文章:SeetaFace開(kāi)源人臉識(shí)別引擎介紹患亿,該文章底部的參考文獻(xiàn)附有論文传蹈,有想深入研究的可以去啃啃論文(全英文的0_0)。
? ? 上面關(guān)于理論的確實(shí)介紹的有點(diǎn)多了步藕,不過(guò)了解一下惦界,可以增加一下自己的眼界。廢話不多說(shuō)了咙冗,下面進(jìn)入正題-應(yīng)用部分沾歪。
4 系統(tǒng)環(huán)境搭建與實(shí)驗(yàn)結(jié)果
通過(guò)對(duì)上述的理論知識(shí)的了解學(xué)習(xí),接下來(lái)進(jìn)行系統(tǒng)的環(huán)境搭建雾消,然后進(jìn)行具體的實(shí)驗(yàn)灾搏。然后對(duì)實(shí)驗(yàn)結(jié)果做一定的分析。
4.1系統(tǒng)的總體設(shè)計(jì)方案
首先使用攝像頭讀取采集的圖像立润,經(jīng)過(guò)人臉檢測(cè)模塊框出人臉狂窑,然后使用面部特征點(diǎn)定位(人臉對(duì)齊)模塊對(duì)5個(gè)面部關(guān)鍵特征點(diǎn)(兩眼中心,鼻尖和兩個(gè)嘴角)進(jìn)行標(biāo)記顯示桑腮,最后使用人臉特征提取與比對(duì)模塊進(jìn)行人臉特征提取和比對(duì)泉哈。在系統(tǒng)中設(shè)置一個(gè)閾值0.7,若比對(duì)后的相似度大于0.7到旦,就認(rèn)定為同一個(gè)人并觸發(fā)警報(bào)系統(tǒng)旨巷。系統(tǒng)的功能框圖如圖6所示巨缘。
4.2 配置系統(tǒng)環(huán)境
4.2.1 配置OpenCV
Open CV中包含很多圖像處理的算法添忘,因此學(xué)會(huì)正確使用Open CV也是人臉識(shí)別研究的一項(xiàng)重要工作。在?VS2017中應(yīng)用Open CV若锁,需要進(jìn)行手動(dòng)配置搁骑,下面給出在VS2017中配置Open CV的詳細(xì)步驟。
1.下載并安裝OpenCV3.4.1與VS2017的軟件又固。
2.配置Open CV環(huán)境變量仲器。
計(jì)算機(jī)->(右鍵)屬性->高級(jí)系統(tǒng)設(shè)置->高級(jí)->環(huán)境變量->(雙擊)系統(tǒng)變量中的path->在變量只里面添加相應(yīng)的路徑。添加的路徑為:“....opencv\build\x64\vc15\bin”仰冠。里面的省略號(hào)請(qǐng)換成自己電腦上的路徑乏冀,例如:E:\opencv\build\x64\vc15\bin。如圖7所示洋只。
3.配置工程目錄與鏈接庫(kù)
需要配置包含目錄和庫(kù)目錄辆沦,首先打開(kāi)昼捍,視圖->解決方案管理器->(右鍵)項(xiàng)目->屬性->VC++目錄。
1)配置包含目錄肢扯。
添加“...opencv\build\include;...opencv\build\include\opencv;...opencv\build
\incude\opencv2”即可妒茬,里面的省略號(hào)請(qǐng)換成自己電腦上的路徑,如圖8所示蔚晨。
2)配置庫(kù)目錄
添加“E:\opencv\build\x64\vc15\lib即可乍钻,里面的省略號(hào)請(qǐng)換成自己電腦上的路徑沮尿,如圖9所示刑赶。
3)配置鏈接庫(kù)
首先打開(kāi)他匪,視圖->解決方案管理器->(右鍵)項(xiàng)目->屬性->鏈接器->輸入->附加依賴(lài)項(xiàng)楼熄,
針對(duì)Debug配置添加“opencv_world341d.lib”,若在Release下彤敛,就添加“opencv_world341.
lib”俱箱。這是OpenCV3版本的方便之處廊移,OpenCV2版本需要添加很多項(xiàng)导披。操作如圖10所示笋粟。
????上述三步配置要注意每次新建工程都要重新配置怀挠,但也可以只配置一次,達(dá)到以后都不用單獨(dú)配置的效果害捕,那么就在屬性管理器->展開(kāi)項(xiàng)目->Debug|x64(或者Release|x64)-(雙擊)進(jìn)Microsoft.Cpp.x64.user绿淋,然后后續(xù)操作和前面三步一樣。
首先尝盼,在開(kāi)源項(xiàng)目平臺(tái)GitHub下載SeetaFace開(kāi)源項(xiàng)目吞滞。
在VS2017中新建三個(gè)dll(動(dòng)態(tài)鏈接庫(kù))項(xiàng)目,將SeetaFace的三個(gè)模塊FaceDetection赴精、FaceAlignment和FaceIdentification分別制作成DLL項(xiàng)目佩捞,然后將生成的lib和dll文件保存下載,以便在項(xiàng)目中直接添加使用蕾哟。在這里就不詳細(xì)地進(jìn)行具體配置說(shuō)明了一忱。
4.3 系統(tǒng)的軟件實(shí)現(xiàn)
系統(tǒng)的硬件部分是基于一個(gè)720p羅技c310的USB攝像頭,進(jìn)行圖像采集工作谭确,所以重要的工作在軟件設(shè)計(jì)方面帘营。下面將進(jìn)行具體的軟件實(shí)現(xiàn)。
4.3.1 系統(tǒng)中的圖像類(lèi)型轉(zhuǎn)換
通過(guò)OpenCV中VideoCapture類(lèi)逐哈,可以進(jìn)行圖像的逐幀采集芬迄,采集到的圖像類(lèi)型為Mat類(lèi)型。SeetaFace中的圖像類(lèi)型為ImageData類(lèi)型昂秃,而且在后面的Qt平臺(tái)下會(huì)使用到的圖片類(lèi)型為QImage禀梳。以上這三種類(lèi)型在某些情況下需要進(jìn)行必要的轉(zhuǎn)換择诈,下面對(duì)具體的類(lèi)型轉(zhuǎn)換進(jìn)行說(shuō)明。
1)Mat類(lèi)型轉(zhuǎn)ImageData
首先將Mat類(lèi)型的圖片轉(zhuǎn)為單通道的灰度圖(如果已是灰度圖就不用轉(zhuǎn)了)出皇,具體轉(zhuǎn)化代碼如下:
cv::Mat img;
cv::cvtColor(frame, img_gray, cv::COLOR_BGR2GRAY);
seeta::ImageData image;
image.data = img.data;
image.width = img.cols;
image.height = img.rows;
image.num_channels = 1;
2)Mat類(lèi)型轉(zhuǎn)QImage
其中需要將Mat類(lèi)型的BGR通道順序變換為QImage的RGB順序羞芍,可以調(diào)用OpenCV中的cvtColor函數(shù)實(shí)現(xiàn),以上是對(duì)兩種圖像類(lèi)型的data部分的格式進(jìn)行調(diào)整郊艘,下一步只需要明確Mat的頭結(jié)構(gòu)里的變量與QImage的頭結(jié)構(gòu)里的變量的對(duì)應(yīng)關(guān)系即可實(shí)現(xiàn)轉(zhuǎn)換荷科,具體轉(zhuǎn)換代碼如下:?
Mat frame,temp;
??????cvtColor(frame, temp, CV_BGR2RGB);
?QImage image = QImage((const unsigned char *)(temp.data), temp.cols,
temp.rows, QImage::Format_RGB888);
3)QImage類(lèi)型轉(zhuǎn)Mat
與2)的類(lèi)似,將QImage的RGB通道順序變換為Mat類(lèi)型的BGR順序纱注,然后將QImage的頭結(jié)構(gòu)里的變量與Mat的頭結(jié)構(gòu)里的變量的對(duì)應(yīng)關(guān)系即可實(shí)現(xiàn)轉(zhuǎn)換畏浆,具體轉(zhuǎn)換代碼如下:
Mat frame;
cv::Mat Temp = cv::Mat(image.height(), image.width(), CV_8UC3,(void*)image.constBits(), image.bytesPerLine());
cvtColor(Temp,frame,CV_RGBA2BGR);
4.3.2 線程設(shè)計(jì)
把系統(tǒng)分為兩個(gè)線程,即主線程與子線程狞贱。主線程主要負(fù)責(zé)UI及一些一般的處理刻获,子線程負(fù)責(zé)人臉特征提取與比對(duì)模塊SeetaFace Identification部分的數(shù)據(jù)處理,因?yàn)檫@塊比較耗資源瞎嬉,如果都放在子線程的話蝎毡,會(huì)是UI卡頓。
1)主線程
主線程部分氧枣,主要進(jìn)行攝像頭的采集圖像并顯示在UI上沐兵,以及人臉對(duì)齊的顯示,然后就是一些其他的小功能便监,例如扎谎,拍照/截圖功能。
下面就主線程的主要功能進(jìn)行分析與說(shuō)明烧董。
因?yàn)槭菍?duì)攝像頭采集的圖像進(jìn)行基本類(lèi)似于實(shí)時(shí)的處理毁靶,因此在系統(tǒng)中設(shè)置定時(shí)器,進(jìn)行定時(shí)處理逊移,因?yàn)橹骶€程中有UI還要工作的原因预吆,故設(shè)置為死循環(huán)的方式不可取。在Qt中的定時(shí)器設(shè)置定時(shí)時(shí)間長(zhǎng)度設(shè)定為35ms螟左,理論上大概每秒能獲取28幀圖像啡浊,基本可以滿足實(shí)時(shí)性的要求。
在獲取攝像頭圖像的基礎(chǔ)上胶背,進(jìn)行5個(gè)面部關(guān)鍵特征點(diǎn)(兩眼中心,鼻尖和兩個(gè)嘴角)的定位并顯示在UI上喘先。主體流程圖如圖11所示钳吟。
2)子線程
????因?yàn)橄到y(tǒng)要基于Qt實(shí)現(xiàn)UI,其他線程中不能操作UI對(duì)象窘拯,只有主線程能對(duì)UI進(jìn)行操作红且,所以將人臉特征提取與比對(duì)模塊SeetaFace Identification放在子線程坝茎。將SeetaFace Identification部分的處理函數(shù)設(shè)置為可控的死循環(huán),設(shè)置bool變量來(lái)對(duì)死循環(huán)進(jìn)行控制暇番。然而嗤放,UI要將處理后的信息顯示出來(lái),因此線程之間要進(jìn)行通信壁酬。
3)線程通信
??????????線程的通信部分可以使用Qt的“信號(hào)與槽”功能次酌,使用連接函數(shù)將信號(hào)與槽函數(shù)連接,將線程中需要傳遞的數(shù)據(jù)放在信號(hào)中舆乔,一旦信號(hào)發(fā)射岳服,就執(zhí)行相應(yīng)的槽函數(shù),來(lái)完成線程之間的通信任務(wù)希俩。流程圖如圖12所示吊宋。
????由圖5所知,要在系統(tǒng)中輸入一張需要進(jìn)行比對(duì)的圖片颜武,在UI中通過(guò)按鈕獲取該圖片的路徑信息璃搜,然后通過(guò)信號(hào)發(fā)射這個(gè)路徑,然后子線程那邊接收到信號(hào)后鳞上,將這個(gè)圖片路徑獲取下來(lái)以便進(jìn)行接下來(lái)的人臉特征提取與比對(duì)腺劣。當(dāng)主線程觸發(fā)人臉識(shí)別的信號(hào)以后,子線程接收后將攝像頭獲取的圖像與輸入圖像進(jìn)行人臉特征提取與比對(duì)因块。最后將人臉比對(duì)的相似度及被認(rèn)定為同一個(gè)人的裁剪人臉通過(guò)信號(hào)發(fā)送給主線程橘原,主線程在UI上進(jìn)行數(shù)據(jù)顯示。
4.4 基于Qt的UI設(shè)計(jì)
Qt是一個(gè)跨平臺(tái)的C++圖形用戶界面應(yīng)用程序框架涡上。它為應(yīng)用程序開(kāi)發(fā)者提供建立藝術(shù)級(jí)圖形界面所需的所有功能趾断。它是完全面向?qū)ο蟮模苋菀讛U(kuò)展吩愧,并且允許真正的組件編程芋酌。
Qt提供了一種稱(chēng)為信號(hào)與槽的方式,使得各個(gè)元件之間的協(xié)同工作變得十分簡(jiǎn)單雁佳。Qt擁有自己的集成開(kāi)發(fā)環(huán)境(IDE)名為Qt Creator脐帝,但為了使各個(gè)部分的銜接方便,在VS2017上安裝一個(gè)叫Qt Visual Studio Tools的Qt插件糖权。這樣就可以進(jìn)行VS加Qt的混合編程堵腹。
1)UI設(shè)計(jì)圖
2)信號(hào)與槽
UI部分主要使用的是按鈕來(lái)進(jìn)行操作,按鈕的點(diǎn)擊信號(hào)與對(duì)應(yīng)的槽函數(shù)如圖14所示星澳。具體的槽函數(shù)請(qǐng)參見(jiàn)附錄中的代碼疚顷。
4.5 實(shí)驗(yàn)結(jié)果與分析
在完成上述的各項(xiàng)準(zhǔn)備工作之后,下面就進(jìn)行最后的整體調(diào)試工作。運(yùn)行已經(jīng)寫(xiě)好的程序腿堤,然后生成應(yīng)用程序界面阀坏,界面樣式如圖15所示。采用了一些邏輯方法笆檀,去除了窗體的邊框忌堂,然后再右上角設(shè)置了一個(gè)關(guān)閉按鈕,這個(gè)關(guān)閉按鈕具有關(guān)閉線程酗洒,退出窗口等其他功能士修。
????接下來(lái),通過(guò)具體的實(shí)驗(yàn)來(lái)進(jìn)行演示操作寝蹈,具體操作為:打開(kāi)攝像頭->人臉檢測(cè)與對(duì)齊->輸入圖片->人臉識(shí)別李命。可以發(fā)現(xiàn)SeetaFace具有很好的識(shí)別能力箫老,在輸入同一個(gè)人的照片后封字,進(jìn)行人臉識(shí)別,可以達(dá)到0.9以上相似度(圖16中的相似度為0.911083)耍鬓,然后顯示發(fā)現(xiàn)目標(biāo)并發(fā)出警報(bào)阔籽,可見(jiàn)效果還是很不錯(cuò)的,如圖16所示牲蜀。
????在實(shí)驗(yàn)過(guò)程中笆制,發(fā)現(xiàn)一個(gè)問(wèn)題,當(dāng)輸入一張同一個(gè)人之前的照片涣达,識(shí)別的相似度會(huì)有所下降在辆,會(huì)比輸入現(xiàn)拍的照片相似度低,但也基本大于0.7(圖18中的相似度為0.76884)度苔,即被判別為同一個(gè)人匆篓,如圖18所示】芤ぃ可能這是SeetaFace算法存在的小缺陷吧鸦概,證明算法還有優(yōu)化的空間。
????基于同樣的原理甩骏,該系統(tǒng)還具備視頻人臉識(shí)別功能窗市。打開(kāi)一段視頻,輸入一張圖片饮笛,即可實(shí)現(xiàn)在視頻中查找到與輸入圖片為同一個(gè)人的人臉咨察,然后將找到后人臉保存在應(yīng)用程序的文件夾中,實(shí)驗(yàn)效果如圖19所示(圖19中的相似度為0.7762)缎浇。
5 總結(jié)
????本文主要利用人臉識(shí)別開(kāi)源項(xiàng)目SeetaFace扎拣,使用OpenCV作為圖像處理工具,結(jié)合集成開(kāi)發(fā)環(huán)境VS2017與C++圖形用戶界面應(yīng)用程序框架Qt實(shí)現(xiàn)一個(gè)人臉識(shí)別的小項(xiàng)目素跺。主要工作是首先進(jìn)行系統(tǒng)各項(xiàng)環(huán)境與功能的搭建與實(shí)施二蓝,在基于開(kāi)源庫(kù)與項(xiàng)目的基礎(chǔ)進(jìn)行了應(yīng)用性的實(shí)驗(yàn)與結(jié)果分析。
????本文首先進(jìn)行了基本知識(shí)與技術(shù)的介紹指厌,進(jìn)行了相關(guān)理論的了解與學(xué)習(xí)刊愚,為接下來(lái)的系統(tǒng)實(shí)施打下了基礎(chǔ)。OpenCV是圖像處理常用的工具踩验,在在學(xué)習(xí)與科研過(guò)程中具有很好的幫助性鸥诽。SeetaFace作為為數(shù)不多的開(kāi)源人臉識(shí)別項(xiàng)目,很值得學(xué)習(xí)與研究箕憾,是一個(gè)很不錯(cuò)的開(kāi)源項(xiàng)目牡借。
????接下來(lái)本文進(jìn)行了系統(tǒng)環(huán)境的搭建與實(shí)驗(yàn),然后根據(jù)實(shí)驗(yàn)進(jìn)行了結(jié)果分析袭异。在系統(tǒng)搭建環(huán)節(jié)钠龙,首先確定了系統(tǒng)的總體設(shè)計(jì)思路,然后進(jìn)行了系統(tǒng)環(huán)境配置御铃。首先配置的是OpenCV碴里,其次進(jìn)行SeetaFace的配置。在系統(tǒng)軟件設(shè)計(jì)中先進(jìn)行了系統(tǒng)中圖像類(lèi)型轉(zhuǎn)換的介紹上真,然后進(jìn)行了多線程的設(shè)計(jì)咬腋。
最后實(shí)施了具體的實(shí)驗(yàn),根據(jù)實(shí)驗(yàn)對(duì)系統(tǒng)進(jìn)行了分析與探討睡互。從實(shí)驗(yàn)結(jié)果發(fā)現(xiàn)該系統(tǒng)具有良好的人臉識(shí)別特性根竿,但也有一些需要優(yōu)化的空間。本文雖然基本整體實(shí)現(xiàn)了系統(tǒng)設(shè)計(jì)的功能就珠,但在許多方面還存在著瑕疵寇壳,還有很多值得改進(jìn)的地方。其實(shí)可以將該系統(tǒng)進(jìn)行嵌入式等相關(guān)平臺(tái)的移植嗓违,進(jìn)一步增加該系統(tǒng)的應(yīng)用價(jià)值九巡。
參考文獻(xiàn)
[1]朱興統(tǒng),習(xí)洋洋.基于C++和OpenCV的人臉識(shí)別系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn)[J].自動(dòng)化與儀器儀表,2014(08):127-128+131.
[2]劉長(zhǎng)亮. 基于眼睛與頭部狀態(tài)的疲勞檢測(cè)系統(tǒng)設(shè)計(jì)[D].大連海事大學(xué),2018.
[3]孫志. 基于OpenCV的人臉識(shí)別算法實(shí)驗(yàn)平臺(tái)研究與實(shí)現(xiàn)[D].吉林大學(xué),2014.
[4] 陶穎軍.基于OpenCV的人臉識(shí)別應(yīng)用[J].計(jì)算機(jī)系統(tǒng)應(yīng)用,2012,21(03):220-223.
[5] 毛星宇,冷雪飛等.OpenCV3編程入門(mén)[M].北京電子工業(yè)出版社蹂季,2015.
[6] VIPL_Face.SeetaFace開(kāi)源人臉識(shí)別引擎介紹.?微信公眾號(hào)-深度學(xué)習(xí)大講堂?2016-09-14
附錄
代碼
主線程部分:
頭文件(OpenVideo.h)
```?
/**
*Copyright (c) 2018 Young Fan.All Right Reserved.
*Filename:
```
*Author: Young Fan
*Date: 2018.5.29 - 7.5
*OpenCV version: 3.4.1
*IDE: Visual Studio 2017
*Description: Demo:Qt + Opecv + VS + SeetaFace(via VIPL)
*/
#pragma once
#include
#include "ui_OpenVideo.h"
#include
#include "MyThread.h" //自定義線程模板(類(lèi))
#include ?//線程頭文件
//SeetaFace
#include
#include
#include
#include
#include "cv.h"
#include "highgui.h"
#include "face_detection.h"
#include "face_alignment.h"
#include
#include
#include "face_identification.h"
#include "recognizer.h"
#include "face_detection.h"
#include "face_alignment.h"
#include "math_functions.h"
#include
#include
#include
#include
class OpenVideo : public QWidget
{
Q_OBJECT
public:
OpenVideo(QWidget *parent = Q_NULLPTR);
protected:
//重寫(xiě)繪圖事件
void paintEvent(QPaintEvent *event);
//重寫(xiě)鼠標(biāo)按下事件
void mousePressEvent(QMouseEvent *event);
//重寫(xiě)鼠標(biāo)移動(dòng)事件
void mouseMoveEvent(QMouseEvent *event);
public slots: //槽函數(shù)
void OpenVideoFile(); //打開(kāi)視頻文件
void OpenCamera(); //打開(kāi)攝像頭
void CloseCamera(); //關(guān)閉攝像頭
void ScreenShot(); //拍照/截圖
void REC(); ?//錄像
void EndREC(); //結(jié)束錄像
void FaceDetectionAlignment(); //人臉檢測(cè)與對(duì)齊
void InputImage(); //輸入圖片
void FaceRecognitionProcess(); //人臉識(shí)別
void nextFrame(); //獲取下一幀圖片
void nextSeetaFaceProcessFrame(); //獲取下一幀處理的圖片
void currentDateAndTime(); ?//動(dòng)態(tài)顯示當(dāng)前日期和時(shí)間
void CloseWindow(); //關(guān)閉窗口
//獲取子線程信號(hào)發(fā)來(lái)的信息
//獲取人臉檢測(cè)與對(duì)齊信息
//void getDetectionAndAlignmentInformation(int faceNum, QImage image);
//獲取人臉識(shí)別信息
void getRecognitionInformation(int gallery_face_num, int probe_face_num,QImage image,float sim);
void NoFace();
signals:
void mainSignal(QString str);
void VideoSignal(QImage image,bool flag);
private:
Ui::OpenVideoClass ui;
cv::Mat frame; //定義一個(gè)Mat變量冕广,用于存儲(chǔ)沒(méi)一幀的圖像
cv::Mat temp; //臨時(shí)變量
cv::VideoCapture capture; //定義VideoCapture對(duì)象,獲取攝像頭
cv::VideoWriter writer; ?//定義VideoWriter對(duì)象偿洁,錄制
QTimer *timer; ?//定時(shí)器
QTimer *timer2; ?//人臉識(shí)別定時(shí)器
QString ImagePath; //定義輸入圖片的路徑
int x = 0;
int y = 0;
bool openREC;
QPoint p; //定義的點(diǎn)
MyThread *myT; //自定義線程模塊(類(lèi))對(duì)象
QThread *thread; //子線程
//人臉檢測(cè)與對(duì)齊
int faceNum;
QImage imageDetectionAndAlignment;
//人臉識(shí)別
int GalleryFaceNum;
int ProbeFaceNum;
QImage CropFace;
float Sim;
bool CameraVideoFlag; //標(biāo)志位,true代表Camera撒汉,false代表Video
};
```
cpp文件(OpenVideo.cpp)
```
#include "OpenVideo.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
//使用Qt多媒體,需要在附加依賴(lài)項(xiàng)里加Qt5Multimedia.lib(debug)/Qt5Multimediad.lib(release)
#include
OpenVideo::OpenVideo(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
//設(shè)置窗口標(biāo)題
setWindowTitle("OpenVideo");
//讓圖片自動(dòng)適應(yīng)label大小
ui.labelOpenVideo->setScaledContents(true);
ui.labelGif->setScaledContents(true);
//-----【顯示Gif動(dòng)畫(huà)】-------
//創(chuàng)建動(dòng)畫(huà)
QMovie *myMovie = new QMovie("./UiFile/2.gif");
//設(shè)置動(dòng)畫(huà)
ui.labelGif->setMovie(myMovie);
//啟動(dòng)動(dòng)畫(huà)
myMovie->start();
//固定主窗口大小
setFixedSize(1125, 511);
//創(chuàng)建定時(shí)器
timer = new QTimer(this);
//-----------【不規(guī)則窗口】-------------
//給窗口去掉邊框涕滋,帶上窗口的flags(標(biāo)記)
setWindowFlags(Qt::FramelessWindowHint | windowFlags());
//把窗口背景設(shè)置為透明,這里不設(shè)置透明也可以睬辐,因?yàn)閳D片不透明
setAttribute(Qt::WA_TranslucentBackground);
//更改按鈕樣式表
ui.CloseButton->setFlat(true); //把按鈕設(shè)置為透明背景,此時(shí)只有按下的時(shí)候才有背景。
ui.CloseButton->setStyleSheet("QPushButton{"
//"background:rgb(176,0,0);"http://按鈕設(shè)置為透明背景溯饵,這里設(shè)置了設(shè)置了也不顯示
//但如果加上"border:2px outset green;"則可以在初始態(tài)侵俗,鼠標(biāo)懸停態(tài)顯示背景色,下面一樣
"}"
"QPushButton:hover{"
"border:1px;"http://當(dāng)按鈕背景是透明的丰刊,則必須加邊框?qū)挾劝ィ拍苡衕over效果,這樣寫(xiě)也行"border:1px outset red;"
"background:rgb(255,96,96);"http://按鈕設(shè)置為透明背景啄巧,這里設(shè)置了設(shè)置了也不顯示
//"border-image:url(E:/C++/Demo/OpenVideo/UiFile/closeWhite.png);"
"}"
"QPushButton:pressed{"
"background:rgb(255,77,77);"http://此時(shí)只有按下的時(shí)候才有背景寻歧。
//"border-image:url(E:/C++/Demo/OpenVideo/UiFile/closeWhite.png);"
"}");
//-----------------------------------------【多線程部分】--------------------------------------------------
//自定義模塊(類(lèi))對(duì)象,分配空間秩仆,不可以指定父對(duì)象码泛,因?yàn)橄旅嬉频阶泳€程
myT = new MyThread;
//創(chuàng)建子線程,指定父對(duì)象
thread = new QThread(this);
//把自定義模塊添加到子線程
myT->moveToThread(thread);
//啟動(dòng)子線程,但是并沒(méi)有啟動(dòng)線程的處理函數(shù),要用信號(hào)和槽啟動(dòng)線程的處理函數(shù)
thread->start();
//線程處理函數(shù)澄耍,必須通過(guò)signal-slot調(diào)用
//這里使用的是按鈕的信號(hào)來(lái)觸發(fā)噪珊,也可以用按鈕的轉(zhuǎn)到槽函數(shù),在里面去發(fā)射信號(hào)
//逾苫,用信號(hào)去調(diào)用模塊類(lèi)中的處理函數(shù)
connect(ui.buttonFaceRecognition,&QPushButton::pressed,myT,&MyThread::FaceRecognitionProcess);//人臉識(shí)別部分
//一旦子模塊類(lèi)中的處理函數(shù)啟動(dòng)卿城,就會(huì)發(fā)射UpdateProcess信號(hào)(線程代碼里寫(xiě)的)
connect(myT,&MyThread::UpdateRecognition,this,&OpenVideo::getRecognitionInformation);//人臉識(shí)別部分
connect(myT, &MyThread::NoFace, this, &OpenVideo::NoFace);
//主線程給子線程通信
connect(this, &OpenVideo::mainSignal, myT, &MyThread::getMainSignal);
connect(this,&OpenVideo::VideoSignal,myT,&MyThread::getVideoSignal);
}
//--------------------------------------------【重寫(xiě)鼠標(biāo)按下事件】--------------------------------------------
void OpenVideo::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) //event->button()返回的是Buttons事件
{
//求坐標(biāo)差值,窗口上鼠標(biāo)點(diǎn)擊當(dāng)前點(diǎn)的坐標(biāo)是相對(duì)于屏幕的
//求差:當(dāng)前點(diǎn)擊坐標(biāo) - 窗口左上角坐標(biāo)
//相對(duì)于窗口的坐標(biāo)用x铅搓,y瑟押,相對(duì)于屏幕用globalPos
//frameGeometry,是帶邊框的,用它可以獲取窗口左上角坐標(biāo)(相對(duì)于屏幕的坐標(biāo))
//移動(dòng)窗口時(shí)是以左上角來(lái)移動(dòng)的星掰,要想點(diǎn)哪就從哪移動(dòng)多望,則要求坐標(biāo)差
p = event->globalPos() - this->frameGeometry().topLeft();
}
}
void OpenVideo::mouseMoveEvent(QMouseEvent *event)
{
//這里因?yàn)槭前醋。适褂胋uttons(帶‘s’的button)
//左鍵按住移動(dòng)窗口
if (event->buttons() & Qt::LeftButton) //event->buttons()返回的是int位字段
{
//移動(dòng)窗口時(shí)是以左上角來(lái)移動(dòng)的氢烘,故要減去p(此時(shí)就相當(dāng)于從該點(diǎn)開(kāi)始移動(dòng))
this->move(event->globalPos() - p);
}
}
//----------------------------------------------【畫(huà)背景圖】--------------------------------------------------
void OpenVideo::paintEvent(QPaintEvent *event)
{
QPainter p(this);
p.drawPixmap(0, 0, width(), height(), QPixmap("./UiFile/background.png"));
}
//-----------------------------------------------【顯示當(dāng)前日期和時(shí)間】------------------------------------
void OpenVideo::currentDateAndTime()
{
//還有其他的顯示時(shí)間日期的方法怀偷,如C語(yǔ)言的宏__DATE__,__TIME__、再比如QTime里也有能顯示時(shí)間與日期
//用arg()也能顯示播玖,但都需要定時(shí)器
ui.labelDateTime->setText(QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss.zzz A"));
}
//----------------------------------------------【關(guān)閉窗口】--------------------------------------------------
void OpenVideo::CloseWindow()
{
//寫(xiě)程序要養(yǎng)成一種習(xí)慣椎工,要做判斷
if (false == thread->isRunning()) //如果線程已停止,則按鈕不在往下執(zhí)行
{
return;
}
myT->setFlag(true); //跳出循環(huán)
thread->quit(); //溫柔地退出
thread->wait(); //等待蜀踏,回收資源
//但如果模塊(類(lèi))還在工作的話(如死循環(huán)while(1)维蒙,即不加這句myT->setFlag(true);),
//則線程并不會(huì)停止還是會(huì)在運(yùn)行
this->close(); //關(guān)閉窗口
delete myT; //釋放資源
}
//---------------------------------------------【打開(kāi)視頻文件】------------------------------------------------
void OpenVideo::OpenVideoFile()
{
//初始化標(biāo)志位
CameraVideoFlag = false;
myT->setFlag(false); //子線程處理函數(shù)循環(huán)
//顯示標(biāo)簽
ui.labelOpenVideo->setVisible(true);
ui.label->setVisible(true);
//創(chuàng)建文件對(duì)話框并獲取路徑
QString path = QFileDialog::getOpenFileName(this,
"open", "../", "Video(*.mp4 *.flv *.mkv *.avi)");
//讀入視頻
capture.open(path.toLocal8Bit().data()); //也可以path.toStdString() ??
//判斷定時(shí)器的激活狀態(tài)
if (false == timer->isActive())
{
//啟動(dòng)定時(shí)器
timer->start(35);
}
//定時(shí)器連接,獲取下一幀圖片
connect(timer, &QTimer::timeout, this, &OpenVideo::nextFrame);
connect(timer, &QTimer::timeout,
[=]()
{
ui.label->setText(QStringLiteral("視頻播放中..."));
ui.label->move(QPoint(x++, y++));
if (x >511)
{
x = 0;
y = 0;
}
}
);
}
//----------------------------------------------【打開(kāi)攝像頭】---------------------------------------------
void OpenVideo::OpenCamera()
{
//初始化標(biāo)志位
CameraVideoFlag = true;
//顯示標(biāo)簽
ui.labelOpenVideo->setVisible(true);
//讀入攝像頭
capture.open(0);
timer->start(35); //定時(shí)35ms
//定時(shí)器連接,獲取下一幀圖片
connect(timer, &QTimer::timeout, this, &OpenVideo::nextFrame);
}
//-----------------------------------【獲取并顯示下一幀圖片】---------------------------------------
void OpenVideo::nextFrame()
{
capture >> frame; //讀取當(dāng)前幀
//圖像在Qt顯示前,必須將Mat型轉(zhuǎn)化成QImage格式,將OpenCV中Mat的BGR格式轉(zhuǎn)化成QImage的RGB格式
cvtColor(frame, temp, CV_BGR2RGB);
//轉(zhuǎn)化成QImage格式
QImage image = QImage((const unsigned char *)(temp.data), temp.cols,
temp.rows, QImage::Format_RGB888);
ui.labelOpenVideo->setPixmap(QPixmap::fromImage(image));
if (!CameraVideoFlag)
{
emit VideoSignal(image, CameraVideoFlag);
}
}
//--------------------------------------【拍照/截圖】------------------------------------------------
void OpenVideo::ScreenShot()
{
//將Mat型temp轉(zhuǎn)為Qt的QImage型
QImage image = QImage((const unsigned char *)(temp.data), temp.cols,
temp.rows, QImage::Format::Format_RGB888);
ui.labelScreenShot->setPixmap(QPixmap::fromImage(image));
//讓圖片自動(dòng)適應(yīng)label大小
ui.labelScreenShot->setScaledContents(true);
//截圖保存
const QPixmap *img = ui.labelScreenShot->pixmap();
QString path = QFileDialog::getSaveFileName(this, "save", "../", "Image(*.png)");
if (false == path.isEmpty()) //判斷路徑是否有效
{
bool isOk = img->save(path);
if (true == isOk)
{
//#pragma execution_character_set("utf-8")
//設(shè)置為UTF-8格式顷锰,顯示中文,不然會(huì)亂碼,但是影響了vs的編碼格式斑响,不能摧毀中文名稱(chēng)窗口
//故選擇用QStringLiteral("中文")進(jìn)行修飾菱属。這樣雖不能全局設(shè)置utf-8,但可以正常摧毀中文名稱(chēng)窗口
QMessageBox::information(this, QStringLiteral("保存完成"), QStringLiteral("截圖保存完成"));
}
}
else
{
//保存文件路徑出錯(cuò)
return;
}
}
//---------------------------------------【錄制視頻】-------------------------------------------
void OpenVideo::REC()
{
double rate = 12.0;//保存視頻的幀率舰罚,12這個(gè)幀率很合適纽门,播放的時(shí)候不慢不快
cv::Size videoSize(frame.cols, frame.rows);
//錄制 VideoWriter writer; 已在頭文件中定義
writer.open("../REC.avi", cv::VideoWriter::fourcc('M', 'J', 'P', 'G'), rate, videoSize, true);
openREC = true;
while (openREC)
{
capture >> frame;
writer.write(frame);//或writer << frame;
//加個(gè)窗口顯示。不加的話沸停,直接設(shè)置循環(huán)會(huì)卡住不動(dòng)
cv::namedWindow("錄制視頻");
imshow("錄制視頻", frame);
cv::waitKey(30);
}
}
//-------------------------------------------------【結(jié)束錄制】------------------------------------------
void OpenVideo::EndREC()
{
cv::destroyWindow("錄制視頻");
openREC = false;
writer.release();//結(jié)束錄制
}
//--------------------------------------------【人臉檢測(cè)與對(duì)齊】-----------------------------------------------
void OpenVideo::FaceDetectionAlignment()
{
//定時(shí)器連接,獲取下一幀圖片
connect(timer, &QTimer::timeout, this, &OpenVideo::nextSeetaFaceProcessFrame);
}
//-----------------------------------【獲取下一幀SeetaFace處理的圖片】----------------------------
void OpenVideo::nextSeetaFaceProcessFrame()
{
std::string MODEL_DIR = "./model/";
//-----------------------------------------【人臉檢測(cè)與對(duì)齊】--------------------------------------
////要檢測(cè)圖像上的人臉膜毁,首先應(yīng)該用模型文件的路徑實(shí)例化seeta::FaceDetection的對(duì)象昭卓。
seeta::FaceDetection detector("./model/seeta_fd_frontal_v1.0.bin");
detector.SetMinFaceSize(40);//設(shè)置要檢測(cè)的人臉的最小尺寸(默認(rèn)值:20愤钾,不受限制),也能設(shè)置最大:face_detector.SetMaxFaceSize(size);
detector.SetScoreThresh(2.f);////設(shè)置檢測(cè)到的人臉的得分閾值(默認(rèn)值:2.0)
detector.SetImagePyramidScaleFactor(0.8f);//設(shè)置圖像金字塔比例因子(0 <因子< 1,默認(rèn)值:0.8)
detector.SetWindowStep(4, 4);//設(shè)置滑動(dòng)窗口的步長(zhǎng)(默認(rèn):4),face_detector.SetWindowStep(step_x, step_y);
capture >> frame;//讀入攝像頭
int pts_num = 5;
cv::Mat img_gray;
if (frame.channels() != 1)
//非單通道的候醒,轉(zhuǎn)為灰度圖
cv::cvtColor(frame, img_gray, cv::COLOR_BGR2GRAY);
else
{
img_gray = frame;
}
seeta::ImageData image_data;
image_data.data = img_gray.data;
image_data.width = img_gray.cols;
image_data.height = img_gray.rows;
image_data.num_channels = 1;
//調(diào)用Detect()來(lái)檢測(cè)人臉能颁,它將作為seeta::FaceInfo的向量(容器)返回。
std::vector faces = detector.Detect(image_data);
int32_t face_num = static_cast(faces.size()); //獲取人臉數(shù)量
seeta::FacialLandmark points[5];//定義面部的5個(gè)標(biāo)記點(diǎn)
//首先應(yīng)該實(shí)例化seeta:: faceAlignment的對(duì)象與模型文件的路徑倒淫。
seeta::FaceAlignment point_detector((MODEL_DIR + "seeta_fa_v1.1.bin").c_str());
//檢測(cè)5點(diǎn)面部標(biāo)記
for (int f = 0; f < face_num; f++) {
point_detector.PointDetectLandmarks(image_data, faces[f], points);//檢測(cè)出人臉標(biāo)記
//將人臉用矩形框出(矩形的范圍由Detect()返回的faces給出)
cv::rectangle(frame, cv::Point(faces[f].bbox.x, faces[f].bbox.y),//bbox是Rect類(lèi)型的結(jié)構(gòu)體
cv::Point(faces[f].bbox.x + faces[f].bbox.width - 1, faces[f].bbox.y + faces[f].bbox.height - 1), CV_RGB(255, 0, 0));
// Visualize the results(形象化結(jié)果伙菊,即把檢測(cè)出的5點(diǎn)標(biāo)記(由PointDetectLandmarks函數(shù)內(nèi)部給出),用小圓環(huán)框出)
for (int i = 0; i < pts_num; i++)
{
cv::circle(frame, cv::Point(points[i].x, points[i].y), 2, CV_RGB(0, 255, 0), -CV_FILLED);
//cv::ellipse(frame, cv::Point(points[i].x, points[i].y),cv::Size(6,2),-45,0,360,cv::Scalar(255,129,0),1,8);
}
}
//圖像在Qt顯示前敌土,必須轉(zhuǎn)化成QImage格式镜硕,將RGBA格式轉(zhuǎn)化成RGB
cvtColor(frame, temp, CV_BGR2RGB);
//轉(zhuǎn)化成QImage格式
QImage image = QImage((const unsigned char *)(temp.data), temp.cols,
temp.rows, QImage::Format_RGB888);
ui.labelFaceNum->setText(QStringLiteral("檢測(cè)到的人臉個(gè)數(shù):%1").arg(face_num));
ui.labelOpenVideo->setPixmap(QPixmap::fromImage(image));
}
//----------------------------------------------【關(guān)閉攝像頭】-------------------------------------------
void OpenVideo::CloseCamera()
{
capture.release();
timer->stop();
//關(guān)閉攝像頭后,對(duì)標(biāo)簽狀態(tài)進(jìn)行控制返干,下面有好幾種方法
//ui.labelOpenVideo->hide(); ?//控件隱藏,Widget類(lèi)的方法
//ui.labelOpenVideo->setVisible(false);//控件是否可見(jiàn),Widget類(lèi)的方法
ui.labelOpenVideo->close();//控件關(guān)閉,Widget類(lèi)的方法兴枯,可與與show()搭配使用;
//關(guān)閉錄制(按鈕復(fù)用)
cv::destroyWindow("錄制視頻");
openREC = false;
//去除“視頻播放中”字樣
ui.label->close();
//先停止子線程
myT->setFlag(true); //跳出循環(huán)
}
//------------------------------------------------【輸入圖片】-------------------------------------------
void OpenVideo::InputImage()
{
ImagePath = QFileDialog::getOpenFileName(this,
QStringLiteral("打開(kāi)圖片"), "../", "Image(*.png *.jpg *.bmp)");
emit mainSignal(ImagePath); //發(fā)出信號(hào)給子線程
ui.InputImageLabel->setPixmap(QPixmap(ImagePath)); //其實(shí)直接放QString類(lèi)型的也可以(即直接放ImagePath)
//讓圖片自動(dòng)適應(yīng)label大小
ui.InputImageLabel->setScaledContents(true);
//動(dòng)態(tài)顯示當(dāng)前日期和時(shí)間(定時(shí)器已在打開(kāi)攝像頭時(shí)激活)
connect(timer, &QTimer::timeout, this, &OpenVideo::currentDateAndTime);
}
//------------------------------------【獲取子線程信號(hào)發(fā)來(lái)的信息】------------------------------------
//獲取人臉識(shí)別模塊的信息
void OpenVideo::getRecognitionInformation(int gallery_face_num, int probe_face_num,QImage image,float sim)
{
GalleryFaceNum = gallery_face_num;
ProbeFaceNum = probe_face_num;
CropFace = image;
Sim = sim;
//更新操作
FaceRecognitionProcess();
}
//-------------------------------------------------【人臉識(shí)別】--------------------------------------------
//描述: 從視頻中找出與輸入圖片對(duì)應(yīng)的人臉
void OpenVideo::FaceRecognitionProcess()
{
if (GalleryFaceNum != 0 && ProbeFaceNum != 0)
{
//顯示相似度
ui.SimilarityLabel->setText(QStringLiteral("相似度為:%1").arg(Sim)); ?//組包,用arg來(lái)顯示float型
if (Sim > 0.7)
{
ui.SimilarityLabel->setText(QStringLiteral("發(fā)現(xiàn)目標(biāo) | 相似度為:%1").arg(Sim));
//printf("\a"); //轉(zhuǎn)義響鈴符矩欠,在Qt中無(wú)效财剖,改用下面的Qt的方式
//播放音頻
QMediaPlayer *player = new QMediaPlayer;
//QUrl::fromLocalFile該函數(shù)接受由斜線分隔(/)的路徑以及該平臺(tái)的本機(jī)分隔符。
//此函數(shù)還接受具有雙前導(dǎo)斜杠(反斜杠)(\\)的路徑來(lái)指示遠(yuǎn)程文件
player->setMedia(QUrl::fromLocalFile("./mp3/Recognition.mp3"));
player->setVolume(80);
player->play();
//----------------------------------【將裁剪的臉部顯示在界面標(biāo)簽上】------------------------------------------
ui.OutputImageLabel->setPixmap(QPixmap::fromImage(CropFace));
ui.OutputImageLabel->setScaledContents(true);
}
}
}
void OpenVideo::NoFace()
{
ui.SimilarityLabel->setText(QStringLiteral("sorry癌淮!檢測(cè)不到人臉"));
}
主函數(shù)cpp文件(main.cpp)
#include "OpenVideo.h"
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
OpenVideo w;
w.show();
return a.exec();
}
```
子線程部分
頭文件(MyThread.h)
```
#ifndef MYTHREAD_H ?//防止頭文件被重復(fù)包含躺坟,這是C語(yǔ)言方式,C++是:#pragma once
#define MYTHREAD_H
//SeetaFace
#include
#include
#include
#include
#include "cv.h"
#include "highgui.h"
#include "face_detection.h"
#include "face_alignment.h"
#include
#include
#include "face_identification.h"
#include "recognizer.h"
#include "face_detection.h"
#include "face_alignment.h"
#include "math_functions.h"
#include
#include
#include
#include
#include
#include
#include //定時(shí)器
#include //QImage頭文件
class MyThread : public QObject
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = nullptr);
//線程處理函數(shù)
void FaceRecognitionProcess(); //人臉識(shí)別
//獲取主線程信號(hào)發(fā)來(lái)的數(shù)據(jù)
void getMainSignal(QString str);
void getVideoSignal(QImage image,bool flag);
//標(biāo)志位函數(shù)
void setFlag(bool flag = true); //括號(hào)里定義了一個(gè)默認(rèn)參數(shù)乳蓄,不傳參的時(shí)候就使用默認(rèn)的
signals:
void UpdateRecognition(int GalleryFaceNum,int ProbeFaceNum,QImage image,float sim);
void NoFace();
private:
cv::Mat frame; //定義一個(gè)Mat變量咪橙,用于存儲(chǔ)每一幀的圖像
cv::Mat temp; //臨時(shí)變量
cv::VideoCapture capture; //定義VideoCapture對(duì)象,獲取攝像頭
QImage image;
//標(biāo)志位
bool isStop;
bool CameraVideoFlag;//標(biāo)志位,true代表Camera虚倒,false代表Video
QString path;
};
#endif // MYTHREAD_H
```
cpp文件(MyThread.cpp)
?```
#include "MyThread.h"
MyThread::MyThread(QObject *parent) : QObject(parent)
{
//設(shè)置初始標(biāo)志位
isStop = false;
CameraVideoFlag = true;
}
void MyThread::setFlag(bool flag)
{
isStop = flag;
}
//----------------------------------------------------【人臉識(shí)別】------------------------------------------------
void MyThread::FaceRecognitionProcess()
{
using namespace seeta;
std::string MODEL_DIR = "./model/";
//用模型文件的路徑實(shí)例化seeta::FaceDetection的對(duì)象
seeta::FaceDetection detector("./model/seeta_fd_frontal_v1.0.bin");
detector.SetMinFaceSize(40);//設(shè)置要檢測(cè)的人臉的最小尺寸(默認(rèn)值:20美侦,不受限制),也能設(shè)置最大:face_detector.SetMaxFaceSize(size);
detector.SetScoreThresh(2.f); //設(shè)置檢測(cè)到的人臉的得分閾值(默認(rèn)值:2.0)
detector.SetImagePyramidScaleFactor(0.8f);//設(shè)置圖像金字塔比例因子(0 <因子< 1,默認(rèn)值:0.8)
detector.SetWindowStep(4, 4);//設(shè)置滑動(dòng)窗口的步長(zhǎng)(默認(rèn):4),face_detector.SetWindowStep(step_x, step_y);
//初始化的人臉對(duì)齊模型
seeta::FaceAlignment point_detector("./model/seeta_fa_v1.1.bin");
//初始化的人臉識(shí)別模型
FaceIdentification face_recognizer((MODEL_DIR + "seeta_fr_v1.0.bin").c_str());
//輸入一張圖片
cv::Mat gallery_img_color = cv::imread(path.toStdString(), 1);
cv::Mat gallery_img_gray;
cv::cvtColor(gallery_img_color, gallery_img_gray, CV_BGR2GRAY);
cv::Mat probe_img_color;
if (CameraVideoFlag)
{
capture.open(0);
}
while (!isStop)
{
//獲取攝像頭/視頻的每一幀畫(huà)面
if (CameraVideoFlag)
{
capture >> probe_img_color;
}
else
{
//接收QImage的格式是RGB裹刮,要轉(zhuǎn)成OpenCV中Mat的BGR格式
probe_img_color = frame;
}
cv::Mat probe_img_gray;
cv::cvtColor(probe_img_color, probe_img_gray, CV_BGR2GRAY);
ImageData gallery_img_data_color(gallery_img_color.cols, gallery_img_color.rows, gallery_img_color.channels());
gallery_img_data_color.data = gallery_img_color.data;//data ?uchar型的指針音榜。Mat類(lèi)分為了兩個(gè)部分:矩陣頭和指向矩陣數(shù)據(jù)部分(所有矩陣值)的指針,data就是指向矩陣數(shù)據(jù)的指針捧弃。
ImageData gallery_img_data_gray(gallery_img_gray.cols, gallery_img_gray.rows, gallery_img_gray.channels());
gallery_img_data_gray.data = gallery_img_gray.data;
ImageData probe_img_data_color(probe_img_color.cols, probe_img_color.rows, probe_img_color.channels());
probe_img_data_color.data = probe_img_color.data;
ImageData probe_img_data_gray(probe_img_gray.cols, probe_img_gray.rows, probe_img_gray.channels());
probe_img_data_gray.data = probe_img_gray.data;
// Detect faces
std::vector gallery_faces = detector.Detect(gallery_img_data_gray);
int32_t gallery_face_num = static_cast(gallery_faces.size());
std::vector probe_faces = detector.Detect(probe_img_data_gray);
int32_t probe_face_num = static_cast(probe_faces.size());
if (gallery_face_num != 0 && probe_face_num != 0)
{
//檢測(cè)5點(diǎn)面部標(biāo)記
seeta::FacialLandmark gallery_points[5];
point_detector.PointDetectLandmarks(gallery_img_data_gray, gallery_faces[0], gallery_points);
seeta::FacialLandmark probe_points[5];
point_detector.PointDetectLandmarks(probe_img_data_gray, probe_faces[0], probe_points);
for (int i = 0; i < 5; i++)
{
cv::circle(gallery_img_color, cv::Point(gallery_points[i].x, gallery_points[i].y), 2,
CV_RGB(0, 255, 0));
cv::circle(probe_img_color, cv::Point(probe_points[i].x, probe_points[i].y), 2,
CV_RGB(0, 255, 0));
}
cv::imwrite("gallery_point_result.jpg", gallery_img_color);
cv::imwrite("probe_point_result.jpg", probe_img_color);
//提取面部特性特征
float gallery_fea[2048];
float probe_fea[2048];
face_recognizer.ExtractFeatureWithCrop(gallery_img_data_color, gallery_points, gallery_fea);
face_recognizer.ExtractFeatureWithCrop(probe_img_data_color, probe_points, probe_fea);
//-------------------------------------------【裁剪臉部】----------------------------------------------------
//創(chuàng)建一個(gè)圖像來(lái)存儲(chǔ)裁剪的臉部
cv::Mat dst_img(face_recognizer.crop_height(),
face_recognizer.crop_width(),
CV_8UC(face_recognizer.crop_channels()));
ImageData dst_img_data(dst_img.cols, dst_img.rows, dst_img.channels());
dst_img_data.data = dst_img.data;
//裁剪
face_recognizer.CropFace(probe_img_data_color, probe_points, dst_img_data);
//保存裁剪的臉部
cv::imwrite("dst_img.jpg", dst_img);
//圖像在Qt顯示前赠叼,必須將Mat型轉(zhuǎn)化成QImage格式擦囊,將RGBA格式轉(zhuǎn)化成RGB
cvtColor(dst_img, temp, CV_BGR2RGB);
//轉(zhuǎn)化成QImage格式
QImage CropFace = QImage((const unsigned char *)(temp.data), temp.cols,
temp.rows, QImage::Format_RGB888);
//計(jì)算兩張臉部的相似度
float sim = face_recognizer.CalcSimilarity(gallery_fea, probe_fea);
//Qt多線程的信號(hào)傳參只能傳基本數(shù)據(jù)類(lèi)型
//Mat或者庫(kù)里面的int_8 / int_32(自定義的數(shù)據(jù)類(lèi)型) 什么的不能傳
//類(lèi)型轉(zhuǎn)換
int GalleryFaceNum = gallery_face_num;
int ProbeFaceNum = probe_face_num;
//發(fā)射信號(hào)
emit UpdateRecognition(GalleryFaceNum, ProbeFaceNum, CropFace, sim);
}
else
{
emit NoFace();
}
}
}
void MyThread::getMainSignal(QString str)
{
//獲取輸入圖片的路徑
path = str;
}
void MyThread::getVideoSignal(QImage image,bool flag)
{
this->image = image;
CameraVideoFlag = flag;
frame = cv::Mat(image.height(), image.width(), CV_8UC3, (void*)image.constBits(), image.bytesPerLine());
//不能放在這里轉(zhuǎn)格式,會(huì)造成不能及時(shí)接收
//故把格式轉(zhuǎn)換放在處理函數(shù)里了
}
```