把Caffe集成到c++项目的流程

时间:2021-10-18 23:47:15

前言

本来用着MatConvNet挺舒服的,奈何集成到c++的项目中实在是痛苦啊,做成DLL没问题,问题是每次运行都要加载MCR,加载一次起码两分钟逼得我在项目里把CNN部分做成mock,测试其他功能的时候禁掉CNN省的加载搞的我烦心。于是我转向了用c++开发的Caffe的怀抱,当然用的是c++的接口和matlab的接口,py的接口没有用过,毕竟从本科开始接触图像处理都是玩的matlab

准备工作

本人运行环境是VS2013,win7 64位,matlab2005rb,小结构网络不用GPU
按着这个兄弟的博文http://blog.csdn.net/jiangjieqazwsx/article/details/53292326来编译就好了,要注意的就是用VS2013,别用10或者12,反正我用12它没办法自动加载依赖库,话说这个caffe的依赖库还真是多。还有最好自己在网上把NugetPackages下下来,省的VS慢慢下了。

这些机器学习框架的基本流程都是:
1.生成训练集,测试集,验证集
2.构建网络
3.开始迭代训练
4.测试
以此为线索简要的介绍一下,不够详细的留言或者私心交流吧

生成训练集

caffe支持的输入格式很多,最常见的是lmdb,leavedb。在你编译好caffe后,在caffe-windows\Build\x64\Release目录下找到convert_imageset.exe程序,用它来生成lmdb。
在caffe-windows\examples\imagenet下有个create_imagenet.sh就是调用这个程序的,你可以根据自己的需求来生成train,val,test,该脚本需要的额外材料是图片的文件命后+标签,自己写个matlab脚本生成一下个文本吧。window下如何运行sh脚本?安装个cygwin吧少年

构建网络

caffe需要三个配置文件,一个训练时的网络结构,一个使用时的网络结构,一个训练信息。在mnist中分别为lenet_train_test.prototxt、deploy.prototxt、lenet.prototxt
之前没有好好看文档,训练好后还想用lenet_train_test去测试,结果肯定是不行啊。一般train_test最后一层是softmax-loss,其上层是fc+label,而deploy最后一层是softmax,本来就是拿来预测的当然上一层也不会有label了。
具体如何去写参考这个http://wenku.baidu.com/link?url=S2mVH6XTS4Y9TOrCGkTPWrqVqy0Dx9waaZdANYt4G30fruX5Pkn5w1CzXHJoCqkZqJuyqoFnN6sLMtu1iNwujlgldjvLcdE7RTzRgBm5jfO

训练

直接给matlab脚本吧,从这个同学那http://blog.csdn.net/zb1165048017/article/details/52653501拿过来的

clear  
clc
close all
format long %设置精度,caffe的损失貌似精度在小数点后面好几位
caffe.reset_all%重设网络,否则载入两个网络会卡住
solver=caffe.Solver('lenet_solver.prototxt'); %载入网络
loss=[];%记录相邻两个loss
accuracy=[];%记录相邻两个accuracy
hold on%画图用的
accuracy_init=0;
loss_init=0;
for i=1:50
solver.step(100);%每迭代一次就取一次loss和accuracy
iter=solver.iter();
loss=solver.net.blobs('loss').get_data();%取训练集的loss
accuracy=solver.test_nets.blobs('accuracy').get_data();%取验证集的accuracy
%画loss折线图
x=[i-1,i];
y=[loss_init loss];
plot(x,y,'r-')
drawnow
loss_init=loss;
end

测试

这里测试是指自己拿单独的图片去试一试,在训练的时候已经给了val的信息了,准确度可以用这个去看
1.matlab版本

clear  
clc
close all
format long %设置精度,caffe的损失貌似精度在小数点后面好几位
caffe.reset_all%重设网络
caffe.set_mode_cpu();%设置CPU模式
model = 'deploy.prototxt';%模
weights = 'snapshot_iter_5000.caffemodel';%参
net=caffe.Net(model,'test');%测
net.copy_from(weights);%获取训练参数
net
im=caffe.io.load_image('../mnist_img/00001-5.bmp');%读取图片
imshow(im)
net.blobs('data').set_data(im);%设置输入层
net.forward_prefilled();%前向网络
pros=net.blobs('prob').get_data();%提取输出层
[~, maxlabel] = max(pros);
maxlabel-1;

注意啊,这里有个坑,同样一张图你试试用caffe.io.load_image和imread去读,看看有什么差别。我当时随便拿了张训练集的图片去测,发现居然算的不对,这个可不正常,那时我就是用imread去读取图片的。imread就是我们正常读图的方式,而caffe.io.load_image则是按列优先去读的,看上去图片被左右翻转在逆时针90度旋转了。

2.cpp版本

其实你去网上搜大部分人都是给的caffe-windows\examples\cpp_classification下面那个cpp,写的比较通用。我这里给个简化版的

Duco_Classifier.h:

