如何在程序中调用Caffe做图像分类

Caffe是目前深度学习比较优秀好用的一个开源库,采样c++和CUDA实现,具有速度快,模型定义方便等优点。学习了几天过后,发现也有一个不方便的地方,就是在我的程序中调用Caffe做图像分类没有直接的接口。Caffe的数据层可以从数据库(支持leveldb、lmdb、hdf5)、图片、和内存中读入。我们要在程序中使用,当然得从内存中读入。参见http://caffe.berkeleyvision.org/tutorial/layers.html#data-layers和MemoryDataLayer源码,我们首先在模型定义文件中定义数据层:

layers {
  name: "mydata"
  type: MEMORY_DATA
  top: "data"
  top: "label"
  transform_param {
    scale: 0.00390625
  }
  memory_data_param {
    batch_size: 10
    channels: 1
    height: 24
    width: 24
  }
}

这里必须设置memory_data_param中的四个参数,对应这些参数可以参见源码中caffe.proto文件。现在,我们可以设计一个Classifier类来封装一下:

#ifndef CAFFE_CLASSIFIER_H
#define CAFFE_CLASSIFIER_H

#include <string>
#include <vector>
#include "caffe/net.hpp"
#include "caffe/data_layers.hpp"
#include <opencv2/core.hpp>
using cv::Mat;

namespace caffe {

template <typename Dtype>
class Classifier {
 public:
  explicit Classifier(const string& param_file, const string& weights_file);
  Dtype test(vector<Mat> &images, vector<int> &labels, int iter_num);
  virtual ~Classifier() {}
  inline shared_ptr<Net<Dtype> > net() { return net_; }
  void predict(vector<Mat> &images, vector<int> *labels);
  void predict(vector<Dtype> &data, vector<int> *labels, int num);
  void extract_feature(vector<Mat> &images, vector<vector<Dtype>> *out);

 protected:
  shared_ptr<Net<Dtype> > net_;
  MemoryDataLayer<Dtype> *m_layer_;
  int batch_size_;
  int channels_;
  int height_;
  int width_;

  DISABLE_COPY_AND_ASSIGN(Classifier);
};
}//namespace
#endif //CAFFE_CLASSIFIER_H

构造函数中我们通过模型定义文件(.prototxt)和训练好的模型(.caffemodel)文件构造一个Net对象,并用m_layer_指向Net中的memory data层,以便待会调用MemoryDataLayer中AddMatVector和Reset函数加入数据。

#include <cstdio>

#include <algorithm>
#include <string>
#include <vector>

#include "caffe/net.hpp"
#include "caffe/proto/caffe.pb.h"
#include "caffe/util/io.hpp"
#include "caffe/util/math_functions.hpp"
#include "caffe/util/upgrade_proto.hpp"
#include "caffe_classifier.h"

