计算机视觉研究院手把手教你深度学习的部署(手势识别,源码已开源)
計(jì)算機(jī)視覺研究院專欄
作者:Edison_G
今天我們繼續(xù)基于姿態(tài)估計(jì)的運(yùn)動(dòng)計(jì)數(shù)APP開發(fā)!
公眾號(hào)ID|ComputerVisionGzq
學(xué)習(xí)群|掃碼在主頁(yè)獲取加入方式
關(guān)注并星標(biāo)
從此不迷路
計(jì)算機(jī)視覺研究院
1
?背景
最近采購(gòu)了一塊新的樹莓派,迫不及待的想要在樹莓派上實(shí)現(xiàn)一個(gè)實(shí)時(shí)的手勢(shì)識(shí)別。從算法的角度講,并不是太難;但是從工程的角度來說,主要有兩個(gè)難點(diǎn),一是手勢(shì)數(shù)據(jù)的采集。大家都知道,深度學(xué)習(xí)的高精度離不開大量的訓(xùn)練數(shù)據(jù),網(wǎng)絡(luò)設(shè)計(jì)的再好,沒有足夠的數(shù)據(jù)是不行的。
因此要想實(shí)現(xiàn)一個(gè)好的手勢(shì)識(shí)別,采集數(shù)據(jù)就成了一個(gè)比較重要的難點(diǎn);另外一個(gè)難點(diǎn)是如何在樹莓派上實(shí)現(xiàn)實(shí)時(shí)的識(shí)別。樹莓派實(shí)際上是一個(gè)使用arm作為處理器的linux系統(tǒng),但是由于芯片的性能不是很強(qiáng),比我們使用的手機(jī)要弱很多,并且樹莓派目前對(duì)vulkan的支持并不好,無法使用vulkan加速,因此對(duì)網(wǎng)絡(luò)的優(yōu)化也是一個(gè)難點(diǎn)。要保證網(wǎng)絡(luò)優(yōu)化后的精度不能下降太多,但計(jì)算量必須要下降很多。?這次就從這兩個(gè)角度出發(fā),實(shí)現(xiàn)一套實(shí)時(shí)的手勢(shì)識(shí)別。
由于手勢(shì)的類型非常多,有識(shí)別數(shù)字的,識(shí)別字母的,識(shí)別動(dòng)作的,這里為了拋磚引玉,設(shè)計(jì)一個(gè)相對(duì)簡(jiǎn)單的識(shí)別"剪刀,石頭,布"的手勢(shì)識(shí)別系統(tǒng),后續(xù)可以用來制作一個(gè)剪刀石頭布的對(duì)戰(zhàn)機(jī)器人。想要實(shí)現(xiàn)其他類型的手勢(shì)識(shí)別,也完全可以按照這個(gè)流程來做。
2
?數(shù)據(jù)采集
對(duì)于數(shù)據(jù)采集,首先看看有沒有開源的手勢(shì)識(shí)別數(shù)據(jù)集。很遺憾,除了收費(fèi)的手勢(shì)識(shí)別數(shù)據(jù)集,基本上都是一些不太完整的手勢(shì)識(shí)別數(shù)據(jù)集。因此我們需要自己采集。工欲善其事必先利其器,自己采集就得有一些比較好的數(shù)據(jù)采集工具。這里我設(shè)計(jì)了一款數(shù)據(jù)采集工具(后臺(tái)回復(fù)“手勢(shì)識(shí)別”即獲取)。大家也可以根據(jù)自己的需要開發(fā)自己的數(shù)據(jù)采集工具。其實(shí)本質(zhì)上并不難,使用pyqt+opencv很容易就能開發(fā)一個(gè)順手的數(shù)據(jù)采集工具。由于基于python開發(fā),所以移植性非常好,既可以在windows下使用,也可以在linux,樹莓派上使用。我設(shè)計(jì)的這個(gè)界面非常簡(jiǎn)潔,如下圖所示:
opencv會(huì)調(diào)用camera開始預(yù)覽,然后設(shè)置一下保存路徑,保存標(biāo)簽,點(diǎn)擊保存圖片,就可以按照設(shè)置的保存間隔進(jìn)行采集數(shù)據(jù)。例如默認(rèn)的保存間隔為30,即30幀保存一張圖片,相當(dāng)于1秒鐘保存一張,如果想要頻率快一些,就將保存間隔設(shè)置的小一點(diǎn)。下面的視頻展示了數(shù)據(jù)采集工具的采集過程,為了展示效果,我把保存間隔設(shè)置為了60幀,大約2秒保存一張圖片。
我把剪刀的標(biāo)簽設(shè)置為0,石頭的標(biāo)簽設(shè)置為1,布的標(biāo)簽設(shè)置為2,最終通過該數(shù)據(jù)收集工具就收集到了三個(gè)文件夾:
接下來需要為訓(xùn)練數(shù)據(jù)創(chuàng)建標(biāo)簽文本。這里我將所有圖片的80%作為訓(xùn)練數(shù)據(jù)數(shù)據(jù)集,剩余的20%作為驗(yàn)證數(shù)據(jù)集。使用python腳本很容易實(shí)現(xiàn)自動(dòng)創(chuàng)建標(biāo)簽文件的腳本,代碼如下:
import os import randomMAX_LABEL=3 #類別的種類數(shù)目label_list=[] for label in range(0,MAX_LABEL+1):for?file?in?os.listdir(str(label)):label_list.append(str(label)+'/'?+?str(file)?+?'?'?+?str(label))????????#對(duì)列表進(jìn)行shuffle操作 random.shuffle(label_list)? count = len(label_list) # 80%作為訓(xùn)練數(shù)據(jù)集 train_count = int(count * 0.8) train_list = label_list[0:train_count] test_list = label_list[train_count:] print('total?count=%d?train_count=%d?test_count=%d'%(count,?train_count,?count-train_count))#?寫入train.txt標(biāo)簽文件 with open('train.txt', 'w') as f:for line in train_list:f.write(line + '')#?寫入test.txt標(biāo)簽文件 with open('test.txt', 'w') as f:for line in test_list:f.write(line + '')3
?網(wǎng)絡(luò)設(shè)計(jì)
完成了數(shù)據(jù)收集,那么就可以開始為手勢(shì)識(shí)別系統(tǒng)設(shè)計(jì)一個(gè)網(wǎng)絡(luò)了。由于需要在樹莓派這樣的低性能硬件上面運(yùn)行CNN,那么可以考慮從輕量級(jí)網(wǎng)絡(luò)中選擇一個(gè)來進(jìn)行優(yōu)化。例如google的mobilenet系列,efficient lite系列,曠世的shufflenet系列,華為的ghostnet等。那這些模型如何選擇呢?我之前有一篇關(guān)于這些輕量級(jí)的模型的評(píng)測(cè),有興趣的可以去看看,《輕量網(wǎng)絡(luò)親測(cè) | 專家從7個(gè)維度全面評(píng)測(cè)輕量級(jí)網(wǎng)絡(luò)》,通過之前的評(píng)測(cè),我發(fā)現(xiàn)shufflenetv2在精度和推理延時(shí)上面有一個(gè)很好的平衡,因此我選擇了shufflenetv2作為手勢(shì)識(shí)別系統(tǒng)的基礎(chǔ)網(wǎng)絡(luò)。直接使用shufflenetv2雖然能夠在樹莓派上較為流暢的運(yùn)行,但是還達(dá)不到實(shí)時(shí)的效果,因此需要對(duì)shufflentv2進(jìn)行一些優(yōu)化,主要是為了降低計(jì)算量,并且能夠盡量保持精度。降低計(jì)算量可以從如下幾個(gè)方面考慮:
降低shufflenet的通道系數(shù)
shufflenetv1/v2在設(shè)計(jì)之初,本身就考慮了應(yīng)用在不同的資源設(shè)備上,因此設(shè)置了一個(gè)通道系數(shù),直接調(diào)整該通道系數(shù),就可以獲得更小計(jì)算量的模型。然而通過實(shí)際測(cè)試,直接將通道系數(shù)從1.0x降低為0.5x,在降低計(jì)算量的同時(shí),也會(huì)對(duì)精度損失較大。因此不采用該方案。
降低輸入分辨率
shufflenet的原始輸入分辨率為224*224,如果將分辨率降低x,那么計(jì)算量將降低x^2,因此收益很大。但是通過測(cè)試發(fā)現(xiàn),直接將分辨率降低,對(duì)精度的影響也會(huì)很大。所以也不采用降低分辨率的方案。
裁剪shufflenetv2不重要的1*1卷積
通過觀察shufflenet的block,可以分為兩種結(jié)構(gòu),一種是每個(gè)stage的第一個(gè)block,該block由于需要降采樣,升維度,所以對(duì)輸入直接復(fù)制成兩份,經(jīng)過branch1,和branch2之后再concat到一起,通道翻倍,如下圖中的降采樣block所示。另外一種普通的block將輸入split成兩部分,一部分經(jīng)過branch2的卷積提取特征后直接與branch1的部分進(jìn)行concat。如下圖中的普通block所示:
一般在DW卷積(depthwise卷積)的前或后使用1*1的卷積處于兩種目的,一種是融合通道間的信息,彌補(bǔ)dw卷積對(duì)通道間信息融合功能的缺失。另一種是為了降維升維,例如mobilenet v2中的inverted reddual模塊。而shufflenet中的block,在branch2中用了2個(gè)1*1卷積,實(shí)際上有一些多余,因?yàn)榇颂幉恍枰M(jìn)行升維降維的需求,那么只是為了融合dw卷積的通道間信息。實(shí)際上有一個(gè)1*1卷積就夠了。因此將上述紅色虛線框中的1*1卷積核刪除。經(jīng)過測(cè)試,精度幾乎不降低,計(jì)算量卻下降了30%。因此裁剪1*1的卷積核將是一個(gè)不錯(cuò)的方法。
加入CSP模塊
csp在大型網(wǎng)絡(luò)上取得了很大的成功。它在每個(gè)stage,將輸入split成兩部分,一部分經(jīng)過原來的路徑,另一部分直接shortcut到stage的尾部,然后concat到一起。這既降低了計(jì)算量,又豐富了梯度信息,減少了梯度的重用,是一個(gè)非常不錯(cuò)的trip。在yolov4,yolov5的目標(biāo)檢測(cè)中,也引入了csp機(jī)制,使用了csp_darknet。此處將csp引入到shufflenet中。并且對(duì)csp做了一定的精簡(jiǎn),最終使用csp stage精簡(jiǎn)版本作為最終的網(wǎng)絡(luò)結(jié)構(gòu)。
經(jīng)過測(cè)試,網(wǎng)絡(luò)雖然能大幅降低計(jì)算量,但是精度降低的也很明顯。分析原因,主要有兩個(gè),一是shufflenetv2本身已經(jīng)使用了在輸入通道split,然后concat的blcok流程,與csp其實(shí)是一樣的,只是csp是基于一個(gè)stage,shufflenetv2是基于一個(gè)block,另外csp本來就是在densenet這種密集連接的網(wǎng)絡(luò)上使用有比較好的效果,在輕量級(jí)網(wǎng)絡(luò)上不見得效果會(huì)好。
因此最終將網(wǎng)絡(luò)設(shè)計(jì)為基于shufflenetv2 1.0x,并精簡(jiǎn)了多余的1*1卷積的版本,命名為:shufflenetv2_liteconv版本。
4
?網(wǎng)絡(luò)訓(xùn)練
收集好了數(shù)據(jù),并且也設(shè)計(jì)好了網(wǎng)絡(luò),那么接下來就是訓(xùn)練了。基于pytroch,大家可以很方便的編寫出一個(gè)簡(jiǎn)單的訓(xùn)練流程。這里我選擇從0開始訓(xùn)練,沒有使用shufflenet v2 1.0x的預(yù)訓(xùn)練模型,因?yàn)槲覀儗?duì)shufflenet做了優(yōu)化,刪除了很多1*1的conv,直接使用預(yù)訓(xùn)練模型會(huì)不匹配,因此從0開始訓(xùn)練。學(xué)習(xí)率可以適當(dāng)?shù)姆糯笠恍?#xff0c;epoch數(shù)目可以適當(dāng)大一些。我把我的訓(xùn)練超參貼出來,大家可以參考使用:
訓(xùn)練epoch:60
初始學(xué)習(xí)率:0.01
學(xué)習(xí)率策略:multistep(35,40)
優(yōu)化器:moment sgd
weight decay:0.0001
最終在訓(xùn)練完50個(gè)epoch之后,loss大約為0.1,測(cè)試集上面的精度為0.98。
5
?網(wǎng)絡(luò)部署
網(wǎng)絡(luò)部署可以采用很多開源的推理庫(kù)。例如mnn,ncnn,tnn等。這里我選擇使用ncnn,因?yàn)閚cnn開源的早,使用的人多,網(wǎng)絡(luò)支持,硬件支持都還不錯(cuò),關(guān)鍵是很多問題都能搜索到別人的經(jīng)驗(yàn),可以少走很多彎路。但是遺憾的是ncnn并不支持直接將pytorch模型導(dǎo)入,需要先轉(zhuǎn)換成onnx格式,然后再將onnx格式導(dǎo)入到ncnn中。另外注意一點(diǎn),將pytroch的模型到onnx之后有許多膠水op,這在ncnn中是不支持的,需要使用另外一個(gè)開源工具:onnx-simplifier對(duì)onnx模型進(jìn)行剪裁,然后再導(dǎo)入到ncnn中。因此整個(gè)過程還有些許繁瑣,為了簡(jiǎn)單,我編寫了從"pytorch模型->onnx模型->onnx模型精簡(jiǎn)->ncnn模型"的轉(zhuǎn)換腳本,方便大家一鍵轉(zhuǎn)換,減少中間過程出錯(cuò)。我把主要流程的代碼貼出來(詳細(xì)的代碼請(qǐng)關(guān)注公眾號(hào)"DL工程實(shí)踐",后臺(tái)回復(fù)“手勢(shì)識(shí)別”四個(gè)字,可獲取)
# 1、pytroch模型導(dǎo)出到onnx模型 torch.onnx.export(net,input,onnx_file,verbose=DETAIL_LOG)# 2、調(diào)用onnx-simplifier工具對(duì)onnx模型進(jìn)行精簡(jiǎn) cmd = 'python -m onnxsim ' + str(onnx_file) + ' ' + str(onnx_sim_file) ret = os.system(str(cmd))# 3、調(diào)用ncnn的onnx2ncnn工具,將onnx模型準(zhǔn)換為ncnn模型 cmd = onnx2ncnn_path + ' ' + str(new_onnx_file) + ' ' + str(ncnn_param_file) + ' ' + str(ncnn_bin_file) ret = os.system(str(cmd))# 4、對(duì)ncnn模型加密(可選步驟)cmd = ncnn2mem_path + ' ' + str(ncnn_param_file) + ' ' + str(ncnn_bin_file) + ' ' + str(ncnn_id_file) + ' ' + str(ncnn_mem_file) ret = os.system(str(cmd))導(dǎo)出到ncnn模型之后,就可以在ncnn模型上運(yùn)行訓(xùn)練好的手勢(shì)識(shí)別庫(kù)。ncnn是基于C++開發(fā)的,因此編寫上層應(yīng)用的時(shí)候使用C++是效率最高的。我為了簡(jiǎn)單,使用python來調(diào)用ncnn的C++庫(kù)也是可以的,不過會(huì)損失一丟丟的性能,但這是值得的,人生苦短,我用python。下面這個(gè)視頻是最終部署好的手勢(shì)識(shí)別程序。
6
?總結(jié)
本次實(shí)踐完成了基于樹莓派的實(shí)時(shí)手勢(shì)識(shí)別,算法上并不復(fù)雜,主要是工程實(shí)踐上的一些問題,例如數(shù)據(jù)的采集,網(wǎng)絡(luò)的優(yōu)化,以及后期的推理轉(zhuǎn)換等。實(shí)際上還有一些工作可以優(yōu)化,例如對(duì)模型的量化,對(duì)數(shù)據(jù)的增強(qiáng)。通過模型量化,可以進(jìn)一步提升運(yùn)算效率,通過數(shù)據(jù)增強(qiáng)可以彌補(bǔ)我們自己采集的數(shù)據(jù)分布單一,過擬合的風(fēng)險(xiǎn),這些問題就留給讀者朋友們自己去思考了。
??THE END?
轉(zhuǎn)載請(qǐng)聯(lián)系本公眾號(hào)獲得授權(quán)
計(jì)算機(jī)視覺研究院學(xué)習(xí)群等你加入!
計(jì)算機(jī)視覺研究院主要涉及深度學(xué)習(xí)領(lǐng)域,主要致力于人臉檢測(cè)、人臉識(shí)別,多目標(biāo)檢測(cè)、目標(biāo)跟蹤、圖像分割等研究方向。研究院接下來會(huì)不斷分享最新的論文算法新框架,我們這次改革不同點(diǎn)就是,我們要著重”研究“。之后我們會(huì)針對(duì)相應(yīng)領(lǐng)域分享實(shí)踐過程,讓大家真正體會(huì)擺脫理論的真實(shí)場(chǎng)景,培養(yǎng)愛動(dòng)手編程愛動(dòng)腦思考的習(xí)慣!
掃碼關(guān)注
計(jì)算機(jī)視覺研究院
公眾號(hào)ID|ComputerVisionGzq
學(xué)習(xí)群|掃碼在主頁(yè)獲取加入方式
總結(jié)
以上是生活随笔為你收集整理的计算机视觉研究院手把手教你深度学习的部署(手势识别,源码已开源)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 9个著名的管理法则
- 下一篇: 西安财经大学“荣耀杯”迷你马拉松——微博