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

来源:互联网 发布:mac 标志发亮 编辑:程序博客网 时间:2024/05/01 20:13
#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 predictionclass 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_表示了数据集的均值,格式为Matstd::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_ONLYCaffe::set_mode(Caffe::CPU);#elseCaffe::set_mode(Caffe::GPU);#endif//【1】加载【网络模型的描述文件】--Load the networknet_.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 fileSetMean(mean_file);                               //【9】进行均值的设置 //【4】加载标签文件---Load labelsstd::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>& lhs2)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 format3)设置数据集的平均值函数参数:        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操作。我么不需要依赖cudaMemcpy2D2)最后一个预处理操作将直接将单独的通道写入输入层函数参数:        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& img2)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);elsesample = img;cv::Mat sample_resized;if (sample.size() != input_geometry_)                //【1】将输入图像的尺寸强制转化为网络规定的输入尺寸cv::resize(sample, sample_resized, input_geometry_);elsesample_resized = sample;cv::Mat sample_float;if (num_channels_ == 3)                              //【2】将输入图像转化成为网络前传合法的数据规格sample_resized.convertTo(sample_float, CV_32FC3);elsesample_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关联起来了,因此在这里实际上是把                                                     //     图像送入了网络的输入blobcv::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");}}