直播回放 https://www2./v2/render/playback?mode=playback&token=846e0ba66b954f43834086dec24ae492 注:文中的ppt是作者在Embedded Linux Conference 2018上的演講“Deep Learning in OpenCV”的ppt 大家好,,我是吳至文,,目前就職于英特爾開源技術(shù)中心,主要從事圖形,、圖像深度學(xué)習(xí)算法方面的開發(fā)和優(yōu)化工作,。很高興有機(jī)會和大家分享一下關(guān)于OpenCV深度學(xué)習(xí)模塊的內(nèi)容,同時(shí),,也會介紹一下我們團(tuán)隊(duì)在OpenCV深度學(xué)習(xí)方面所做的一些工作,。 本次分享的主要內(nèi)容包含以下幾個(gè)方面: 首先,我會介紹一下OpenCV和深度學(xué)習(xí)的背景知識,;然后,,介紹今天的主題——OpenCV深度學(xué)習(xí)模塊;接下來,,會簡單介紹我們團(tuán)隊(duì)在OpenCL加速方面所做的工作,,以及開發(fā)的一個(gè)Vulkan后端;最后,會以一個(gè)例子的形式來展示如何使用DNN模塊開發(fā)深度神經(jīng)網(wǎng)絡(luò)的應(yīng)用,。 一,, OpenCV背景介紹 首先,什么是OpenCV呢,?我相信做過圖形圖像,、計(jì)算機(jī)視覺應(yīng)用開發(fā)的同學(xué)可能對OpenCV都不會陌生。OpenCV是一個(gè)包含了2500多個(gè)經(jīng)過優(yōu)化的計(jì)算機(jī)視覺和機(jī)器學(xué)習(xí)算法的開源計(jì)算機(jī)視覺庫,。換句話說,,目前主流的、比較知名的計(jì)算機(jī)視覺算法和論文在OpenVC里都能找到相應(yīng)的實(shí)現(xiàn),。OpenCV不僅僅是一個(gè)很好用的開發(fā)工具集,,它同時(shí)對有志于學(xué)習(xí)計(jì)算機(jī)視覺開發(fā)的學(xué)生也是一個(gè)寶庫。OpenVC支持C,、C++和Python語言,但是從OpenCV 4.0開始,,C語言的API就逐漸被清除出去了,,現(xiàn)在比較常用的API是C++和Python語言的。此外,,OpenCV也是一個(gè)很活躍的開源項(xiàng)目,,到目前為止它在Github上有兩萬多個(gè)Forks。 2018年11月份,,OpenCV發(fā)布了4.0的版本,。在這個(gè)版本有了比較大的變化,大概有以下這幾點(diǎn):首先,,它使用了C++11標(biāo)準(zhǔn)編譯器,,并且移除了大多數(shù)的C 語言的API接口;另外,,它不再對之前的版本有二進(jìn)制的兼容,,同時(shí)它使用了大量AVX2的指令集優(yōu)化,從而大大提高了一些算法在CPU上的運(yùn)行效率,;再者就是,,它具有更小的內(nèi)存占用以及支持OpenVINO作為DNN模塊的后端。OpenVINO對于有的同學(xué)可能比較陌生,,它是英特爾發(fā)布的一個(gè)針對深度學(xué)習(xí)視覺應(yīng)用的SDK,。OpenVINO支持各種設(shè)備上的加速,包括CPU,、GPU和VPU上面的加速,,我們在后面還會提及這個(gè)內(nèi)容。 二, 深度神經(jīng)網(wǎng)絡(luò)的關(guān)鍵概念 接下來,,我將介紹一些深度神經(jīng)網(wǎng)絡(luò)的關(guān)鍵概念,。 深度神經(jīng)網(wǎng)絡(luò)最基本的組成單元是神經(jīng)元,我們在文獻(xiàn)中一般稱作Node,、 Neuron或Perceptron,。一個(gè)神經(jīng)元會對多個(gè)輸入進(jìn)行加權(quán)和的運(yùn)算,然后經(jīng)過一個(gè)激活函數(shù),,最后輸出一個(gè)響應(yīng)結(jié)果,。多個(gè)神經(jīng)元就組成了網(wǎng)絡(luò)的層,我們將神經(jīng)網(wǎng)絡(luò)的第一層稱為輸入層,,一般用來加載輸入數(shù)據(jù),,如一幅圖像。我們將神經(jīng)網(wǎng)絡(luò)的最后一層稱為輸出層,,根據(jù)具體網(wǎng)絡(luò)結(jié)構(gòu)的不同,,輸出層的含義也會不同。以分類網(wǎng)絡(luò)為例,,輸出層的每個(gè)節(jié)點(diǎn)表示屬于某個(gè)類別的概率大小,。我們將在輸入層和輸出層之間的層稱為隱層,所謂的深度神經(jīng)網(wǎng)絡(luò)就是隱層數(shù)大于1的神經(jīng)網(wǎng)絡(luò),。 接下來是網(wǎng)絡(luò)訓(xùn)練,。我們可以把神經(jīng)網(wǎng)絡(luò)看成一個(gè)復(fù)雜的函數(shù),在這個(gè)函數(shù)里有許多參數(shù)是未知的,,因此我們需要通過訓(xùn)練來確定這些參數(shù),。為了方便理解,我把訓(xùn)練大體分為四個(gè)步驟:第一步,,選定訓(xùn)練參數(shù),,如學(xué)習(xí)比例、批次大小,、損失函數(shù)類型,,初始化網(wǎng)絡(luò)權(quán)重;第二步,、設(shè)置輸入數(shù)據(jù),,然后進(jìn)行前向的網(wǎng)絡(luò)運(yùn)算;第三步,、比較運(yùn)算結(jié)果和真實(shí)結(jié)果的差異,;第四步、進(jìn)行反向傳播運(yùn)算,,然后修改網(wǎng)絡(luò)參數(shù),,再回到第二步直到差異足夠小,,或者人為終止訓(xùn)練過程。雖然整個(gè)訓(xùn)練過程看起來比較復(fù)雜,,但是深度學(xué)習(xí)框架會幫我們把這些事完成的,,深度學(xué)習(xí)框架有Tensorflow、Caffe和Torch等,。因此,,我們只需要設(shè)計(jì)好網(wǎng)絡(luò)結(jié)構(gòu)、選定訓(xùn)練參數(shù),,剩下的事就可以交給框架去做,。 在通過足夠的訓(xùn)練之后,我們就可以確定所有的網(wǎng)絡(luò)參數(shù),,那么這個(gè)復(fù)雜的函數(shù)就可以確定了,。然后,我們輸入數(shù)據(jù)來通過深度學(xué)習(xí)庫計(jì)算函數(shù)結(jié)果的過程就叫推理,。與訓(xùn)練相比,,推理過程簡單的多。上圖羅列了幾個(gè)使用了深度神經(jīng)網(wǎng)絡(luò)的計(jì)算機(jī)視覺應(yīng)用場景,,如人臉識別,、對象語義分割以及目標(biāo)檢測的應(yīng)用。 三,, OpenCV深度學(xué)習(xí)模塊 從OpenCV 3.3版本開始,OpenCV加入了深度學(xué)習(xí)模塊,,但這個(gè)模塊它只提供推理功能,,而不涉及訓(xùn)練,與此同時(shí)它支持多種深度學(xué)習(xí)框架,,比如Tensorflow,,Caffe,Torch和Darknet,。 聽到這里,,可能有的同學(xué)會問:“既然我們已經(jīng)有了Tensorflow、Caffe,、Torch這些深度學(xué)習(xí)框架,,為什么還要在OpenCV中再實(shí)現(xiàn)一個(gè)呢?這是不是在重復(fù)造輪子呢,?”其實(shí)不是的,,有下面幾個(gè)理由:第一、輕量,,由于DNN模塊只實(shí)現(xiàn)了推理功能,,它的代碼量,、編譯運(yùn)行開銷與其他深度學(xué)習(xí)框架比起來會少很多;第二,、方便使用,,DNN模塊提供了內(nèi)建的CPU和GPU加速且無須依賴第三方庫,如果在之前項(xiàng)目使用了OpenCV,,那么通過DNN模塊可以很方便的無縫的為原項(xiàng)目添加神經(jīng)網(wǎng)絡(luò)推理能力,;第三、通用性,,DNN模塊支持多種網(wǎng)絡(luò)模型格式,,因此用戶無須額外進(jìn)行網(wǎng)絡(luò)模型的轉(zhuǎn)換就可以直接使用,同時(shí)它還支持多種運(yùn)算設(shè)備和操作系統(tǒng),,比如CPU,、GPU、VPU等,,操作系統(tǒng)包括Linux,、Windows、安卓和MacOS,。 目前,,OpenCV的DNN模塊支持40多種層的類型,基本涵蓋了常見的網(wǎng)絡(luò)運(yùn)算需求,,而且新的類型也在不斷的加入當(dāng)中,。 如上圖所示,這里列出的網(wǎng)絡(luò)架構(gòu)都是經(jīng)過了很好的測試,。它們在OpenCV中能很好支持的,,基本涵蓋了常用的對象檢測和語義分割的類別,我們可以直接拿來使用,。 接下來給大家介紹DNN模塊的架構(gòu),。如上圖所示,從而往下,,第一層是語言綁定,,它支持Python和Java,其中Python用的比較多,,因?yàn)殚_發(fā)起來會比較方便,。此外,在第一層中還包括準(zhǔn)確度測試,、性能測試以及一些示例程序,。第二層是C++的API層,這屬于是原生的API,,它的功能包括加載網(wǎng)絡(luò)模型,、推理運(yùn)算以及獲取網(wǎng)絡(luò)輸出,。第三層是實(shí)現(xiàn)層,它包括模型轉(zhuǎn)換器,、DNN引擎,、層實(shí)現(xiàn)等。模型轉(zhuǎn)換器負(fù)責(zé)將各種網(wǎng)絡(luò)模型格式轉(zhuǎn)換成DNN模塊內(nèi)部的表示,,DNN引擎負(fù)責(zé)內(nèi)部網(wǎng)絡(luò)的組織和優(yōu)化,,層實(shí)現(xiàn)是各種層運(yùn)算的具體實(shí)現(xiàn)過程。第四層是加速層,,它包括CPU加速,、GPU加速、Halide加速和新加入的Intel推理引擎加速,。前三個(gè)均是DNN模塊的內(nèi)建實(shí)現(xiàn),,無須外部依賴就直接可以使用。CPU加速用到了SSE和AVX指令以及大量的多線程元語,,而OpenCL加速是針對GPU進(jìn)行并行運(yùn)算的加速,,這也是我們團(tuán)隊(duì)工作的主要內(nèi)容。Halide是一個(gè)實(shí)驗(yàn)性的實(shí)現(xiàn),,并且性能一般,,因此不建議使用。Intel推理引擎加速需要安裝OpenVINO庫,,它可以實(shí)現(xiàn)在CPU,、GPU和VPU上的加速,在GPU上內(nèi)部會調(diào)用clDNN庫來做GPU上的加速,,在CPU上內(nèi)部會調(diào)用MKL-DNN來做CPU加速,,而Movidius主要是在VPU上使用的專用庫來進(jìn)行加速。 DNN模塊采用Backend和Target來管理各種加速方法,。Backend分為三種類型:第一種是OpenCV Backend,這是OpenCV默認(rèn)的Backend,;第二種是Halide Backend,,第三種是推理引擎Backend。Target指的是最終的運(yùn)算設(shè)備,,它包括四種類型,,分別是CPU設(shè)備、OpenCL設(shè)備,、OpenCL_FP16設(shè)備以及MYRIAD設(shè)備,。強(qiáng)調(diào)一下,OpenCL和OpenCL_FP16實(shí)際上都是GPU設(shè)備,,OpenCL_FP16設(shè)備指的是權(quán)重值的數(shù)據(jù)格式為16位浮點(diǎn)數(shù),,OpenCL設(shè)備指的是權(quán)重值的數(shù)據(jù)格式為32位浮點(diǎn)數(shù),。MYRIAD設(shè)備是Movidius公司提供的VPU設(shè)備。我們通過Backend和Target的不同組合可以來決定具體的加速方法,。舉個(gè)例子,,如果你有Movidius的運(yùn)算棒,則可以通過SetPreferobleBackend API將Backend設(shè)置成Inference-NEGINE,,通過SetPreferobleTarget API將Target設(shè)置成MYRIAD,,然后你的網(wǎng)絡(luò)運(yùn)算將會在MYRIAD設(shè)備上進(jìn)行,而不再用任何的CPU資源,。 除了上述的加速后端外,,DNN模塊還做了一些網(wǎng)絡(luò)層面的優(yōu)化。由于在內(nèi)部使用了統(tǒng)一的網(wǎng)絡(luò)表示,,網(wǎng)絡(luò)層級的優(yōu)化對DNN支持的所有格式的網(wǎng)絡(luò)模型都有好處,。下面介紹兩種網(wǎng)絡(luò)層級的優(yōu)化方法: 一)層融合 第一種優(yōu)化方法是層融合的優(yōu)化。它是通過對網(wǎng)絡(luò)結(jié)構(gòu)的分析,,把多個(gè)層合并到一起,,從而降低網(wǎng)絡(luò)復(fù)雜度和減少運(yùn)算量。下面舉幾個(gè)具體的例子: 如上圖所示,,在本例中黃色方框代表的是最終被融合掉的網(wǎng)絡(luò)層,,在這種情況下,卷積層后面的BatchNorm層,、Scale層和RelU層都被合并到了卷積層當(dāng)中,。這樣一來,四個(gè)層運(yùn)算最終變成了一個(gè)層運(yùn)算,,這種結(jié)構(gòu)多出現(xiàn)在ResNet50的網(wǎng)絡(luò)架構(gòu)當(dāng)中,。 如上圖所示,在本例中,,網(wǎng)絡(luò)結(jié)構(gòu)將卷積層1和Eltwise Layer和RelU Layer合并成一個(gè)卷積層,,將卷積層2作為第一個(gè)卷積層新增的一個(gè)輸入。這樣一來,,原先的四個(gè)網(wǎng)絡(luò)層變成了兩個(gè)網(wǎng)絡(luò)層運(yùn)算,,這種結(jié)構(gòu)也多出現(xiàn)于ResNet50的網(wǎng)絡(luò)架構(gòu)當(dāng)中。 如上圖所示,,在本例中,,這種網(wǎng)絡(luò)結(jié)構(gòu)是把三個(gè)層的輸出通過連接層連接之后輸入到后續(xù)層,這種情況可以把中間的連接層直接去掉,,將三個(gè)網(wǎng)絡(luò)層輸出直接接到第四層的輸入上面,,這種網(wǎng)絡(luò)結(jié)構(gòu)多出現(xiàn)SSD類型的網(wǎng)絡(luò)架構(gòu)當(dāng)中。 二)內(nèi)存復(fù)用 第二種優(yōu)化方式是內(nèi)存復(fù)用的優(yōu)化,。深度神經(jīng)網(wǎng)絡(luò)運(yùn)算過程當(dāng)中會占用非常大量的內(nèi)存資源,,一部分是用來存儲權(quán)重值,,另一部分是用來存儲中間層的運(yùn)算結(jié)果。我們考慮到網(wǎng)絡(luò)運(yùn)算是一層一層按順序進(jìn)行的,,因此后面的層可以復(fù)用前面的層分配的內(nèi)存,。 上圖是一個(gè)沒有經(jīng)過優(yōu)化的內(nèi)存重用的運(yùn)行時(shí)的存儲結(jié)構(gòu),紅色塊代表的是分配出來的內(nèi)存,,綠色塊代表的是一個(gè)引用內(nèi)存,,藍(lán)色箭頭代表的是引用方向。數(shù)據(jù)流是自下而上流動的,,層的計(jì)算順序也是自下而上進(jìn)行運(yùn)算,。每一層都會分配自己的輸出內(nèi)存,這個(gè)輸出被后續(xù)層引用為輸入,。對內(nèi)存復(fù)用也有兩種方法: 第一種內(nèi)存復(fù)用的方法是輸入內(nèi)存復(fù)用,。如上圖所示,如果我們的層運(yùn)算是一個(gè)in-place模式,,那么我們無須為輸出分配內(nèi)存,,直接把輸出結(jié)果寫到輸入的內(nèi)存當(dāng)中即可。in-place模式指的是運(yùn)算結(jié)果可以直接寫回到輸入而不影響其他位置的運(yùn)算,,如每個(gè)像素點(diǎn)做一次Scale的運(yùn)算,。類似于in-place模式的情況,就可以使用輸入內(nèi)存復(fù)用的方式,。 第二種內(nèi)存復(fù)用的方法是后續(xù)層復(fù)用前面層的輸出,。如上圖所示,在這個(gè)例子中,,Layer3在運(yùn)算時(shí),,Layer1和Layer2已經(jīng)完成了運(yùn)算。此時(shí),,Layer1的輸出內(nèi)存已經(jīng)空閑下來,,因此,Layer3不需要再分配自己的內(nèi)存,,直接引用Layer1的輸出內(nèi)存即可,。由于深度神經(jīng)網(wǎng)絡(luò)的層數(shù)可以非常多,這種復(fù)用情景會大量的出現(xiàn),,使用這種復(fù)用方式之后,網(wǎng)絡(luò)運(yùn)算的內(nèi)存占用量會下降30%~70%,。 接下來,,我會為大家介紹一下我們團(tuán)隊(duì)在深度學(xué)習(xí)模塊中做的一些工作。 四,, OpenCL加速 OpenCL的加速是一個(gè)內(nèi)建的加速實(shí)現(xiàn),,它是可以直接使用而不依賴與外部加速庫的,,只需安裝有OpenCL的運(yùn)行時(shí)環(huán)境即可。此外,,它還支持32位浮點(diǎn)數(shù)據(jù)格式和16位浮點(diǎn)數(shù)據(jù)格式,。如果我們想要使用OpenCL加速,只需要把Backend設(shè)置成OpenCV,,把Target設(shè)置成OpenCL或者OpenCL_FP16即可,。 在OpenCL的加速方案中,我們提供了一組經(jīng)過高度優(yōu)化的卷積運(yùn)算和auto-tuning方案,,來為特定的GPU和卷積運(yùn)算找到最佳的卷積核,。簡單地說,auto-tuning方案針對每個(gè)卷積任務(wù),,會選擇不同的子塊大小進(jìn)行運(yùn)算,,然后選出用時(shí)最短的子塊大小來作為卷積和的配置。DNN模塊中內(nèi)置了一些已經(jīng)設(shè)好的卷積和配置,,用戶也可以為自己的網(wǎng)絡(luò)和GPU重新運(yùn)行一次auto-tuning,,從而找到最佳的卷積核。如果想要設(shè)置auto-tuning,,則需要設(shè)置環(huán)境變量OpenCV_OCL4DNN_CONFIG_PATH,,讓它指向一個(gè)可寫的目錄。這樣一來,,DNN模塊就會把最佳的卷積核配置存儲在這個(gè)目錄下,。注意,如果打開了auto-tuing,,那么第一次運(yùn)行某個(gè)網(wǎng)絡(luò)模型的時(shí)間就會比較長,。 對于OpenCL的驅(qū)動,我們建議使用Neo,。Neo是開源Intel GPU的OpenCL驅(qū)動,,它支持Gen 8以及Gen 8之后的英特爾GPU。我們建議盡量使用最新的版本,,根據(jù)我們的調(diào)試經(jīng)驗(yàn),,越新的版本性能越好。 最后,,上圖是一個(gè)CPU和GPU加速的對比圖,,其中一列是OpenCL的加速,其中另一列是C++的加速,。CPU是i7-6770,、8核、2.6G,GPU是Iris Pro Graphics 580的,,這種CPU和GPU都算是比較強(qiáng)勁的配置,。 我們可以看到,OpenCL加速之后的運(yùn)算時(shí)間比CPU會短很多,,但也不是所有的情況都是這樣的,。對于不同的CPU,這個(gè)數(shù)據(jù)有所不同,,大家可以通過上面的網(wǎng)站鏈接查看到在其他CPU配置下的CPU和GPU運(yùn)算時(shí)間的對比,。 五, Vulkan后端 Vulkan后端是由我開發(fā)的一個(gè)基于Vulkan Computer Shade的 DNN加速方案,,目前已經(jīng)合并到OpenCV的主分支,,OpenCV 4.0里就包含有Vulkan backend,感興趣的同學(xué)可以通過上圖的鏈接了解一下技術(shù)細(xì)節(jié),。 如果要使用Vulkan backend,,將backend類型設(shè)置成VKCOM,將target設(shè)置成Vulkan即可,。Vulkan后端可以讓DNN模塊在更多的平臺上使用到GPU的加速,。例如,安卓系統(tǒng)中是不支持OpenCL的,,但是它支持Vulkan,,這種情況就可以通過Vulkan backend來加速。 六,, 應(yīng)用實(shí)例 最后一部分,,這是一個(gè)通過DNN開發(fā)的用于對象檢測的端到端的應(yīng)用,下面我會分部分來詳細(xì)講解這些代碼段,。 在這里使用的是Python的接口,,采用Python語言來開發(fā),模型使用的是MobileNetSSD模型,。首先,,引入OpenCV的Python包,代碼第2行,、第4行,、第5行則是指定MobileNetSSD的模型以及它的Graph描述文件。然后,,設(shè)置輸入Image的大小為300*300,,置信度閾值設(shè)置為0.5,第9行的均值是用來做圖像域處理的一個(gè)數(shù)值,。第10行是可分類的類別,,說明我們的MobileNETSSD是一個(gè)可以對20個(gè)類別進(jìn)行分類的模型,,我們也可以有97或者1000個(gè)類別的模型,但是那樣的模型會比較大,。第16行則是打開一個(gè)Camera設(shè)備采集圖像。 從第19行到第26行就是所有的DNN相關(guān)的代碼段,,可以看到使用起來是非常簡單的,。第19行是加載網(wǎng)絡(luò)模型,并返回一個(gè)網(wǎng)絡(luò)對象,。從第20行開始進(jìn)入一個(gè)while循環(huán),,逐幀處理攝像頭讀入的數(shù)據(jù)。第22行是讀入的數(shù)據(jù),,第23行是對這個(gè)讀入的Image做Resize,,讓它符合網(wǎng)絡(luò)模型對輸入數(shù)據(jù)的大小要求。第24行是調(diào)用DNN模塊的BlobFromImage API對輸入的Image做預(yù)處理,,這里主要是對輸入數(shù)據(jù)做規(guī)則化處理,,即先減均值,再乘以一個(gè)Scale,。這些都是MobileNETSSD網(wǎng)絡(luò)在訓(xùn)練中引入的均值和Scale,,在推理中也需要把它用作輸入Image的預(yù)處理,我們將處理好的數(shù)據(jù)稱為blob,。在第25行把這個(gè)blob設(shè)置為網(wǎng)絡(luò)的輸入,,第26行來調(diào)用網(wǎng)絡(luò)的Forward做推理預(yù)算,然后得到最終的輸出結(jié)果Detections,,Detections記錄了在這一幀圖像中檢測出來的所有對象,,并且每個(gè)對象會以一個(gè)Vector的形式來描述。 接下來,,在這個(gè)循環(huán)中對每一個(gè)對象進(jìn)行可視化處理,,也就是把檢測出來的對象描繪在原圖像上。在第47行是取出對象的置信值與之前設(shè)置的閾值進(jìn)行比較,,如果超過了閾值,,我們就判定它是一個(gè)可信的對象,將其繪制到原圖上面,。接下來的代碼段就是繪制對象的代碼段以及繪制對象類別的代碼段,,最后是將繪制好對象方框的原圖顯示出來,隨后整個(gè)程序結(jié)束,。在OpenCV的代碼庫當(dāng)中有許多基于DNN的示例程序,,包括C++、Python,,大家感興趣則可以在上面的鏈接中去看一下,。 |
|