修改Caffe C++预测代码以支持多输入

我实现了一个Caffe C++示例的修改版本,虽然它运行得很好,但由于它一次只能接受一张图片,速度非常慢。理想情况下,我希望能够向Caffe传递一个包含200张图片的向量,并为每张图片返回最佳预测结果。我从Fanglin Wang那里得到了很好的帮助,并实施了他的一些建议,但仍然在尝试找出如何从每张图片中获取最佳结果时遇到了一些麻烦。

现在Classify方法接收一个cv::Mat对象的向量(变量input_channels),这是一个灰度浮点图像的向量。我已经在代码中删除了预处理方法,因为我不需要将这些图像转换为浮点数或减去平均图像。我还尝试去掉N变量,因为我只想为每张图片返回最佳预测和概率。

#include "Classifier.h"using namespace caffe;using std::string;Classifier::Classifier(const string& model_file, const string& trained_file, const string& label_file) {#ifdef CPU_ONLY  Caffe::set_mode(Caffe::CPU);#else  Caffe::set_mode(Caffe::GPU);#endif  /* 加载网络。 */  net_.reset(new Net<float>(model_file, TEST));  net_->CopyTrainedLayersFrom(trained_file);  Blob<float>* input_layer = net_->input_blobs()[0];  num_channels_ = input_layer->channels();  input_geometry_ = cv::Size(input_layer->width(), input_layer->height());  /* 加载标签。 */  std::ifstream labels(label_file.c_str());  CHECK(labels) << "无法打开标签文件 " << label_file;  string line;  while (std::getline(labels, line))    labels_.push_back(string(line));  Blob<float>* output_layer = net_->output_blobs()[0];  CHECK_EQ(labels_.size(), output_layer->channels())    << "标签数量与输出层维度不同。";}static bool PairCompare(const std::pair<float, int>& lhs, const std::pair<float, int>& rhs) {  return lhs.first > rhs.first;}/* 返回向量v中前N个最大值的索引。 */static std::vector<int> Argmax(const std::vector<float>& v, int N) {  std::vector<std::pair<float, int> > pairs;  for (size_t i = 0; i < v.size(); ++i)    pairs.push_back(std::make_pair(v[i], i));  std::partial_sort(pairs.begin(), pairs.begin() + N, pairs.end(), PairCompare);  std::vector<int> result;  for (int i = 0; i < N; ++i)    result.push_back(pairs[i].second);  return result;}/* 返回前N个预测结果。 */std::vector<Prediction> Classifier::Classify(const std::vector<cv::Mat> &input_channels) {  std::vector<float> output = Predict(input_channels);    std::vector<int> maxN = Argmax(output, 1);    int idx = maxN[0];    predictions.push_back(std::make_pair(labels_[idx], output[idx]));    return predictions;}std::vector<float> Classifier::Predict(const std::vector<cv::Mat> &input_channels, int num_images) {  Blob<float>* input_layer = net_->input_blobs()[0];  input_layer->Reshape(num_images, num_channels_,                       input_geometry_.height, input_geometry_.width);  /* 向前传递维度更改到所有层。 */  net_->Reshape();  WrapInputLayer(&input_channels);  net_->ForwardPrefilled();  /* 将输出层复制到std::vector */  Blob<float>* output_layer = net_->output_blobs()[0];  const float* begin = output_layer->cpu_data();  const float* end = begin + num_images * output_layer->channels();  return std::vector<float>(begin, end);}/* 将网络的输入层包装在单独的cv::Mat对象中(每个通道一个)。这样我们可以节省一次memcpy操作,并且不需要依赖cudaMemcpy2D。最后的预处理操作将直接将单独的通道写入输入层。 */void Classifier::WrapInputLayer(std::vector<cv::Mat>* input_channels) {  Blob<float>* input_layer = net_->input_blobs()[0];  int width = input_layer->width();  int height = input_layer->height();  float* input_data = input_layer->mutable_cpu_data();  for (int i = 0; i < input_layer->channels() * num_images; ++i) {    cv::Mat channel(height, width, CV_32FC1, input_data);    input_channels->push_back(channel);    input_data += width * height;  }}

