OpenCV4學習筆記(63)——dnn模塊之調用MobileNetSSD模型實現常見目標檢測

本次要整理的內容是:在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:本人的註釋比較雜,既有自己的心得體會也有網上查閱資料時摘抄下的知識內容,所以如有雷同,純屬我向前輩學習的致敬,如果有前輩覺得我的筆記內容侵犯了您的知識產權,請和我聯繫,我會將涉及到的博文內容刪除,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章