今天开始主要整理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 调用拍照 opencv调用dnn_c++


opencv 调用拍照 opencv调用dnn_opencv_02


识别效果

opencv 调用拍照 opencv调用dnn_opencv 调用拍照_03


opencv 调用拍照 opencv调用dnn_opencv 调用拍照_04

总的来说,我们需要学习了解的是在OpenCV中如何去调用这些预训练好的模型,主要是输入输出数据的组织和转换,今天使用的googlenet模型的数据组织其实是比较简单的,其识别效果也不做评价了,因为在它标签集分类中可以看出大部分都是野生动物。。。

今天的笔记就到这吧~

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