博主所在的教研室要使用Caffe实现机械臂的控制环节中的预测部分,其中涉及到了Caffe的编译以及LSTM层的添加到最终的caffe的动态库和静态库的编译及使用,整个过程一言难尽,特写此博客纪念。
首先是caffe的编译,网上的教程都是大同小异,我只配置了CPU版本的caffe,这里参考的是这位大神的博客,附上链接https://blog.csdn.net/zb1165048017/article/details/51355143,万分感谢!
接下来由于我的工程中是用训练好的网络模型进行预测,需要在caffe里添加LSTM层,这也是非常麻烦且耗费时间的事情,这里参考的是另一位大神的博客,链接附上https://blog.csdn.net/zb1165048017/article/details/59112034,感谢万分!
实际上将添加了LSTM的caffe编译好只是万里长征第一步,最重要的还是真正把caffe用起来,caffe的Windows版本自带了一个分类网络工程给大家练手,具体教程可以看这位大神https://blog.csdn.net/qq_14845119/article/details/52541622,其中需要训练好的模型以及网络结构文件,这里给大家我的百度云:链接:https://pan.baidu.com/s/1wuCmeMsDMwuk8Rya21xBow
提取码:gxq0
但是我写这篇博客的主要目的还是记录一下libcaffe.lib以及libcaffe.dll的编译,我在网上搜索到的资料很少,所以写下这篇博客希望大家看完之后可以少走一些弯路。
这里再提醒大家一次,无论是动态库还是静态库的编译都是在你已经将caffe编译好的基础上进行的!
动态库编译!
其实下载下来的caffe已经为dll的创建铺垫了前95%的工作,我们需要的是将caffe中定义的函数接口导出,供客户端程序调用。c++编写dll的方法是在定义class的头文件中先添加宏定义,如下:
#ifdef DLL_TEST_API
#else
#define DLL_TEST_API _declspec(dllimport)
#endif
Class DLL_TEST_API CDLLTest
{
Public:
CDLLTest();
~CDLLTest();
int Add(int a, int b);
};
打开编译好的caffe中的include目录,找出文件Blob.hpp net.hpp caffe.pb.h(需要修改文件名)common.hpp io.hpp db.hpp benchmark.hpp upgrade.hpp signal_handler.hpp solver.hpp parallel.hpp math_functions.hpp syncedmem.hpp,按上述方法添加代码,这里举一个例子:
#ifndef CAFFE_UTIL_DB_HPP
#define CAFFE_UTIL_DB_HPP
#ifdef BUILD_DLL
#define OS_API __declspec(dllexport)
#else
#define OS_API __declspec(dllimport)
#endif
#include <string>
#include "caffe/common.hpp"
#include "caffe/proto/caffe_pb.h"
namespace caffe { namespace db {
enum Mode { READ, WRITE, NEW };
class OS_API Cursor {
public:
Cursor() { }
virtual ~Cursor() { }
virtual void SeekToFirst() = 0;
virtual void Next() = 0;
virtual string key() = 0;
virtual string value() = 0;
virtual bool valid() = 0;
DISABLE_COPY_AND_ASSIGN(Cursor);
};
class Transaction {
public:
Transaction() { }
virtual ~Transaction() { }
virtual void Put(const string& key, const string& value) = 0;
virtual void Commit() = 0;
DISABLE_COPY_AND_ASSIGN(Transaction);
};
class OS_API DB {
public:
DB() { }
virtual ~DB() { }
virtual void Open(const string& source, Mode mode) = 0;
virtual void Close() = 0;
virtual Cursor* NewCursor() = 0;
virtual Transaction* NewTransaction() = 0;
DISABLE_COPY_AND_ASSIGN(DB);
};
OS_API DB* GetDB(DataParameter::DB backend);
OS_API DB* GetDB(const string& backend);
} // namespace db
} // namespace caffe
#endif // CAFFE_UTIL_DB_HPP
这些加完代码还是远远不够的!你的网络中用到了哪些layer,就要在include/layers目录下找到相应的头文件,同样在其中添加上述宏定义。我修改了以下头文件input_layer.hpp embed_layer.hpp dropout_layer.hpp lstm_layer.hpp inner_product_layer.hpp softmax_layer.hpp slice_layer.hpp split_layer.hpp scale_layer.hpp eltwise_layer.hpp reducation_layer.hpp concat_layer.hpp recurrent.hpp。这里再举一个例子:
#ifndef CAFFE_ELTWISE_LAYER_HPP_
#define CAFFE_ELTWISE_LAYER_HPP_
#ifdef BUILD_DLL
#define OS_API __declspec(dllexport)
#else
#define OS_API __declspec(dllimport)
#endif
#include <vector>
#include "caffe/blob.hpp"
#include "caffe/layer.hpp"
#include "caffe/proto/caffe.pb.h"
namespace caffe {
/**
* @brief Compute elementwise operations, such as product and sum,
* along multiple input Blobs.
*
* TODO(dox): thorough documentation for Forward, Backward, and proto params.
*/
template <typename Dtype>
class OS_API EltwiseLayer : public Layer<Dtype> {
public:
explicit EltwiseLayer(const LayerParameter& param)
: Layer<Dtype>(param) {}
virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top);
virtual void Reshape(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top);
virtual inline const char* type() const { return "Eltwise"; }
virtual inline int MinBottomBlobs() const { return 2; }
virtual inline int ExactNumTopBlobs() const { return 1; }
protected:
virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top);
virtual void Forward_gpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top);
virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);
virtual void Backward_gpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);
EltwiseParameter_EltwiseOp op_;
vector<Dtype> coeffs_;
Blob<int> max_idx_;
bool stable_prod_grad_;
};
} // namespace caffe
#endif // CAFFE_ELTWISE_LAYER_HPP_
这里有一个问题,caffe.pb.h是根据src\caffe\proto\caffe.proto自动生成的,所以编译的时候还要把这个文件改名字,否则就把修改后的caffe.pb.h覆盖,我改成了caffe_pb.h.但是,只做这些改动还是不够,在caffe目录下的src\caffe\proto中的caffe.pb.cc文件也需要修改,因为其中定义了一些函数会在前面提到的一些层的头文件中使用,同样将上述宏定义加入caffe.pb.cc.
一定要记得在预处理器定义中添加BUILD_DLL.
最后再提醒大家不要忘了在配置属性中将配置类型改为dll.
现在右键libcaffe点击重新生成即可!生成的libcaffe.dll在build\x64\Debug/Release中。生成dll的同时还会生成一个libcaffe.lib,这与接下来要编译的静态库不同,它一般是一些索引信息,记录了dll中函数的入口和位置。
静态库编译!
有了动态库编译的经验,静态库编译起来就非常easy了。我们不需要在前面提到的头文件加那些接口定义,只需要在编译好的caffe基础上把设置中的配置属性——常规——配置类型中改为静态库编译即可。
现在右键libcaffe点击重新生成即可!可以发现现在生成的libcaffe.lib与动态库编译生成的lib大小差距悬殊,因为它的索引和实现都在其中。
有问题的铁汁可以在评论区一起分享,我会尽我所能为大家解答!