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:本人的注释比较杂,既有自己的心得体会也有网上查阅资料时摘抄下的知识内容,所以如有雷同,纯属我向前辈学习的致敬,如果有前辈觉得我的笔记内容侵犯了您的知识产权,请和我联系,我会将涉及到的博文内容删除,谢谢!

猜你喜欢

转载自blog.csdn.net/weixin_45224869/article/details/105998026
今日推荐