欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-30 18:45:25
...

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下。

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

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"

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

打开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");
}

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

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

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

附件: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。