mini-caffe编译,用BLVC caffe编译的mnist模型进行测试

来源:互联网 发布:淘宝pc端搭配套餐 编辑:程序博客网 时间:2024/06/16 11:39

mini-caffe是最小化Caffe的运行版本,只用来forward,运算效率高、占用小,因此其极其适合用于在线测试。但是,如果你自己实现了非官方caffe的Layer,同样需要在mini-caffe中自己实现对应的计算代码。

这篇文章用VS2015编译mini-caffe项目,记载caffe训练mnist数据生成的模型和deploy.prototxt,对测试图片进行分类。

1、编译准备

下载mini-caffe项目,https://github.com/luoyetx/mini-caffe,解压到自己的存放目录即可。

另外,mini-caffe编译需要protobuf库,同样需要自己下载编译,下载地址https://github.com/google/protobuf

在我这里,mini-caffe存放地址如下;protobuf解压存放在mini-caffe-master\3rdparty\src\protobuf下。

image image

2、编译protobuf

打开CMake工具,指定source目录为E:/Program Files/Tools/mini-caffe-master/3rdparty/src/protobuf/cmake,

build目录为E:/Program Files/Tools/mini-caffe-master/3rdparty/src/protobuf/cmake/build。

点击Configure,选择编译器Visual Studio 14 2015 Win64,取消勾选protobuf_BUILD_TESTS和protobuf_MSVC_STATIC_RUNTIME,再次点击Configure,再点击Generate,就会生成protobuf.sln解决方案。

同样也可以用cmake命令:

cd 3rdparty/src/protobuf/cmakemkdir buildcd buildcmake .. -Dprotobuf_BUILD_TESTS=OFF -Dprotobuf_MSVC_STATIC_RUNTIME=OFF -G "Visual Studio 14 2015 Win64"

image

打开protobuf.sln,编译生成Debug和Release版本,保存在protobuf/CMake/build/Debug和protobuf/CMake/build/Release文件夹下面。

有了这2个版本的库文件,就可以编译mini-caffe了。

3、编译mini-caffe

编译mini-cafe之前,有2个准备步骤。

(1)先要复制依赖文件到指定位置,大致过程为:

拷贝3rdparty\src\protobuf\src下的google文件夹到mini-caffe-master\3rdparty\include\下 (其实只需要.h文件);

拷贝protobuf\cmake\build\Debug\libprotobufd.lib 和 protobuf\cmake\build\Release\libprotobuf.lib到3rdparty\lib\下;

拷贝 protobuf\cmake\build\Release\protoc.exe 到 3rdparty\bin\下;

(2)生成caffe.pb.h和caffe.pb.cc

利用前面复制的操作,执行下面代码即可

"./3rdparty/bin/protoc" -I="./src/proto" --cpp_out="./src/proto" "./src/proto/caffe.proto"

mini-caffe已经做好了上述两个步骤步骤的脚本,可以分别点击mini-caffe-master目录下的两个批处理文件执行,分别为copydeps.bat 和 generatepb.bat。

完成上述两个准备步骤之后,就可以用CMake生成mini-caffe.sln了,命令行或者CMake的窗口工具都可以。(可以选择是否使用GPU)

mkdir buildcd buildcmake .. -G "Visual Studio 14 2015 Win64"

同样,编译可以生成Debug和Release两个版本的ceffe.lib和caffe.dll。后面就可以拿这个开始测试了。

4、测试mnist模型

在前面的博客中有用官方的classification.exe实现对mnist手写字符图像的识别(传送门),尽管改写后从篇幅上略有减小,但是整个网络中还有不少的东西可以去掉,例如网络反向计算的数据,在测试中不会用到的计算层等等。相较而言,mini-caffe只包含了forward计算,从网络Net上去掉了不需要的东西,并且在前向计算后清空了中间的所有层数据,极大的减小了空间的消耗,并且计算速度较快。

