日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

基于傅里叶算子的手势识别

發(fā)布時(shí)間:2024/5/7 编程问答 63 豆豆
生活随笔 收集整理的這篇文章主要介紹了 基于傅里叶算子的手势识别 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

這是我的本科畢設(shè)題目,剛開(kāi)始接觸機(jī)器學(xué)習(xí)這方面,感謝CSDN和GitHub上的大佬,網(wǎng)上類(lèi)似項(xiàng)目很多,方法也有很多,自己順帶進(jìn)行了整理,邊做畢設(shè)邊分享一下自己學(xué)習(xí)心得吧,也算是梳理一下所學(xué)知識(shí),各大佬有什么好的建議還請(qǐng)指出,不吝賜教。

項(xiàng)目簡(jiǎn)介:基于Win10 + Python3.7的環(huán)境,利用Python的OpenCV、Sklearn和PyQt5等庫(kù)搭建了一個(gè)較為完整的手勢(shì)識(shí)別系統(tǒng),用于識(shí)別日常生活中1-10的靜態(tài)手勢(shì)。

整個(gè)項(xiàng)目的資源:https://download.csdn.net/download/qq_41562704/11471042(包含手勢(shì)庫(kù)和已訓(xùn)練的模型,可以直接運(yùn)行使用)

環(huán)境:Win10 + Python3.7 + OpenCV3.4.5,各個(gè)庫(kù)的安裝就不多說(shuō)了

最終的效果圖如圖所示:

?

整個(gè)項(xiàng)目分為四個(gè)部分,即預(yù)處理,特征提取,模型訓(xùn)練,界面設(shè)計(jì)

預(yù)處理

1.獲取手勢(shì)

2.圖像預(yù)處理

2.1去噪

2.2 膚色檢測(cè) + 二值化處理

2.3 形態(tài)學(xué)處理

2.4 輪廓提取

特征提取

3 傅里葉算子提取

4 建立特征庫(kù)

4.1 數(shù)據(jù)增強(qiáng)

4.2 計(jì)算手勢(shì)庫(kù)的特征

模型訓(xùn)練

5 訓(xùn)練SVM模型

界面設(shè)計(jì)

6 PyQt設(shè)計(jì)界面


預(yù)處理

這部分需要完成攝像頭錄制手勢(shì)后,提取出手的輪廓線

這部分參考資料:

https://blog.csdn.net/ifruoxi/article/details/78091954(獲取手勢(shì),基于Python)

https://blog.csdn.net/qq_22527639/article/details/81501565(膚色檢測(cè):方法全,理論介紹的也很全面,基于C++)

https://blog.csdn.net/shadow_guo/article/details/43602051(基于RGB空間膚色檢測(cè),基于Python)

https://blog.csdn.net/weixin_40893939/article/details/84527037(基于HSV空間和YCrCb空間膚色檢測(cè),基于Python)

https://blog.csdn.net/Eastmount/article/details/83581277(腐蝕膨脹理論介紹,基于Python)

https://blog.csdn.net/dz4543/article/details/80655067(輪廓提取,基于Python)

1.獲取手勢(shì)

主要是調(diào)用OpenCV,創(chuàng)建main.py和 picture.py

main.py 當(dāng)前負(fù)責(zé)錄像,picture負(fù)責(zé)處理圖像

