深度神经网络(DNN)之使用GoogleNet进行图像分类

前提环境

本文所用环境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数据集

部分操作

引入头文件

#include <opencv2/opencv.hpp>		//opencv头文件
#include <opencv2/dnn.hpp>			//包含dnn模块的头文件
#include <iostream>			//输入输出流
#include <fstream>				//文件流进行txt文件读取

using namespace cv;			//OpenCV的空间
using namespace cv::dnn;			//包含dnn的命名空间
using namespace std;		//输出输出流的空间

用于加载模型的函数

(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)

当上面两个设置之后,他在执行网络进行推算时就会执行此计算后台进行计算(不同的计算后台有不同的效果,速度也有差别)

具体操作步骤

1.加载模型(读取网络信息)

部分代码如下:

	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池化层等信息,我们可以清晰的了解此网络结构

2.构建输入

参考链接: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:默认深度为浮点型

3.输入网络并推测得到输出

	//设置输入
	//前面已经将图像预处理完成,就可以执行分类了
	//现在要将其输入到创建的网络中
	net.setInput(inputBlob);

	//进行推断得到输出
	//让网络执行得到output,调用forward可以得到一个结果
	//此处不给参数,得到的是最后一层的结果,也可以输入层数得到任何一层的输出结果
	Mat probMat = net.forward();	//通过前面的输出层看最后一层,可以知道输出100个分类,每个分类的得分是多少
									//softmax层出来的总和为1,找到max值对应的index就知道对应的分类了,在classification_classes_ILSVRC2012.txt可以知道具体名称
	//上方得到的probMat是1000*1*1(有imagewatch得到)

4.解析输出结果

在上面使用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);

输出结果如下图
在这里插入图片描述

完整代码

#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>			//包含dnn模块的头文件
#include <iostream>
#include <fstream>				//文件流进行txt文件读取

using namespace cv;
using namespace cv::dnn;			//包含dnn的命名空间
using namespace std;

vector<string> readLabels();		//将类名的txt文件读取到容器中

int main() {

	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,权重路径和描述文件,不同网络参数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,类型,姓名打印出来(可以明白此网络有哪些结构信息了)
	}
	
	Mat src = imread("G:/OpenCV/opencv笔记所用图片/plane.jpg");	//plane
	if (src.empty()) {
		cout << "could not load image.." << endl;
		getchar();
		return -1;
	}
	imshow("src", src);

	有些通道类型是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:是否进行通道转换(此处根据model.yml文件进行设置),参数6:剪切,参数7:默认深度为浮点型

	//上方得到的inputBlob是4维的(在变量窗口看dim),所以在imagewatch中无法查看

	//设置输入
	//前面已经将图像预处理完成,就可以执行分类了
	//现在要将其输入到创建的网络中
	net.setInput(inputBlob);

	//进行推断得到输出
	//让网络执行得到output,调用forward可以得到一个结果
	//此处不给参数,得到的是最后一层的结果,也可以输入层数得到任何一层的输出结果
	Mat probMat = net.forward();	//通过前面的输出层看最后一层,可以知道输出100个分类,每个分类的得分是多少
									//softmax层出来的总和为1,找到max值对应的index就知道对应的分类了,在classification_classes_ILSVRC2012.txt可以知道具体名称
	//上方得到的probMat是1000*1*1(有imagewatch得到)

//此处通过forward()推测得到的probMat是2维的,所以在imagewatch中可以直观的查看(有些网络得到的是4维的无法查看)

	//要解析输出(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);

	waitKey(0);
	return 0;
}


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;
}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吾名招财

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值