2016.12.14:
代码已知bug:
由于类中有全局变量,声明多个对象时,全局变量指向会改变,造成结果错误并且有内存泄漏。因此该份代码只能声明一个Classifier对象。
新的代码已经重新封装,并且做成多标签输出,下载地址:http://download.****.net/detail/sinat_30071459/9715053
主要修改:http://blog.****.net/sinat_30071459/article/details/53611678
因为caffe-windows-master的代码比较多,看着很乱,所以想到把它封装成一个类来调用做图像分类,这里以GPU版本为例,记录一下自己封装成DLL和如何使用封装后的DLL的过程。
1.打开解决方案
首先,需要修改解决方案配置(默认是Release),我们新建一个叫ReleaseGPU,平台修改为x64(因为用到的其他DLL是64位,配置成win32会出错),如下:

这里我将caffelib的项目名改成了type_recognition_ver2_api_gpu,配置好ReleaseGPU后,右键项目type_recognition_ver2_api_gpu——>属性,配置属性页:
(1)配置属性——常规

(2)C/C++——常规——附加包含目录:
../../3rdparty/include
../../src
../../include
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v7.5\include
预处理器——预处理器定义添加:
TYPE_RECOGNITION_LINK_SHARED
TYPE_RECOGNITION_API_EXPORTS
(3)链接器——常规——附加库目录:
../../3rdparty/lib
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v7.5\lib\x64
(4)链接器——输入——附加依赖项:
kernel32.lib
user32.lib
gdi32.lib
winspool.lib
shell32.lib
ole32.lib
oleaut32.lib
uuid.lib
comdlg32.lib
advapi32.lib
cudart.lib
cublas.lib
curand.lib
libprotobuf.lib
hdf5_tools.lib
hdf5_hl_fortran.lib
hdf5_fortran.lib
hdf5_hl_f90cstub.lib
hdf5_f90cstub.lib
hdf5_cpp.lib
hdf5_hl_cpp.lib
hdf5_hl.lib
hdf5.lib
zlib.lib
szip.lib
opencv_world300.lib
shlwapi.lib
leveldb.lib
cublas_device.lib
cuda.lib
libglog.lib
lmdb.lib
cudnn.lib
libopenblas.dll.a
libgflags.lib
这样就配置好了。
2.添加文件
(1)添加classfy.h和classify.cpp
-
//classify.h
-
#pragma once
-
#include <caffe/caffe.hpp>
-
#include <opencv2/core/core.hpp>
-
#include <opencv2/highgui/highgui.hpp>
-
#include <opencv2/imgproc/imgproc.hpp>
-
#include <iosfwd>
-
#include <memory>
-
#include <utility>
-
#include <vector>
-
#include <iostream>
-
#include <string>
-
#include <fstream>
-
#include <sstream>
-
-
using namespace caffe; // NOLINT(build/namespaces)
-
//using namespace boost::property_tree;
-
using std::string;
-
-
/* Pair (number, confidence) representing a prediction. */
-
typedef std::pair<int, float> Prediction;
-
-
class ClassifierImpl {
-
public:
-
ClassifierImpl::ClassifierImpl(){};
-
ClassifierImpl(const string& model_file,
-
const string& trained_file,
-
const string& mean_file,
-
const string& label_file);
-
-
std::vector<Prediction> Classify(const cv::Mat& img, int N = 2);
-
-
private:
-
void SetMean(const string& mean_file);
-
-
std::vector<float> Predict(const cv::Mat& img);
-
-
void WrapInputLayer(std::vector<cv::Mat>* input_channels);
-
-
void Preprocess(const cv::Mat& img,
-
std::vector<cv::Mat>* input_channels);
-
-
private:
-
shared_ptr<Net<float> > net_;
-
cv::Size input_geometry_;
-
int num_channels_;
-
cv::Mat mean_;
-
};
-
//classify.cpp
-
#include "classify.h"
-
ClassifierImpl::ClassifierImpl(const string& model_file,
-
const string& trained_file,
-
const string& mean_file,
-
const string& label_file)
-
{
-
#ifdef CPU_ONLY
-
Caffe::set_mode(Caffe::CPU);
-
#else
-
Caffe::set_mode(Caffe::GPU);
-
#endif
-
-
/* Load the network. */
-
net_.reset(new Net<float>(model_file, TEST));
-
net_->CopyTrainedLayersFrom(trained_file);
-
-
CHECK_EQ(net_->num_inputs(), 1) << "Network should have exactly one input.";
-
CHECK_EQ(net_->num_outputs(), 1) << "Network should have exactly one output.";
-
-
Blob<float>* input_layer = net_->input_blobs()[0];
-
num_channels_ = input_layer->channels();
-
CHECK(num_channels_ == 3 || num_channels_ == 1)
-
<< "Input layer should have 1 or 3 channels.";
-
input_geometry_ = cv::Size(input_layer->width(), input_layer->height());
-
-
/* Load the binaryproto mean file. */
-
SetMean(mean_file);
-
-
Blob<float>* output_layer = net_->output_blobs()[0];
-
-
}
-
-
static bool PairCompare(const std::pair<float, int>& lhs,
-
const std::pair<float, int>& rhs) {
-
return lhs.first > rhs.first;
-
}
-
-
/* Return the indices of the top N values of vector v. */
-
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;
-
}
-
-
/* Return the top N predictions. */
-
std::vector<Prediction> ClassifierImpl::Classify(const cv::Mat& img, int N) {
-
std::vector<float> output = Predict(img);
-
-
std::vector<int> maxN = Argmax(output, N);
-
std::vector<Prediction> predictions;
-
for (int i = 0; i < N; ++i) {
-
int idx = maxN[i];
-
predictions.push_back(std::make_pair(idx, output[idx]));
-
}
-
-
return predictions;
-
}
-
-
/* Load the mean file in binaryproto format. */
-
void ClassifierImpl::SetMean(const string& mean_file) {
-
BlobProto blob_proto;
-
ReadProtoFromBinaryFileOrDie(mean_file.c_str(), &blob_proto);
-
-
/* Convert from BlobProto to Blob<float> */
-
Blob<float> mean_blob;
-
mean_blob.FromProto(blob_proto);
-
CHECK_EQ(mean_blob.channels(), num_channels_)
-
<< "Number of channels of mean file doesn't match input layer.";
-
-
/* The format of the mean file is planar 32-bit float BGR or grayscale. */
-
std::vector<cv::Mat> channels;
-
float* data = mean_blob.mutable_cpu_data();
-
for (int i = 0; i < num_channels_; ++i) {
-
/* Extract an individual channel. */
-
cv::Mat channel(mean_blob.height(), mean_blob.width(), CV_32FC1, data);
-
channels.push_back(channel);
-
data += mean_blob.height() * mean_blob.width();
-
}
-
-
/* Merge the separate channels into a single image. */
-
cv::Mat mean;
-
cv::merge(channels, mean);
-
/* Compute the global mean pixel value and create a mean image
-
* filled with this value. */
-
cv::Scalar channel_mean = cv::mean(mean);
-
mean_ = cv::Mat(input_geometry_, mean.type(), channel_mean);
-
}
-
std::vector<float> ClassifierImpl::Predict(const cv::Mat& img) {
-
Blob<float>* input_layer = net_->input_blobs()[0];
-
input_layer->Reshape(1, num_channels_,
-
input_geometry_.height, input_geometry_.width);
-
/* Forward dimension change to all layers. */
-
net_->Reshape();
-
std::vector<cv::Mat> input_channels;
-
WrapInputLayer(&input_channels);
-
-
Preprocess(img, &input_channels);
-
-
net_->ForwardPrefilled();
-
-
/* Copy the output layer to a std::vector */
-
Blob<float>* output_layer = net_->output_blobs()[0];
-
const float* begin = output_layer->cpu_data();
-
const float* end = begin + output_layer->channels();
-
return std::vector<float>(begin, end);
-
}
-
-
/* Wrap the input layer of the network in separate cv::Mat objects
-
* (one per channel). This way we save one memcpy operation and we
-
* don't need to rely on cudaMemcpy2D. The last preprocessing
-
* operation will write the separate channels directly to the input
-
* layer. */
-
void ClassifierImpl::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(); ++i) {
-
cv::Mat channel(height, width, CV_32FC1, input_data);
-
input_channels->push_back(channel);
-
input_data += width * height;
-
}
-
}
-
-
void ClassifierImpl::Preprocess(const cv::Mat& img,
-
std::vector<cv::Mat>* input_channels) {
-
/* Convert the input image to the input image format of the network. */
-
cv::Mat sample;
-
if (img.channels() == 3 && num_channels_ == 1)
-
cv::cvtColor(img, sample, CV_BGR2GRAY);
-
else if (img.channels() == 4 && num_channels_ == 1)
-
cv::cvtColor(img, sample, CV_BGRA2GRAY);
-
else if (img.channels() == 4 && num_channels_ == 3)
-
cv::cvtColor(img, sample, CV_BGRA2BGR);
-
else if (img.channels() == 1 && num_channels_ == 3)
-
cv::cvtColor(img, sample, CV_GRAY2BGR);
-
else
-
sample = img;
-
-
cv::Mat sample_resized;
-
if (sample.size() != input_geometry_)
-
cv::resize(sample, sample_resized, input_geometry_);
-
else
-
sample_resized = sample;
-
-
cv::Mat sample_float;
-
if (num_channels_ == 3)
-
sample_resized.convertTo(sample_float, CV_32FC3);
-
else
-
sample_resized.convertTo(sample_float, CV_32FC1);
-
-
cv::Mat sample_normalized;
-
cv::subtract(sample_float, mean_, sample_normalized);
-
-
/* This operation will write the separate BGR planes directly to the
-
* input layer of the network because it is wrapped by the cv::Mat
-
* objects in input_channels. */
-
cv::split(sample_normalized, *input_channels);
-
-
CHECK(reinterpret_cast<float*>(input_channels->at(0).data)
-
== net_->input_blobs()[0]->cpu_data())
-
<< "Input channels are not wrapping the input layer of the network.";
-
}
然后,我们再写一个导出类即可(如下(2))。
(2)添加type_recognition_ver2_api_gpu.h和type_recognition_ver2_api_gpu.cpp
-
//type_recognition_ver2_api_gpu.h
-
#ifndef TYPE_RECOGNITION_API_H_ //保证头文件包含一次
-
#define TYPE_RECOGNITION_API_H_
-
-
#ifdef TYPE_RECOGNITION_LINK_SHARED
-
#if defined(__GNUC__) && __GNUC__ >= 4
-
#define TYPE_RECOGNITION_API __attribute__ ((visibility("default")))
-
#elif defined(__GNUC__)
-
#define TYPE_RECOGNITION_API
-
#elif defined(_MSC_VER)
-
#if defined (TYPE_RECOGNITION_API_EXPORTS)
-
#define TYPE_RECOGNITION_API __declspec(dllexport)
-
#else
-
#define TYPE_RECOGNITION_API __declspec(dllimport)
-
#endif
-
#else
-
#define TYPE_RECOGNITION_API
-
#endif
-
#else
-
#define TYPE_RECOGNITION_API
-
#endif
-
-
#include <opencv2/core/core.hpp>
-
#include <string>
-
#include <vector>
-
/* Pair (label, confidence) representing a prediction. */
-
typedef std::pair<int, float> Prediction;
-
class TYPE_RECOGNITION_API Classifier //导出类
-
{
-
public:
-
Classifier(){};
-
~Classifier();
-
Classifier(const std::string& model_file,
-
const std::string& trained_file,
-
const std::string& mean_file,
-
const std::string& label_file);
-
std::vector<Prediction> Classify(const cv::Mat& img, int N = 2);
-
};
-
-
#endif
-
//type_recognition_ver2_api_gpu.cpp
-
#include "type_recognition_ver2_api_gpu.h"
-
#include "classify.h"
-
ClassifierImpl *impl = NULL;
-
Classifier::Classifier(const std::string& model_file,
-
const std::string& trained_file,
-
const std::string& mean_file,
-
const std::string& label_file)
-
{
-
#ifdef _MSC_VER
-
#pragma comment( linker, "/subsystem:windows")
-
#endif
-
impl = new ClassifierImpl(model_file, trained_file, mean_file, label_file);
-
}
-
-
Classifier::~Classifier()
-
{
-
//impl->~ClassifierImpl();
-
if (impl)
-
delete impl;
-
}
-
std::vector<Prediction> Classifier::Classify(const cv::Mat& img, int N)
-
{
-
return impl->Classify(img, N);
-
}
这时,右键type_recognition_ver2_api_gpu项目,生成即可,在ReleaseGPU文件夹内即可得到如下文件:

用到的文件是dll和lib文件,使用这些文件方法如下。
3.使用DLL
将type_recognition_ver2_api_gpu.dll复制到caffe-windows-master\3rdparty\bin;
将type_recognition_ver2_api_gpu.lib复制到caffe-windows-master\3rdparty\lib;
将type_recognition_ver2_api_gpu.h复制到caffe-windows-master\3rdparty\include;
然后,新建一个控制台项目,配置成x64,
右键项目配置如下:
C/C++——常规——附加包含目录:(这里路径自己修改)
********\3rdparty\include
链接器——常规——附加库目录:
********\3rdparty\lib
链接器——输入——附加依赖项:
将type_recognition_ver2_api_gpu.lib和opencv_world300.lib加进去,
然后,为项目添加一个cpp文件:
-
#include <iostream>
-
#include <string>
-
#include <opencv2/core/core.hpp>
-
#include <opencv2/highgui/highgui.hpp>
-
#include "type_recognition_ver2_api_gpu.h"
-
int main(int argc, char** argv)
-
{
-
std::string model_file("./model/deploy.prototxt");
-
std::string trained_file("./model/net.caffemodel");
-
std::string mean_file("./model/type_mean.binaryproto");
-
std::string label_file("./model/typelabels.txt");
-
-
Classifier myclassifier(model_file, trained_file, mean_file, label_file);
-
-
-
cv::Mat img = cv::imread("../image/automobile/000001.jpg", -1);
-
-
std::vector<Prediction> result = myclassifier.Classify(img);
-
Prediction p = result[0];
-
std::cout <<"类别:"<< p.first << "确信度:" << p.second << "\n";
-
getchar();
-
return 0;
-
}
结果:

(下面链接的代码有bug,声明多个对象会出问题,新的代码:http://blog.****.net/sinat_30071459/article/details/53735600)
封装好的代码加入了OpenCV显示图像,可通过链接下载:http://download.****.net/detail/sinat_30071459/9568131
是一个txt文件,因为****上传限制,上传到了百度云,txt里面有百度云链接。
将Classification\CLassificationDLL\bin加入环境变量后即可。
效果如下:(把Freetype库也加了进去,标签可以显示中文)