main.py

  • import cv2
  • import picture as pic
  • font = cv2.FONT_HERSHEY_SIMPLEX #設(shè)置字體
  • size = 0.5 #設(shè)置大小
  • width, height = 300, 300 #設(shè)置拍攝窗口大小
  • x0,y0 = 300, 100 #設(shè)置選取位置
  • cap = cv2.VideoCapture(0) #開(kāi)攝像頭
  • if __name__ == "__main__":
  • while(1):
  • ret, frame = cap.read() #讀取攝像頭的內(nèi)容
  • frame = cv2.flip(frame, 2)
  • roi = pic.binaryMask(frame, x0, y0, width, height) #取手勢(shì)所在框圖并進(jìn)行處理
  • key = cv2.waitKey(1) & 0xFF#按鍵判斷并進(jìn)行一定的調(diào)整
  • #按'j''l''u''j'分別將選框左移,右移,上移,下移
  • #按'q'鍵退出錄像
  • if key == ord('i'):
  • y0 += 5
  • elif key == ord('k'):
  • y0 -= 5
  • elif key == ord('l'):
  • x0 += 5
  • elif key == ord('j'):
  • x0 -= 5
  • if key == ord('q'):
  • break
  • cv2.imshow('frame', frame) #播放攝像頭的內(nèi)容
  • cap.release()
  • cv2.destroyAllWindows() #關(guān)閉所有窗口
  • 2.圖像預(yù)處理

    預(yù)處理在picture.py中完成。

    預(yù)處理的主要步驟為:去噪 -> 膚色檢測(cè) -> 二值化 -> 形態(tài)學(xué)處理?-> 輪廓提取,其中最麻煩的兩項(xiàng)為膚色檢測(cè)和輪廓提取。

    2.1去噪

    即濾波,主要是為了實(shí)現(xiàn)對(duì)圖像噪聲的消除,增強(qiáng)圖像的效果,其實(shí)個(gè)人感覺(jué)這里濾波的作用不是很明顯,也可以選擇不濾波,在膚色檢測(cè)后會(huì)有二次濾波。

  • #以3*3的模板進(jìn)行均值濾波
  • blur = cv2.blur(roi, (3,3))
  • #以3*3的模板進(jìn)行高斯濾波,最后一個(gè)參數(shù)表示x與y方向的標(biāo)準(zhǔn)差,給0的話,函數(shù)會(huì)自己運(yùn)算
  • blur = cv2.GaussianBlur(roi, (3,3), 0)
  • #中值濾波
  • blur = cv2.medianBlur(roi,5)
  • #雙邊濾波,9為區(qū)域的直徑,后面兩個(gè)參數(shù)是空間高斯函數(shù)標(biāo)準(zhǔn)差和灰度值相似性高斯函數(shù)標(biāo)準(zhǔn)差
  • blur = cv2.bilateralFilter(img,9,75,75)
  • 均值濾波器、高斯濾波器、中值濾波器、雙邊濾波器都可以進(jìn)行使用。推薦使用雙邊濾波器,該濾波器考慮了圖像的空間關(guān)系,也考慮圖像的灰度關(guān)系。雙邊濾波同時(shí)使用了空間高斯權(quán)重和灰度相似性高斯權(quán)重,確保了邊界不會(huì)被模糊掉。不過(guò)我在處理中直接省去了去噪這個(gè)過(guò)程。

    2.2 膚色檢測(cè) + 二值化處理

    picture.py

    方法一:基于RGB顏色空間

    判斷條件:

    在均勻光照下,R>95 AND G>40 B>20 AND MAX(R,G,B)-MIN(R,G,B)>15 AND ABS(R-G)>15 AND R>G AND R>B;

    在側(cè)光拍攝環(huán)境下,R>220 AND G>210 AND B>170 AND ABS(R-G)<=15 AND R>B AND G>B

  • import cv2
  • import numpy as np
  • def binaryMask(frame, x0, y0, width, height):
  • cv2.rectangle(frame,(x0,y0),(x0+width, y0+height),(0,255,0)) #畫(huà)出截取的手勢(shì)框圖
  • roi = frame[y0:y0+height, x0:x0+width] #獲取手勢(shì)框圖
  • cv2.imshow("roi", roi) #顯示手勢(shì)框圖
  • res = skinMask(roi) #進(jìn)行膚色檢測(cè)
  • cv2.imshow("res", res) #顯示膚色檢測(cè)后的圖像
  • return res
  • ##########方法一###################
  • ##########BGR空間的手勢(shì)識(shí)別#########
  • def skinMask(roi):
  • rgb = cv2.cvtColor(roi, cv2.COLOR_BGR2RGB) #轉(zhuǎn)換到RGB空間
  • (R,G,B) = cv2.split(rgb) #獲取圖像每個(gè)像素點(diǎn)的RGB的值,即將一個(gè)二維矩陣拆成三個(gè)二維矩陣
  • skin = np.zeros(R.shape, dtype = np.uint8) #掩膜
  • (x,y) = R.shape #獲取圖像的像素點(diǎn)的坐標(biāo)范圍
  • for i in range(0, x):
  • for j in range(0, y):
  • #判斷條件,不在膚色范圍內(nèi)則將掩膜設(shè)為黑色,即255
  • if (abs(R[i][j] - G[i][j]) > 15) and (R[i][j] > G[i][j]) and (R[i][j] > B[i][j]):
  • if (R[i][j] > 95) and (G[i][j] > 40) and (B[i][j] > 20) \
  • and (max(R[i][j],G[i][j],B[i][j]) - min(R[i][j],G[i][j],B[i][j]) > 15):
  • skin[i][j] = 255
  • elif (R[i][j] > 220) and (G[i][j] > 210) and (B[i][j] > 170):
  • skin[i][j] = 255
  • res = cv2.bitwise_and(roi,roi, mask = skin) #圖像與運(yùn)算
  • return res
  • 效果圖:

    方法二:基于HSV顏色空間

    判斷條件:0<=H<=20,S>=48,V>=50

    膚色檢測(cè)的方式不同影響的是skinMask,之后的代碼只是修改skinMask函數(shù),picture.py中其他代碼不需要改動(dòng)。

  • ##########方法二###################
  • ########HSV顏色空間H范圍篩選法######
  • def skinMask(roi):
  • low = np.array([0, 48, 50]) #最低閾值
  • high = np.array([20, 255, 255]) #最高閾值
  • hsv = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) #轉(zhuǎn)換到HSV空間
  • mask = cv2.inRange(hsv,low,high) #掩膜,不在范圍內(nèi)的設(shè)為255
  • res = cv2.bitwise_and(roi,roi, mask = mask) #圖像與運(yùn)算
  • return res
  • 效果圖:

    方法三:橢圓膚色檢測(cè)模型

    在YCrCb空間,膚色像素點(diǎn)會(huì)聚集到一個(gè)橢圓區(qū)域。先定義一個(gè)橢圓模型,然后將每個(gè)RGB像素點(diǎn)轉(zhuǎn)換到Y(jié)CrCb空間比對(duì)是否在橢圓區(qū)域,是的話判斷為皮膚。

  • ##########方法三###################
  • #########橢圓膚色檢測(cè)模型##########
  • def skinMask(roi):
  • skinCrCbHist = np.zeros((256,256), dtype= np.uint8)
  • cv2.ellipse(skinCrCbHist, (113,155),(23,25), 43, 0, 360, (255,255,255), -1) #繪制橢圓弧線
  • YCrCb = cv2.cvtColor(roi, cv2.COLOR_BGR2YCR_CB) #轉(zhuǎn)換至YCrCb空間
  • (y,Cr,Cb) = cv2.split(YCrCb) #拆分出Y,Cr,Cb值
  • skin = np.zeros(Cr.shape, dtype = np.uint8) #掩膜
  • (x,y) = Cr.shape
  • for i in range(0, x):
  • for j in range(0, y):
  • if skinCrCbHist [Cr[i][j], Cb[i][j]] > 0: #若不在橢圓區(qū)間中
  • skin[i][j] = 255
  • res = cv2.bitwise_and(roi,roi, mask = skin)
  • return res
  • 效果圖:

    ?

    方法四:YCrCb顏色空間的Cr分量+Otsu法閾值分割算法

    針對(duì)YCrCb中Cr分量的處理,對(duì)CR通道單獨(dú)進(jìn)行Otsu處理,Otsu方法opencv里用threshold,Otsu算法是對(duì)圖像的灰度級(jí)進(jìn)行聚類(lèi)。

  • ################方法四####################
  • ####YCrCb顏色空間的Cr分量+Otsu法閾值分割算法
  • def skinMask(roi):
  • YCrCb = cv2.cvtColor(roi, cv2.COLOR_BGR2YCR_CB) #轉(zhuǎn)換至YCrCb空間
  • (y,cr,cb) = cv2.split(YCrCb) #拆分出Y,Cr,Cb值
  • cr1 = cv2.GaussianBlur(cr, (5,5), 0)
  • _, skin = cv2.threshold(cr1, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) #Ostu處理
  • res = cv2.bitwise_and(roi,roi, mask = skin)
  • return res
  • 效果圖:

    方法五:Cr,Cb范圍篩選法

    該方法與方法一、二類(lèi)似,不同的只是顏色空間不相同

    判斷條件:133<=Cr<=173 77<=Cb<=127

  • ##########方法五###################
  • ########Cr,Cb范圍篩選法###########
  • def skinMask(roi):
  • YCrCb = cv2.cvtColor(roi, cv2.COLOR_BGR2YCR_CB) #轉(zhuǎn)換至YCrCb空間
  • (y,cr,cb) = cv2.split(YCrCb) #拆分出Y,Cr,Cb值
  • skin = np.zeros(cr.shape, dtype = np.uint8)
  • (x,y) = cr.shape
  • for i in range(0, x):
  • for j in range(0, y):
  • #每個(gè)像素點(diǎn)進(jìn)行判斷
  • if(cr[i][j] > 130) and (cr[i][j] < 175) and (cb[i][j] > 77) and (cb[i][j] < 127):
  • skin[i][j] = 255
  • res = cv2.bitwise_and(roi,roi, mask = skin)
  • return res
  • 效果圖:

    方法六:OpenCV自帶AdaptiveSkinDetector

    關(guān)于該函數(shù)的使用可以參考http://www.cnblogs.com/tornadomeet/archive/2012/11/20/2778740.html

    最終方案選擇:在幾種方式中選擇效果比較好的,RGB和HSV的效果一般,而且曝光的話,效果更差,YCrCb是一個(gè)單獨(dú)把亮度分離開(kāi)來(lái)的顏色模型,使用這個(gè)顏色模型的話,像膚色不會(huì)受到光線亮度而發(fā)生改變,方法三和四均可。

    2.3 形態(tài)學(xué)處理

    即便是比較好的膚色檢測(cè)算法,分割出來(lái)的手勢(shì),也難免有黑點(diǎn),或者背景有白點(diǎn),這時(shí)候需要對(duì)分割出來(lái)的手勢(shì)圖進(jìn)行進(jìn)一步處理,主要是腐蝕膨脹兩個(gè)操作。

    腐蝕和膨脹是針對(duì)白色部分(高亮部分而言)。從數(shù)學(xué)角度來(lái)說(shuō),膨脹或者腐蝕操作就是將圖像(或圖像的一部分區(qū)域,稱之為A)與核(稱之為B)進(jìn)行卷積。?
    膨脹就是求局部最大值操作,即計(jì)算核B覆蓋的區(qū)域的像素點(diǎn)的最大值,并把這個(gè)最大值賦值給參考點(diǎn)指定的像素,這樣就會(huì)使圖像中的高亮區(qū)域逐漸增長(zhǎng)。?
    腐蝕就是求局部最小值操作,即計(jì)算核B覆蓋的區(qū)域的像素點(diǎn)的最小值,并把這個(gè)最小值賦值給參考點(diǎn)指定的像素,這樣就會(huì)使圖像中的高亮區(qū)域逐漸減少。?

    開(kāi)運(yùn)算:先腐蝕后膨脹,去除孤立的小點(diǎn),毛刺

    閉運(yùn)算:先膨脹后腐蝕,填平小孔,彌合小裂縫

    在binaryMask函數(shù)中return前面添加以下代碼,進(jìn)行開(kāi)運(yùn)算

  • kernel = np.ones((3,3), np.uint8) #設(shè)置卷積核
  • erosion = cv2.erode(res, kernel) #腐蝕操作
  • cv2.imshow("erosion",erosion)
  • dilation = cv2.dilate(erosion, kernel)#膨脹操作
  • cv2.imshow("dilation",dilation)
  • 效果如圖:

    可以看到背景雜質(zhì)點(diǎn)去掉了

    2.4 輪廓提取

    在binaryMask函數(shù)中return前面添加以下代碼,對(duì)膚色檢測(cè)后的圖像提取手勢(shì)區(qū)域

  • binaryimg = cv2.Canny(res, 50, 200) #二值化,canny檢測(cè)
  • h = cv2.findContours(binaryimg,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE) #尋找輪廓
  • contours = h[1] #提取輪廓
  • ret = np.ones(res.shape, np.uint8) #創(chuàng)建黑色幕布
  • cv2.drawContours(ret,contours,-1,(255,255,255),1) #繪制白色輪廓
  • cv2.imshow("ret", ret)
  • 特征提取

    這部分主要任務(wù)是對(duì)第一部分提取的輪廓點(diǎn)坐標(biāo)提取出他們的傅里葉描述子,建立手勢(shì)特征庫(kù)

    參考資料:

    https://github.com/timfeirg/Fourier-Descriptors(提取特征代碼比較完整)

    https://github.com/alessandroferrari/elliptic-fourier-descriptors(橢圓傅里葉描述子的提取)

    https://www.cnblogs.com/edie0902/p/3658174.html(傅里葉算子的數(shù)學(xué)思想)

    3 傅里葉算子提取

    將picture.py中的提取輪廓點(diǎn)部分刪去,添加

  • import fourierDescriptor as fd
  • ret, fourier_result = fd.fourierDesciptor(res)
  • 創(chuàng)建fourierDescriptor.py

    在這個(gè)文件中完成對(duì)輪廓點(diǎn)坐標(biāo)的傅里葉描述子的提取,具體代碼如下:

  • import cv2
  • import numpy as np
  • MIN_DESCRIPTOR = 32 # surprisingly enough, 2 descriptors are already enough
  • ##計(jì)算傅里葉描述子
  • def fourierDesciptor(res):
  • #Laplacian算子進(jìn)行八鄰域檢測(cè)
  • gray = cv2.cvtColor(res, cv2.COLOR_BGR2GRAY)
  • dst = cv2.Laplacian(gray, cv2.CV_16S, ksize = 3)
  • Laplacian = cv2.convertScaleAbs(dst)
  • contour = find_contours(Laplacian)#提取輪廓點(diǎn)坐標(biāo)
  • contour_array = contour[0][:, 0, :]#注意這里只保留區(qū)域面積最大的輪廓點(diǎn)坐標(biāo)
  • ret_np = np.ones(dst.shape, np.uint8) #創(chuàng)建黑色幕布
  • ret = cv2.drawContours(ret_np,contour[0],-1,(255,255,255),1) #繪制白色輪廓
  • contours_complex = np.empty(contour_array.shape[:-1], dtype=complex)
  • contours_complex.real = contour_array[:,0]#橫坐標(biāo)作為實(shí)數(shù)部分
  • contours_complex.imag = contour_array[:,1]#縱坐標(biāo)作為虛數(shù)部分
  • fourier_result = np.fft.fft(contours_complex)#進(jìn)行傅里葉變換
  • #fourier_result = np.fft.fftshift(fourier_result)
  • descirptor_in_use = truncate_descriptor(fourier_result)#截短傅里葉描述子
  • #reconstruct(ret, descirptor_in_use)
  • return ret, descirptor_in_use
  • def find_contours(Laplacian):
  • #binaryimg = cv2.Canny(res, 50, 200) #二值化,canny檢測(cè)
  • h = cv2.findContours(Laplacian,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE) #尋找輪廓
  • contour = h[1]
  • contour = sorted(contour, key = cv2.contourArea, reverse=True)#對(duì)一系列輪廓點(diǎn)坐標(biāo)按它們圍成的區(qū)域面積進(jìn)行排序
  • return contour
  • #截短傅里葉描述子
  • def truncate_descriptor(fourier_result):
  • descriptors_in_use = np.fft.fftshift(fourier_result)
  • #取中間的MIN_DESCRIPTOR項(xiàng)描述子
  • center_index = int(len(descriptors_in_use) / 2)
  • low, high = center_index - int(MIN_DESCRIPTOR / 2), center_index + int(MIN_DESCRIPTOR / 2)
  • descriptors_in_use = descriptors_in_use[low:high]
  • descriptors_in_use = np.fft.ifftshift(descriptors_in_use)
  • return descriptors_in_use
  • ##由傅里葉描述子重建輪廓圖
  • def reconstruct(img, descirptor_in_use):
  • #descirptor_in_use = truncate_descriptor(fourier_result, degree)
  • #descirptor_in_use = np.fft.ifftshift(fourier_result)
  • #descirptor_in_use = truncate_descriptor(fourier_result)
  • #print(descirptor_in_use)
  • contour_reconstruct = np.fft.ifft(descirptor_in_use)
  • contour_reconstruct = np.array([contour_reconstruct.real,
  • contour_reconstruct.imag])
  • contour_reconstruct = np.transpose(contour_reconstruct)
  • contour_reconstruct = np.expand_dims(contour_reconstruct, axis = 1)
  • if contour_reconstruct.min() < 0:
  • contour_reconstruct -= contour_reconstruct.min()
  • contour_reconstruct *= img.shape[0] / contour_reconstruct.max()
  • contour_reconstruct = contour_reconstruct.astype(np.int32, copy = False)
  • black_np = np.ones(img.shape, np.uint8) #創(chuàng)建黑色幕布
  • black = cv2.drawContours(black_np,contour_reconstruct,-1,(255,255,255),1) #繪制白色輪廓
  • cv2.imshow("contour_reconstruct", black)
  • #cv2.imwrite('recover.png',black)
  • return black
  • 這里需要注意:

    輪廓提取后進(jìn)行了二次去噪,即只保留區(qū)域面積最大的曲線,效果如圖

    其次關(guān)于利用傅里葉算子重建輪廓圖,在實(shí)際使用過(guò)程中并不需要,僅僅為了在測(cè)試階段檢驗(yàn)效果

    取32項(xiàng)傅里葉算子的時(shí)候,重建效果如下,基本可以還原手勢(shì)形狀。

    4 建立特征庫(kù)

    這個(gè)部分的任務(wù)是采集手勢(shì)1-10,同時(shí)利用旋轉(zhuǎn)平移等操作對(duì)得到的手勢(shì)庫(kù)進(jìn)行擴(kuò)充。然后對(duì)整個(gè)手勢(shì)庫(kù)中的每張照片中的手勢(shì)輪廓線計(jì)算傅里葉描述子并保存。我采取的方案是每個(gè)手勢(shì)采集20份,然后擴(kuò)充為200份,這里的數(shù)目可以自己調(diào)節(jié)。保存格式為"x_i",表示手勢(shì)_x的第i張圖片

    4.1 數(shù)據(jù)增強(qiáng)

    在數(shù)據(jù)增強(qiáng)前,先采集手勢(shì),在項(xiàng)目文件夾中創(chuàng)建一個(gè)“image”文件夾保存樣本庫(kù),"test_image"保存測(cè)試庫(kù)(看需求,可以不用)

    創(chuàng)建data_augmention.py

    對(duì)測(cè)試庫(kù)進(jìn)行操作的時(shí)候僅僅需要修改一下path及相關(guān)的數(shù)字

  • import random
  • import cv2
  • path = './' + 'image' + '/'
  • #旋轉(zhuǎn)
  • def rotate(image, scale=0.9):
  • angle = random.randrange(-90, 90)#隨機(jī)角度
  • w = image.shape[1]
  • h = image.shape[0]
  • #rotate matrix
  • M = cv2.getRotationMatrix2D((w/2,h/2), angle, scale)
  • #rotate
  • image = cv2.warpAffine(image,M,(w,h))
  • return image
  • if __name__ == "__main__":
  • for i in range(5, 6):
  • cnt = 21#計(jì)數(shù)
  • for j in range(1, 21):
  • roi = cv2.imread(path + str(i) + '_' + str(j)+'.png')
  • for k in range(12):
  • img_rotation = rotate(roi)#旋轉(zhuǎn)
  • cv2.imwrite(path + str(i) + '_' + str(cnt)+ '.png',img_rotation)
  • cnt += 1
  • img_flip = cv2.flip(img_rotation,1)#翻轉(zhuǎn)
  • cv2.imwrite(path + str(i) + '_' + str(cnt)+ '.png',img_flip)
  • cnt += 1
  • print(i,'_',j,'完成')
  • 4.2 計(jì)算手勢(shì)庫(kù)的特征

    創(chuàng)建loadData.py文件

  • import fourierDescriptor as fd
  • import cv2
  • import numpy as np
  • path = './' + 'feature' + '/'
  • path_img = './' + 'image' + '/'
  • if __name__ == "__main__":
  • for i in range(1, 11):
  • for j in range(1, 201):
  • roi = cv2.imread(path_img + str(i) + '_' + str(j) + '.png')
  • descirptor_in_use = abs(fd.fourierDesciptor(roi))
  • fd_name = path + str(i) + '_' + str(j) + '.txt'
  • # fd_name = path + str(i) + '.txt'
  • with open(fd_name, 'w', encoding='utf-8') as f:
  • temp = descirptor_in_use[1]
  • for k in range(1, len(descirptor_in_use)):
  • x_record = int(100 * descirptor_in_use[k] / temp)
  • f.write(str(x_record))
  • f.write(' ')
  • f.write('\n')
  • print(i, '_', j, '完成')
  • 對(duì)手勢(shì)庫(kù)中10個(gè)手勢(shì)的200份圖片一一進(jìn)行操作,保存的特征的格式與圖片格式一致,用txt文件進(jìn)行保存

    模型訓(xùn)練

    這個(gè)部分的主要任務(wù)是利用已有的樣本庫(kù)訓(xùn)練SVM模型并保存

    這部分參考資料:

    https://cuijiahua.com/blog/2017/11/ml_8_svm_1.html(SVM的原理)

    https://cuijiahua.com/blog/2017/11/ml_9_svm_2.html(sklearn的使用)

    https://blog.csdn.net/qysh123/article/details/80063447(SVM調(diào)參)

    5 訓(xùn)練SVM模型

    使用網(wǎng)格搜索法進(jìn)行調(diào)參,利用joblib模塊保存模型

  • import numpy as np
  • from os import listdir
  • from sklearn.externals import joblib
  • from functools import reduce
  • from sklearn.svm import SVC
  • from sklearn.model_selection import GridSearchCV
  • import matplotlib.pyplot as plt
  • path = './' + 'feature' + '/'
  • model_path = "./model/"
  • test_path = "./test_feature/"
  • test_accuracy = []
  • #讀txt文件并將每個(gè)文件的描述子改為一維的矩陣存儲(chǔ)
  • def txtToVector(filename, N):
  • returnVec = np.zeros((1,N))
  • fr = open(filename)
  • lineStr = fr.readline()
  • lineStr = lineStr.split(' ')
  • for i in range(N):
  • returnVec[0, i] = int(lineStr[i])
  • return returnVec
  • def tran_SVM(N):
  • svc = SVC()
  • parameters = {'kernel':('linear', 'rbf'),
  • 'C':[1, 3, 5, 7, 9, 11, 13, 15, 17, 19],
  • 'gamma':[0.00001, 0.0001, 0.001, 0.1, 1, 10, 100, 1000]}#預(yù)設(shè)置一些參數(shù)值
  • hwLabels = []#存放類(lèi)別標(biāo)簽
  • trainingFileList = listdir(path)
  • m = len(trainingFileList)
  • trainingMat = np.zeros((m,N))
  • for i in range(m):
  • fileNameStr = trainingFileList[i]
  • classNumber = int(fileNameStr.split('_')[0])
  • hwLabels.append(classNumber)
  • trainingMat[i,:] = txtToVector(path+fileNameStr,N)#將訓(xùn)練集改為矩陣格式
  • print("數(shù)據(jù)加載完成")
  • clf = GridSearchCV(svc, parameters, cv=5, n_jobs=8)#網(wǎng)格搜索法,設(shè)置5-折交叉驗(yàn)證
  • clf.fit(trainingMat,hwLabels)
  • print(clf.return_train_score)
  • print(clf.best_params_)#打印出最好的結(jié)果
  • best_model = clf.best_estimator_
  • print("SVM Model save...")
  • save_path = model_path + "svm_efd_" + "train_model.m"
  • joblib.dump(best_model,save_path)#保存最好的模型
  • def test_SVM(clf,N):
  • testFileList = listdir(test_path)
  • errorCount = 0#記錄錯(cuò)誤個(gè)數(shù)
  • mTest = len(testFileList)
  • for i in range(mTest):
  • fileNameStr = testFileList[i]
  • classNum = int(fileNameStr.split('_')[0])
  • vectorTest = txtToVector(test_path+fileNameStr,N)
  • valTest = clf.predict(vectorTest)
  • #print("分類(lèi)返回結(jié)果為%d\t真實(shí)結(jié)果為%d" % (valTest, classNum))
  • if valTest != classNum:
  • errorCount += 1
  • print("總共錯(cuò)了%d個(gè)數(shù)據(jù)\n錯(cuò)誤率為%f%%" % (errorCount, errorCount/mTest * 100))
  • ####訓(xùn)練 + 驗(yàn)證#####
  • if __name__ == "__main__":
  • tran_SVM(31)
  • clf = joblib.load(model_path + "svm_efd_" + "train_model.m")
  • test_SVM(clf,31)
  • 訓(xùn)練結(jié)果如圖:

    界面設(shè)計(jì)

    字面意思,這部分的任務(wù)就是設(shè)計(jì)一個(gè)界面可以實(shí)時(shí)調(diào)用已經(jīng)訓(xùn)練好的模型預(yù)測(cè)手勢(shì)

    小聲嘀咕一句:原來(lái)Python有這么好用的寫(xiě)界面的庫(kù)呀(。-ω-)zzz

    這個(gè)部分也不是必須的,只不過(guò)為了稍微好看那么一點(diǎn),可以直接修改main.py,使得按p的時(shí)候就進(jìn)行預(yù)測(cè)

  • import classfier as cf
  • ......
  • elif key == ord('p'):
  • descirptor_in_use = abs(fourier_result)
  • fd_test = np.zeros((1,31))
  • temp = descirptor_in_use[1]
  • for k in range(1,len(descirptor_in_use)):
  • fd_test[0,k-1] = int(100 * descirptor_in_use[k] / temp)
  • test_svm = cf.test_fd(fd_test)
  • print("test_svm =",test_svm)
  • test_svm_efd = cf.test_efd(efd_test)
  • print("test_svm_efd =",test_svm_efd)
  • cv2.imshow('frame', frame) #播放攝像頭的內(nèi)容
  • 這部分參考資料:

    https://blog.csdn.net/niuyongjie/article/details/81161559(PyQt安裝,我安裝的是5.11.3版本)

    http://code.py40.com/pyqt5/16.html(PyQt教程)

    6 PyQt設(shè)計(jì)界面

    創(chuàng)建myGUI.py文件

    第一次用PyQt這個(gè)庫(kù),所以尺寸方面的控制用來(lái)最古老的方式(數(shù)字控制)還請(qǐng)大家見(jiàn)諒(╥╯^╰╥)

  • import sys
  • from PyQt5.QtWidgets import QApplication, QWidget, QToolTip, \
  • QPushButton,QMessageBox,QDesktopWidget, QLabel
  • from PyQt5.QtGui import QFont,QIcon,QPixmap,QImage
  • from PyQt5.QtCore import QTimer
  • import cv2
  • import picture as pic
  • import classify as cf
  • import numpy as np
  • class myWindow(QWidget):
  • def __init__(self,parent = None):
  • super(myWindow,self).__init__(parent)
  • self.timer_camera = QTimer()
  • self.cap = cv2.VideoCapture()
  • self.initUI()
  • self.slot_init()
  • def initUI(self):
  • self.mylabel()
  • self.myButton()
  • self.myLabelPic()
  • self.setFixedSize(670,520)
  • self.center()
  • self.setWindowIcon(QIcon('icon.jpg'))
  • self.setWindowTitle('gesture recognition')
  • def mylabel(self):
  • label_roi = QLabel('原圖',self)
  • label_roi.setStyleSheet("QLabel{font-size:18px;}")
  • label_roi.resize(60,30)
  • label_roi.move(120,15)
  • label_res = QLabel('輪廓線', self)
  • label_res.setStyleSheet("QLabel{font-size:18px;}")
  • label_res.resize(60, 30)
  • label_res.move(480, 15)
  • label_pre = QLabel('預(yù)測(cè)', self)
  • label_pre.setStyleSheet("QLabel{font-size:20px;}")
  • label_pre.resize(50,30)
  • label_pre.move(400,400)
  • label_result = QLabel('結(jié)果', self)
  • label_result.setStyleSheet("QLabel{font-size:20px;}")
  • label_result.resize(50, 30)
  • label_result.move(400,430)
  • def myLabelPic(self):
  • self.label_show_roi = QLabel(self)
  • self.label_show_roi.setFixedSize(301,301)
  • self.label_show_roi.move(20,50)
  • self.label_show_roi.setStyleSheet("QLabel{background:white;}")
  • self.label_show_roi.setAutoFillBackground(True)
  • self.label_show_ret = QLabel(self)
  • self.label_show_ret.setFixedSize(301, 301)
  • self.label_show_ret.move(350, 50)
  • self.label_show_ret.setStyleSheet("QLabel{background:white;}")
  • self.label_show_ret.setAutoFillBackground(True)
  • self.label_show_recognition = QLabel('0',self)
  • self.label_show_recognition.setStyleSheet("QLabel{background:white;}")
  • self.label_show_recognition.setStyleSheet("QLabel{font-size:50px;}")
  • self.label_show_recognition.setFixedSize(100,100)
  • self.label_show_recognition.move(500, 380)
  • self.label_show_recognition.setAutoFillBackground(True)
  • def myButton(self):
  • QToolTip.setFont(QFont('SansSerif', 10))
  • self.button_open_camera = QPushButton('打開(kāi)相機(jī)', self)
  • self.button_open_camera.setToolTip('按i,k,j,l可以進(jìn)行上下左右調(diào)整')
  • self.button_open_camera.resize(100,30)
  • self.button_open_camera.move(100, 400)
  • self.butoon_recognition = QPushButton('開(kāi)始預(yù)測(cè)', self)
  • self.butoon_recognition.setFixedSize(100, 30)
  • self.butoon_recognition.move(100, 450)
  • def slot_init(self):
  • self.button_open_camera.clicked.connect(self.button_open_camera_click)
  • self.butoon_recognition.clicked.connect(self.button_recognition_click)
  • self.timer_camera.timeout.connect(self.show_camera)
  • def button_open_camera_click(self):
  • if self.timer_camera.isActive() == False:
  • self.cap.open(0)
  • self.timer_camera.start(30)
  • self.button_open_camera.setText(u'關(guān)閉相機(jī)')
  • else:
  • self.timer_camera.stop()
  • self.cap.release()
  • self.label_show_roi.clear()
  • self.label_show_ret.clear()
  • self.label_show_recognition.setText('0')
  • self.button_open_camera.setText(u'打開(kāi)相機(jī)')
  • def button_recognition_click(self):
  • descirptor_in_use = abs(self.fourier_result)
  • fd_test = np.zeros((1, 31))
  • temp = descirptor_in_use[1]
  • for k in range(1, len(descirptor_in_use)):
  • fd_test[0, k - 1] = int(100 * descirptor_in_use[k] / temp)
  • efd_test = np.zeros((1, 15))
  • for k in range(1, len(self.efd_result)):
  • temp = np.sqrt(self.efd_result[k][0] ** 2 + self.efd_result[k][1] ** 2) + np.sqrt(
  • self.efd_result[k][2] ** 2 + self.efd_result[k][3] ** 2)
  • efd_test[0, k - 1] = (int(1000 * temp))
  • test_knn, test_svm = cf.test_fd(fd_test)
  • print("test_knn =", test_knn)
  • print("test_svm =", test_svm)
  • test_knn_efd, test_svm_efd = cf.test_efd(efd_test)
  • print("test_knn_efd =", test_knn_efd)
  • print("test_svm_efd =", test_svm_efd)
  • num = [0]*11
  • num[test_knn[0]] += 1
  • num[test_svm[0]] += 1
  • num[test_knn_efd[0]] += 1
  • num[test_svm_efd[0]] += 1
  • res = 0
  • for i in range(1, 11):
  • if num[i] >= 2:
  • res = i
  • break
  • print(res)
  • self.label_show_recognition.setText(str(res))
  • def show_camera(self):
  • width, height = 300, 300 # 設(shè)置拍攝窗口大小
  • x0, y0 = 300, 100 # 設(shè)置選取位置
  • flag, frame = self.cap.read()
  • roi, res, ret, self.fourier_result, self.efd_result = pic.binaryMask(frame, x0, y0, width, height)
  • roi = cv2.cvtColor(roi, cv2.COLOR_BGR2RGB)
  • show_roi = QImage(roi.data, roi.shape[1], roi.shape[0], QImage.Format_RGB888)
  • show_ret = QImage(ret.data, ret.shape[1], ret.shape[0], QImage.Format_Grayscale8)
  • self.label_show_roi.setPixmap(QPixmap.fromImage(show_roi))
  • self.label_show_ret.setPixmap(QPixmap.fromImage(show_ret))
  • def closeEvent(self, QCloseEvent):
  • reply = QMessageBox.question(self, 'Message',"Are you sure to quit?",
  • QMessageBox.Yes |QMessageBox.No, QMessageBox.No)
  • if reply == QMessageBox.Yes:
  • if self.cap.isOpened():
  • self.cap.release()
  • if self.timer_camera.isActive():
  • self.timer_camera.stop()
  • QCloseEvent.accept()
  • else:
  • QCloseEvent.ignore()
  • def center(self):
  • qr = self.frameGeometry()
  • cp = QDesktopWidget().availableGeometry().center()
  • qr.moveCenter(cp)
  • self.move(qr.topLeft())
  • if __name__ == "__main__":
  • app = QApplication(sys.argv)
  • win = myWindow()
  • win.show()
  • sys.exit(app.exec())
  • 后面幾個(gè)部分由于時(shí)間關(guān)系,講得不如前面的詳細(xì),向大家表示歉意,一些細(xì)節(jié)部分建議大家下載我的源碼來(lái)看。有問(wèn)題的地方歡迎討論~源碼中另外也用了橢圓傅里葉描述子作為特征。

    總結(jié)

    以上是生活随笔為你收集整理的基于傅里叶算子的手势识别的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。