本文所用环境VS2017+OpenCV4.4+win10
在贾老师的github上有模型的完整文件 https://github.com/gloomyfish1998/opencv_tutorial/ 本文将其下载到D:\OpenCV\project\下
打开下列文件路径 D:\OpenCV\project\opencv_tutorial-master\data\models\googlenet 可以看到三个文件(在程序中都会用到)
bvlc_googlenet.caffemodel这是个caffe模型,在OpenCV中支持离线加载,不依赖caffe,这是模型的权重文件bvlc_googlenet.prototxt这是模型的描述文件classification_classes_ILSVRC2012.txt是一个标签文件,使用editplus打开可以看到1000个分类标签• Inception模型 来自Google - Going Deeper with Convolutions, CVPR 2015 • 输入:[NxCxHxW], 通道顺序:RGB or BGR • 输出:softmax层 – prob,Nx1000 • 基于ImageNet数据集
(1)在OpenCV中加载模型要通过readNetWork函数 (2)readNet函数有三个参数,参数1加载网络(caffe、TensorFlow等),参数2,3为权重路径和描述文件 (3)或者readNetFromCaffe(TensorFlow、darknet、ONNX等),意思为读进来的必须是caffe模型,此外还支持TensorFlow等模型,只有两个参数 (4)还有readNetFromModelOptimizer从dnn模型优化器中读取模型优化后的模型
Net net = readNetFromCaffe(protxt, bin_model); //此处只能加载caffe的(OpenCVdnn模块支持设置不同的计算后台和在不同的设备上进行)
设置计算后台 net.setPreferableBackend(DNN_BACKEND_OPENCV); //setPreferableBackend实际计算后台,default默认是DNN_BACKEND_OPENCV作为计算后台,使用此就行(也有加速后台) 设置计算设备 net.setPreferableTarget(DNN_TARGET_CPU); //设置在什么设备上进行计算(opencl(需要有interl图形卡)、FPGA、CPU)当上面两个设置之后,他在执行网络进行推算时就会执行此计算后台进行计算(不同的计算后台有不同的效果,速度也有差别)
部分代码如下:
string bin_model = "D:/OpenCV/project/opencv_tutorial-master/data/models/googlenet/bvlc_googlenet.caffemodel"; //定义模型权重文件的加载路径 string protxt = "D:/OpenCV/project/opencv_tutorial-master/data/models/googlenet/bvlc_googlenet.prototxt"; //定义模型描述文件的加载路径 //Imagenet支持1000个分类,分类类别在classification_classes_ILSVRC2012.txt可以看到,一直到1000 //有了模型的权重和描述文件就可以加载模型了 //在OpenCV中加载模型要通过readNetWork, //readNet函数有三个参数,参数1加载网络(caffe、TensorFlow等),参数2,权重路径,参数3,描述文件 //或者readNetFromCaffe(TensorFlow、darknet、ONNX等),意思为读进来的必须是caffe模型,此外还支持TensorFlow等模型,只有两个参数 //还有readNetFromModelOptimizer从dnn模型优化器中读取模型优化后的模型 Net net = readNetFromCaffe(protxt, bin_model); //设置计算后台(OpenCVdnn模块支持设置不同的计算后台和在不同的设备上进行) net.setPreferableBackend(DNN_BACKEND_OPENCV); //setPreferableBackend实际计算后台,default默认是DNN_BACKEND_OPENCV作为计算后台,使用此就行(也有加速后台ENGINE) net.setPreferableTarget(DNN_TARGET_CPU); //设置在什么设备上进行计算(opencl(需要有interl图形卡)、FPGA、CPU) //当上面两个设置之后,他在执行网络进行推算时就会执行此计算后台进行计算(不同的计算后台有不同的效果,速度也有差别) //获取各层信息 vector<string> layer_names = net.getLayerNames(); //此时我们就可以获取所有层的名称了,有了这些可以将其ID取出 for (int i = 0; i < layer_names.size(); i++) { int id = net.getLayerId(layer_names[i]); //通过name获取其id auto layer = net.getLayer(id); //通过id获取layer printf("layer id:%d,type:%s,name:%s\n", id, layer->type.c_str(), layer->name.c_str()); //将每一层的id,类型,姓名打印出来(可以明白此网络有哪些结构信息了) //printf("name2:%s\n", layer_names[i].c_str()); }输出网络的每层信息如下 可以看到共有142层网络信息,其中有Convolution卷积层,relu激活函数,pooling池化层等信息,我们可以清晰的了解此网络结构
参考链接:https://www.pianshen.com/article/9895277588/
使用模型实现预测的时候,需要读取图像作为输入,网络模型支持的输入数据是四维的输入,所以要把读取到的Mat对象转换为四维张量,OpenCV的提供的API为如下:
Mat blobFromImage( InputArray image, //输入图像 double scalefactor = 1.0, //默认是1.0表示0-255范围的 const Size & size = Size(), //网络接受的数据大小 const Scalar & mean = Scalar(), //表示训练时数据集的均值 bool swapRB = false, //是否互换Red与Blur通道(有些网络需要的输入通道类型为RGB类型,而OpenCV读取的是BGR类型) bool crop = false, //crop是剪切 int ddepth = CV_32F //ddepth是数据类型。 ) 构建输入可以参考OpenCV自带的配置文件,根据此文件进行构建输入 D:\OpenCV\opencv-4.4.0-vc14_vc15\opencv\sources\samples\dnn\models.yml 打开文件可以看到,上方链接为模型的下载链接,下方参数为构建输入,进行图像预处理时的参数 在描述文件中也有构建输入时的部分信息以下为构建输入的部分代码
Mat src = imread("G:/OpenCV/opencv笔记所用图片/plane.jpg"); //plane if (src.empty()) { cout << "could not load image.." << endl; getchar(); return -1; } imshow("src", src); 因为googlenet的通道类型是RGB通道的,而OpenCVread读取的是BGR通道的,要进行通道转换(实际在后面的blobFromImage就能进行通道转换,此处无需转换) //Mat rgb; //cvtColor(src, rgb, COLOR_BGR2RGB); //构建输入 //使用blobFromImage函数对单张图像进行预处理,使其符合网络的输入 //对于网络来说原始输入层的时候都有指定的输入大小 int w = 224; int h = 224; Mat inputBlob = blobFromImage(src, 1.0, Size(w, h),Scalar(104, 117, 123), false,false); //我们要将图像resize成224*224的才是我们神经网络可以接受的宽高 //参数1:输入图像,参数2:默认1.0表示0-255范围的,参数3:设置输出的大小,参数4:均值对所有数据中心化预处理,参数5:是否进行通道转换,参数6:,参数7:默认深度为浮点型在上面使用forward函数得到的输出结果都保存在probMat 变量中,里面是1000*1的1通道的Mat数组,1000对应1000个分类标签,每个值都是在0-1之间,值越大表示当前处于位置的类别的可能性就越大。
首先读取标签文件,此标签文件中的类名需要按一定顺序得到,令其与索引index一一对应,在后面的打印分类得到的类名时会用到。 定义一个读取文件的函数readLabels()
vector<string> readLabels() { string label_map_txt = "D:/OpenCV/project/opencv_tutorial-master/data/models/googlenet/classification_classes_ILSVRC2012.txt"; vector<string> classNames; ifstream fp(label_map_txt); if (!fp.is_open()) { printf("could not find the file \n"); exit(-1); } std::string name; while (!fp.eof()) { //当没有结束时就一直读取 getline(fp, name); //每次读取一行 if (name.length()) { classNames.push_back(name); } } fp.close(); //关闭文件输入流 return classNames; }main函数中的部分代码
//要解析输出(forward得到的) //读取标签文件 vector<string> names = readLabels(); //使用自定义函数读取特定文件中的类名到names中,在后面输出对应名称时会用到 //对数据进行序列化(变成1行n列的,可以在后面进行方便的知道是哪个index了) Mat prob = probMat.reshape(1, 1); //reshape函数可以进行序列化,(输出为1通道1行的数据,参数1:1个通道,参数2:1行)将输出结果变成1行n列的,但前面probMat本身就是1000*1*1 //实际结果probMat和prob相同 //当其他网络probMat需要序列化的时候,reshape就可以了 //此时找到最大的那个 Point classNum; double classProb; minMaxLoc(prob, NULL, &classProb, NULL, &classNum);//此时只获取最大值及最大值位置,最小值不管他 int index = classNum.x; //此时得到的是最大值的列坐标。就是其类的索引值,就可以知道其类名了 printf("\n current index=%d,possible:%2f,name=%s\n",index,classProb,names[index].c_str()); // current index=812,possible:0.999675,用editplus打开classification_classes_ILSVRC2012.txt,找到第813行(因为812+1)就是结果 //这样自己在classification_classes_ILSVRC2012.txt找答案挺麻烦的,我们使用程序打开此文件,得到类名即可 //此时可以将名称打印到图片上去 putText(src, names[index].c_str(), Point(50, 50), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0, 0, 255), 2, 8); imshow("result", src);输出结果如下图