【Caffe的C++接口使用说明四)】Caffe中分类C++接口Demo源代码的解析

#ifndef CAFFE_CAFFE_HPP_
#define CAFFE_CAFFE_HPP_

#include "caffe/blob.hpp"                        //【1】Blob是caffe中处理和传递实际数据的数据封装包,blob是按C风格连续存储的N维数据
#include "caffe/common.hpp"                      //【2】
#include "caffe/filler.hpp"                      //【3】
#include "caffe/layer.hpp"                       //【4】Layer是Caffe模型的本质 内容和执行计算的基本单元
#include "caffe/layer_factory.hpp"               //【5】    
#include "caffe/net.hpp"                         //【6】如果将Blob比作Caffe的砖石,Layer比作墙面,那么Net更像工匠手中的图纸   
#include "caffe/parallel.hpp"                    //【7】并行化
#include "caffe/proto/caffe.pb.h"                //【8】
#include "caffe/solver.hpp"                      //【9】Solver通过协调Net的前向推断计算和反向梯度计算,来对参数进行更新,从而达到减小Loss的目的
#include "caffe/solver_factory.hpp"              //【10】
#include "caffe/util/benchmark.hpp"              //【11】
#include "caffe/util/io.hpp"                     //【12】Caffe的数据读取层
#include "caffe/util/upgrade_proto.hpp"          //【13】

#endif  // CAFFE_CAFFE_HPP_

/*********************************************************************************************************************
文件说明:
         1)Caffe深度学习库中C++分类接口的说明
		 2)基于C++和Caffe的分类程序
开发环境:
         Caffe+NVIDIA7.5+OpenCv+windows+VS2013+STL
时间地点:
         陕西师范大学 文津楼 2017.5.30
作    者:
         九 月
*********************************************************************************************************************/
#include <caffe/caffe.hpp>                     //【1】Caffe深度学习框架的头文件
#include <opencv2/core/core.hpp>               //【2】OpenCv中的核心功能模块头文件
#include <opencv2/highgui/highgui.hpp>         //【3】高层GUI图形用户界面头文件
#include <opencv2/imgproc/imgproc.hpp>         //【4】OpenCv的图像处理模块头文件
#include <algorithm>                           //【5】STL中的头文件
#include <iosfwd>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <iostream>


using namespace caffe;  
using std::string;


typedef std::pair<string, float> Prediction;       //【1】记录每一个类别的名称及其概率Pair (label, confidence) representing a prediction

