色婷婷AⅤ一区二区三区|亚洲精品第一国产综合亚AV|久久精品官方网视频|日本28视频香蕉

          "); //-->

          博客專欄

          EEPW首頁 > 博客 > 計算機視覺研究院手把手教你深度學習的部署

          計算機視覺研究院手把手教你深度學習的部署

          發(fā)布人:CV研究院 時間:2021-09-16 來源:工程師 發(fā)布文章

          以下文章來源于DL工程實踐 ,作者DDX

          背景

          最近采購了一塊新的樹莓派,迫不及待的想要在樹莓派上實現(xiàn)一個實時的手勢識別。從算法的角度講,并不是太難;但是從工程的角度來說,主要有兩個難點,一是手勢數(shù)據(jù)的采集。大家都知道,深度學習的高精度離不開大量的訓練數(shù)據(jù),網絡設計的再好,沒有足夠的數(shù)據(jù)是不行的。

          因此要想實現(xiàn)一個好的手勢識別,采集數(shù)據(jù)就成了一個比較重要的難點;另外一個難點是如何在樹莓派上實現(xiàn)實時的識別。樹莓派實際上是一個使用arm作為處理器的linux系統(tǒng),但是由于芯片的性能不是很強,比我們使用的手機要弱很多,并且樹莓派目前對vulkan的支持并不好,無法使用vulkan加速,因此對網絡的優(yōu)化也是一個難點。要保證網絡優(yōu)化后的精度不能下降太多,但計算量必須要下降很多。 這次就從這兩個角度出發(fā),實現(xiàn)一套實時的手勢識別。

          由于手勢的類型非常多,有識別數(shù)字的,識別字母的,識別動作的,這里為了拋磚引玉,設計一個相對簡單的識別"剪刀,石頭,布"的手勢識別系統(tǒng),后續(xù)可以用來制作一個剪刀石頭布的對戰(zhàn)機器人。想要實現(xiàn)其他類型的手勢識別,也完全可以按照這個流程來做。


           數(shù)據(jù)采集

          對于數(shù)據(jù)采集,首先看看有沒有開源的手勢識別數(shù)據(jù)集。很遺憾,除了收費的手勢識別數(shù)據(jù)集,基本上都是一些不太完整的手勢識別數(shù)據(jù)集。因此我們需要自己采集。工欲善其事必先利其器,自己采集就得有一些比較好的數(shù)據(jù)采集工具。這里我設計了一款數(shù)據(jù)采集工具(后臺回復“手勢識別”即獲?。?。大家也可以根據(jù)自己的需要開發(fā)自己的數(shù)據(jù)采集工具。其實本質上并不難,使用pyqt+opencv很容易就能開發(fā)一個順手的數(shù)據(jù)采集工具。由于基于python開發(fā),所以移植性非常好,既可以在windows下使用,也可以在linux,樹莓派上使用。我設計的這個界面非常簡潔,如下圖所示:

          1.jpg

          opencv會調用camera開始預覽,然后設置一下保存路徑,保存標簽,點擊保存圖片,就可以按照設置的保存間隔進行采集數(shù)據(jù)。例如默認的保存間隔為30,即30幀保存一張圖片,相當于1秒鐘保存一張,如果想要頻率快一些,就將保存間隔設置的小一點。下面的視頻展示了數(shù)據(jù)采集工具的采集過程,為了展示效果,我把保存間隔設置為了60幀,大約2秒保存一張圖片。

          我把剪刀的標簽設置為0,石頭的標簽設置為1,布的標簽設置為2,最終通過該數(shù)據(jù)收集工具就收集到了三個文件夾:

          2.jpg

          接下來需要為訓練數(shù)據(jù)創(chuàng)建標簽文本。這里我將所有圖片的80%作為訓練數(shù)據(jù)數(shù)據(jù)集,剩余的20%作為驗證數(shù)據(jù)集。使用python腳本很容易實現(xiàn)自動創(chuàng)建標簽文件的腳本,代碼如下:

          import os
          import random
          MAX_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))        
          #對列表進行shuffle操作
          random.shuffle(label_list) 
          count = len(label_list)
          # 80%作為訓練數(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標簽文件
          with open('train.txt', 'w') as f:
              for line in train_list:
                  f.write(line + '')
          # 寫入test.txt標簽文件
          with open('test.txt', 'w') as f:
              for line in test_list:
                  f.write(line + '')


           網絡設計

          完成了數(shù)據(jù)收集,那么就可以開始為手勢識別系統(tǒng)設計一個網絡了。由于需要在樹莓派這樣的低性能硬件上面運行CNN,那么可以考慮從輕量級網絡中選擇一個來進行優(yōu)化。例如google的mobilenet系列,efficient lite系列,曠世的shufflenet系列,華為的ghostnet等。那這些模型如何選擇呢?我之前有一篇關于這些輕量級的模型的評測,有興趣的可以去看看,《輕量網絡親測 | 專家從7個維度全面評測輕量級網絡》,通過之前的評測,我發(fā)現(xiàn)shufflenetv2在精度和推理延時上面有一個很好的平衡,因此我選擇了shufflenetv2作為手勢識別系統(tǒng)的基礎網絡。直接使用shufflenetv2雖然能夠在樹莓派上較為流暢的運行,但是還達不到實時的效果,因此需要對shufflentv2進行一些優(yōu)化,主要是為了降低計算量,并且能夠盡量保持精度。降低計算量可以從如下幾個方面考慮:

          降低shufflenet的通道系數(shù)

          shufflenetv1/v2在設計之初,本身就考慮了應用在不同的資源設備上,因此設置了一個通道系數(shù),直接調整該通道系數(shù),就可以獲得更小計算量的模型。然而通過實際測試,直接將通道系數(shù)從1.0x降低為0.5x,在降低計算量的同時,也會對精度損失較大。因此不采用該方案。

          降低輸入分辨率

          shufflenet的原始輸入分辨率為224*224,如果將分辨率降低x,那么計算量將降低x^2,因此收益很大。但是通過測試發(fā)現(xiàn),直接將分辨率降低,對精度的影響也會很大。所以也不采用降低分辨率的方案。

          裁剪shufflenetv2不重要的1*1卷積

          通過觀察shufflenet的block,可以分為兩種結構,一種是每個stage的第一個block,該block由于需要降采樣,升維度,所以對輸入直接復制成兩份,經過branch1,和branch2之后再concat到一起,通道翻倍,如下圖中的降采樣block所示。另外一種普通的block將輸入split成兩部分,一部分經過branch2的卷積提取特征后直接與branch1的部分進行concat。如下圖中的普通block所示:

          3.png

          一般在DW卷積(depthwise卷積)的前或后使用1*1的卷積處于兩種目的,一種是融合通道間的信息,彌補dw卷積對通道間信息融合功能的缺失。另一種是為了降維升維,例如mobilenet v2中的inverted reddual模塊。而shufflenet中的block,在branch2中用了2個1*1卷積,實際上有一些多余,因為此處不需要進行升維降維的需求,那么只是為了融合dw卷積的通道間信息。實際上有一個1*1卷積就夠了。因此將上述紅色虛線框中的1*1卷積核刪除。經過測試,精度幾乎不降低,計算量卻下降了30%。因此裁剪1*1的卷積核將是一個不錯的方法。

          加入CSP模塊

          csp在大型網絡上取得了很大的成功。它在每個stage,將輸入split成兩部分,一部分經過原來的路徑,另一部分直接shortcut到stage的尾部,然后concat到一起。這既降低了計算量,又豐富了梯度信息,減少了梯度的重用,是一個非常不錯的trip。在yolov4,yolov5的目標檢測中,也引入了csp機制,使用了csp_darknet。此處將csp引入到shufflenet中。并且對csp做了一定的精簡,最終使用csp stage精簡版本作為最終的網絡結構。

          4.png

          經過測試,網絡雖然能大幅降低計算量,但是精度降低的也很明顯。分析原因,主要有兩個,一是shufflenetv2本身已經使用了在輸入通道split,然后concat的blcok流程,與csp其實是一樣的,只是csp是基于一個stage,shufflenetv2是基于一個block,另外csp本來就是在densenet這種密集連接的網絡上使用有比較好的效果,在輕量級網絡上不見得效果會好。

          因此最終將網絡設計為基于shufflenetv2 1.0x,并精簡了多余的1*1卷積的版本,命名為:shufflenetv2_liteconv版本。


           網絡訓練

          收集好了數(shù)據(jù),并且也設計好了網絡,那么接下來就是訓練了?;趐ytroch,大家可以很方便的編寫出一個簡單的訓練流程。這里我選擇從0開始訓練,沒有使用shufflenet v2 1.0x的預訓練模型,因為我們對shufflenet做了優(yōu)化,刪除了很多1*1的conv,直接使用預訓練模型會不匹配,因此從0開始訓練。學習率可以適當?shù)姆糯笠恍?,epoch數(shù)目可以適當大一些。我把我的訓練超參貼出來,大家可以參考使用:

          訓練epoch:60

          初始學習率:0.01

          學習率策略:multistep(35,40)

          優(yōu)化器:moment sgd

          weight decay:0.0001

          最終在訓練完50個epoch之后,loss大約為0.1,測試集上面的精度為0.98。


           網絡部署

          網絡部署可以采用很多開源的推理庫。例如mnn,ncnn,tnn等。這里我選擇使用ncnn,因為ncnn開源的早,使用的人多,網絡支持,硬件支持都還不錯,關鍵是很多問題都能搜索到別人的經驗,可以少走很多彎路。但是遺憾的是ncnn并不支持直接將pytorch模型導入,需要先轉換成onnx格式,然后再將onnx格式導入到ncnn中。另外注意一點,將pytroch的模型到onnx之后有許多膠水op,這在ncnn中是不支持的,需要使用另外一個開源工具:onnx-simplifier對onnx模型進行剪裁,然后再導入到ncnn中。因此整個過程還有些許繁瑣,為了簡單,我編寫了從"pytorch模型->onnx模型->onnx模型精簡->ncnn模型"的轉換腳本,方便大家一鍵轉換,減少中間過程出錯。我把主要流程的代碼貼出來(詳細的代碼請關注公眾號"DL工程實踐",后臺回復“手勢識別”四個字,可獲?。?/p>

          # 1、pytroch模型導出到onnx模型
          torch.onnx.export(net,input,onnx_file,verbose=DETAIL_LOG)
          # 2、調用onnx-simplifier工具對onnx模型進行精簡
          cmd = 'python -m onnxsim ' + str(onnx_file) + ' ' + str(onnx_sim_file)
          ret = os.system(str(cmd))
          # 3、調用ncnn的onnx2ncnn工具,將onnx模型準換為ncnn模型
          cmd = onnx2ncnn_path + ' ' + str(new_onnx_file) + ' ' + str(ncnn_param_file) + ' ' + str(ncnn_bin_file)
          ret = os.system(str(cmd))
          # 4、對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))

          導出到ncnn模型之后,就可以在ncnn模型上運行訓練好的手勢識別庫。ncnn是基于C++開發(fā)的,因此編寫上層應用的時候使用C++是效率最高的。我為了簡單,使用python來調用ncnn的C++庫也是可以的,不過會損失一丟丟的性能,但這是值得的,人生苦短,我用python。下面這個視頻是最終部署好的手勢識別程序。


           總結

          本次實踐完成了基于樹莓派的實時手勢識別,算法上并不復雜,主要是工程實踐上的一些問題,例如數(shù)據(jù)的采集,網絡的優(yōu)化,以及后期的推理轉換等。實際上還有一些工作可以優(yōu)化,例如對模型的量化,對數(shù)據(jù)的增強。通過模型量化,可以進一步提升運算效率,通過數(shù)據(jù)增強可以彌補我們自己采集的數(shù)據(jù)分布單一,過擬合的風險,這些問題就留給讀者朋友們自己去思考了。

          *博客內容為網友個人發(fā)布,僅代表博主個人觀點,如有侵權請聯(lián)系工作人員刪除。



          關鍵詞: AI

          相關推薦

          技術專區(qū)

          關閉