///
/// 基于CNN的图片识别器
/// 1.最大识别100*100的图片
/// 2.只识别灰度图
#ifndef _DUCO_CLASSIFIER_H_
#define _DUCO_CLASSIFIER_H_
#include<iostream>
#include<vector>
#include<caffe/caffe.hpp>
using namespace caffe;
using namespace std;
#ifndef BYTE
#define BYTE unsigned char
#endif
class Duco_Classifer{
public:
Duco_Classifer();
~Duco_Classifer();
//加载模型 deploy.prototxt
void LoadingModel(const char* modelFile);
//加载训练权重参数 .caffemodel
void LoadingTrainedPara(const char* trainedFile);
//识别 返回最大标签,从0开始,如果为-1则表示置信度过低
int Recognition(BYTE *pImg, int w, int h);
private:

private:
Net<float> *m_pNet;//CNN网络
float m_imgF32[100 * 100];//识别图像
};

#endif

Duco_Classifier.cpp:

#include"Duco_Classifier.h"
#include"Duco_Classifer_Def.h"
//构造
Duco_Classifer::Duco_Classifer(){
Caffe::set_mode(Caffe::CPU);
m_pNet = NULL;
memset(m_imgF32, 0, sizeof(m_imgF32));
}

//析构
Duco_Classifer::~Duco_Classifer(){
if(m_pNet != NULL)
delete[]m_pNet;
}

//加载模型
void Duco_Classifer::LoadingModel(const char* modelFile){
if (m_pNet != NULL)//删除上一个模型
delete []m_pNet;
m_pNet = new(std::nothrow)Net<float>(modelFile,TEST);
}

//加载训练权重参数 .caffemodel
void Duco_Classifer::LoadingTrainedPara(const char* trainedFile){
m_pNet->CopyTrainedLayersFrom(trainedFile);
}

//识别 返回最大标签,从0开始,
int Duco_Classifer::Recognition(BYTE *pImg, int w, int h){
int i, x, y, maxIndex;
float *pCur,max;
//1.修改数据类型并归一化到[0,1]
for (i = 0; i < w*h; i++){
m_imgF32[i] = pImg[i] * 1.0L / 255;
}
//2.识别
Blob<float>* input_blobs = m_pNet->input_blobs()[0];
memcpy(input_blobs->mutable_cpu_data(), m_imgF32,
sizeof(float)* input_blobs->count());//w*h
m_pNet->ForwardPrefilled();
Blob<float>* output_layer = m_pNet->output_blobs()[0];
const float* pBegin = output_layer->cpu_data();
const float* pEnd = pBegin + output_layer->channels();
//3.返回top 1
maxIndex = 0;
max = *pBegin;
i = 0;
for (const float *pCur = pBegin; pCur < pEnd; i++,pCur++){
DUCO_LOG("%.5f ",*pCur);
if (*pCur>max){
max = *pCur;
maxIndex = i;
}
}
DUCO_LOG("\n");
return maxIndex;
}

调用:

//测试接口
#include<iostream>
#include<vector>
#include<caffe/caffe.hpp>
#include"bmpFile.h"
#include"Duco_Classifier.h"
#include"Duco_Classifer_Def.h"
using namespace caffe;
using namespace std;

const char *model_file = "deploy.prototxt";
const char *trained_file = "snapshot_iter_5000.caffemodel";

FILE *g_fout;
int main(){
g_fout = fopen("log.txt", "w+");
int w, h;
Duco_Classifer classiferMNIST;
classiferMNIST.LoadingModel(model_file);
classiferMNIST.LoadingTrainedPara(trained_file);
BYTE *pImg = Read8BitBmpFile2Img("0.bmp", &w, &h);
int ret = classiferMNIST.Recognition(pImg, w, h);
DUCO_LOG("%d\n", ret);
return 0;
}

我的项目里面图像处理的基本元素都是BYTE,如果你自己项目是opencv的,把元素修改成mat就行,道理都是一样的。此外那个def.h里面定义了一个简单日志的宏

#define DUCO_LOG(...) \
fprintf(g_fout, __VA_ARGS__); fflush(g_fout);

基本流程就是1.设置CPU模式或GPU模式 2.加载模型 3.加载参数 4.向输入层传递数据 5.前向计算 6.取输出层数据Read8BitBmpFile2Img是个读位图的函数,网上自己搜一下吧

代码是很简单的,关键要建立项目有些麻烦。参考以下两篇博客
http://blog.csdn.net/wuzhiyang95_xiamen/article/details/52574668
http://blog.csdn.net/ws_20100/article/details/50499948
第一篇是讲配置的,就是告诉你要加哪里include哪些lib。第二篇是解决你肯定会碰到的问题
因为matlab遇到的那个坑,我开始在cpp里面也把图片类似的旋转了一边,发现结果不对,cpp里面的图片正常读去就行了,不要做旋转操作的。

小结

写的比较简单,主要是汇总近日收集到的有用的博客,以及整个流程。希望跟我有一样需求的同学能节省点时间。以后在项目中再遇到caffe的坑再和各位分享吧。