class Classifier 
{
public:                                            //【0】带参数的构造函数
	Classifier(const string& model_file,           //【1】预测阶段的网络模型描述文件
		       const string& trained_file,         //【2】已经训练好的caffemodel文件所在的路径
		       const string& mean_file,            //【3】均值文件所在的路径
		       const string& label_file);          //【4】类别标签文件所在的路径
	                                               //【5】Classify计算神经网络的前向传播,得到srcImg属于各个类别的概率(置信度)
	std::vector<Prediction> Classify(const cv::Mat& img, int N = 5);
private:
	void SetMean(const string& mean_file);         //【1】SetMean函数主要进行均值设定,每张检测图输入后会进行减去均值的操作,这个均值可以
	                                               //     是模型使用的数据集图像的均值  
	std::vector<float> Predict(const cv::Mat& img);//【2】Predict函数是Classify类的主要组成部分,将srcImg送入网络进行前向传播,得到最后的类别
	                                               //【3】WrapInputLayer函数将srcImg各通道(input_channels)放入网络的输入blob中 
	void WrapInputLayer(std::vector<cv::Mat>* input_channels);
	                                               //【4】Preprocess函数将输入图像img按通道分开(input_channels) 
	void Preprocess(const cv::Mat& img,std::vector<cv::Mat>* input_channels);

private:
	shared_ptr<Net<float>>  net_;                  //【1】net_表示caffe中的网络
	cv::Size                input_geometry_;       //【2】input_geometry_表示了输入图像的高宽,同时也是网络数据层中单通道图像的高宽
	int                     num_channels_;         //【3】num_channels_表示了输入图像的通道数
	cv::Mat                 mean_;                 //【4】mean_表示了数据集的均值,格式为Mat
	std::vector<string>     labels_;               //【5】字符串向量labels_表示了各个标签
};
/*************************************************************************************************************************
函数说明:
        类类型Classifier的构造函数
函数参数:
		const string& model_file,                  //【1】预测阶段的网络模型描述文件
		const string& trained_file,                //【2】已经训练好的caffemodel文件所在的路径
		const string& mean_file,                   //【3】均值文件所在的路径
		const string& label_file);                 //【4】类别标签文件所在的路径
**************************************************************************************************************************/
Classifier::Classifier(const string& model_file,const string& trained_file,const string& mean_file,const string& label_file)
{
#ifdef CPU_ONLY
	Caffe::set_mode(Caffe::CPU);
#else
	Caffe::set_mode(Caffe::GPU);
#endif

	//【1】加载【网络模型的描述文件】--Load the network
	net_.reset(new Net<float>(model_file, TEST));     //【1】从model_file路径下的prototxt初始化网络结构
	net_->CopyTrainedLayersFrom(trained_file);        //【2】从trained_file路径下的caffemodel文件读入训练完毕的网络参数
	                                                  //【3】核验是不是只输入了一张图像,输入的blob结构为(N,C,H,W),在这里,N只能为1     
	CHECK_EQ(net_->num_inputs(), 1)  << "Network should have exactly one input.";
	                                                  //【4】核验输出的blob结构,输出的blob结构同样为(N,C,W,H),在这里,N同样只能为1  
	CHECK_EQ(net_->num_outputs(), 1) << "Network should have exactly one output.";


	//【2】网络的基本数据单元
	Blob<float>* input_layer = net_->input_blobs()[0];//【5】获取网络输入的blob,表示网络的数据层
	num_channels_ = input_layer->channels();          //【6】获取输入的通道数  
	                                                  //【7】核验输入图像的通道数是否为3或者1,网络只接收3通道或1通道的图片 
	CHECK(num_channels_ == 3 || num_channels_ == 1)<< "Input layer should have 1 or 3 channels.";
	                                                  //【8】获取输入图像的尺寸(宽与高)  
	input_geometry_ = cv::Size(input_layer->width(), input_layer->height());

	//【3】加载均值文件---Load the binaryproto mean file
	SetMean(mean_file);                               //【9】进行均值的设置 

	//【4】加载标签文件---Load labels
	std::ifstream labels(label_file.c_str());         //【10】从标签文件路径读入定义的标签文件
	CHECK(labels) << "Unable to open labels file " << label_file;
	std::string line;
	while (std::getline(labels, line))
	{
		labels_.push_back(string(line));              //【11】将所有的标签放入labels_这个Vector容器
	}


	                                                  //【12】output_layer指向网络最后的输出,举个例子,最后的分类器采用softmax分类,
	                                                  //      且类别有10类,那么,输出的blob就会有10个通道,每个通道的长宽都为1(因为
	                                                  //      是10个数,这10个数表征输入属于10类中每一类的概率,这10个数之和应该为1),
	                                                  //      输出blob的结构为(1,10,1,1)
	Blob<float>* output_layer = net_->output_blobs()[0];
	CHECK_EQ(labels_.size(), output_layer->channels())<< "Number of labels is different from the output layer dimension.";
}
/*************************************************************************************************************************
函数说明:
        PairCompare函数比较分类得到的物体属于某两个类别的概率的大小,若属于lhs的概率大于属于rhs的概率,返回真,否则返回假
函数参数:
        1)const std::pair<float, int>& lhs
		2)const std::pair<float, int>& rhs
**************************************************************************************************************************/
static bool PairCompare(const std::pair<float, int>& lhs,const std::pair<float, int>& rhs) 
{
	return lhs.first > rhs.first;
}
/*************************************************************************************************************************
函数说明:
        1)Argmax函数返回前N个得分概率的类标
		2)Return the indices of the top N values of vector v
函数参数:
        1)const std::vector<float>& v
        2) int N
**************************************************************************************************************************/
static std::vector<int> Argmax(const std::vector<float>& v, int N) 
{
	std::vector<std::pair<float, int> > pairs;
	for (size_t i = 0; i < v.size(); ++i)
	{
		pairs.push_back(std::make_pair(v[i], i));            //【1】按照分类结果存储输入属于每一个类的概率以及类标
	}                                                        //【2】partial_sort函数按照概率大小筛选出pairs中概率最大的N个组
	                                                         //     合,并将它们按照概率从大到小放在pairs的前N个位置
	std::partial_sort(pairs.begin(), pairs.begin() + N, pairs.end(), PairCompare);

	std::vector<int> result;
	for (int i = 0; i < N; ++i)
	{
		result.push_back(pairs[i].second);                   //【3】将前N个较大的概率对应的类标放在result中  
	}
	return result;
}
/*************************************************************************************************************************
函数说明:
         1)返回前N个预测
         2)Return the top N predictions
函数参数:
         1)const cv::Mat& img
         2)int N
**************************************************************************************************************************/
std::vector<Prediction> Classifier::Classify(const cv::Mat& img, int N) 
{
	std::vector<float> output = Predict(img);           //【1】进行网络的前向传输,得到输入属于每一类的概率,存储在output中
	N = std::min<int>(labels_.size(), N);               //【2】找到想要得到的概率较大的前N类,这个N应该小于等于总的类别数目  
	std::vector<int> maxN = Argmax(output, N);          //【3】找到概率最大的前N类,将他们按概率由大到小将类标存储在maxN中
	std::vector<Prediction> predictions;
	for (int i = 0; i < N; ++i) 
	{
		int idx = maxN[i];                              //【4】在labels_找到分类得到的概率最大的N类对应的实际的名称  
		predictions.push_back(std::make_pair(labels_[idx], output[idx]));
	}
	return predictions;
}

