【Caffe的C++接口使用说明六)】caffe中分类接口C++源代码的再次解读以及测试文件的完善
2017-07-26 18:20
519 查看
/************************************************************************************************************************ 文件说明: 1)caffe深度学习框架中C++分类接口的说明 2)基于C++和caffe的分类程序 测试文件: https://pan.baidu.com/disk/home#list/vmode=list&path=%2Fcaffe%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB%E7%AC%94%E8%AE%B0 使用流程: 1)libcaffe项目编译成功之后,将classification项目设为启动项 2)根据main函数中的路径,下载测试此程序需要的文件,并且将文件添加到相应的目录下 3)将caffe源代码中的代码修改为此代码 4)运行程序 开发环境: Caffe+NIVIDIA CUDA7.5+OpenCv+windows+VS2013+STL 时间地点: 陕西师范大学 文津楼 2017.7.25 作 者: 九 月 *************************************************************************************************************************/ #include <caffe/caffe.hpp> //【1】caffe深度学习框架的头文件 #ifdef USE_OPENCV //【2】预处理命令,以此选择参与编译的程序代码段 #include <opencv2/core/core.hpp> //【3】OpenCv中的核心功能模块头文件 #include <opencv2/highgui/highgui.hpp> //【4】高层GUI图形用户界面的头文件 #include <opencv2/imgproc/imgproc.hpp> //【5】图像处理模块的头文件 #endif // USE_OPENCV #include <algorithm> //【6】C++特有的STL模板算法的头文件 #include <iosfwd> //【7】C++STL中输入输出流的前向定义头文件 #include <memory> //【8】C++中,内存管理相关函数的头文件 #include <string> //【9】C++中,STL中字符串序列容器的头文件 #include <utility> //【10】此头文件定义了std::pair类型,属于STL #include <vector> //【11】C++中,STL向量容器的头文件 /************************************************************************************************************************* **************************************************************************************************************************/ using namespace caffe; using std::string; typedef std::pair<string, float> Prediction; //【1】记录每一个类别的名称及其概率,这是一个键值对 /************************************************************************************************************************* 模块说明: caffe源码中分类接口的类定义 **************************************************************************************************************************/ 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按通道分开 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的构造函数 函数参数: 1)const string& model_file--------------预测阶段的网络模型描述文件 2)const string& trained_file------------已经训练好的caffemodel文件所在的路径 3)const string& mean_file---------------均值文件所在的路径 4)const string& label_file--------------类别标签文件所在的路径 ************************************************************************************************************************************/ 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,H,W),在这里,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); //【4】加载标签文件-----------------Load labels std::ifstream labels(label_file.c_str()); CHECK(labels) << "Unable to open labels file " << label_file; string line; while (std::getline(labels, line)) { labels_.push_back(string(line)); //【9】将所有的标签放入labels这个vector容器中 } //【10】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], static_cast<int>(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; } /************************************************************************************************************************* 函数说明: 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) { ::google::InitGoogleLogging(argv[0]); string model_file = "F:\\caffeInstall2013\\caffe-master\\models\\bvlc_reference_caffenet\\deploy.prototxt"; string trained_file = "F:\\caffeInstall2013\\caffe-master\\models\\bvlc_reference_caffenet\\bvlc_reference_caffenet.caffemodel"; string mean_file = "F:\\caffeInstall2013\\caffe-master\\models\\bvlc_reference_caffenet\\imagenet_mean.binaryproto"; string label_file = "F:\\caffeInstall2013\\caffe-master\\models\\bvlc_reference_caffenet\\synset_words.txt"; string file = "F:\\caffeInstall2013\\caffe-master\\examples\\images\\cat.jpg"; 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); /* Print the top N predictions. */ 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"); }
相关文章推荐
- 【Caffe的C++接口使用说明(三)】Ubuntu14.04下Caffe利用训练好的模型进行分类的C++接口使用说明(三)
- 【Caffe的C++接口使用说明(一)】caffe_windows下的第一个测试程序学习教程
- 【Caffe的C++接口使用说明四)】Caffe中分类C++接口Demo源代码的解析
- 【Caffe的C++接口使用说明五)】win10+cuda7.5+caffe+vs2013环境搭建(CPU版本)
- Caffe-使用C++的分类接口
- 【Caffe的C++接口使用说明二)】caffe_windows下的C++接口的使用
- 使用深度学习Caffe框架的C++接口进行物体分类
- FastDFS的配置、部署与API使用解读(8)FastDFS多种文件上传接口详解
- C++文件如何在linux下生成动态库So,以及如何使用这个动态库
- syslogd以及syslog.conf文件解读说明
- 如何使用opencv的c++接口来读取、写结构体数组到xml文件中
- 对于C++中头文件内使用条件编译的说明
- C++模板类函数的使用以及如何使用时间作为文件名保存文件
- C/C++:递归使用:使用递归反向写文件(例子)以及字符串翻转输出
- C++ 中关于 iostream、源文件命名约定以及运行 GNU (微软)编译器的几点说明
- 【测试技术】ant在测试中的使用@文件以及目录的读写删和复制
- C++ VS 2008中ATL的创建,使用。并在C++项目以及C#项目中进行调用测试
- 转载:FastDFS的配置、部署与API使用解读(8)FastDFS多种文件上传接口详解
- C++文件如何在linux下生成动态库So,以及如何使用这个动态库
- Hi3531 SDK 安装以及升级使用说明 分类: HI3531 2013-08-20 17:26 2657人阅读 评论(0) 收藏