前言
前面的博文中,我试了如何使用caffe训练得到想要的模型与其如何使用别人成熟的模型微调优化自己训练的模型,那么得到训练好的模型之后如何在自己的项目中呢,我这里使用opencv的DNN模块调用caffe训练好的模型,DNN是opencv3.0之后开始添加的功能,实现的语言是C++。
一、环境准备
1.windows 7 64位,Visual Studio 2015,opencv3.3加opencv_contrib,boost, win7、opencv3.3、opencv_contrib、vs2015相关配置我前面也有介绍过。
2.训练好的caffe模型,deploy.prototxt,标签文件labels.txt的文件建议按前面的博文生成相应的文件。
二、代码实现
代码用到opencv3的dnn模块和boost库,而boost是用来处理文件操作的,如果不用测试整个文件夹的图像,就不用引入boost库,可以把void categoryTestDir(string src_dir,string dst_dir)、void createDir(string path, vector dir_name)、vector getFileNameFromDir(string root_path)这三个成员函数的声明和代码部分注释掉。
类的头文件:ImageClassification.h
#pragma once
#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/core/utils/trace.hpp>
#include <fstream>
#include <iostream>
#include <cstdlib>
//boost 库
#include <boost/filesystem.hpp>
#include<boost/thread/thread.hpp>
using namespace std;
using namespace cv;
using namespace cv::dnn;
//定义一个boost库的命名空间
namespace fs = boost::filesystem;
class ImageClassification
{
public:
ImageClassification();
//构造函数,传入三个路径
ImageClassification(string _model_txt_path, string _model_bin_path, string _labels_path);
//得到置信值最大的类
void getMaxClass(const Mat &probBlob);
//读取标签文件
vector<string> readClassNames(string labels_path);
//对单张图像进行分类
void classification(string image_path);
//对整个文件夹的图像进行分类
void categoryTestDir(string src_dir,string dst_dir);
//创建文件夹
void createDir(string path, vector<string> dir_name);
//得到文件夹下第一层文件夹名
vector<string> getFileNameFromDir(string root_path);
~ImageClassification();
private:
int class_id;
double max_val;
string model_txt_path, model_bin_path, labels_path;
};
类的实现文件:ImageClassification.cpp
#include "ImageClassification.h"
ImageClassification::ImageClassification()
{
}
ImageClassification::ImageClassification(string _model_txt_path, string _model_bin_path, string _labels_path)
{
model_bin_path = _model_bin_path;
model_txt_path = _model_txt_path;
labels_path = _labels_path;
}
void ImageClassification::getMaxClass(const Mat &prob_mat)
{
//改变矩阵的通道数或对矩阵元素进行序列化
Mat probMat = prob_mat.reshape(1, 1);
Point max_loc;
//查找全局最小和最大数组元素并返回它们的值和它们的位置
minMaxLoc(prob_mat, NULL, &max_val, NULL, &max_loc);
class_id = max_loc.x;
}
vector<string> ImageClassification::readClassNames(string labels_path)
{
vector<string> class_names;
ifstream fp(labels_path);
if (!fp.is_open())
{
cerr << "无法打开标签文件:" << labels_path << endl;
exit(-1);
}
string name;
while (!fp.eof())
{
getline(fp, name);
if (name.length())
{
class_names.push_back(name.substr(name.find(' ') + 1));
}
}
fp.close();
return class_names;
}
void ImageClassification::classification(string image_path)
{
//合成网络
Net net = dnn::readNetFromCaffe(model_txt_path, model_bin_path);
//判断网络是否生成成功
if (net.empty())
{
cerr << "无法打开DNN网络文件!" << endl;
exit(-1);
}
cerr << "打开DNN文件成功!" << endl;
//读取图片
Mat img = imread(image_path);
if (img.empty())
{
cerr << "无法打开图像:" << image_path << endl;
exit(-1);
}
//转换成GoogleNet能接受的数据格式(blob)文件
//Mat inputBlob = blobFromImage(img, 1, Size(227, 227));
//缩放图像,改变图像的通道顺序,现在图像是一个224x224x3的3维阵列;
//使用blobFromImages把这个图像转换成一个1x3x224x224形状的4维对象(blob)。
Mat inputBlob = blobFromImage(img, 1.0f, Size(224, 224), Scalar(104, 117, 123));
Mat prob;
//将构建的blob传入网络data层
net.setInput(inputBlob, "data");
//前向预测
//在前向传输过程中,每一个网络层的输出都被计算,但在本例程中仅从“prob”层输出
prob = net.forward("prob");
//找出最高的概率ID存储在classId,对应的标签值在classProb中
getMaxClass(prob);
//打印出结果
vector<string> class_names = readClassNames(labels_path);
string out = "Best class:" + class_names.at(class_id);
putText(img, out, Point(5, 30), FONT_HERSHEY_SIMPLEX, 0.8, Scalar(0, 255, 0), 2, 6);
string probability = "Probability:" + to_string(max_val * 100) + "%";
putText(img, probability, Point(5, 60), FONT_HERSHEY_SIMPLEX, 0.8, Scalar(0, 255, 0), 2, 6);
imshow("image", img);
waitKey(0);
}
void ImageClassification::categoryTestDir(string src_dir,string dst_dir)
{
//合成网络
Net net = dnn::readNetFromCaffe(model_txt_path, model_bin_path);
//判断网络是否生成成功
if (net.empty())
{
cerr << "无法打开DNN网络文件!" << endl;
exit(-1);
}
cerr << "打开DNN文件成功!" << endl;
fs::directory_iterator begin_train(src_dir);
fs::directory_iterator end_train;
//得到标签种类
vector<string> class_names = readClassNames(labels_path);
//按标签种类创建文件夹
createDir(dst_dir, class_names);
//得到目标文件夹第一层文件夹名
vector<string> dir_name = getFileNameFromDir(dst_dir);
//遍历路径下面的所有测试图像
for (; begin_train != end_train; ++begin_train)
{
string file_name = begin_train->path().string();
fs::path file(file_name);
Mat img;
if (file.extension().string() != ".jpg")
{
continue;
}
//读取图片
img = imread(file_name);
if (img.empty())
{
cerr << "无法打开图像:" << file_name << endl;
exit(-1);
}
//转换成GoogleNet能接受的数据格式(blob)文件
//Mat inputBlob = blobFromImage(img, 1, Size(227, 227));
//缩放图像,改变图像的通道顺序,现在图像是一个224x224x3的3维阵列;
//使用blobFromImages把这个图像转换成一个1x3x224x224形状的4维对象(blob)。
Mat inputBlob = blobFromImage(img, 1.0f, Size(224, 224), Scalar(104, 117, 123));
Mat prob;
//将构建的blob传入网络data层
net.setInput(inputBlob, "data");
//前向预测
//在前向传输过程中,每一个网络层的输出都被计算,但在本例程中仅从“prob”层输出
prob = net.forward("prob");
//找出最高的概率ID存储在classId,对应的标签值在classProb中
getMaxClass(prob);
//移动图像到相关类的文件夹
for (int j = 0; j < dir_name.size(); j++)
{
if (class_names.at(class_id) == dir_name.at(j))
{
string str_file = file.stem().string();
imwrite(dst_dir + class_names.at(class_id) + "/" + str_file + ".jpg", img);
}
}
//在图像上打印输出
string out = "Best class:" + class_names.at(class_id);
putText(img, out, Point(5, 30), FONT_HERSHEY_SIMPLEX, 0.8, Scalar(0, 255, 0), 2, 6);
string probability = "Probability:" + to_string(max_val * 100) + "%";
putText(img, probability, Point(5, 60), FONT_HERSHEY_SIMPLEX, 0.8, Scalar(0, 255, 0), 2, 6);
imshow("image", img);
waitKey(10);
}
}
//创建文件夹
void ImageClassification::createDir(string path, vector<string> dir_name)
{
fs::path dir(path);
// 判断路径是否存在
if (fs::exists(dir))
{
for (int i = 0; i < dir_name.size(); i++)
{
string temp = path + dir_name.at(i);
fs::path new_dir(temp);
if (!fs::exists(new_dir))
{
fs::create_directory(temp);
}
}
}
}
//得到路径下第一层的文件夹
vector<string> ImageClassification::getFileNameFromDir(string root_path)
{
vector<string> dir_name;
boost::filesystem::path dir(root_path);
//vector<string> class_name = readClassNames("E:/LIB/caffe-windows/data/fine_tuning/CPP/labels.txt");
// 判断路径是否存在
if (boost::filesystem::exists(dir))
{
boost::filesystem::directory_iterator itEnd;
boost::filesystem::directory_iterator itDir(dir);
string file_name;
// 遍历路径下所有文件
for (; itDir != itEnd; itDir++)
{
file_name = itDir->path().string();
// 判断文件是否是文件夹
if (boost::filesystem::is_directory(file_name.c_str()))
{
int last_index = file_name.find_last_of("\\");
string name = file_name.substr(last_index + 1);
dir_name.push_back(name);
}
}
}
return dir_name;
}
ImageClassification::~ImageClassification()
{
}
主文件:main.cpp
#include "ImageClassification.h"
int main()
{
//读取模型参数路径
String model_txt_path = "E:/LIB/caffe-windows/data/fine_tuning/CPP/deploy.prototxt";
//模型结构文件路径
String model_bin_path = "E:/LIB/caffe-windows/data/fine_tuning/CPP/train_iter_800.caffemodel";
//标签文件路径
string labels_name = "E:/LIB/caffe-windows/data/fine_tuning/CPP/labels.txt";
//单张读取图片
string image_file_path = "E:/LIB/caffe-windows/data/fine_tuning/test/4126.jpg";
//测试图像路径
string test_path = "E:/LIB/caffe-windows/data/fine_tuning/test/";
//保存测试图像路径
string dst_path = "E:/LIB/caffe-windows/data/fine_tuning/";
//实例化对象
ImageClassification classificaton(model_txt_path, model_bin_path, labels_name);
//测试单张图像
classificaton.classification(image_file_path);
//测试整个文件夹
//classificaton.categoryTestDir(test_path, dst_path);
system("pause");
return 0;
}
三、测试代码
1.测试结果
2.把测试图像转换的时候,有一个要注意的地方,转换的参数会影响准确率,如下面两张图:
3.再对整个文件夹进行测试的时候,可以看到还是有一些判断不准确的图像,这个要继续优化。
后记
以上是从caffe训练开始到使用opencv调用训练好的模型的全部步骤。