/* Load the mean file in binaryproto format. */
/*************************************************************************************************************************
函数说明:
        1)加载一个binaryproto格式的均值文件
        2)Load the mean file in binaryproto format
		3)设置数据集的平均值
函数参数:
        const string& mean_file:均值文件的存储路径
**************************************************************************************************************************/
void Classifier::SetMean(const string& mean_file) 
{
	BlobProto blob_proto;                            //【1】用定义的均值文件路径将均值文件读入proto中
	ReadProtoFromBinaryFileOrDie(mean_file.c_str(), &blob_proto);
	                                                 //【2】Convert from BlobProto to Blob<float>
	Blob<float> mean_blob;
	mean_blob.FromProto(blob_proto);                 //【3】将proto中存储的均值文件转移到blob中
	                                                 //【4】核验均值的通道数是否等于输入图像的通道数,如果不相等的话则为异常
	CHECK_EQ(mean_blob.channels(), num_channels_)<< "Number of channels of mean file doesn't match input layer.";

	                                                 //【5】The format of the mean file is planar 32-bit float BGR or grayscale
	                                                 //【6】均值文件的格式为32位的浮点型的BGR图像或者灰度图像
	std::vector<cv::Mat> channels;                   //【7】将mean_blob中的数据转化为Mat时的存储向量 
	float* data = mean_blob.mutable_cpu_data();      //【8】指向均值blob的指针
	for (int i = 0; i < num_channels_; ++i) 
	{
		                                             //【1】提取一个单独的通道---Extract an individual channel
		                                             //【2】存储均值文件的每一个通道转化得到的Mat 
		cv::Mat channel(mean_blob.height(), mean_blob.width(), CV_32FC1, data);
		channels.push_back(channel);                 //【3】将均值文件的所有通道转化成的Mat一个一个地存储到channels中
		                                             //【4】在均值文件上移动一个通道  
		data += mean_blob.height() * mean_blob.width();
	}

	cv::Mat mean;                                    //【5】将分离的通道合并成一个单独的图像
	cv::merge(channels, mean);                       //【6】将得到的所有通道合成为一张图 

	/* Compute the global mean pixel value and create a mean image filled with this value. */
	cv::Scalar channel_mean = cv::mean(mean);        //【7】求得均值文件的每个通道的平均值,记录在channel_mean中
	                                                 //【8】用上面求得的各个通道的平均值初始化mean_,作为数据集图像的均值
	mean_ = cv::Mat(input_geometry_, mean.type(), channel_mean);
}
/*************************************************************************************************************************
函数说明:
        神经网络的前向传播函数
函数参数:
        const cv::Mat& img
**************************************************************************************************************************/
std::vector<float> Classifier::Predict(const cv::Mat& img) 
{
	Blob<float>* input_layer = net_->input_blobs()[0];//【1】input_layer是网络的输入blob
	                                                  //【2】表示网络只输入一张图像,图像的通道数是num_channels_,高为
	                                                  //     input_geometry_.height,宽为input_geometry_.width  
	input_layer->Reshape(1, num_channels_,input_geometry_.height, input_geometry_.width);

	net_->Reshape();                                  //【3】初始化网络的各层  

	std::vector<cv::Mat> input_channels;              //【4】存储输入图像的各个通道 
	WrapInputLayer(&input_channels);                  //【5】将存储输入图像的各个通道的input_channels放入网络的输入blob中
	Preprocess(img, &input_channels);                 //【6】将img的各通道分开并存储在input_channels中  
	net_->ForwardPrefilled();                         //【7】进行网络的前向传输

	                                                  //【8】Copy the output layer to a std::vector
                                                      //【9】output_layer指向网络输出的数据,存储网络输出数据的blob的规格是(1,c,1,1)  
	Blob<float>* output_layer = net_->output_blobs()[0];
	const float* begin = output_layer->cpu_data();    //【10】begin指向输入数据对应的第一类的概率
	                                                  //【11】end指向输入数据对应的最后一类的概率
	const float* end = begin + output_layer->channels();
	return std::vector<float>(begin, end);            //【12】返回输入数据经过网络前向计算后输出的对应于各个类的分数  
}
/*************************************************************************************************************************
函数说明:
        1)将网络的输入层封装在单独的cv::Mat对象(每个通道一个).这样我们可以节省一个memcpy操作。我么不需要依赖cudaMemcpy2D
		2)最后一个预处理操作将直接将单独的通道写入输入层
函数参数:
        std::vector<cv::Mat>* input_channels
**************************************************************************************************************************/
void Classifier::WrapInputLayer(std::vector<cv::Mat>* input_channels) 
{
	Blob<float>* input_layer = net_->input_blobs()[0]; //【1】input_layer指向网络输入的blob  

	int width  = input_layer->width();                 //【2】得到网络指定的输入图像的宽  
	int height = input_layer->height();                //【3】得到网络指定的输入图像的高
	                                                   //【4】input_data指向网络的输入blob 
	float* input_data = input_layer->mutable_cpu_data();

	for (int i = 0; i < input_layer->channels(); ++i) 
	{                                                  //【5】将网络输入blob的数据同Mat关联起来
		cv::Mat channel(height, width, CV_32FC1, input_data);
		input_channels->push_back(channel);            //【6】将上面的Mat同input_channels关联起来
		input_data += width * height;                  //【7】一个一个通道地操作
	}
}
/*************************************************************************************************************************
函数说明:
        1)将输入图像转化为网络中输出图像的格式
		2)Convert the input image to the input image format of the network
函数参数:
        1)const cv::Mat& img
		2)std::vector<cv::Mat>* input_channels
**************************************************************************************************************************/
void Classifier::Preprocess(const cv::Mat& img,std::vector<cv::Mat>* input_channels) 
{
	cv::Mat sample;
	if (img.channels() == 3 && num_channels_ == 1)
		cv::cvtColor(img, sample, CV_BGR2GRAY);
	else if (img.channels() == 4 && num_channels_ == 1)
		cv::cvtColor(img, sample, CV_BGRA2GRAY);
	else if (img.channels() == 4 && num_channels_ == 3)
		cv::cvtColor(img, sample, CV_BGRA2BGR);
	else if (img.channels() == 1 && num_channels_ == 3)
		cv::cvtColor(img, sample, CV_GRAY2BGR);
	else
		sample = img;

	cv::Mat sample_resized;
	if (sample.size() != input_geometry_)                //【1】将输入图像的尺寸强制转化为网络规定的输入尺寸
		cv::resize(sample, sample_resized, input_geometry_);
	else
		sample_resized = sample;

	cv::Mat sample_float;
	if (num_channels_ == 3)                              //【2】将输入图像转化成为网络前传合法的数据规格
		sample_resized.convertTo(sample_float, CV_32FC3);
	else
		sample_resized.convertTo(sample_float, CV_32FC1);

	cv::Mat sample_normalized;
	cv::subtract(sample_float, mean_, sample_normalized);//【3】将图像减去均值
	                                                     //【4】将减去均值的图像分散在input_channels中,由于在WrapInputLayer函数中,
	                                                     //     input_channels已经和网络的输入blob关联起来了,因此在这里实际上是把
	                                                     //     图像送入了网络的输入blob
	cv::split(sample_normalized, *input_channels);

	CHECK(reinterpret_cast<float*>(input_channels->at(0).data)== net_->input_blobs()[0]->cpu_data())
		                       << "Input channels are not wrapping the input layer of the network.";
}

