在上一篇博客点击打开链接中,笔者提到了对caffe分类程序的工程化,那么本篇博客笔者就来实践一下对caffe分类的工程化。
首先进行一下需求分析:在工程中,往往是用一张图片作为输入,并返回该输入图片的分类结果,也就是说,需要把分类程序放在一个.so链接库中,并在主函数中调用该链接库。
请各位读者朋友们注意,笔者在进行实验时,还是引用了上一篇博客中提到的检测图片中是否包含岔路口的模型,因此请对实验内容不了解的读者朋友简单浏览一下笔者上一篇博客点击打开链接,在本篇博客中笔者主要讲解如何将caffe工程化。
首先,笔者的文件夹中内容如下:
其中,lib文件夹下内容为:
笔者先说明一下lib文件夹下的classification.hpp和classification.cpp,先将源码放在下面:
首先是classification.hpp的源码:
#ifndef CLASSIFICATION_HPP #define CLASSIFICATION_HPP #include "caffe/caffe.hpp" #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <algorithm> #include <iosfwd> #include <memory> #include <string> #include <utility> #include <vector> #include <sstream> using namespace caffe; using namespace std; using namespace cv; class Classifier { public: Classifier();//构造函数进行对象初始化 vector<float> Classify(const Mat& img);//Classify函数进行网络前传获得输入对应于各类的概率 private: void Init_Img(const Mat& img, std::vector<Mat>* input_channels);//将输入图片放入网络的输入blob private: shared_ptr<Net<float> > net_; Size input_geometry_; int num_channels_; Mat mean_; }; #endif然后是classification.cpp的源码:
#include "caffe/caffe.hpp" #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <algorithm> #include <iosfwd> #include <memory> #include <string> #include <utility> #include <vector> #include <sstream> #include "classification.hpp" using namespace caffe; using namespace std; using namespace cv; class Classifier; Classifier::Classifier() { Caffe::set_mode(Caffe::GPU);//设置caffe工作模式为GPU string model_file = "/home/ubuntu/classification_modified/lib/fork_net.prototxt";//网络定义文件 string trained_file = "/home/ubuntu/classification_modified/lib/fork.caffemodel";//训练得到的网络参数参数文件 net_.reset(new Net<float>(model_file, TEST));//设置网络为训练模式,只进行前向传播 net_->CopyTrainedLayersFrom(trained_file);//导入网络参数 Blob<float>* input_layer = net_->input_blobs()[0]; input_geometry_ = Size(input_layer->width(), input_layer->height());//获取网络规定的输入图像尺寸(宽与高) Blob<float>* output_layer = net_->output_blobs()[0]; } vector<float> Classifier::Classify(const Mat& img) { num_channels_ = 3;//在这里网络接受三通道的图片 Blob<float>* input_layer = net_->input_blobs()[0]; input_layer->Reshape(1, num_channels_, input_geometry_.height, input_geometry_.width);//网络输入blob(1,3,height,width) net_->Reshape();//初始化网络各层 std::vector<Mat> input_channels; Init_Img(img, &input_channels);//将输入图片放入网络的输入blob net_->Forward();//网络前向传播 Blob<float>* output_layer = net_->output_blobs()[0]; const float* begin = output_layer->cpu_data(); const float* end = begin + output_layer->channels(); return vector<float>(begin, end);//获取输入图片属于各个类的概率 } void Classifier::Init_Img(const Mat& img, std::vector<Mat>* input_channels) { Blob<float>* input_layer = net_->input_blobs()[0]; int width = input_layer->width();//网络规定的输入图像的宽 int height = input_layer->height();//网络规定的输入图像的高 float* input_data = input_layer->mutable_cpu_data(); for (int i = 0; i < input_layer->channels(); ++i) { Mat channel(height, width, CV_32FC1, input_data); input_channels->push_back(channel); input_data += width * height; }//这个for循环将网络的输入blob同input_channels关联起来 Mat sample_float; img.convertTo(sample_float, CV_32FC3); Mat sample_normalized; subtract(sample_float, Scalar(129.52675, 128.78506, 116.44242), sample_normalized);//减去均值,均值可以自己求,也可以通过.binaryproto均值文件求出 split(sample_normalized, *input_channels);//将输入图片放入input_channels,即放入了网络的输入blob }
在classification.cpp中,构造函数Classifier()进行了初始化工作,而Classify函数则进行了网络的前传工作,Classify函数调用了Init_Img函数,该函数将输入图片放入网络的输入blob中,值得注意的是,在图片减去均值的时候,笔者省略掉了均值文件,取而代之的是直接减去了数据集的均值,在这里数据集的均值可以自己求(数据集图片每个通道的均值),或者可以利用训练模型时生成的均值文件(.binaryproto)通过caffe官方的classification.cpp中的SetMean函数求。Classify函数最后直接返回输入图片对应每个类别的概率,放在一个vector<float>中,笔者省略了标签文件。
然后,lib文件夹下的CMakeLists.txt文件如下所示:
cmake_minimum_required (VERSION 2.8) SET (SRC_LIST classification.cpp) include_directories ( /home/ubuntu/caffe/include /usr/local/include /usr/local/cuda/include /usr/include /home/ubuntu/caffe/cmake ) add_library(classification SHARED ${SRC_LIST})请注意在add_library表示编译时在lib文件夹下会生成libclassification.so文件,下面在lib文件夹下执行
cmake .
make
两个指令:
大家可以看到在lib文件夹下已经生成了libclassification.so
现在我们到lib的上一级文件夹,笔者这里是classification_modified文件夹,在里面的main.cpp代码如下所示:
#include "classification.hpp" #include <vector> using namespace std; int main(int argc, char** argv) { clock_t start_time1,end_time1,start_time2,end_time2; ::google::InitGoogleLogging(argv[0]); start_time1 = clock(); Classifier classifier;//进行类对象初始化 end_time1 = clock(); double seconds1 = (double)(end_time1-start_time1)/CLOCKS_PER_SEC; cout<<"init time="<<seconds1<<"s"<<endl; string file = "/home/ubuntu/classification_modified/test_images/dCut2.jpg"; Mat img = imread(file, -1);//读入待判别的图片 start_time2 = clock(); vector<float> result = classifier.Classify(img);//进行网络前传 end_time2 = clock(); double seconds2 = (double)(end_time2-start_time2)/CLOCKS_PER_SEC; cout<<"classify time="<<seconds2<<"s"<<endl; for(size_t i=0;i<result.size();++i)//输出输入图片属于各个类的概率 cout<<result[i]<<endl; return 0; }main.cpp对应的CMakeLists.txt文件如下所示:
cmake_minimum_required (VERSION 2.8) project (classification_modified) add_executable(classification_modified main.cpp) include_directories ( /home/ubuntu/caffe/include /home/ubuntu/classification_modified/lib /usr/local/include /usr/local/cuda/include /usr/include ) target_link_libraries(classification_modified /home/ubuntu/classification_modified/lib/libclassification.so /home/ubuntu/caffe/build/lib/libcaffe.so /usr/lib/arm-linux-gnueabihf/libopencv_highgui.so /usr/lib/arm-linux-gnueabihf/libopencv_core.so /usr/lib/arm-linux-gnueabihf/libopencv_imgproc.so /usr/lib/arm-linux-gnueabihf/libglog.so /usr/lib/arm-linux-gnueabihf/libboost_system.so )请注意在target_link_libraries中一定要加上生成的libclassification.so文件,然后在文件夹下执行
cmake .
make
两条语句,生成可执行文件。
然后执行这个可执行文件,在main.cpp中规定的待检测的图片为
结果为:
可见,得到的图中不包含岔路口的概率为0.00975813,而包括岔路口的概率为0.990242,大功告成!
欢迎阅读笔者后续博客,各位读者朋友的支持与鼓励是我最大的动力!
written by jiong
珍惜淡定的心境 苦过后更加清