namespace caffe {

template <typename Dtype>
Classifier<Dtype>::Classifier(const string& param_file, const string& weights_file) : net_()
{
  net_.reset(new Net<Dtype>(param_file, TEST));
  net_->CopyTrainedLayersFrom(weights_file);
  //m_layer_ = (MemoryDataLayer<Dtype>*)net_->layer_by_name("mnist").get();
  m_layer_ = (MemoryDataLayer<Dtype>*)net_->layers()[0].get();
  batch_size_ = m_layer_->batch_size();
  channels_ = m_layer_->channels();
  height_ = m_layer_->height();
  width_ = m_layer_->width();
}

template <typename Dtype>
Dtype Classifier<Dtype>::test(vector<Mat> &images, vector<int> &labels, int iter_num)
{
    m_layer_->AddMatVector(images, labels);
    //
    int iterations = iter_num;
    vector<Blob<Dtype>* > bottom_vec;

  vector<int> test_score_output_id;
  vector<Dtype> test_score;
  Dtype loss = 0;
  for (int i = 0; i < iterations; ++i) {
    Dtype iter_loss;
    const vector<Blob<Dtype>*>& result =
        net_->Forward(bottom_vec, &iter_loss);
    loss += iter_loss;
    int idx = 0;
    for (int j = 0; j < result.size(); ++j) {
      const Dtype* result_vec = result[j]->cpu_data();
      for (int k = 0; k < result[j]->count(); ++k, ++idx) {
        const Dtype score = result_vec[k];
        if (i == 0) {
          test_score.push_back(score);
          test_score_output_id.push_back(j);
        } else {
          test_score[idx] += score;
        }
        const std::string& output_name = net_->blob_names()[
            net_->output_blob_indices()[j]];
        LOG(INFO) << "Batch " << i << ", " << output_name << " = " << score;
      }
    }
  }
  loss /= iterations;
  LOG(INFO) << "Loss: " << loss;
  return loss;
}

template <typename Dtype>
void Classifier<Dtype>::predict(vector<Mat> &images, vector<int> *labels)
{
    int original_length = images.size();
    if(original_length == 0)
        return;
    int valid_length = original_length / batch_size_ * batch_size_;
    if(original_length != valid_length)
    {
        valid_length += batch_size_;
        for(int i = original_length; i < valid_length; i++)
        {
            images.push_back(images[0].clone());
        }
    }
    vector<int> valid_labels, predicted_labels;
    valid_labels.resize(valid_length, 0);
    m_layer_->AddMatVector(images, valid_labels);
    vector<Blob<Dtype>* > bottom_vec;
    for(int i = 0; i < valid_length / batch_size_; i++)
    {
        const vector<Blob<Dtype>*>& result = net_->Forward(bottom_vec);
        const Dtype * result_vec = result[1]->cpu_data();
        for(int j = 0; j < result[1]->count(); j++)
        {
            predicted_labels.push_back(result_vec[j]);
        }
    }
    if(original_length != valid_length)
    {
        images.erase(images.begin()+original_length, images.end());
    }
    labels->resize(original_length, 0);
    std::copy(predicted_labels.begin(), predicted_labels.begin() + original_length, labels->begin());
}

template <typename Dtype>
void Classifier<Dtype>::predict(vector<Dtype> &data, vector<int> *labels, int num)
{
    int size = channels_*height_*width_;
    CHECK_EQ(data.size(), num*size);
    int original_length = num;
    if(original_length == 0)
        return;
    int valid_length = original_length / batch_size_ * batch_size_;
    if(original_length != valid_length)
    {
        valid_length += batch_size_;
        for(int i = original_length; i < valid_length; i++)
        {
            for(int j = 0; j < size; j++)
                data.push_back(0);
        }
    }
    vector<int> predicted_labels;
    Dtype * label_ = new Dtype[valid_length];
    memset(label_, 0, valid_length);
    m_layer_->Reset(data.data(), label_, valid_length);
    vector<Blob<Dtype>* > bottom_vec;
    for(int i = 0; i < valid_length / batch_size_; i++)
    {
        const vector<Blob<Dtype>*>& result = net_->Forward(bottom_vec);
        const Dtype * result_vec = result[1]->cpu_data();
        for(int j = 0; j < result[1]->count(); j++)
        {
            predicted_labels.push_back(result_vec[j]);
        }
    }
    if(original_length != valid_length)
    {
        data.erase(data.begin()+original_length*size, data.end());
    }
    delete [] label_;
    labels->resize(original_length, 0);
    std::copy(predicted_labels.begin(), predicted_labels.begin() + original_length, labels->begin());
}
template <typename Dtype>
void Classifier<Dtype>::extract_feature(vector<Mat> &images, vector<vector<Dtype>> *out)
{
    int original_length = images.size();
    if(original_length == 0)
        return;
    int valid_length = original_length / batch_size_ * batch_size_;
    if(original_length != valid_length)
    {
        valid_length += batch_size_;
        for(int i = original_length; i < valid_length; i++)
        {
            images.push_back(images[0].clone());
        }
    }
    vector<int> valid_labels;
    valid_labels.resize(valid_length, 0);
    m_layer_->AddMatVector(images, valid_labels);
    vector<Blob<Dtype>* > bottom_vec;
    out->clear();
    for(int i = 0; i < valid_length / batch_size_; i++)
    {
        const vector<Blob<Dtype>*>& result = net_->Forward(bottom_vec);
        const Dtype * result_vec = result[0]->cpu_data();
        const int dim = result[0]->count(1);
        for(int j = 0; j < result[0]->num(); j++)
        {
            const Dtype * ptr = result_vec + j * dim;
            vector<Dtype> one_;
            for(int k = 0; k < dim; ++k)
                one_.push_back(ptr[k]);
            out->push_back(one_);
        }
    }
    if(original_length != valid_length)
    {
        images.erase(images.begin()+original_length, images.end());
        out->erase(out->begin()+original_length, out->end());
    }
}
INSTANTIATE_CLASS(Classifier);
}  // namespace caffe

由于加入的数据个数必须是batch_size的整数倍,所以我们在加入数据时采用填充的方式。

CHECK_EQ(num % batch_size_, 0) <<
      "The added data must be a multiple of the batch size.";  //AddMatVector

在模型文件的最后,我们把训练时的loss层改为argmax层:

layers {
  name: "predicted"
  type: ARGMAX
  bottom: "prob"
  top: "predicted"
}

作者:waring  出处:http://www.cnblogs.com/waring  欢迎转载或分享,但请务必声明文章出处。

时间: 2024-10-19 18:11:10

