Caffe C++接口实现图像分类(非官方Classification.cpp)
属性文件配置,请参考在下的另一篇博客~~关于vs 下caffe属性配置文件的文章。
【更新20190105】增加caffe_layer.h
本文代码使用Caffe C++接口和OpenCVC实现了使用基于Caffe的卷积神经网络的图像分类,将Caffe的模型加载,前向传播等操作封装为CaffeClassification类,main.cpp用了共享内存的方式将结果输出至内存,为了实现进程间通信而做的工作,在这里做个分享和记录:
main.cpp
#include"CaffeClassification.h"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
cv::Mat GetImgMask(cv::Mat inputImage, cv::Size size);
void drawRect(cv::Mat& image, vector<cv::Rect> rects);
vector<cv::Rect> findRect(vector<vector<cv::Point>> contours);
boost::shared_ptr< Net<float> > caffe_net;
string Transform_Double2String(double number);
#define BUF_SIZE 1025
char szName[] = "NameOfMappingObject"; // 共享内存的名字
int main(int argc,char **argv) {
if (argc < 6){
cout << "输入参数不足" << endl;
}
// 创建共享文件句柄
HANDLE hMapFile = CreateFileMapping(
INVALID_HANDLE_VALUE, // 物理文件句柄
NULL, // 默认安全级别
PAGE_READWRITE, // 可读可写
0, // 高位文件大小
BUF_SIZE, // 地位文件大小
szName // 共享内存名称
);
char *pBuf = (char *)MapViewOfFile(
hMapFile, // 共享内存的句柄
FILE_MAP_ALL_ACCESS, // 可读写许可
0,
0,
BUF_SIZE
);
int index = 0;
char index_c[3];
char class_id[3];
_itoa(index, index_c, 10);
string index_str;
string message;
//-------------------------------------------------------------
string net_file = argv[1];
string weight_file = argv[2];
string mean_file = argv[3];
string label_file = argv[4];
string image_path = argv[5];
//string net_file = "VGG16-deploy.prototxt";
//string weight_file = "VGG_iter_60000.caffemodel";
//string mean_file = "train_lmdb_128.binaryproto";
CaffeClassification cc(net_file,weight_file,mean_file, label_file);
fstream images;
images.open(image_path, ios::in);
string line;
vector<int> inputLayerSize = cc.GetInputLayerDataSize();
cv::Mat frame;
cv::Mat image;
char c = '0';
while (getline(images,line)) {
clock_t start, finish;
double totaltime;
start = clock();
//cap.read(frame);
frame = cv::imread(line);
if (frame.empty()) {
break;
}
image = cc.GetImgMask(frame, cv::Size(inputLayerSize.at(2), inputLayerSize.at(3)));
cc.Forward(image);
CCR ccr=cc.GetResult();
finish = clock();
totaltime = (double)(finish - start) / CLOCKS_PER_SEC;
cout << "run time =" << totaltime << "s" << endl;
//----------------------------------------------------------
_itoa(index++, index_c, 10);
index_str = index_c;
_itoa(ccr.class_id, class_id, 10);
message = index_str + ": id=" + (string)class_id+" name="+ccr.class_name+" accuracy="+Transform_Double2String(ccr.accuracy);
strncpy(pBuf, message.data(), BUF_SIZE - 1);
pBuf[BUF_SIZE - 1] = '\0';
}
images.close();
UnmapViewOfFile(pBuf);
CloseHandle(hMapFile);
return 0;
}
void drawRect(cv::Mat& image, vector<cv::Rect> rects) {
if (rects.size() > 0) {
for (int i = 0; i < rects.size(); i++) {
if (rects.at(i).height < 30) {
continue;
}
else {
rectangle(image, rects.at(i), cv::Scalar(255, 0, 255));
cv::Mat buf_img(image(rects.at(i)));
}
}
}
}
vector<cv::Rect> findRect(vector<vector<cv::Point>> contours) {
vector<cv::Rect> rects;
//轮廓 预处理
for (int i = 0; i < contours.size(); i++) {
//求最小包围矩形
cv::Rect rect = boundingRect(contours.at(i));
if (rect.height > 5 && rect.width > 5) {
rects.push_back(rect);
}
}
return rects;
}
string Transform_Double2String(double number) {
stringstream ss;
ss << number;
return ss.str();
}
caffe_layer.h
#include<caffe/common.hpp>
#include<caffe/layers/batch_norm_layer.hpp>
#include<caffe/layers/bn_layer.hpp>
#include<caffe/layers/bias_layer.hpp>
#include <caffe/layers/concat_layer.hpp>
#include <caffe/layers/conv_layer.hpp>
#include <caffe/layers/dropout_layer.hpp>
#include<caffe/layers/input_layer.hpp>
#include <caffe/layers/inner_product_layer.hpp>
#include "caffe/layers/lrn_layer.hpp"
#include <caffe/layers/pooling_layer.hpp>
#include <caffe/layers/relu_layer.hpp>
#include "caffe/layers/softmax_layer.hpp"
#include<caffe/layers/scale_layer.hpp>
#include<caffe/layers/upsample_layer.hpp>
namespace caffe
{
extern INSTANTIATE_CLASS(BatchNormLayer);
extern INSTANTIATE_CLASS(BNLayer);
extern INSTANTIATE_CLASS(BiasLayer);
extern INSTANTIATE_CLASS(InputLayer);
extern INSTANTIATE_CLASS(InnerProductLayer);
extern INSTANTIATE_CLASS(DropoutLayer);
extern INSTANTIATE_CLASS(ConvolutionLayer);
REGISTER_LAYER_CLASS(Convolution);
extern INSTANTIATE_CLASS(ReLULayer);
REGISTER_LAYER_CLASS(ReLU);
extern INSTANTIATE_CLASS(PoolingLayer);
REGISTER_LAYER_CLASS(Pooling);
extern INSTANTIATE_CLASS(LRNLayer);
REGISTER_LAYER_CLASS(LRN);
extern INSTANTIATE_CLASS(SoftmaxLayer);
REGISTER_LAYER_CLASS(Softmax);
extern INSTANTIATE_CLASS(ScaleLayer);
extern INSTANTIATE_CLASS(ConcatLayer);
extern INSTANTIATE_CLASS(UpsampleLayer);
}
CaffeClassification.hpp
#pragma once
#include<caffe.hpp>
#include <string>
#include <vector>
#include<time.h>
#include<opencv2/opencv.hpp>
#include"caffe_layer.h"
using namespace caffe;
using namespace std;
//using namespace cv;
typedef struct CCR{
int class_id;
string class_name;
double accuracy;
}CCR;
class CaffeClassification
{
public:
CaffeClassification();
CaffeClassification(const string& model_file, const string& weight_file,const string& label_file);
CaffeClassification(const string& model_file, const string& weight_file,const string& mean_file,const string& label_file);
void Forward(cv::Mat image);
vector<int> GetInputLayerDataSize();
CCR GetResult();
cv::Mat GetImgMask(cv::Mat inputImage, cv::Size size);
~CaffeClassification();
std::vector<string> labels;
private:
boost::shared_ptr< Net<float> > caffe_net;
//<num channel width height>
int inputLayerSize[4];
std::vector<float> outputLayerData;
int outputLayerSize[4];
int num_channels_;
cv::Size input_geometry_;
//图像均值
cv::Mat mean_;
void SetMean(const string& mean_file);
};
CaffeClassification.cpp
#include "CaffeClassification.h"
CaffeClassification::CaffeClassification()
{
}
CaffeClassification::CaffeClassification(const string& model_file, const string& weight_file, const string& label_file) {
#ifdef CPU_ONLY
Caffe::set_mode(Caffe::CPU);
#else
Caffe::set_mode(Caffe::GPU);
#endif
/* Load the network. */
caffe_net.reset(new Net<float>(model_file, TEST));
caffe_net->CopyTrainedLayersFrom(weight_file);
CHECK_EQ(caffe_net->num_inputs(), 1) << "Network should have exactly one input.";
CHECK_EQ(caffe_net->num_outputs(), 1) << "Network should have exactly one output.";
Blob<float>* input_layer = caffe_net->input_blobs()[0];
inputLayerSize[0] = input_layer->num();
inputLayerSize[1] = input_layer->channels();
inputLayerSize[2] = input_layer->width();
inputLayerSize[3] = input_layer->height();
cout << "Data Layer Size:" << endl;
cout << inputLayerSize[0] << " " << inputLayerSize[1] << " " << inputLayerSize[2] << " " << inputLayerSize[3] << endl;
Blob<float>* output_layer = caffe_net->output_blobs()[0];
outputLayerSize[0] = output_layer->num();
outputLayerSize[1] = output_layer->channels();
outputLayerSize[2] = output_layer->width();
outputLayerSize[3] = output_layer->height();
cout << "Out Layer Size:" << endl;
cout << outputLayerSize[0] << " " << outputLayerSize[1] << " " << outputLayerSize[2] << " " << outputLayerSize[3] << endl;
}
CaffeClassification::CaffeClassification(const string& model_file, const string& weight_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. */
caffe_net.reset(new Net<float>(model_file, TEST));
caffe_net->CopyTrainedLayersFrom(weight_file);
CHECK_EQ(caffe_net->num_inputs(), 1) << "Network should have exactly one input.";
CHECK_EQ(caffe_net->num_outputs(), 1) << "Network should have exactly one output.";
Blob<float>* input_layer = caffe_net->input_blobs()[0];
num_channels_ = input_layer->channels();
input_geometry_ = cv::Size(input_layer->width(), input_layer->height());
inputLayerSize[0] = input_layer->num();
inputLayerSize[1] = input_layer->channels();
inputLayerSize[2] = input_layer->width();
inputLayerSize[3] = input_layer->height();
cout << "Data Layer Size:" << endl;
cout << inputLayerSize[0] << " " << inputLayerSize[1] << " " << inputLayerSize[2] << " " << inputLayerSize[3] << endl;
SetMean(mean_file);
Blob<float>* output_layer = caffe_net->output_blobs()[0];
outputLayerSize[0] = output_layer->num();
outputLayerSize[1] = output_layer->channels();
outputLayerSize[2] = output_layer->width();
outputLayerSize[3] = output_layer->height();
cout << "Out Layer Size:" << endl;
cout << outputLayerSize[0] << " " << outputLayerSize[1] << " " << outputLayerSize[2] << " " << outputLayerSize[3] << endl;
//读取label
fstream f_label_file;
f_label_file.open(label_file,ios::in);
string line;
while (getline(f_label_file,line)) {
labels.push_back(line);
}
}
void CaffeClassification::Forward(cv::Mat image) {
if (image.rows != inputLayerSize[2] || image.cols != inputLayerSize[3]) {
cout << "图片尺寸归一化,背景填充【0,0,0】" << endl;
image = GetImgMask(image, cv::Size(inputLayerSize[2], inputLayerSize[3]));
}
//这个其实是为了获得net_网络的输入层数据的指针,然后后面我们直接把输入图片数据拷贝到这个指针里面
Blob<float>* input_layer = caffe_net->input_blobs()[0];
std::vector<cv::Mat> input_channels;
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;
}
cv::Mat sample_float;
if (input_layer->channels() == 3)
image.convertTo(sample_float, CV_32FC3);
//sample_float = sample_float*0.00390625;
cv::Mat sample_normalized;
cv::subtract(sample_float, mean_, sample_normalized);
//imshow("sample_float", sample_float);
cv::split(sample_normalized, input_channels);
caffe_net->Forward();
//把最后一层输出值,保存到vector中,结果就是返回每个类的概率
Blob<float>* output_layer = caffe_net->output_blobs()[0];
const float* begin = output_layer->cpu_data();
const float* end = begin + output_layer->channels()*output_layer->width()*output_layer->height();
outputLayerData = std::vector<float>(begin, end);
}
CCR CaffeClassification::GetResult() {
cv::Mat result(outputLayerSize[2], outputLayerSize[3], CV_8UC3, cv::Scalar(0, 0, 0));
double max = 0.0;
double min = 0.0;
int index = 0;
for (int i = 0; i < outputLayerData.size(); i++) {
if (max < outputLayerData.at(i)) {
max = outputLayerData.at(i);
index = i;
}
}
CCR ccr{ index,labels.at(index),max };
cout << "Class ID:" << ccr.class_id << " Label Name:"<< ccr.class_name << " Accuracy:" << ccr.accuracy << endl;
return ccr;
}
vector<int> CaffeClassification::GetInputLayerDataSize() {
vector<int> size;
for (int i = 0; i < 4; i++) {
size.push_back(inputLayerSize[i]);
}
return size;
}
cv::Mat CaffeClassification::GetImgMask(cv::Mat inputImage, cv::Size size) {
int s;
if (inputImage.rows > inputImage.cols) {
s = inputImage.rows;
}
else {
s = inputImage.cols;
}
cv::Mat image_temp_ep(s, s, CV_8UC3, cv::Scalar(0, 0, 0));
if (inputImage.channels() == 1) {
cvtColor(image_temp_ep, image_temp_ep, CV_BGR2GRAY);
}
cv::Mat image_temp_ep_roi = image_temp_ep(cv::Rect((s - inputImage.cols) / 2, (s - inputImage.rows) / 2, inputImage.cols, inputImage.rows));
cv::Mat dstNormImg;
addWeighted(image_temp_ep_roi, 0., inputImage, 1.0, 0., image_temp_ep_roi);
resize(image_temp_ep, dstNormImg, size, 0, 0, 1); //大小归一化
return dstNormImg;
}
CaffeClassification::~CaffeClassification()
{
}
/* Load the mean file in binaryproto format. */
void CaffeClassification::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);
}