封装caffe-windows-master为动态链接库

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会出错),如下:

封装caffe-windows-master为动态链接库


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

(1)配置属性——常规

封装caffe-windows-master为动态链接库

(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

[plain] view plain copy
  1. //classify.h  
  2. #pragma once  
  3. #include <caffe/caffe.hpp>  
  4. #include <opencv2/core/core.hpp>  
  5. #include <opencv2/highgui/highgui.hpp>  
  6. #include <opencv2/imgproc/imgproc.hpp>  
  7. #include <iosfwd>  
  8. #include <memory>  
  9. #include <utility>  
  10. #include <vector>  
  11. #include <iostream>  
  12. #include <string>  
  13. #include <fstream>  
  14. #include <sstream>  
  15.   
  16. using namespace caffe;  // NOLINT(build/namespaces)  
  17. //using namespace boost::property_tree;  
  18. using std::string;  
  19.   
  20. /* Pair (number, confidence) representing a prediction. */  
  21. typedef std::pair<int, float> Prediction;  
  22.   
  23. class ClassifierImpl {  
  24. public:  
  25.     ClassifierImpl::ClassifierImpl(){};  
  26.     ClassifierImpl(const string& model_file,  
  27.         const string& trained_file,  
  28.         const string& mean_file,  
  29.         const string& label_file);  
  30.   
  31.     std::vector<Prediction> Classify(const cv::Mat& img, int N = 2);  
  32.   
  33. private:  
  34.     void SetMean(const string& mean_file);  
  35.   
  36.     std::vector<float> Predict(const cv::Mat& img);  
  37.   
  38.     void WrapInputLayer(std::vector<cv::Mat>* input_channels);  
  39.   
  40.     void Preprocess(const cv::Mat& img,  
  41.         std::vector<cv::Mat>* input_channels);  
  42.   
  43. private:  
  44.     shared_ptr<Net<float> > net_;  
  45.     cv::Size input_geometry_;  
  46.     int num_channels_;  
  47.     cv::Mat mean_;  
  48. };  

[plain] view plain copy
  1. //classify.cpp  
  2. #include "classify.h"  
  3. ClassifierImpl::ClassifierImpl(const string& model_file,  
  4.     const string& trained_file,  
  5.     const string& mean_file,  
  6.     const string& label_file)  
  7. {  
  8. #ifdef CPU_ONLY  
  9.     Caffe::set_mode(Caffe::CPU);  
  10. #else  
  11.     Caffe::set_mode(Caffe::GPU);  
  12. #endif  
  13.   
  14.     /* Load the network. */  
  15.     net_.reset(new Net<float>(model_file, TEST));  
  16.     net_->CopyTrainedLayersFrom(trained_file);  
  17.   
  18.     CHECK_EQ(net_->num_inputs(), 1) << "Network should have exactly one input.";  
  19.     CHECK_EQ(net_->num_outputs(), 1) << "Network should have exactly one output.";  
  20.   
  21.     Blob<float>* input_layer = net_->input_blobs()[0];  
  22.     num_channels_ = input_layer->channels();  
  23.     CHECK(num_channels_ == 3 || num_channels_ == 1)  
  24.         << "Input layer should have 1 or 3 channels.";  
  25.     input_geometry_ = cv::Size(input_layer->width(), input_layer->height());  
  26.   
  27.     /* Load the binaryproto mean file. */  
  28.     SetMean(mean_file);  
  29.   
  30.     Blob<float>* output_layer = net_->output_blobs()[0];  
  31.       
  32. }  
  33.   
  34. static bool PairCompare(const std::pair<float, int>& lhs,  
  35.     const std::pair<float, int>& rhs) {  
  36.     return lhs.first > rhs.first;  
  37. }  
  38.   
  39. /* Return the indices of the top N values of vector v. */  
  40. static std::vector<int> Argmax(const std::vector<float>& v, int N) {  
  41.     std::vector<std::pair<float, int> > pairs;  
  42.     for (size_t i = 0; i < v.size(); ++i)  
  43.         pairs.push_back(std::make_pair(v[i], i));  
  44.     std::partial_sort(pairs.begin(), pairs.begin() + N, pairs.end(), PairCompare);  
  45.   
  46.     std::vector<int> result;  
  47.     for (int i = 0; i < N; ++i)  
  48.         result.push_back(pairs[i].second);  
  49.     return result;  
  50. }  
  51.   
  52. /* Return the top N predictions. */  
  53. std::vector<Prediction> ClassifierImpl::Classify(const cv::Mat& img, int N) {  
  54.     std::vector<float> output = Predict(img);  
  55.   
  56.     std::vector<int> maxN = Argmax(output, N);  
  57.     std::vector<Prediction> predictions;  
  58.     for (int i = 0; i < N; ++i) {  
  59.         int idx = maxN[i];  
  60.         predictions.push_back(std::make_pair(idx, output[idx]));  
  61.     }  
  62.   
  63.     return predictions;  
  64. }  
  65.   
  66. /* Load the mean file in binaryproto format. */  
  67. void ClassifierImpl::SetMean(const string& mean_file) {  
  68.     BlobProto blob_proto;  
  69.     ReadProtoFromBinaryFileOrDie(mean_file.c_str(), &blob_proto);  
  70.   
  71.     /* Convert from BlobProto to Blob<float> */  
  72.     Blob<float> mean_blob;  
  73.     mean_blob.FromProto(blob_proto);  
  74.     CHECK_EQ(mean_blob.channels(), num_channels_)  
  75.         << "Number of channels of mean file doesn't match input layer.";  
  76.   
  77.     /* The format of the mean file is planar 32-bit float BGR or grayscale. */  
  78.     std::vector<cv::Mat> channels;  
  79.     float* data = mean_blob.mutable_cpu_data();  
  80.     for (int i = 0; i < num_channels_; ++i) {  
  81.         /* Extract an individual channel. */  
  82.         cv::Mat channel(mean_blob.height(), mean_blob.width(), CV_32FC1, data);  
  83.         channels.push_back(channel);  
  84.         data += mean_blob.height() * mean_blob.width();  
  85.     }  
  86.   
  87.     /* Merge the separate channels into a single image. */  
  88.     cv::Mat mean;  
  89.     cv::merge(channels, mean);  
  90.     /* Compute the global mean pixel value and create a mean image  
  91.     * filled with this value. */  
  92.     cv::Scalar channel_mean = cv::mean(mean);  
  93.     mean_ = cv::Mat(input_geometry_, mean.type(), channel_mean);  
  94. }  
  95. std::vector<float> ClassifierImpl::Predict(const cv::Mat& img) {  
  96.     Blob<float>* input_layer = net_->input_blobs()[0];  
  97.     input_layer->Reshape(1, num_channels_,  
  98.         input_geometry_.height, input_geometry_.width);  
  99.     /* Forward dimension change to all layers. */  
  100.     net_->Reshape();  
  101.     std::vector<cv::Mat> input_channels;  
  102.     WrapInputLayer(&input_channels);  
  103.   
  104.     Preprocess(img, &input_channels);  
  105.   
  106.     net_->ForwardPrefilled();  
  107.   
  108.     /* Copy the output layer to a std::vector */  
  109.     Blob<float>* output_layer = net_->output_blobs()[0];  
  110.     const float* begin = output_layer->cpu_data();  
  111.     const float* end = begin + output_layer->channels();  
  112.     return std::vector<float>(begin, end);  
  113. }  
  114.   
  115. /* Wrap the input layer of the network in separate cv::Mat objects  
  116. * (one per channel). This way we save one memcpy operation and we  
  117. * don't need to rely on cudaMemcpy2D. The last preprocessing  
  118. * operation will write the separate channels directly to the input  
  119. * layer. */  
  120. void ClassifierImpl::WrapInputLayer(std::vector<cv::Mat>* input_channels) {  
  121.     Blob<float>* input_layer = net_->input_blobs()[0];  
  122.   
  123.     int width = input_layer->width();  
  124.     int height = input_layer->height();  
  125.     float* input_data = input_layer->mutable_cpu_data();  
  126.     for (int i = 0; i < input_layer->channels(); ++i) {  
  127.         cv::Mat channel(height, width, CV_32FC1, input_data);  
  128.         input_channels->push_back(channel);  
  129.         input_data += width * height;  
  130.     }  
  131. }  
  132.   
  133. void ClassifierImpl::Preprocess(const cv::Mat& img,  
  134.     std::vector<cv::Mat>* input_channels) {  
  135.     /* Convert the input image to the input image format of the network. */  
  136.     cv::Mat sample;  
  137.     if (img.channels() == 3 && num_channels_ == 1)  
  138.         cv::cvtColor(img, sample, CV_BGR2GRAY);  
  139.     else if (img.channels() == 4 && num_channels_ == 1)  
  140.         cv::cvtColor(img, sample, CV_BGRA2GRAY);  
  141.     else if (img.channels() == 4 && num_channels_ == 3)  
  142.         cv::cvtColor(img, sample, CV_BGRA2BGR);  
  143.     else if (img.channels() == 1 && num_channels_ == 3)  
  144.         cv::cvtColor(img, sample, CV_GRAY2BGR);  
  145.     else  
  146.         sample = img;  
  147.   
  148.     cv::Mat sample_resized;  
  149.     if (sample.size() != input_geometry_)  
  150.         cv::resize(sample, sample_resized, input_geometry_);  
  151.     else  
  152.         sample_resized = sample;  
  153.   
  154.     cv::Mat sample_float;  
  155.     if (num_channels_ == 3)  
  156.         sample_resized.convertTo(sample_float, CV_32FC3);  
  157.     else  
  158.         sample_resized.convertTo(sample_float, CV_32FC1);  
  159.   
  160.     cv::Mat sample_normalized;  
  161.     cv::subtract(sample_float, mean_, sample_normalized);  
  162.   
  163.     /* This operation will write the separate BGR planes directly to the  
  164.     * input layer of the network because it is wrapped by the cv::Mat  
  165.     * objects in input_channels. */  
  166.     cv::split(sample_normalized, *input_channels);  
  167.   
  168.     CHECK(reinterpret_cast<float*>(input_channels->at(0).data)  
  169.         == net_->input_blobs()[0]->cpu_data())  
  170.         << "Input channels are not wrapping the input layer of the network.";  
  171. }  

然后,我们再写一个导出类即可(如下(2))。

(2)添加type_recognition_ver2_api_gpu.h和type_recognition_ver2_api_gpu.cpp

[plain] view plain copy
  1. //type_recognition_ver2_api_gpu.h  
  2. #ifndef TYPE_RECOGNITION_API_H_  //保证头文件包含一次  
  3. #define TYPE_RECOGNITION_API_H_  
  4.   
  5. #ifdef TYPE_RECOGNITION_LINK_SHARED  
  6. #if defined(__GNUC__) && __GNUC__ >= 4  
  7. #define TYPE_RECOGNITION_API __attribute__ ((visibility("default")))  
  8. #elif defined(__GNUC__)  
  9. #define TYPE_RECOGNITION_API  
  10. #elif defined(_MSC_VER)  
  11. #if defined (TYPE_RECOGNITION_API_EXPORTS)  
  12. #define TYPE_RECOGNITION_API __declspec(dllexport)  
  13. #else  
  14. #define TYPE_RECOGNITION_API __declspec(dllimport)  
  15. #endif  
  16. #else  
  17. #define TYPE_RECOGNITION_API  
  18. #endif  
  19. #else  
  20. #define TYPE_RECOGNITION_API  
  21. #endif  
  22.   
  23. #include <opencv2/core/core.hpp>  
  24. #include <string>  
  25. #include <vector>  
  26. /* Pair (label, confidence) representing a prediction. */  
  27. typedef std::pair<int, float> Prediction;  
  28. class TYPE_RECOGNITION_API Classifier //导出类  
  29. {  
  30. public:  
  31.     Classifier(){};  
  32.     ~Classifier();  
  33.     Classifier(const std::string& model_file,  
  34.         const std::string& trained_file,  
  35.         const std::string& mean_file,  
  36.         const std::string& label_file);  
  37.     std::vector<Prediction> Classify(const cv::Mat& img, int N = 2);  
  38. };  
  39.   
  40. #endif  

[plain] view plain copy
  1. //type_recognition_ver2_api_gpu.cpp  
  2. #include "type_recognition_ver2_api_gpu.h"  
  3. #include "classify.h"  
  4. ClassifierImpl *impl = NULL;  
  5. Classifier::Classifier(const std::string& model_file,  
  6.     const std::string& trained_file,  
  7.     const std::string& mean_file,  
  8.     const std::string& label_file)  
  9. {  
  10. #ifdef _MSC_VER  
  11. #pragma comment( linker, "/subsystem:windows")  
  12. #endif  
  13.     impl = new ClassifierImpl(model_file, trained_file, mean_file, label_file);  
  14. }  
  15.   
  16. Classifier::~Classifier()  
  17. {  
  18.     //impl->~ClassifierImpl();  
  19.     if (impl)  
  20.         delete impl;  
  21. }  
  22. std::vector<Prediction> Classifier::Classify(const cv::Mat& img, int N)  
  23. {  
  24.     return impl->Classify(img, N);  
  25. }  
这时,右键type_recognition_ver2_api_gpu项目,生成即可,在ReleaseGPU文件夹内即可得到如下文件:

封装caffe-windows-master为动态链接库
用到的文件是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文件:

[plain] view plain copy
  1. #include <iostream>  
  2. #include <string>  
  3. #include <opencv2/core/core.hpp>  
  4. #include <opencv2/highgui/highgui.hpp>  
  5. #include "type_recognition_ver2_api_gpu.h"  
  6. int main(int argc, char** argv)  
  7. {  
  8.     std::string model_file("./model/deploy.prototxt");  
  9.     std::string trained_file("./model/net.caffemodel");  
  10.     std::string mean_file("./model/type_mean.binaryproto");  
  11.     std::string label_file("./model/typelabels.txt");  
  12.   
  13.     Classifier myclassifier(model_file, trained_file, mean_file, label_file);  
  14.   
  15.       
  16.     cv::Mat img = cv::imread("../image/automobile/000001.jpg", -1);  
  17.   
  18.     std::vector<Prediction> result = myclassifier.Classify(img);  
  19.     Prediction p = result[0];  
  20.     std::cout <<"类别:"<< p.first << "确信度:" << p.second << "\n";  
  21.     getchar();  
  22.     return 0;  
  23. }  
结果:

封装caffe-windows-master为动态链接库


下面链接的代码有bug,声明多个对象会出问题,新的代码:http://blog.****.net/sinat_30071459/article/details/53735600

封装好的代码加入了OpenCV显示图像,可通过链接下载:http://download.****.net/detail/sinat_30071459/9568131  是一个txt文件,因为****上传限制,上传到了百度云,txt里面有百度云链接。

将Classification\CLassificationDLL\bin加入环境变量后即可。

效果如下:(把Freetype库也加了进去,标签可以显示中文)

封装caffe-windows-master为动态链接库