caffe之classification.cpp 接口源码解读

      第一次写博客,平时我学习东西一般都是从博客上看的,受益匪浅,也觉得养成个写博客的习惯是对自己知识的巩固是极好的,当然如果能帮到别人,那就再好不过了~

      我们知道Caffe本身就是用c++写的,在用caffe训练model时,可以使用Python、Matlab、C++三种接口去训练,然而它本身不提供可视化,所以也只能借助其它的库或者接口,绝大部分就是用python和matlab去可视化,然而对于那些对python和matlab语言不太了解的c++er(比如我),真的很痛苦,因为只能按照教程一步步依葫芦画瓢,有时候代码不work了,自己也找不到原因,所以这篇博客是写给那些用想用c++进行特征可视化,或者说想直接提取图片经过网络中某一层产生的特征向量的同学,给他们一些参考。

      为了不给大家迷惑,我还是分为三个部分来分享吧,这是第一部分:了解caffe的C++接口是怎么工作的

      我们知道,当已经训练好一个model,我们需要用这个model对一张图片进行分类时,caffe是提供了这么一个命令行接口(即c++接口),假设当前已经在caffe根目录下

./build/examples/cpp_classification/classification.bin \        #调用接口
************/deploy.prorotxt  \                                 #网络模型的描述文件
***********/caffemodel  \                                       #训练好的model
***********/mean.binaryproto   \                                #训练时加载的均值文件
***********/synset_words.txt    \                               #标签对应的类别名,比如2类猫狗分类问题,可以是第一行 0 cat  第二行 1 dog
***********/test.jpg                                            #需要测试的图片路径

      程序就会打印出top5的标签以及对应的置信度。现在,我们需要分析这个接口是怎么运作的,也就是看看它的源文件(因为接下来的可视化接口,就是在这个文件上修改进一步得到我们自己想要的功能,比如显示权重图、输出特征图等),该文件保存路径是caffe根目录下的 examples/cpp_classification/classification.cpp,大家可以看看这份源代码

因为代码比较长,有266行,我这就注释一些必要的,对下文有帮助的代码行.

先看主函数

string model_file   = argv[1];      //网络模型描述文件
string trained_file = argv[2];      //训练好的model 
string mean_file    = argv[3];      //均值文件 
string label_file   = argv[4];      //标签对应的类别名
Classifier classifier(model_file, trained_file, mean_file, label_file); //核心的c++类,也就是我们的分类器,由上面四个文件初始化
string file = argv[5];         //需要测试的图片 
std::cout << "---------- Prediction for "
          << file << " ----------" << std::endl;
cv::Mat img = cv::imread(file, -1);       //读取图片到OpenCV中的Mat类中,名字为img
CHECK(!img.empty()) << "Unable to decode image " << file;
std::vector<Prediction> predictions = classifier.Classify(img); //分类函数,对图片img进行分类,得到N个预测结果,这里N默认为5,可以自己修改
/* Print the top N predictions. */
for (size_t i = 0; i < predictions.size(); ++i) {    //分别打印N个预测的标签,以及置信度。按照置信度从大到小排序
   Prediction p = predictions[i];
   std::cout << std::fixed << std::setprecision(4) << p.second << " - \""
             << p.first << "\"" << std::endl;
   }
}

思路很清晰,就是先由四个文件,初始化了一个CNN分类器,然后对需要测试图片,进行分类识别,输出标签和置信度,其中最重要的一行代码,就是

std::vector<Prediction> predictions = classifier.Classify(img);
其对一张图片进行识别分类,所以我们还要继续跟踪,看看Classify函数内部干了点啥,进去后,你会发现,该函数第一行代码,是这样的

std::vector<float> output = Predict(img);
也就是说对于测试图片img,经过一个函数Predict后,转化成了一个float型的向量,所以,我们还得跟踪,其Predict函数是怎么实现的, (这个函数至关重要)

std::vector<float> Classifier::Predict(const cv::Mat& img) {
   Blob<float>* input_layer = net_->input_blobs()[0]; //这是网络的数据输入层
   //调整数据输入层的尺寸,使之符合输入图片的尺寸,比如比如1*3*227*227,也就是一张图片,三个通道,图片尺寸是227*227
   input_layer->Reshape(1, num_channels_,input_geometry_.height, input_geometry_.width);
   /* Forward dimension change to all layers. */
   //整个网络,根据输入层,以及构造函数中的caffemodel.deploy.prototxt等文件中的参数(比如卷积核个数,卷积核大小啊),
   //一层层地开辟空间,已为接下来的计算做好准备
   net_->Reshape(); 

   //接下来三行,将测试图片img送入网络的数据输入层,并且做了一系列的预处理、归一化、缩放等操作
   //具体的,可以看函数的实现细节,这里就不细讲
   std::vector<cv::Mat> input_channels;
   WrapInputLayer(&input_channels);
   Preprocess(img, &input_channels);

   net_->Forward();   //这是一个很重要的操作,图片送入网络后,必须进行前向传播,网络中各层才有响应,以及最后才能输入分类结果
   
   Blob<float>* output_layer = net_->output_blobs()[0];   //网络的输出层,比如imagenet中是1000类,则该层的输出层的维度是1*1000
   //很容易看出来,下面三行代码,就是取出网络的输出层的N维向量(比如imagenet中是1000维),然后返回
   const float* begin = output_layer->cpu_data();
   const float* end = begin + output_layer->channels();
   return std::vector<float>(begin, end);
}
结合注释,相信大家很同意看出这个函数到底干了点什么,总而言之,就是说,这个函数,给它一张测试图片,然后它就把这张图片,丢进网络里,进行一次前向传播,然后再把网络的输出返回,也就是将一张图片,映射成一个特征向量(相信大家也许猜到了,这个特征向量,正好就是网络最后一层的softmax层的输出值),,比如猫狗2分类这个案例中,假设猫是0类,狗是1类,我们将一个猫的图片丢进这个函数,那么一个理想的分类器的话,这个函数会返回一个特征向量【1,0】,第一个数1,表示0类也就是猫类的置信度,第二个数0表示1类也就是狗类的置信度。 (千万记住,caffe中标签是从0开始依次计数的。。。)

摁,弄明白Predict这个函数后,我们再回到Classify函数内部,我们会看到,它正就是对图片经过Predict函数返回的特征向量,再进行处理,得到top N 置信度及对应的标签的过程,具体实现大家自行查看~


现在,我们知道了用训练好的model对图片进行识别分类的源码,而其中的Predict是最为重要,因为内部进行了网络的前向传播(必须进行前向传播,网络中各层才有响应,以及最后才能输入分类结果),那么接下来的网络权值可视化(严格来讲,网络权重的可视化,其实是不需要前向传播也能看到的,因为那是一个model已经学习好的参数,加载model就可以得到参数,并不需要前向传播一次),以及图片经过网络某一层的特征图可视化,都可以在这个函数中添加代码实现!

明天会更新(2)  用c++进行model的网络权值可视化,比如学到的conv1层、conv2层权重参数

                      (3)     用c++结合OpenCV,测试图片经过网络中各层产生的对应的特征图的可视化





猜你喜欢

转载自blog.csdn.net/zxj942405301/article/details/71176400
今日推荐