#include <caffe/caffe.hpp>#include <opencv2/core/core.hpp>#include <opencv2/highgui/highgui.hpp>#include <opencv2/imgproc/imgproc.hpp>#include <chrono>/*! \brief Timer */class Timer {using Clock = std::chrono::high_resolution_clock;public:/*! \brief start or restart timer */inline void Tic() {start_ = Clock::now();}/*! \brief stop timer */inline void Toc() {end_ = Clock::now();}/*! \brief return time in ms */inline double Elasped() {auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_ - start_);return duration.count();}private:Clock::time_point start_, end_;};using namespace caffe;  // NOLINT(build/namespaces)using std::string;static bool PairCompare(const std::pair<float, int>& lhs,const std::pair<float, int>& rhs) {return lhs.first > rhs.first;}void main(){string model_file =   R"(E:\ProgramData\caffe-windows\data\mnist\windows\lenet.prototxt)";string trained_file = R"(E:\ProgramData\caffe-windows\data\mnist\windows\snapshot_lenet_mean\_iter_10000.caffemodel)";string mean_file =    R"(E:\ProgramData\caffe-windows\data\mnist\windows\mean.binaryproto)";string label_file =   R"(E:\ProgramData\caffe-windows\data\mnist\windows\synset_words.txt)";string file =         R"(E:\ProgramData\caffe-windows\data\mnist\windows\3.bmp)";//////////////////////////////////////////////////////////////////////////////  读取网络Net, 指定数据类型是 float, 后面有关cv::Mat的type应该为 CV_32Fif (caffe::GPUAvailable()) caffe::SetMode(caffe::GPU, 0); // 若有GPU可用,设置为GPU模式shared_ptr<caffe::Net> net_( new caffe::Net(model_file));net_->CopyTrainedLayersFrom(trained_file);//////////////////////////////////////////////////////////////////////////// //  标签数据(可不用)cv::Size input_geometry_;int num_channels_;cv::Mat mean_;std::vector<string> labels_ = { "zero","one","two","three","four","five","six","seven","Eight","Nine" };////////////////////////////////////////////////////////////////////////////// 输入层,只能通过blob_by_name获取,没有input_blobs()函数//shared_ptr<caffe::Blob> input_layer = net_->blob_by_name("data");// 小写,尽管lenet.prototxt输入层name为"Data"input_geometry_ = cv::Size(input_layer->width(), input_layer->height());num_channels_ = input_layer->channels();std::cout << "input shape:    "<< input_layer->shape_string()<< std::endl;std::cout << "input size:     "<< input_geometry_<< std::endl; std::cout << "input channels: " << num_channels_<< std::endl;//////////////////////////////////////////////////////////////////////////////  从文件中读取均值图像数据shared_ptr<Blob> mean_blob = ReadBlobFromFile(mean_file); std::vector<cv::Mat> channels;float* data = mean_blob->mutable_cpu_data();//for (int i = 0; i < num_channels_; ++i) {//cv::Mat channel(mean_blob->height(), mean_blob->width(), CV_32FC1, data);//channels.push_back(channel);//data += mean_blob->height() * mean_blob->width();//}//cv::Mat mean;//cv::merge(channels, mean);// 代替上面代码程序,这里已经明确是 1*1*28*28 的Blob,  输入Blob的shape一样cv::Mat mean(mean_blob->height(), mean_blob->width(), CV_32FC1, data); cv::Scalar channel_mean = cv::mean(mean); // 均值图像, 每个像素为 均值图像的平均亮度值mean_ = cv::Mat(input_geometry_, mean.type(), channel_mean);//////////////////////////////////////////////////////////////////////////////  读入图像,并将图像数据写入到网络输入层Blobcv::Mat img = cv::imread(file, -1);if (!img.data) {std::cout << "Unable to decode image.\nQuit." << std::endl;system("pause");return;}// 明确输入是  单通道数据 1*1*28*28if (img.channels() == 3 && num_channels_ == 1)cv::cvtColor(img, img, cv::COLOR_BGR2GRAY);else if (img.channels() == 4 && num_channels_ == 1)cv::cvtColor(img, img, cv::COLOR_BGRA2GRAY);//else if (img.channels() == 4 && num_channels_ == 3)cv::cvtColor(img, img, cv::COLOR_BGRA2BGR);//else if (img.channels() == 1 && num_channels_ == 3)cv::cvtColor(img, img, cv::COLOR_GRAY2BGR);if (img.size() != input_geometry_)cv::resize(img, img, input_geometry_);img.convertTo(img, CV_32FC1);//input_layer->Reshape({ 1,1,28,28 });  // 不需要执行,已经是确定的float *dataInput = input_layer->mutable_cpu_data();//img -= mean_;  // 去均值//img.copyTo(cv::Mat(input_geometry_, mean_.type(), dataInput));// 去均值后图像数据拷贝到 data blob,方法1//memcpy(dataInput, img.data, sizeof(float)*img.cols*img.rows);// 去均值后图像数据拷贝到 data blob,方法2// 注释img -= mean_; 这两句代替上面的拷贝 cv::Mat dataImg(input_geometry_, CV_32FC1, dataInput);// 去均值后图像数据拷贝到 data blob,方法3dataImg = img - mean_;//////////////////////////////////////////////////////////////////////////////进行forward之后,仅保留第一层和最后一层的Blob数据, 也就是blob_by_name参数只能为"data"和"prob"auto t1 = cv::getTickCount();net_->Forward();auto t2 = cv::getTickCount();std::cout << " Forward time: " << (t2 - t1) / cv::getTickFrequency() * 1000 << " ms" << std::endl;shared_ptr<caffe::Blob> output_layer = net_->blob_by_name("prob"); // InnerProduct 输出std::cout <<" output layer shape: " << output_layer->shape_string() << std::endl;const float* begin = output_layer->cpu_data();const float* end = begin + /*output_layer->channels()*/output_layer->shape(1);   // 只有2维,shape为(1,10)std::vector<float> OutRes = std::vector<float>(begin, end);//  channels()同shape(1), 也即N*C*W*H 的C, 尽管没有W和H//////////////////////////////////////////////////////////////////////////////  输出层的结果进行排序,打印输出//int N = 5;std::vector<std::pair<float, int> > pairs;for (size_t i = 0; i < OutRes.size(); ++i)pairs.push_back(std::make_pair(OutRes[i], static_cast<int>(i)));std::partial_sort(pairs.begin(), pairs.begin() + N, pairs.end(), PairCompare);std::vector<std::pair<std::string, float> > result;std::cout << "=========== Prediction ============" << std::endl;for (int i = 0; i < N; ++i){int idx = pairs[i].second;//result.push_back(std::make_pair(labels_[idx], OutRes[idx]));std::cout << std::fixed  <<  OutRes[idx] << "  " << labels_[idx]<< std::endl;}system("pause");}


在训练生成模型时指定了均值文件,因此最好在测试时减去均值,使测试结果更加准确。

测试图片(放大后显示),减去均值测试结果,不减均值测试结果,依次如下图所示:


imageimageimage


附件:VS2015编译的64位mini-caffe库下载地址http://download.csdn.net/download/wanggao_1990/10000792