更新

非常感谢你的帮助@Shai,我按照你的建议进行了修改,但似乎遇到了一些奇怪的编译问题,我无法解决(我已经解决了一些问题)。

这些是我所做的更改:

头文件:

#ifndef __CLASSIFIER_H__#define __CLASSIFIER_H__#include <caffe/caffe.hpp>#include <opencv2/core/core.hpp>#include <opencv2/highgui/highgui.hpp>#include <opencv2/imgproc/imgproc.hpp>#include <algorithm>#include <iosfwd>#include <memory>#include <string>#include <utility>#include <vector>using namespace caffe;  // NOLINT(build/namespaces)using std::string;/* 表示预测的对(label, confidence)。 */typedef std::pair<string, float> Prediction;class Classifier { public:  Classifier(const string& model_file,             const string& trained_file,             const string& label_file);  std::vector< std::pair<int,float> > Classify(const std::vector<cv::Mat>& img); private:  std::vector< std::vector<float> > Predict(const std::vector<cv::Mat>& img, int nImages);  void WrapInputLayer(std::vector<cv::Mat>* input_channels, int nImages);  void Preprocess(const std::vector<cv::Mat>& img,                  std::vector<cv::Mat>* input_channels, int nImages); private:  shared_ptr<Net<float> > net_;  cv::Size input_geometry_;  int num_channels_;  std::vector<string> labels_;};#endif /* __CLASSIFIER_H__ */

类文件:

#define CPU_ONLY#include "Classifier.h"using namespace caffe;  // NOLINT(build/namespaces)using std::string;Classifier::Classifier(const string& model_file,                       const string& trained_file,                       const string& label_file) {#ifdef CPU_ONLY  Caffe::set_mode(Caffe::CPU);#else  Caffe::set_mode(Caffe::GPU);#endif  /* 加载网络。 */  net_.reset(new Net<float>(model_file, TEST));  net_->CopyTrainedLayersFrom(trained_file);  CHECK_EQ(net_->num_inputs(), 1) << "网络应该只有一个输入。";  CHECK_EQ(net_->num_outputs(), 1) << "网络应该只有一个输出。";  Blob<float>* input_layer = net_->input_blobs()[0];  num_channels_ = input_layer->channels();  CHECK(num_channels_ == 3 || num_channels_ == 1)    << "输入层应该有1或3个通道。";  input_geometry_ = cv::Size(input_layer->width(), input_layer->height());  /* 加载标签。 */  std::ifstream labels(label_file.c_str());  CHECK(labels) << "无法打开标签文件 " << label_file;  string line;  while (std::getline(labels, line))    labels_.push_back(string(line));  Blob<float>* output_layer = net_->output_blobs()[0];  CHECK_EQ(labels_.size(), output_layer->channels())    << "标签数量与输出层维度不同。";}static bool PairCompare(const std::pair<float, int>& lhs,                        const std::pair<float, int>& rhs) {  return lhs.first > rhs.first;}/* 返回向量v中前N个最大值的索引。 */static std::vector<int> Argmax(const std::vector<float>& v, int N) {  std::vector<std::pair<float, int> > pairs;  for (size_t i = 0; i < v.size(); ++i)    pairs.push_back(std::make_pair(v[i], i));  std::partial_sort(pairs.begin(), pairs.begin() + N, pairs.end(), PairCompare);  std::vector<int> result;  for (int i = 0; i < N; ++i)    result.push_back(pairs[i].second);  return result;}std::vector< std::pair<int,float> > Classifier::Classify(const std::vector<cv::Mat>& img) {  std::vector< std::vector<float> > output = Predict(img, img.size());  std::vector< std::pair<int,float> > predictions;  for ( int i = 0 ; i < output.size(); i++ ) {    std::vector<int> maxN = Argmax(output[i], 1);    int idx = maxN[0];    predictions.push_back(std::make_pair(labels_[idx], output[idx]));  }  return predictions;}std::vector< std::vector<float> > Classifier::Predict(const std::vector<cv::Mat>& img, int nImages) {  Blob<float>* input_layer = net_->input_blobs()[0];  input_layer->Reshape(nImages, num_channels_,                       input_geometry_.height, input_geometry_.width);  /* 向前传递维度更改到所有层。 */  net_->Reshape();  std::vector<cv::Mat> input_channels;  WrapInputLayer(&input_channels, nImages);  Preprocess(img, &input_channels, nImages);  net_->ForwardPrefilled();  /* 将输出层复制到std::vector */  Blob<float>* output_layer = net_->output_blobs()[0];  std::vector <std::vector<float> > ret;  for (int i = 0; i < nImages; i++) {    const float* begin = output_layer->cpu_data() + i*output_layer->channels();    const float* end = begin + output_layer->channels();    ret.push_back( std::vector<float>(begin, end) );  }  return ret;}/* 将网络的输入层包装在单独的cv::Mat对象中 *(每个通道一个)。这样我们可以节省一次memcpy操作,并且我们 *不需要依赖cudaMemcpy2D。最后的预处理 *操作将直接将单独的通道写入输入 *层。 */void Classifier::WrapInputLayer(std::vector<cv::Mat>* input_channels, int nImages) {  Blob<float>* input_layer = net_->input_blobs()[0];  int width = input_layer->width();  int height = input_layer->height();  float* input_data = input_layer->mutable_cpu_data();  for (int i = 0; i < input_layer->channels()* nImages; ++i) {    cv::Mat channel(height, width, CV_32FC1, input_data);    input_channels->push_back(channel);    input_data += width * height;  }}void Classifier::Preprocess(const std::vector<cv::Mat>& img,                            std::vector<cv::Mat>* input_channels, int nImages) {  for (int i = 0; i < nImages; i++) {      vector<cv::Mat> channels;      cv::split(img[i], channels);      for (int j = 0; j < channels.size(); j++){           channels[j].copyTo((*input_channels)[i*num_channels_[0]+j]);      }  }}

