今天開始主要整理OpenCV中dnn模塊的使用,包括各種神經網絡模型的加載、調用,輸入輸出數據的組織等等內容。而今天要記錄的是OpenCV中一個自帶神經網絡模型——googlenet模型的使用,這個模型是由caffe框架訓練出來、主要針對多種野生動物的識別。下面開始通過代碼逐步整理在OpenCV中對該模型進行調用,並對圖像進行識別分類的流程。
首先我們需要加載googlenet模型的模型文件(.caffemodel)、配置文件(.prototxt),使用readNet()
這個API來實現模型的加載。注意的是,在OpenCV中既可以通過readNet()
對所有支持的神經網絡模型進行加載,也可以使用針對某一種網絡框架訓練出的模型進行加載,都有相應的API可以調用,這些後續再整理。在這裏我們使用readNet()
,其參數含義如下:
(1)參數model:加載已訓練模型的路徑,不同模型對應不同後綴:
*.caffemodel ——Caffe
*.pb——TensorFlow
*.t7 or *.net——Torch
*.weights——Darknet
*.bin——DLDT
(2)參數config:已訓練模型的描述文件:
*.prototxt ——Caffe
*.pbtxt ——TensorFlow
Torch中沒有config文件
*.cfg ——Darknet
*.xml ——DLDT
(3)參數framework:聲明加載的模型是由哪個框架訓練出來的,也可以不加,函數會根據讀取模型的格式來自己判斷。
代碼演示如下:
string caffe_model_path = "D:\\opencv_c++\\opencv_tutorial\\data\\models\\googlenet(animal)\\bvlc_googlenet.caffemodel";
string caffe_config = "D:\\opencv_c++\\opencv_tutorial\\data\\models\\googlenet(animal)\\bvlc_googlenet.prototxt";
string labels = "D:\\opencv_c++\\opencv_tutorial\\data\\models\\googlenet(animal)\\classification_classes_ILSVRC2012.txt";
Net caffe_net = readNet(caffe_model_path, caffe_config);
然後我們獲取這個模型的相關信息,例如每一層神經層的類型、名稱等等,當然這一步實際上可以忽略,這裏只是作爲演示。代碼演示如下:
vector<string> caffe_layer_name = caffe_net.getLayerNames(); //獲取模型每層的名稱
for (int i = 0; i < caffe_layer_name.size(); i++)
{
int caffe_layer_id = caffe_net.getLayerId(caffe_layer_name[i]); //根據每層的名稱來獲取每層的id
Ptr<dnn::Layer> caffe_layer = caffe_net.getLayer(caffe_layer_id); //根據id去索引到每一層
//獲取每一層的類型、名稱
string layer_type = caffe_layer->type;
string layer_name = caffe_layer->name;
cout << caffe_layer_id << " layer_type: " << layer_type << "; layer_name: " << layer_name << endl;
}
然後我們需要讀取該模型的標籤集,並進行一定處理,以便後續分類ID的索引。
//讀取分類標籤集
ifstream fp(labels);
vector<string>class_labels;
if (!fp.is_open())
{
cout << "labels file can't open" << endl;
exit(-1);
}
while (!fp.eof()) //如果指針不位於文件末尾,就繼續讀取文件
{
string class_name;
getline(fp, class_name); //讀取文件中的每一行數據
if (0 != class_name.length()) //如果不是空行,就將這一行的數據保存起來
{
class_labels.push_back(class_name);
}
}
fp.close();
接下來我們讀取圖像
Mat test_image = imread("D:\\opencv_c++\\opencv_tutorial\\data\\images\\tem.jpg");
resize(test_image, test_image, Size(700, 700));
imshow("test_image", test_image);
Mat inputBlob = blobFromImage(test_image, 1.0, Size(224, 224), Scalar(104, 117, 123), true, false, 5);
注意無法直接將圖像作爲神經網絡的輸入,我們需要將圖像轉換爲4維的blob才能作爲輸入。通過blobFromImage()
進行轉換,其參數含義如下:
(1)image:輸入圖像(具有1、3或4通道)
(2)size:輸出圖像的空間大小
(3)mean:從通道中減去平均值的標量,如果參數image具有BGR順序且參數swapRB爲true ,則值應按(平均值R,平均值G,平均值B)順序排列。該值由模型訓練時所確定,需要通過查詢模型資料得知。
(4)scalefactor:對參數image的縮放比例
(5)swapRB: 表示是否交換3通道圖像中的第一個和最後一個通道的標誌
(6)crop:表示是否在調整大小後裁剪圖像
(7)ddepth:輸出blob的深度,選擇CV_32F或CV_8U。
經過處理後將blob輸入給神經網絡,再進行前向傳播得到預測結果,也就是分類結果。
caffe_net.setPreferableBackend(DNN_BACKEND_INFERENCE_ENGINE);
caffe_net.setPreferableTarget(DNN_TARGET_CPU);
caffe_net.setInput(inputBlob);
Mat prob = caffe_net.forward();
這裏的前兩句代碼是設置神經網絡運算的計算後臺和目標設備,DNN_BACKEND_INFERENCE_ENGINE
需要搭配openVINO使用,如果沒有的話就設置DNN_BACKEND_OPENCV
,否則會報錯。
前向傳播得到的結果是一個1行、1000列、單通道的矩陣,其中每一列的值就是每一個分類的置信度,由於總共是1000個分類,所以有1000列。我們需要找到其中置信度最大的像素座標,那麼該座標的x值對應的就是在標籤集中分類的ID,通過這個ID去索引到預測的分類。代碼演示如下:
prob = prob.reshape(1, 1);
double maxval;
Point maxloc;
minMaxLoc(prob, NULL, &maxval, NULL, &maxloc);
int classID = maxloc.x;
string className = class_labels[classID];
putText(test_image, className, Point(20, 20),FONT_HERSHEY_SIMPLEX,1, Scalar(0, 255, 0), 2);
cout << "運行時間: " << run_time << endl;
imshow("test_image", test_image);
下面是運行效果:
該模型每一層的類型和名稱
識別效果
總的來說,我們需要學習瞭解的是在OpenCV中如何去調用這些預訓練好的模型,主要是輸入輸出數據的組織和轉換,今天使用的googlenet模型的數據組織其實是比較簡單的,其識別效果也不做評價了,因爲在它標籤集分類中可以看出大部分都是野生動物。。。
今天的筆記就到這吧~
PS:本人的註釋比較雜,既有自己的心得體會也有網上查閱資料時摘抄下的知識內容,所以如有雷同,純屬我向前輩學習的致敬,如果有前輩覺得我的筆記內容侵犯了您的知識產權,請和我聯繫,我會將涉及到的博文內容刪除,謝謝!