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

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。(建议下载release下的)

在我这里,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/cmake
mkdir build
cd build
cmake .. -Dprotobuf_BUILD_TESTS=OFF -Dprotobuf_MSVC_STATIC_RUNTIME=OFF -G "Visual Studio 14 2015 Win64"

image

扫描二维码关注公众号,回复: 125404 查看本文章

打开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 build
cd build
cmake .. -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_32F

	if (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);

	//////////////////////////////////////////////////////////////////////////
	//
	//  读入图像,并将图像数据写入到网络输入层Blob

	cv::Mat img = cv::imread(file, -1);
	if (!img.data) {
		std::cout << "Unable to decode image.\nQuit." << std::endl;
		system("pause");
		return;
	}

	// 明确输入是  单通道数据 1*1*28*28
	if (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,方法3
	dataImg = 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


5、可能遇到的错误

当使用cudnn版本不同,可能会遇到一些错误,例如使用cudnn6.0时,会提示"caffe cudnnSetConvolution2dDescriptor error: too few arguments in function call ...”的错误,查看cudnn发现需要dataType::type类型的参数。若用cudnn6,需要修改有关所有的调用地方的模板类型,包括头文件和源文件。建议用cudnn5.1。

猜你喜欢

转载自blog.csdn.net/wanggao_1990/article/details/78128797