本次要整理的內容是:在OpenCV中使用dnn模塊來調用MobileNetSSD神經網絡模型,並實現常見目標檢測。MobileNetSSD,顧名思義是一個適用於移動端的ssd神經網絡模型,其運行速度是比較可觀的,可以用在實時檢測當中。在本次筆記中,先使用一張圖像作爲演示,傳入MobileNetSSD模型中並進行識別,然後再調用筆記本自帶的攝像頭實現一個實時的常見目標檢測。
對於MobileNetSSD模型,由於比較輕量化,所以它提高運行速度的同時,卻只能對二十種常見物品進行檢測,下面所列出的清單是MobileNetSSD模型的標籤集。(有一說一,牛馬這些真的是常見目標嗎。。。)
背景
飛機、自行車、鳥、船、瓶子、總線、汽車、貓、椅子、牛、餐桌、狗、馬、摩托車、人、盆栽、羊、沙發、火車、電視監視器
下面開始通過代碼逐步實現。
首先我們需要加載MobileNetSSD模型
string model_path = "D:\\opencv_c++\\opencv_tutorial\\data\\models\\ssd\\MobileNetSSD_deploy.caffemodel";
string config_path = "D:\\opencv_c++\\opencv_tutorial\\data\\models\\ssd\\MobileNetSSD_deploy.prototxt";
string label_path = "D:\\opencv_c++\\opencv_tutorial\\data\\models\\ssd\\labels.txt";
Net MobileNetSSD = readNetFromCaffe(config_path, model_path);
然後設置dnn模塊的計算後臺和目標設備,在我的電腦上測試不同的設置方式,實時FPS效果如下: CUDA+CUDA > INFERENCE_ENGINE+CPU > OPENCV+OPENCL > OPENCV+CPU.
所以如果有N系顯卡的朋友還是最好使用CUDA來加速吧,如果不行的話,英特爾的openVINO也是一個不錯的選擇。
MobileNetSSD.setPreferableBackend(DNN_BACKEND_INFERENCE_ENGINE);
MobileNetSSD.setPreferableTarget(DNN_TARGET_CPU);
然後再加載標籤集
//加載標籤集
ifstream fp(label_path);
if (!fp.is_open())
{
cout << "model file open fail" << endl;
}
vector<string> labels;
while (!fp.eof())
{
string label_name;
getline(fp, label_name);
labels.push_back(label_name);
}
fp.close();
接着讀取測試圖像,並轉換爲4維blob傳入MobileNetSSD模型中進行前向傳播。
前向傳播結果是一個四維矩陣prob,第一維是輸入圖像編號、第二維是輸入圖像數量、第三維是檢測的每個目標、第四維是每個目標的信息,包含類別ID、置信度、目標框的左上角和右下角座標點等,其中座標是該點相對於圖像寬高的比例。
對於單獨一張輸入測試圖像,prob的第一維度和第二維度的值都是1。我們需要的信息是第三維度和第四維度,所以將第三和第四維度組織成Mat對象,即檢測結果矩陣。在這裏,我們將得到的4維前向傳播結果矩陣prob轉換爲2維預測結果矩陣detection。
Mat test_image = imread("D:\\opencv_c++\\opencv_tutorial\\data\\images\\CC4.jpg");
resize(test_image, test_image, Size(800, 600));
Mat inputBlob = blobFromImage(test_image, 0.008, Size(300, 300), Scalar(127.5, 127.5, 127.5), true, false);
MobileNetSSD.setInput(inputBlob);
Mat prob = MobileNetSSD.forward();
Mat detection(prob.size[2], prob.size[3], CV_32F, prob.ptr<float>());
得到的detection矩陣如下:
detection矩陣中,每一行就是prob矩陣的第三維度,也就是檢測到的一個目標。
每一行中的每一列代表不同含義,主要關注以下列數:
第二列,表示目標的類別ID;
第三列,表示該目標分類的置信度;
第四到第七列,分別表示目標矩形框的左上角x、y座標和右下角x、y座標,注意這裏的值是原座標與寬高的比例值,在使用的時候需要乘上圖像寬高才能得到正確的座標。
我們設置一個閾值,用來判斷目標檢測的置信度是否達到要求,如果某個目標的置信度大於閾值則表示它分類的正確率比較高,就把它的目標矩形繪製出來。
最後顯示檢測結果。
float confidence_thresh = 0.8;
for (int row = 0; row < detection.rows; row++) //每一行是檢測到的一個目標
{
float confidence = detection.at<float>(row, 2);
if (confidence > confidence_thresh)
{
int class_id = detection.at<float>(row, 1);
int top_left_pt_x = detection.at<float>(row, 3) * test_image.cols;
int top_left_pt_y = detection.at<float>(row, 4) * test_image.rows;
int button_right_x = detection.at<float>(row, 5) * test_image.cols;
int button_right_y = detection.at<float>(row, 6) * test_image.rows;
int box_width = button_right_x - top_left_pt_x;
int box_height = button_right_y - top_left_pt_y;
Rect box(top_left_pt_x, top_left_pt_y, box_width, box_height);
rectangle(test_image, box, Scalar(0, 255, 0), 1, 8, 0);
putText(test_image, labels[class_id], Point(top_left_pt_x + 10, top_left_pt_y + 50), FONT_HERSHEY_SIMPLEX, 0.7, Scalar(0, 0, 255), 1, 8);
}
}
imshow("test_image", test_image);
int end = getTickCount();
float run_time = (double(end) - double(start)) / getTickFrequency();
cout << run_time << "s" << endl;
目標檢測效果如下:
到這裏我們就實現了在OpenCV中使用dnn模塊加載MobileNetSSD模型並進行常見目標檢測的功能。下面我們利用筆記本的攝像頭來實現實時目標檢測,演示代碼如下:
VideoCapture capture;
capture.open(0);
if (!capture.isOpened())
{
cout << "capture can't open" << endl;
exit(-1);
}
Mat test_image;
while(capture.read(test_image))
{
int start = getTickCount();
//resize(test_image, test_image, Size(500, 700));
Mat inputBlob = blobFromImage(test_image, 0.008, Size(300, 300), Scalar(127.5, 127.5, 127.5), true, false);
MobileNetSSD.setInput(inputBlob);
//前向傳播結果是一個四維矩陣,第一維是輸入圖像編號;第三維是檢測到的每個目標;第四維是每個目標的信息,包含目標類別、置信度、目標框的左上角和右下角座標點等,其中座標是該點相對於圖像寬高的比例
Mat prob = MobileNetSSD.forward();
//對於單獨一張輸入測試圖像,第一維和第二維度都是1,將第三維度和第四維度的size形成Mat對象,即檢測結果,並將檢測結果矩陣的值指向前向傳播結果矩陣
Mat detection(prob.size[2], prob.size[3], CV_32F, prob.ptr<float>());
float confidence_thresh = 0.8;
for (int row = 0; row < detection.rows; row++) //每一行是檢測到的一個目標
{
float confidence = detection.at<float>(row, 2);
if (confidence > confidence_thresh)
{
int class_id = detection.at<float>(row, 1);
int top_left_pt_x = detection.at<float>(row, 3) * test_image.cols;
int top_left_pt_y = detection.at<float>(row, 4) * test_image.rows;
int button_right_x = detection.at<float>(row, 5) * test_image.cols;
int button_right_y = detection.at<float>(row, 6) * test_image.rows;
int box_width = button_right_x - top_left_pt_x;
int box_height = button_right_y - top_left_pt_y;
Rect box(top_left_pt_x, top_left_pt_y, box_width, box_height);
rectangle(test_image, box, Scalar(0, 255, 0), 1, 8, 0);
putText(test_image, labels[class_id], Point(top_left_pt_x + 10, top_left_pt_y + 50), FONT_HERSHEY_SIMPLEX, 0.7, Scalar(0, 0, 255), 1, 8);
}
}
int end = getTickCount();
double t = (double(end) - double(start)) / getTickFrequency();
int fps = 1 / t;
putText(test_image, to_string(fps), Point(20, 20), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 255), 1, 8);
imshow("test_image", test_image);
char ch = waitKey(1);
if (ch == 27)
{
break;
}
}
capture.release();
效果如下(這個馬賽克打得好):
好的本次筆記到此結束,謝謝閱讀。
PS:本人的註釋比較雜,既有自己的心得體會也有網上查閱資料時摘抄下的知識內容,所以如有雷同,純屬我向前輩學習的致敬,如果有前輩覺得我的筆記內容侵犯了您的知識產權,請和我聯繫,我會將涉及到的博文內容刪除,謝謝!