回答:

如果我正确理解了你的问题,你输入n张图片,期望得到n(label, prob),但只得到了一对这样的结果。

我认为这些修改应该能解决你的问题:

  1. Classifier::Predict应该返回一个vector< vector<float> >,即每个输入图像的概率向量。这是一个大小为nvector,其中每个vector的大小为output_layer->channels()

    std::vector< std::vector<float> > Classifier::Predict(const std::vector<cv::Mat> &input_channels,                     int num_images) {  // 这里的代码相同...  /* 这里进行更改:将输出层复制到std::vector */  Blob<float>* output_layer = net_->output_blobs()[0];  std::vector< std::vector<float> > ret;  for ( int i = 0 ; i < num_images ; i++ ) {      const float* begin = output_layer->cpu_data() + i*output_layer->channels();      const float* end = begin + output_layer->channels();      ret.push_back( std::vector<float>(begin, end) );  }  return ret;}
  2. Classifier::Classify中,你需要独立地通过Argmax处理每个vector<float>

     std::vector< std::pair<int,float> >  Classifier::Classify(const std::vector<cv::Mat> &input_channels) {   std::vector< std::vector<float> > output = Predict(input_channels);   std::vector< std::pair<int,float> > predictions;   for ( int i = 0 ; i < output.size(); i++ ) {       std::vector<int> maxN = Argmax(output[i], 1);       int idx = maxN[0];       predictions.push_back(std::make_pair(labels_[idx], output[idx]));   }   return predictions; }

Related Posts

Keras Dense层输入未被展平

这是我的测试代码: from keras import…

无法将分类变量输入随机森林

我有10个分类变量和3个数值变量。我在分割后直接将它们…

如何在Keras中对每个输出应用Sigmoid函数?

这是我代码的一部分。 model = Sequenti…

如何选择类概率的最佳阈值?

我的神经网络输出是一个用于多标签分类的预测类概率表: …

在Keras中使用深度学习得到不同的结果

我按照一个教程使用Keras中的深度神经网络进行文本分…

‘MatMul’操作的输入’b’类型为float32,与参数’a’的类型float64不匹配

我写了一个简单的TensorFlow代码,但不断遇到T…

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注