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