基于OPENCV的手势识别技术
基于OPENCV的手勢(shì)識(shí)別技術(shù)
?
前言:
??本篇博客主要介紹基于OPENCV的手勢(shì)識(shí)別程序,代碼為C++,OPENCV版本為OPENCV4會(huì)有較為詳細(xì)的實(shí)現(xiàn)流程和源碼,并且做到源碼盡量簡(jiǎn)單,注釋也自認(rèn)為較為清晰,希望能幫助到大家。(源碼將放在文章末尾的鏈接中,代碼較為粗糙,有錯(cuò)誤歡迎大家指出。)
?
?
一、手勢(shì)識(shí)別流程圖
??首先是對(duì)于流程圖的簡(jiǎn)單說(shuō)明:兩條線是分開(kāi)進(jìn)行的,兩者在比對(duì)分類之前是不會(huì)互相影響的(當(dāng)然有部分函數(shù),例如提取特征的函數(shù),是在兩條線中都是會(huì)使用到的),因此可以分別完成兩條線的工作。如果作為需要分工的項(xiàng)目,可以按照這兩部分進(jìn)行分工,最后進(jìn)行整合。
?
??而關(guān)于兩條線的順序,個(gè)人認(rèn)為是優(yōu)先進(jìn)行神經(jīng)網(wǎng)絡(luò)的訓(xùn)練部分(也就是下方線路),原因是對(duì)于要識(shí)別的手勢(shì),首先要有對(duì)應(yīng)的樣本,優(yōu)先去尋找樣本,以此確定能夠識(shí)別的手勢(shì)。(當(dāng)然,要是已經(jīng)找到了樣本,先做上方線路也沒(méi)啥太大問(wèn)題)
?
本篇文章的順序:
先講對(duì)于圖片的處理:
??因?yàn)橄让靼讏D片如何處理,并知道提取特征的方法,才能理解要把什么東西放到神經(jīng)網(wǎng)絡(luò)里,得到的數(shù)據(jù)又是什么。
再講神經(jīng)網(wǎng)路的搭建:
??神經(jīng)網(wǎng)路的搭建其實(shí)就是一個(gè)模板性的東西,計(jì)算機(jī)并不知道你要識(shí)別的東西到底是什么,是數(shù)字還是手勢(shì),對(duì)于計(jì)算機(jī)來(lái)說(shuō)它只是一堆數(shù)據(jù),它只負(fù)責(zé)給你找到——你輸入的數(shù)據(jù)在網(wǎng)絡(luò)里跟哪個(gè)數(shù)據(jù)最為匹配,然后就給你輸出。
?
?
二、讀取圖片、獲取皮膚部分及二值化
?
1)讀取圖片部分:
??并沒(méi)有過(guò)多好說(shuō)的,直接使用imread函數(shù)對(duì)圖片進(jìn)行讀入。(注意,讀入的圖片應(yīng)該是彩色的而不是灰度圖,否則無(wú)法進(jìn)行后面的皮膚區(qū)域獲取)
?
2)獲取皮膚部分及二值化:
??關(guān)于皮膚部分的獲取,這里列出幾種算法。由于圖片的光照等的不同,不同算法的優(yōu)劣也很難對(duì)比,各位自行選擇算法。
?
①基于RGB顏色空間的簡(jiǎn)單閾值膚色識(shí)別:
?
??根據(jù)他人的研究,我們可以知道有這樣的一條判別式來(lái)用于膚色檢測(cè)
?
R>95 && G>40 && B>20 && R>G && R>B && Max(R,G,B)-Min(R,G,B)>15 && Abs(R-G)>15
?
??有了條判別式,我們就能夠很容易地寫出代碼來(lái)實(shí)現(xiàn)膚色檢測(cè)。但該算法對(duì)于光線的抗干擾能力較弱,光線稍微不好就識(shí)別不出皮膚點(diǎn)。
??代碼說(shuō)明:首先對(duì)彩色圖片分離開(kāi)R、G、B分量,然后根據(jù)公式,將每一個(gè)點(diǎn)的R、G、B分量代入公式中進(jìn)行判斷,符合條件的點(diǎn)我們可以認(rèn)為它是皮膚中的一個(gè)點(diǎn)。(其中的MyMax和MyMin是自寫的判斷大小函數(shù))將認(rèn)為是皮膚的點(diǎn)的值置為255,就可以達(dá)到二值化的效果。
??可以看到除了部分因?yàn)楣饩€問(wèn)題導(dǎo)致的陰影沒(méi)有被識(shí)別出來(lái)以外,幾乎所有皮膚點(diǎn)都被識(shí)別出來(lái)了,效果還過(guò)得去。
?
?
②基于橢圓皮膚模型的皮膚檢測(cè)
?
??研究發(fā)現(xiàn),將皮膚映射到Y(jié)CrCb空間,則在YCrCb空間中皮膚的像素點(diǎn)近似成一個(gè)橢圓的分布。因此如果我們得到了一個(gè)CrCb的橢圓,對(duì)于一個(gè)點(diǎn)的坐標(biāo)(Cr, Cb),我們只需判斷它是否在橢圓內(nèi)(包括邊界)就可以得知它是不是膚色點(diǎn)。
??該算法對(duì)于光線的敏感性沒(méi)有這么高,基本上該檢測(cè)到的皮膚都能夠檢測(cè)到,抗干擾能力相對(duì)較強(qiáng)。(原因大概是YCrCb中Y分量表示明亮度,而而“Cr”和“Cb” 表示的則是色度,作用是描述影像色彩及飽和度)
??代碼說(shuō)明:首先用OPENCV自帶的函數(shù)生成一個(gè)橢圓的模型,然后將RGB圖片轉(zhuǎn)換為YCrCb圖片,分離開(kāi)Y,Cr,Cb,對(duì)于原圖片里的每一個(gè)點(diǎn),判斷其是否在橢圓內(nèi),在,則認(rèn)為該點(diǎn)是一個(gè)皮膚點(diǎn)。
??可以看到幾乎所有的皮膚部分都被識(shí)別出來(lái)了,效果比上一個(gè)算法看上去要好不少。
?
?
③基于YCrCb顏色空間Cr,Cb范圍篩選法
?
??這種方法與第一種方法在原理上是一樣的,只不過(guò)這次將顏色空間變?yōu)榱薡CrCb空間。同樣的,我們有公式
?
Cr>133 && Cr<173 && Cb>77 && Cb<127
?
??將CrCb分量代入這條公式進(jìn)行判斷,就能得到皮膚點(diǎn),此處不進(jìn)行過(guò)多的講述。
??我們可以看到效果也是挺不錯(cuò)的(相比第一種方法得到的),與第二種方法不分伯仲。
?
?
④YCrCb顏色空間Cr分量+Otsu法閾值分割
?
??首先我們要知道YCrCb色彩空間是什么:YCrCb即YUV,其中“Y”表示明亮度(Luminance或Luma),也就是灰階值;而“U”和“V” 表示的則是色度(Chrominance或Chroma),作用是描述影像色彩及飽和度,用于指定像素的顏色。其中,Cr反映了RGB輸入信號(hào)紅色部分與RGB信號(hào)亮度值之間的差異。(來(lái)自百度百科)
?
所以,該方法的原理也十分簡(jiǎn)單:
?
1、將RGB圖像轉(zhuǎn)換到Y(jié)CrCb顏色空間,提取Cr分量圖像
2、對(duì)Cr做自適應(yīng)二值化閾值分割處理(Otsu法)
Mat getSkin4(Mat& ImageIn) {Mat Image = ImageIn.clone();Mat ycrcb_Image;cvtColor(Image, ycrcb_Image, COLOR_BGR2YCrCb);//轉(zhuǎn)換色彩空間vector<Mat>y_cr_cb;split(ycrcb_Image, y_cr_cb);//分離YCrCbMat CR = y_cr_cb[1];//圖片的CR分量Mat CR1;Mat Binary = Mat::zeros(Image.size(), CV_8UC1);GaussianBlur(CR, CR1, Size(3, 3), 0, 0);//對(duì)CR分量進(jìn)行高斯濾波,得到CR1(注意這里一定要新建一張圖片存放結(jié)果)threshold(CR1, Binary, 0, 255, THRESH_OTSU);//用系統(tǒng)自帶的threshold函數(shù),對(duì)CR分量進(jìn)行二值化,算法為自適應(yīng)閾值的OTSU算法return Binary; }??代碼說(shuō)明:前面的轉(zhuǎn)換色彩空間不再贅述,關(guān)鍵點(diǎn)在于系統(tǒng)的二值化函數(shù)threshold,使用了OTSU的算法對(duì)圖像前景和背景進(jìn)行區(qū)分。(請(qǐng)不清楚threshold函數(shù)使用和OTSU算法的讀者自行查找資料,這里由于篇幅問(wèn)題不展開(kāi)解釋)
可以看到效果也不錯(cuò),基本也能識(shí)別出來(lái)。
?
?
⑤OPENCV自帶的膚色檢測(cè)類AdaptiveSkinDetector
(但是我沒(méi)用過(guò),僅是放上來(lái)讓大家知道…)
?
?
總結(jié):
??對(duì)于以上幾種方法,很難說(shuō)到底哪一種會(huì)更加準(zhǔn)確,根據(jù)環(huán)境不同,圖片不同,算法之間的優(yōu)劣也有差別。因此有條件的可以每一種都嘗試,看看在自己的環(huán)境下哪種算法的效果最好。我在代碼中使用的是方法②,也僅供參考。
?
?
三、獲取輪廓及特征(特征為傅里葉描繪子)
?
原理:(請(qǐng)務(wù)必看看)
?
1)什么是圖像的特征?
??或許換個(gè)問(wèn)題你就理解了,什么是人的特征?我們或許可以認(rèn)為,會(huì)直立行走,能制造和使用工具,這些就是人的特征。然后,進(jìn)一步,什么是手勢(shì)0的特征?是一根手指都沒(méi)有伸出來(lái),這就是手勢(shì)0的特征。手勢(shì)1呢?只伸出了一根手指對(duì)吧?這就是特征。
?
2)為什么要獲取圖像特征?
??我們舉個(gè)比較簡(jiǎn)單(但是并不真實(shí))的例子:你輸入了一張二值化后的圖片(假設(shè)是手勢(shì)2,即我們前面的圖片里的手勢(shì)),我們通過(guò)一個(gè)獲取特征的函數(shù),獲得了這個(gè)手勢(shì)的特征值,假設(shè)這一連串的值是[1,2,3,4,5,6]。
??而在神經(jīng)網(wǎng)絡(luò)里,手勢(shì)0的特征值被認(rèn)為是[4,4,3,3,2,2],手勢(shì)1被認(rèn)為是[9,8,7,6,5,4,],手勢(shì)2被認(rèn)為是[1,2,3,4,5,5]。那么你輸入了[1,2,3,4,5,6],你認(rèn)為最匹配的是哪個(gè)手勢(shì)呢?顯然就是手勢(shì)2。
??而這就是神經(jīng)網(wǎng)絡(luò)的工作原理。你輸入一串代表特征的數(shù)值,預(yù)測(cè)函數(shù)會(huì)在神經(jīng)網(wǎng)絡(luò)里去找跟這串?dāng)?shù)值最相似的那個(gè)結(jié)果,把它告訴你:這就是我判斷的手勢(shì)結(jié)果。
??而圖像的特征,就是上面我們所說(shuō)的[1,2,3,4,5,6]這串?dāng)?shù)字。我們要想辦法獲取這個(gè)特征向量,扔到神經(jīng)網(wǎng)絡(luò)里去讓它識(shí)別。至于神經(jīng)網(wǎng)絡(luò)怎么知道[1,2,3,4,5,5]代表手勢(shì)2,[4,4,3,3,2,2]代表手勢(shì)0,我們?cè)诤竺娴纳窠?jīng)網(wǎng)絡(luò)部分會(huì)說(shuō)到。
?
3)要獲取什么特征,以及怎么獲取?
??首先,對(duì)于手勢(shì)來(lái)說(shuō),我們獲取的特征應(yīng)該是對(duì)旋轉(zhuǎn)和縮放都不敏感的。為什么呢?你總不希望你的手旋轉(zhuǎn)了45°系統(tǒng)就識(shí)別不出來(lái)了吧?你總不希望你離得遠(yuǎn)一點(diǎn)系統(tǒng)就識(shí)別不出來(lái)了吧?所以,我們要找到一個(gè)方法來(lái)描述你的手勢(shì),并且這個(gè)方法對(duì)于旋轉(zhuǎn)和縮放都不那么敏感。
??而在本項(xiàng)目中,我們使用的是傅里葉描繪子,這是一種邊界描繪子(就是專門用來(lái)描繪邊界的),它對(duì)于旋轉(zhuǎn)以及縮放并不十分敏感。這里簡(jiǎn)單說(shuō)一下傅里葉描繪子的原理。對(duì)于坐標(biāo)系內(nèi)的一個(gè)點(diǎn),通常我們用(X,Y)來(lái)表示。但這就是兩個(gè)數(shù)值了,有沒(méi)有什么辦法用一個(gè)數(shù)值來(lái)表示?幸好我們有復(fù)數(shù)這個(gè)東西!對(duì)于點(diǎn)(X,Y)我們可以寫成X+iY,這不就變成一個(gè)數(shù)了嗎?這能將一個(gè)二維的問(wèn)題轉(zhuǎn)變?yōu)橐痪S的問(wèn)題。
??而我們應(yīng)該知道,任何一個(gè)周期函數(shù)都可以展開(kāi)為傅里葉級(jí)數(shù),所以我們可以通過(guò)傅里葉級(jí)數(shù)來(lái)描繪一個(gè)周期變換的函數(shù)。那么你可能會(huì)問(wèn),這個(gè)二值化后的手勢(shì)圖,跟周期變換函數(shù)有什么關(guān)系啊?還真有!沿邊界曲線上一個(gè)動(dòng)點(diǎn)s[k] = [x(k),y(k)]就是一個(gè)以形狀邊界周長(zhǎng)為周期的函數(shù)。
??為什么外輪廓是周期函數(shù)?假設(shè)有一個(gè)點(diǎn)在外輪廓上向著一個(gè)方向移動(dòng),轉(zhuǎn)了一圈,兩圈…,那這是不是周期函數(shù)?所以問(wèn)題就解決了,我們可以用傅里葉描繪子來(lái)描繪手勢(shì)的外輪廓,從而描繪手勢(shì)。
??關(guān)于傅里葉描繪子更詳細(xì)的介紹以及推導(dǎo)建議各位去查閱相關(guān)資料,這里不打算過(guò)多說(shuō)(畢竟估計(jì)也沒(méi)幾個(gè)人感興趣)
?
傅里葉描繪子原理
?
我這里直接給出計(jì)算公式:
其中a(u)就是傅里葉級(jí)數(shù)的第n項(xiàng),k就是圖像中第k個(gè)點(diǎn)。
?
??當(dāng)然,這個(gè)公式可以使用歐拉公式進(jìn)行展開(kāi),對(duì)于編程實(shí)現(xiàn)來(lái)說(shuō)會(huì)更加簡(jiǎn)單。但是僅僅是這個(gè)公式是不夠的,因?yàn)檫@樣計(jì)算出來(lái)的傅里葉描繪子與形狀尺度,方向和曲線起始點(diǎn)S0都有關(guān)系,顯然不是我們想要的。所以,我們還要以a(1)為基準(zhǔn),進(jìn)行歸一化,最終得到:
??由于形狀的能量大多集中在低頻部分,高頻部分一般很小且容易受到干擾,所以我們只取前12位就足夠了。
?
?
具體實(shí)現(xiàn):
?
??首先,要描繪手勢(shì)的外輪廓,我們就需要先得到手勢(shì)的外輪廓。幸好,OPENCV有對(duì)應(yīng)的函數(shù)讓我們使用,就是findContours函數(shù)。
這樣,我們就找到了圖片的全部輪廓,并且存在了二維向量contours中。然后我們就可以計(jì)算傅里葉描繪子了。
/***計(jì)算圖像的傅里葉描繪子***//***傅里葉變換后的系數(shù)儲(chǔ)存在f[d]中***/vector<float>f;vector<float>fd;//最終傅里葉描繪子前14位Point p;for (int i = 0; i < max_size; i++)//主要的計(jì)算部分{float x, y, sumx = 0, sumy = 0;for (int j = 0; j < max_size; j++){p = contours[contour_num].at(j);x = p.x;y = p.y;sumx += (float)(x * cos(2 * CV_PI * i * j / max_size) + y * sin(2 * CV_PI * i * j / max_size));sumy += (float)(y * cos(2 * CV_PI * i * j / max_size) - x * sin(2 * CV_PI * i * j / max_size));}f.push_back(sqrt((sumx * sumx) + (sumy * sumy)));}fd.push_back(0);//放入了標(biāo)志位‘0’,并不影響最終結(jié)果for (int k = 2; k < 16; k++)//進(jìn)行歸一化,然后放入最終結(jié)果中{f[k] = f[k] / f[1];fd.push_back(f[k]);}out = Mat::zeros(1, fd.size(), CV_32F);//out是用于輸出的手勢(shì)特征for (int i = 0; i < fd.size(); i++){out.at<float>(i) = fd[i];}計(jì)算完了傅里葉描繪子,我們就算是拿到了手勢(shì)的特征了。
?
?
四:找到訓(xùn)練樣本
?
??首先,我們要明確找到訓(xùn)練樣本是很重要的,因?yàn)橛?xùn)練樣本決定了你能識(shí)別到的是什么。要是你找到了身份證數(shù)字的訓(xùn)練樣本,那么你就能識(shí)別身份證的數(shù)字;要是你找到了車牌號(hào)碼的數(shù)字樣本,那么你就能識(shí)別車牌;而我們?cè)谶@個(gè)項(xiàng)目要找到手勢(shì)的樣本。
??不過(guò)在這里我可以將我找到的樣本分享給大家,我將其一并打包在了項(xiàng)目文件中,大家有需要的可以下載我的工程文件。其中有10個(gè)手勢(shì),從0到9,希望能幫助到大家。
?
?
五:樣本的特征提取
?
??對(duì)于樣本的特征提取,其實(shí)跟前面我們說(shuō)到的特征提取沒(méi)有什么不同。只不過(guò)我們這里需要的是對(duì)大量的樣本進(jìn)行特征提取,并且將其存放在一個(gè)文件中,方便我們后續(xù)進(jìn)行的神經(jīng)網(wǎng)絡(luò)訓(xùn)練。
??在該段代碼運(yùn)行完之后,在文件夾下會(huì)生成一個(gè)ann_xml.xml文件,里面存放著所有樣本的特征值和一個(gè)標(biāo)簽矩陣(標(biāo)簽矩陣如下圖)
??其中的getFeatures函數(shù)與前面用到的特征提取函數(shù)是完全一致的。但是在這部分我們是可以省去找到皮膚部分的函數(shù),因?yàn)闃颖窘o到我們的就已經(jīng)是只有皮膚部分的圖片了。所以我們只需要進(jìn)行二值化并找到最大的輪廓,提取特征。
?
?
六:訓(xùn)練神經(jīng)網(wǎng)絡(luò)
?
??一些想法:神經(jīng)網(wǎng)絡(luò)的訓(xùn)練看起來(lái)像是比較難的一個(gè)部分,但其實(shí)并不是,看起來(lái)難的原因也許是之前并沒(méi)有接觸過(guò)神經(jīng)網(wǎng)絡(luò),覺(jué)得無(wú)從下手。(所以在寫這部分代碼之前應(yīng)該先去了解一下什么是神經(jīng)網(wǎng)絡(luò),不需要過(guò)于深入,知道基本的工作原理即可)但其實(shí)只要寫過(guò)一次,就會(huì)發(fā)現(xiàn)這是一個(gè)模板性的東西,寫過(guò)了就是從0到1的變化。當(dāng)然在寫該部分的代碼時(shí)會(huì)遇到很多的小問(wèn)題,比如OPENCV版本的不同,有部分網(wǎng)上的代碼使用的是OPENCV2,對(duì)于OPENCV3就不適用了。
?
廢話不多說(shuō)了直接開(kāi)始吧,首先是訓(xùn)練部分的函數(shù)代碼:
代碼說(shuō)明:
?
1、首先我們要將ann_xml.xml文件中的數(shù)據(jù)讀入到程序中,包括樣本的特征還有標(biāo)簽矩陣。
?
2、然后設(shè)置神經(jīng)網(wǎng)絡(luò)的基本參數(shù)。首先,設(shè)置3層的神經(jīng)網(wǎng)絡(luò),即1層輸入層,1層隱藏層,1層輸出層。
?
輸入層的結(jié)點(diǎn)數(shù)?這卻決于我們一張圖片獲得的特征值的數(shù)量。(根據(jù)第三大點(diǎn)中說(shuō)到的,我們?nèi)「道锶~描繪子的前12位)
?
隱藏層的結(jié)點(diǎn)數(shù)?我們?cè)谠擁?xiàng)目中將隱藏層設(shè)置為24。
?
輸出層的結(jié)點(diǎn)數(shù)?我們最后想讓神經(jīng)網(wǎng)絡(luò)識(shí)別幾個(gè)手勢(shì),就設(shè)置為多少。比如該項(xiàng)目最后要識(shí)別四個(gè)手勢(shì),就設(shè)置為4。
?
setTrainMethod(訓(xùn)練方法)?使用ANN_MLP::BACKPROP,即反向傳播神經(jīng)網(wǎng)絡(luò),這個(gè)較為常用。
?
setActivationFunction(激活函數(shù))?一般常用的就是Sigmoid 函數(shù),即下圖的函數(shù)
setTermCriteria為迭代終止準(zhǔn)則的設(shè)置
?
trainClasses變量 ?這個(gè)變量建議大家畫(huà)個(gè)圖理解一下。到底和class的區(qū)別是什么?雖然都是作為一個(gè)標(biāo)簽矩陣,但還是有一些不同。
?
train函數(shù)?這個(gè)就是神經(jīng)網(wǎng)絡(luò)的訓(xùn)練函數(shù),把相應(yīng)的參數(shù)代入進(jìn)去,就能夠訓(xùn)練出一個(gè)神經(jīng)網(wǎng)絡(luò)。
?
??需要注意的是,這個(gè)函數(shù)不需要每一次都識(shí)別都運(yùn)行,(而且運(yùn)行該函數(shù)需要花費(fèi)較多的時(shí)間)只要我們運(yùn)行一次,把神經(jīng)神經(jīng)網(wǎng)絡(luò)保存下來(lái),下次識(shí)別的時(shí)候直接讀取神經(jīng)網(wǎng)絡(luò),就可以進(jìn)行識(shí)別了。具體如下:
這樣就能保存一個(gè)ann_param的文件了,下次使用只需要
Ptr<ANN_MLP> ann = ANN_MLP::load("ann_param");//讀取神經(jīng)網(wǎng)絡(luò)?
?
七:比對(duì)分類,預(yù)測(cè)結(jié)果
?
預(yù)測(cè)函數(shù)的作用就是幫助我們對(duì)比圖片的特征與神經(jīng)網(wǎng)絡(luò)里哪個(gè)最像,然后把這個(gè)作為結(jié)果輸出。所以在這里我們要獲取要預(yù)測(cè)的圖片的特征,然后使用預(yù)測(cè)函數(shù)predict進(jìn)行預(yù)測(cè)。最后找到邏輯最大值,作為結(jié)果。
output變量存儲(chǔ)的是“該手勢(shì)對(duì)應(yīng)每個(gè)手勢(shì)的可能性”,找到最大的那個(gè)可能,作為結(jié)果輸出。至此,我們獲得了我們想要的那個(gè)結(jié)果。
?
?
結(jié)尾:
?
我們最后把main函數(shù)放上來(lái),這樣就能更清楚地看清流程:
然后我再把源代碼鏈接放在這里,需要的可以下載來(lái)看看(我盡量把積分調(diào)低,讓大家即使覺(jué)得沒(méi)啥用也不會(huì)虧多少積分)
?
https://download.csdn.net/download/qq_42884797/13658137
?
參考文獻(xiàn)及鏈接:
?
趙三琴,丁為民,劉徳營(yíng).基于傅里葉描述子的稻飛虱形狀識(shí)別[J].農(nóng)業(yè)機(jī)械學(xué)報(bào),2009,40(8):181~184
https://blog.csdn.net/qq_41562704/article/details/88975569
https://blog.csdn.net/javastart/article/details/97615918
總結(jié)
以上是生活随笔為你收集整理的基于OPENCV的手势识别技术的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 苹果6手机怎么录屏_OPPO手机怎么录屏
- 下一篇: ISPRS_Potsdam,Vaihin