int main(int argc, char** argv) 
{
	string model_file   = "F:\\caffeT\\caffe-windows-master\\models\\bvlc_reference_caffenet\\deploy.prototxt";
	string trained_file = "F:\\caffeT\\caffe-windows-master\\models\\bvlc_reference_caffenet\\bvlc_reference_caffenet.caffemodel";
	string mean_file    = "F:\\caffeT\\caffe-windows-master\\models\\bvlc_reference_caffenet\\imagenet_mean.binaryproto";
	string label_file   = "F:\\caffeT\\caffe-windows-master\\models\\bvlc_reference_caffenet\\synset_words.txt";
	string file         = "F:\\caffeT\\caffe-windows-master\\examples\\images\\cat.jpg";
	                                                               //【1】进行检测网络的初始化  
	Classifier classifier(model_file, trained_file, mean_file, label_file);

	std::cout << "---------- Prediction for "<< file << " ----------" << std::endl;

	cv::Mat img = cv::imread(file, -1);

	CHECK(!img.empty()) << "Unable to decode image " << file;

    std::vector<Prediction> predictions = classifier.Classify(img);//【2】进行网络的前向计算,并且取到概率最大的前N类对应的类别名称
	                                                               //【3】Print the top N predictions
	                                                               //【4】打印出概率最大的前N类并给出概率
	for (size_t i = 0; i < predictions.size(); ++i) 
	{
		Prediction p = predictions[i];
		std::cout << std::fixed << std::setprecision(4) << p.second << " - \""<< p.first << "\"" << std::endl;
		std::system("pause");
	}
}

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值