來源:OpenCV學堂
作者:gloomyfish
前言
OpenCV DNN模塊支持的圖像語義分割網絡FCN是基于VGG16作為基礎網絡,運行速度很慢,無法做到實時語義分割。2016年提出的ENet實時語義分割網絡基于編碼與解碼的網絡語義分割方式,類似UNet網絡,通過構建自定義Block塊,在Cityscapes, CamVid, SUN數據集上實現了性能與實時雙提高。
ENet網絡結構
作者從ResNet網絡結構設計中收到啟發,定義兩個新的Block結構,如下:
其中a是初始Block,非重疊2x2最大池化,左側卷積步長為2,然后13個filters之連接合并,該結構注意是收到了Inception改進模型的啟發。B是ENet的bottleneck模塊,其中卷積可能是正常卷積、空洞卷積、反卷積,使用3x3或者5x5的filters,最終合并在一起是按空間位置相加。兩個1x1的卷積分別用來降低維度與擴展,使用BN/Dropout正則化,PReLU非線性激活。最終的ENet網絡模型結構如下:
其中stage2跟stage3結構相同,stage4跟stage5屬于解碼部分。
設計考量
常見的深度學習語義分割模型在下采樣操作上的兩個缺點:一是降低Feature Map的分辨率會導致圖像空間信息損失,特別是圖像邊緣信息,這個對語義分割精度有明顯影響;二是像素級別的語義分割網絡要求輸入跟輸出的分辨率保持一致,這個就要求強的下采樣跟強的上采樣必須對稱,這個增加了模型的計算與參數量。其中第一個問題在FCN與SegNet網絡中通過在編碼階段疊加Feature Map與在解碼階段通過稀疏上采樣來抑制,但是強的下采樣依然對整個語義分割精度有傷害,要在設計時候適當的加以限制。
但是下采樣同樣可以幫助獲得較大的感受野,區分不同的類別,作者發現空洞卷積在這個方面特別有幫助,ENet為了獲得實時性能,采用了早期下采樣策略來降低計算SegNet跟UNet都是對稱的網絡結構,ENet采用大的編碼網絡,小的解碼網絡實現的不對稱結構,編碼網絡實現分類任務,解碼網絡主要是優化細節,更好的輸出結果。
此外作者在設計過程中還考慮了非線性激活、空洞卷積、正則化方式的影響。
OpenCV DNN使用ENet道路分割
OpenCV DNN模塊從OpenCV4.0版本開始支持ENet網絡模型加載與解析,其中的道路分割模型可以從下面的地址下載:
https://github.com/e-lab/ENet-training
在OpenCV DNN使用該模型時轉換Blob輸入相關參數信息如下:
mean: [0, 0, 0]
scale: 0.00392
width: 512
height: 256
rgb: true
classes: "enet-classes.txt"
其中分類文件enet-classes.txt可以從OpenCV的sample/data/dnn中發現。輸出的數據格式為:Nx20xHxW,其中N=1表示每次輸入的一張圖像,20是基于Cityscapes數據集訓練的20個類別標簽,H跟W是輸入時圖像分辨率(512x256)。
最初版本代碼實現
該代碼實現是來自C++版本的翻譯,完整的演示代碼如下:
#loadCNNmodelbin_model="D:/projects/models/enet/model-best.net";net=cv.dnn.readNetFromTorch(bin_model)#readinputdataframe=cv.imread("D:/images/software.jpg");blob=cv.dnn.blobFromImage(frame,0.00392,(512,256),(0,0,0),True,False);cv.imshow("input",frame)#Runamodelnet.setInput(blob)score=net.forward()#Putefficiencyinformation.t,_=net.getPerfProfile()label='Inferencetime:%.2fms'%(t*1000.0/cv.getTickFrequency())print(score.shape)#generatecolortablecolor_lut=[]n,con,h,w=score.shapeforiinrange(con):b=np.random.randint(0,256)g=np.random.randint(0,256)r=np.random.randint(0,256)color_lut.append((b,g,r))maxCl=np.zeros((h,w),dtype=np.int32);maxVal=np.zeros((h,w),dtype=np.float32);#findmaxscorefor20channelsonpixel-wiseforiinrange(con):forrowinrange(h):forcolinrange(w):t=maxVal[row,col]s=score[0,i,row,col]ifs>t:maxVal[row,col]=smaxCl[row,col]=i#colorfulthesegmentationimagesegm=np.zeros((h,w,3),dtype=np.uint8)forrowinrange(h):forcolinrange(w):index=maxCl[row,col]segm[row,col]=color_lut[index]h,w=frame.shape[:2]segm=cv.resize(segm,(w,h),None,0,0,cv.INTER_NEAREST)print(segm.shape,frame.shape)frame=cv.addWeighted(frame,0.2,segm,0.8,0.0)cv.putText(frame,label,(0,15),cv.FONT_HERSHEY_SIMPLEX,0.5,(0,255,0))cv.imshow("ENet-Demo",frame)cv.imwrite("D:/result.png",frame)cv.waitKey(0)cv.destroyAllWindows()
總的來說比較啰嗦!
修改后代碼熟實現
上面是我在2019年3月份時候在OpenCV研習社 的代碼分享,當時主要是把C++代碼直接翻譯過來,并沒有太多考慮,今天又重新看了一下感覺自己寫了點垃圾代碼,所以重新整理了一下,把輸出解析的部分基于Numpy跟OpenCV-Python函數做了簡化,最終得到的代碼如下:
1#loadCNNmodel 2bin_model="D:/projects/models/enet/model-best.net"; 3net=cv.dnn.readNetFromTorch(bin_model) 4#readinputdata 5frame=cv.imread("D:/images/spacecity.png"); 6blob=cv.dnn.blobFromImage(frame,0.00392,(512,256),(0,0,0),True,False); 7cv.imshow("input",frame) 8h,w,c=frame.shape 910#Runamodel11net.setInput(blob)12score=net.forward()13#Putefficiencyinformation.14t,_=net.getPerfProfile()15label='Inferencetime:%.2fms'%(t*1000.0/cv.getTickFrequency())16score=np.squeeze(score)17score=score.transpose((1,2,0))18score=np.argmax(score,2)19mask=np.uint8(score)20mask=cv.cvtColor(mask,cv.COLOR_GRAY2BGR)21cv.normalize(mask,mask,0,255,cv.NORM_MINMAX)22cmask=cv.applyColorMap(mask,cv.COLORMAP_JET)23cmask=cv.resize(cmask,(w,h))24dst=cv.addWeighted(frame,0.7,cmask,0.3,0)25cv.putText(dst,label,(50,50),cv.FONT_HERSHEY_SIMPLEX,0.75,(0,0,255),2)26cv.imshow("dst",dst)27cv.waitKey(0)
總的執行時間也大大減少,主要去除了一些無謂的循環解析輸出數據部分。CPU上10+FPS 應該沒問題!實時get!
審核編輯 黃昊宇
-
cpu
+關注
關注
68文章
10878瀏覽量
212167 -
人工智能
+關注
關注
1792文章
47409瀏覽量
238923
發布評論請先 登錄
相關推薦
評論