超详细!使用OpenCV深度学习模块在图像分类下的应用实践
專注計(jì)算機(jī)視覺前沿資訊和技術(shù)干貨
微信公眾號(hào):極市平臺(tái)
官網(wǎng):https://www.cvmart.net/
極市導(dǎo)讀:本文來自6月份出版的新書《OpenCV深度學(xué)習(xí)應(yīng)用與性能優(yōu)化實(shí)踐》,由Intel與阿里巴巴高級(jí)圖形圖像專家聯(lián)合撰寫,系統(tǒng)地介紹了OpenCV DNN 推理模塊原理和實(shí)踐。
深度學(xué)習(xí)理論的廣泛研究促進(jìn)了其在不同場(chǎng)景的應(yīng)用。在計(jì)算機(jī)視覺領(lǐng)域,圖像分類、目標(biāo)檢測(cè)、語義分割和視覺風(fēng)格變換等基礎(chǔ)任務(wù)的性能也因?yàn)椴捎昧松疃葘W(xué)習(xí)的方法而有了飛躍性的提升。本章將為讀者梳理深度學(xué)習(xí)方法在這些基本應(yīng)用場(chǎng)景的應(yīng)用情況,并結(jié)合OpenCV深度學(xué)習(xí)模塊的示例程序,從源代碼和實(shí)際運(yùn)行兩個(gè)層面進(jìn)行講解。下文對(duì)書中圖像分類部分內(nèi)容進(jìn)行摘錄:
圖像分類是計(jì)算機(jī)視覺領(lǐng)域的基礎(chǔ)任務(wù)之一,在各種基于視覺的人工智能應(yīng)用中,圖像分類都扮演著重要的角色。例如,在智能機(jī)器人應(yīng)用中,我們需要對(duì)所采集的視頻中的每一幀進(jìn)行主要物體的檢測(cè)和分類,并以此作為進(jìn)一步?jīng)Q策的基礎(chǔ)。
近些年,圖像分類與深度學(xué)習(xí)的飛速發(fā)展有著密不可分的關(guān)系。在2012年的ILSVRC (ImageNet Large Scale Visual Recognition Competition,ImageNet大規(guī)模視覺識(shí)別挑戰(zhàn)賽)大賽上,AlexNet橫空出世,以壓倒性優(yōu)勢(shì)戰(zhàn)勝了傳統(tǒng)圖像分類算法而奪得冠軍,開啟了計(jì)算機(jī)視覺領(lǐng)域的深度學(xué)習(xí)革命。2015年,ResNet首次在圖像分類準(zhǔn)確度上戰(zhàn)勝人類。2017年,隨著SENet的奪冠,最后一屆ILSVRC大賽落下帷幕。下面為大家梳理一下歷屆ILSVRC大賽中出現(xiàn)的經(jīng)典網(wǎng)絡(luò)結(jié)構(gòu)。
圖像分類經(jīng)典網(wǎng)絡(luò)結(jié)構(gòu)
自2012年ILSVRC大賽AlexNet奪冠以來,直至2017年最后一屆SENet奪冠,所有冠軍都被各種深度神經(jīng)網(wǎng)絡(luò)所摘得。歷屆ILSVRC大賽的經(jīng)典網(wǎng)絡(luò)結(jié)構(gòu)及其特點(diǎn)如表9-1所示。
這些網(wǎng)絡(luò)結(jié)構(gòu)不僅可應(yīng)用在圖像分類中,而且可作為其他計(jì)算機(jī)視覺任務(wù)(如目標(biāo)檢測(cè)、語義分割和視覺風(fēng)格變換)的骨干(backbone)網(wǎng)絡(luò),用來提取圖像特征。因此,它們對(duì)整個(gè)計(jì)算機(jī)視覺技術(shù)的發(fā)展有著深遠(yuǎn)的影響。
下面我們摘錄OpenCV官方Wiki上的DNN模塊運(yùn)行效率統(tǒng)計(jì)表,看一下AlexNet、GoogLeNet和ResNet-50在OpenCV DNN模塊中的運(yùn)行效率。
測(cè)試系統(tǒng)軟硬件配置如下:
各軟件組件的版本信息如表9-2所示。
CPU實(shí)現(xiàn)的運(yùn)行時(shí)間如表9-3所示,該時(shí)間取的是50次運(yùn)行的中位數(shù)時(shí)間,中位數(shù)時(shí)間可以排除多次推理運(yùn)算中某些過于異常的值對(duì)平均值的干擾。另外,所有網(wǎng)絡(luò)模型都采用32位浮點(diǎn)數(shù)據(jù)格式進(jìn)行計(jì)算。在神經(jīng)網(wǎng)絡(luò)的推理計(jì)算中,可以采用量化方法把32位浮點(diǎn)精度的模型參數(shù)降低到16位浮點(diǎn)精度以節(jié)省數(shù)據(jù)讀取帶寬提高運(yùn)算效率,但是并不是所有算法都支持針對(duì)16位浮點(diǎn)精度的實(shí)現(xiàn),為了便于比較,測(cè)試都采用32位浮點(diǎn)精度。
GPU實(shí)現(xiàn)的運(yùn)行時(shí)間如表9-4所示。
從上面的數(shù)據(jù)可以看到,DNN模塊的OpenCL實(shí)現(xiàn)跟原生C++實(shí)現(xiàn)性能相近,而Intel-Caffe MKLDNN的加速性能最好,原因是多方面的。首先MKLDNN是針對(duì)Intel CPU進(jìn)行高度優(yōu)化的神經(jīng)網(wǎng)絡(luò)計(jì)算庫(kù),能夠充分發(fā)揮Intel CPU的性能。其次,該測(cè)試使用的CPU硬件性能比較強(qiáng)勁(8核心,4.0GHz運(yùn)行頻率),而集成的GPU是中低配置。最后,測(cè)試的3種網(wǎng)絡(luò)模型的運(yùn)算量不算太大,未能充分發(fā)揮GPU的并發(fā)特性。
接下來,我們以GoogLeNet(Inception-v1)為例,詳細(xì)講解其網(wǎng)絡(luò)結(jié)構(gòu)和設(shè)計(jì)原理,然后結(jié)合OpenCV中的圖像分類示例程序講解GoogLeNet模型的實(shí)際使用。
GoogLeNet
GoogLeNet自2014年提出以來,總共演進(jìn)了4個(gè)版本,由于第1版是后續(xù)幾個(gè)版本的基礎(chǔ),本節(jié)主要介紹2014年的第1版,即GoogLeNet v1。
GoogLeNet v1是2014年ILSVRC大賽的冠軍模型,它延續(xù)了自LeNet以來的典型卷積網(wǎng)絡(luò)結(jié)構(gòu),即多個(gè)卷積層前后堆疊,然后通過全連接層輸出最終的特征值。GoogLeNet的結(jié)構(gòu)如圖9-1所示。
下面對(duì)圖9-1中各列進(jìn)行解釋。type列表示層或者模塊的類型,其中inception代表一個(gè)Inception模塊,GoogLeNet中總共堆疊了9個(gè)Inception模塊,convolution表示卷積層,max pool表示最大池化層,avg pool表示平均池化層,dropout代表隨機(jī)裁剪操作,linear是全連接層,softmax表示最后對(duì)輸出特征值進(jìn)行sotfmax操作。patch size列表示卷積核大小,stride表示卷積運(yùn)算的步進(jìn)值。output size列表示輸出特征圖的長(zhǎng)、寬和通道數(shù)。Depth列表示該層或者模塊重復(fù)連接的次數(shù)。#1×1,#3×3,#5×5列分別表示Inception模塊中的1×1,3×3,5×5卷積核大小的卷積分支的輸出通道數(shù)。pool proj列表示池化投影的輸出通道數(shù)。#3×3 reduce和#5×5 reduce列表示Inception結(jié)構(gòu)中3×3和5×5卷積核卷積之前的1×1卷積的輸出通道數(shù)。params列表示參數(shù)數(shù)目。ops列表示運(yùn)算量。Inception模塊是GoogLeNet的最大創(chuàng)新點(diǎn),它的初衷是增加卷積核尺寸種類的同時(shí)降低訓(xùn)練參數(shù)數(shù)量,下面對(duì)Inception v1模塊進(jìn)行講解,它的結(jié)構(gòu)如圖9-1所示。
Inception模塊使用1×1卷積對(duì)前層數(shù)據(jù)進(jìn)行降維處理并分成多路,然后用3×3,5×5卷積對(duì)降維后的分支進(jìn)行卷積運(yùn)算,同時(shí)將各個(gè)卷積結(jié)果和3×3最大池化的結(jié)果按通道進(jìn)行連接。這種創(chuàng)新的結(jié)構(gòu)使得網(wǎng)絡(luò)參數(shù)大大降低的同時(shí)保留了很好的特征表達(dá)能力,達(dá)到了深度和參數(shù)數(shù)量的雙贏。
為什么使用多種尺寸的卷積核有助于提高特征表達(dá)能力呢?我們以圖9-3為例,最左邊的狗占據(jù)了圖的大部分,中間的狗占了圖的一部分,而最右邊的狗占了圖的很小一部分。采用多種尺寸的卷積核可以學(xué)習(xí)到不同尺度的特征,使網(wǎng)絡(luò)具有更好的特征適應(yīng)性。
接下來,我們結(jié)合DNN模塊圖像分類示例程序看一下圖像分類應(yīng)用的具體實(shí)現(xiàn)。
圖像分類程序源碼分析
我們借助OpenCV的示例程序來介紹圖像分類應(yīng)用的主要步驟。OpenCV DNN模塊示例程序囊括了各種不同應(yīng)用場(chǎng)景,它們有著相似的代碼結(jié)構(gòu)和流程,如圖9-4所示。各種示例應(yīng)用源代碼的區(qū)別主要體現(xiàn)在最后一步:推理結(jié)果的解析和可視化。本節(jié)將詳細(xì)講解代碼的每個(gè)步驟,之后各節(jié)的源碼分析將重點(diǎn)聚焦于應(yīng)用特定的參數(shù)及推理結(jié)果的解析和可視化。
下面分析圖像分類示例程序源碼。
首先引入必要的頭文件,參見代碼清單9-1。其中,fstream和sstream是C++標(biāo)準(zhǔn)庫(kù)頭文件,用于文件讀取和文本處理。dnn.hpp、imgproc.hpp、highgui.hpp提供OpenCV API聲明,common.hpp提供了一些DNN示例程序通用的函數(shù),例如,查找輸入文件位置,從模型配置文件中讀取默認(rèn)的運(yùn)行時(shí)參數(shù)等。
9-1 引入必要的頭文件
#include <fstream> #include <sstream> #include <opencv2/dnn.hpp> #include <opencv2/imgproc.hpp> #include <opencv2/highgui.hpp> #include "common.hpp"代碼清單9-2定義了命令行參數(shù),下面逐一講解。
9-2 命令行參數(shù)定義
std::string keys="{ help h | | Print help message. }""{ @alias | | An alias name of model to extract preprocessing parameters from models.yml file. }""{ zoo | models.yml | An optional path to file with preprocessing parameters }""{ input i | | Path to input image or video file. Skip this argument to capture frames from a camera.}""{ framework f | | Optional name of an origin framework of the model. Detect it automatically if it does not set. }""{ classes | Optional path to a text file with names of classes. }""{ backend | 0 | Choose one of computation backends: ""0: automatically (by default), ""1: Halide language (http://halide-lang.org/), ""2: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), ""3: OpenCV implementation }""{ target | 0 | Choose one of target computation devices: ""0: CPU target (by default), ""1: OpenCL, ""2: OpenCL fp16 (half-float precision), ""3: VPU }";接下來引用命名空間,參見代碼清單9-3。我們的代碼用到了cv和dnn命名空間中的API,通過顯式聲明命名空間,方便后續(xù)的API調(diào)用。
9-3 聲明命名空間及定義全局變量
using namespace cv; using namespace dnn;接下來定義用于存放類別名稱的變量classes:
std::vector<std::string> classes;下面進(jìn)入主函數(shù)。
首先,解析命令行參數(shù),參見代碼清單9-4。
9-4 主函數(shù)(解析命令行參數(shù))
int main(int argc, char** argv) {CommandLineParser parser(argc, argv, keys);const std::string modelName=parser.get<String>("@alias");const std::string zooFile=parser.get<String>("zoo");keys +=genPreprocArguments(modelName, zooFile);parser=CommandLineParser(argc, argv, keys);parser.about("Use this script to run classification deep learning networks using OpenCV.");if (argc==1 || parser.has("help")){parser.printMessage();return 0;}float scale=parser.get<float>("scale");Scalar mean=parser.get<Scalar>("mean");bool swapRB=parser.get<bool>("rgb");int inpWidth=parser.get<int>("width");int inpHeight=parser.get<int>("height");String model=findFile(parser.get<String>("model"));String config=findFile(parser.get<String>("config"));String framework=parser.get<String>("framework");int backendId=parser.get<int>("backend");int targetId=parser.get<int>("target");如果命令行參數(shù)提供了類別文件路徑,則解析類別文件并將類別名稱存儲(chǔ)到全局變量classes,參見代碼清單9-5。
9-5 主函數(shù)(類別文件解析)
if (parser.has("classes")) {std::string file=parser.get<String>("classes");std::ifstream ifs(file.c_str());if (!ifs.is_open())CV_Error(Error::StsError, "File " + file + " not found");std::string line;while (std::getline(ifs, line)){classes.push_back(line);} }接下來進(jìn)行異常情況檢查,包括命令行參數(shù)異常,以及缺失模型文件異常,參加代碼清單9-6。
9-6 主函數(shù)(異常情況檢查)
if (!parser.check()){parser.printErrors();return 1;}CV_Assert(!model.empty());加載網(wǎng)絡(luò)模型,創(chuàng)建DNN模塊網(wǎng)絡(luò)對(duì)象,并設(shè)置加速后端和目標(biāo)運(yùn)算設(shè)備,參加代碼清單9-7。
9-7 主函數(shù)(初始化網(wǎng)絡(luò)并創(chuàng)建顯示窗口)
Net net=readNet(model, config, framework); net.setPreferableBackend(backendId); net.setPreferableTarget(targetId);接下來,創(chuàng)建用于顯示結(jié)果的窗口對(duì)象。代碼如下:
static const std::string kWinName="Deep learning image classification in OpenCV"; namedWindow(kWinName, WINDOW_NORMAL);然后,創(chuàng)建圖像輸入對(duì)象cap,用于讀取指定的圖片、視頻文件,參見代碼清單9-8。如果沒有指定圖片或視頻文件,則從攝像頭讀取視頻幀。
9-8 主函數(shù)(創(chuàng)建圖像輸入對(duì)象)
VideoCapture cap; if (parser.has("input")) cap.open(parser.get<String>("input")); elsecap.open(0);接下來進(jìn)入圖像處理循環(huán),循環(huán)起始部分通過cap對(duì)象讀取一幀圖像,參見代碼清單9-9。
9-9 圖像處理循環(huán)(讀取一幀圖像)
Mat frame, blob; while (waitKey(1) < 0) {cap >> frame;if (frame.empty()){waitKey();break;}然后調(diào)用blobFromImage()函數(shù)將讀入的圖像轉(zhuǎn)換成網(wǎng)絡(luò)模型的輸入(blob),并設(shè)置網(wǎng)絡(luò)對(duì)象,參見代碼清單9-10。blobFromImage()函數(shù)會(huì)對(duì)圖像進(jìn)行一系列的預(yù)處理,包括調(diào)整大小、減均值、交換紅藍(lán)顏色通道等,最終返回一個(gè)一維數(shù)組(N、C、H、W)。其中,N代表批大小,實(shí)時(shí)應(yīng)用中通常為1,即一次處理一幀圖像數(shù)據(jù);C代表圖像通道數(shù),一般為3,即R、G、B三種顏色;H、W分別代表圖像的高度和寬度。
9-10 圖像處理循環(huán)(設(shè)置網(wǎng)絡(luò)輸入)
blobFromImage(frame, blob, scale, Size(inpWidth, inpHeight), mean, swapRB, false); net.setInput(blob);接下來運(yùn)行網(wǎng)絡(luò)模型推理,代碼如下:
Mat prob=net.forward();網(wǎng)絡(luò)推理的輸出數(shù)據(jù)對(duì)象prob包含1000個(gè)概率值,分別對(duì)應(yīng)1000個(gè)圖像類別。
至此,網(wǎng)絡(luò)推理運(yùn)算部分結(jié)束,接下來進(jìn)行推理結(jié)果的解析和可視化,參見代碼清單9-11和代碼清單9-12。
9-11 圖像處理循環(huán)(解析網(wǎng)絡(luò)推理輸出)
Point classIdPoint; double confidence; // 找到概率值最大的類別id,該類別為圖像所屬分類 minMaxLoc(prob.reshape(1, 1), 0, &confidence, 0, &classIdPoint); int classId=classIdPoint.x;9-12 圖像處理循環(huán)(可視化推理結(jié)果)
// 獲取網(wǎng)絡(luò)推理運(yùn)算耗時(shí),并疊加到原始圖像上 std::vector<double> layersTimes; double freq=getTickFrequency() / 1000; double t=net.getPerfProfile(layersTimes) / freq; std::string label=format("Inference time: %.2f ms", t); putText(frame, label, Point(0, 15), FONT_HERSHEY_SIMPLEX,0.5, Scalar(0, 255, 0)); // 將圖像類別標(biāo)簽和概率值疊加到原始圖像上 label=format("%s: %.4f", (classes.empty() ?format("Class #%d", classId).c_str() :classes[classId].c_str()),confidence); putText(frame, label, Point(0, 40), FONT_HERSHEY_SIMPLEX,0.5, Scalar(0, 255, 0)); // 顯示圖像 imshow(kWinName, frame);}// 循環(huán)結(jié)束,退出主函數(shù)return 0; }以上內(nèi)容摘自**《OpenCV深度學(xué)習(xí)應(yīng)用與性能優(yōu)化實(shí)踐》**一書,經(jīng)出版方授權(quán)發(fā)布。
關(guān)注極市平臺(tái)公眾號(hào)(ID:extrememart),獲取計(jì)算機(jī)視覺前沿資訊/技術(shù)干貨/招聘面經(jīng)等
總結(jié)
以上是生活随笔為你收集整理的超详细!使用OpenCV深度学习模块在图像分类下的应用实践的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Label Assign综述:提升目标检
- 下一篇: 计算成本缩减100倍!港中文提出语义分割