如何在程序中调用Caffe做图像分类的相关文章

在android程序中调用shell命令与脚本

最近做android的一个功能就是调用shell命令来进行一些系统级别的操作,比如说是关机开机之类的,现在总结一下具体的用法以及遇到的坑(基于我所用到的,没用到的我就不说了) (1) Runtime.getRuntime().exec("ls"); 这是最简单的一种,你输入后就能就会执行ls命令,如果要获得输出的话可以这样写 Process p = Runtime.getRuntime().exec("ls"); String data = null; Buffer

Native Application 开发详解(直接在程序中调用 ntdll.dll 中的 Native API,有内存小、速度快、安全、API丰富等8大优点)

文章目录:                   1. 引子: 2. Native Application Demo 展示: 3. Native Application 简介: 4. Native Application 有何妙用: 5. MJ0011 关于 Native Application 的文章整理: 6. 互联网上其他关于 Native Application 的文章整理: 7. 小结: 1. 引子: 其实在好久以前就看了 MJ0011 翻译的那个<Native 应用程序详细>系列的文

C++程序中调用WebService的实现

前言 因为最近的项目中需要运用到在MFC程序中调用WebService里面集成好了的函数,所以特意花了一天的时间来研究WebService的构建以及如何在MFC的程序中添加Web引用,进而来实现在C++ MFC中调用那些WebService中写好的函数,中间也是遇到了一些不懂和不解的地方,好在通过度娘上的一些资料和自己的研究逐一的解决了,写这篇文章的主要目的是,第一:让自己记得更清楚,也方便以后不记得了可以及时回想起来.第二:让其他的一些和我碰到一样问题的朋友能更好的解决此问题.内容仅供参考,如

Live555 中的客户端动态库.so的调用方式之一 程序中调用

1.  打开动态链接库:    #include <dlfcn.h>    void *dlopen(const char *filename, int flag);    该函数返回操作句柄,如:    void *pHandle = dlopen(strSoFilePath, RTLD_LAZY); 2.  取动态对象地址:    #include <dlfcn.h>    void *dlsym(void *pHandle, char *symbol);    dlsym根据

WinCE平台的C#程序中调用MessageBeep发出一些系统自带的声音,而不用使用playsound

[DllImport("coredll.dll", EntryPoint = "MessageBeep")] public static extern bool MessageBeep(int iType); int i = 0x00000040; ClassPublicFunction.MessageBeep(i); 声音的类型 public enum BeepType {  SimpleBeep = -1,  IconAsterisk = 0x00000040,

c程序中调用matlab

c程序调用matlab 方法一: 在c程序中调用matlab引擎(相当于打开一个精简版matlab然后往里输入命令,即客户机/服务器模式, c程序为客户机,matlab作为本地服务器) 方法二:将m文件打包成dll文件,然后在c语言环境下调用 优缺点分析: 方法一,易于实现,可以实时监控程序的运行,但独立性差,速度慢,需要安装完整版matlab,且每次调用都会启动matlab.exe进程: 方法二,实现复杂,调试麻烦,但只需要安装mcr(matlab component runtime),耗费资

使用AllocConsole在Win32程序中调用控制台调试输出

近期一个Win32窗口项目中,调试时经常需要输出调试信息以追踪数据流及程序运行状态. 起初我封装了一系列文件操作,实现了日志形式的调试信息输出,但在后期的使用过程中越发觉得颇不顺手.那么,如何方便地在Win32程序中使用控制台进行调试输出?答案如题:AllocConsole函数和C-Runtime的freopen函数.具体操作流程如下: 打开控制台 重定向输出流至控制台 执行调试信息输出操作 完整代码如下: SetConsoleTitle(_T("Debug Output")); fr

利用vs2010制作C语言 dll文件,并在其它程序中调用该dll文件

一.为什么需要dll 代码复用是提高软件开发 效率的重要途径.一般而言,只要某部分代码具有通用性,就可将它构造成相对独立的功能模块并在之后的项目中重复使用.比较常见的例子是各种应用程序框架, 如ATL.MFC等,它们都以源代码的形式发布.由于这种复用是“源码级别”的,源代码完全暴露给了程序员,因而称之为“白盒复用”.“白盒复用”的缺点 比较多,总结起来有4点. 暴露了源代码: 容易与程序员的“普通”代码发生命名冲突: 多份拷贝,造成存储浪费: 更新功能模块比较困难. 实际上,以上4点概括起来就是

java程序中调用dos shell命令 -- 此处以调用doc命令为例

/**  *  Java调用windows的DOS命令  */ public class RunDocInJava{     public static void main(String[] args) {         InputStream ins = null;         String[] cmd = new String[] { "cmd.exe", "/c", "ipconfig" };  // 命令行         try