我实现了一个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)
,但只得到了一对这样的结果。
我认为这些修改应该能解决你的问题:
-
Classifier::Predict
应该返回一个vector< vector<float> >
,即每个输入图像的概率向量。这是一个大小为n
的vector
,其中每个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;}
-
在
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; }