前言
1.OpenCV中的ML模块实现了前馈人工神经网络,具体地说是多层感知器(MLP),是最常用的神经网络类型。 MLP由输入层,输出层和一个或多个隐藏层组成。 MLP的每一层包括一个或多个与来自上一层和下一层的神经元定向连接的神经元。关于ANN_MLP的具体说明可以看opencv的官方文档。
2.我这里要是使用ANN_MLP神经网络来实现0到9的印刷数字识别,使用的OpenCV版本是3.30,IDE是VS2015,实现语言是C++,还使用了boost来进行文件读取的相关操作。
样本准备
1.先准备0到9的样本,分别放在相应的文件目录下,这是我保存的格式:
2.每个目录下放着对的样本,我这里每个字母都有50个样本,对应的文件没有特殊要求。
代码
1.训练代码
//训练函数
//root_path样本地址
//model_path保存模型路径加文件名,后续为xml
void trainChar(string &root_path, string &model_path)
{
vector<string> dir_path;
int dir_number;
getFileNameFromDir(root_path, dir_path, dir_number);
//图像的行
const int image_rows = 8;
//图像的列
const int image_cols = 16;
//要训练的类别
const int class_sum = 10;
//每个类别的样本个数
const int images_sum = 50;
if (dir_number != class_sum)
{
cout << "要训练的种类与当前目录下的种类和差异!" << endl;
return;
}
//每一行一个训练样本
float trainingData[class_sum*images_sum][image_rows*image_cols] = { { 0 } };
//训练样本标签
float labels[class_sum*images_sum][class_sum] = { { 0 } };
Mat src, resize_img, train_img;
//这里读文件的方式不是很好,用boost会好一些
for (int i = 0; i < dir_path.size(); i++)
{
//cout << dir_path.at(i) << endl;
int k = 0;
fs::directory_iterator begin_iter(dir_path.at(i));
fs::directory_iterator end_iter;
//获取该目录下的所有文件名
for (; begin_iter != end_iter; ++begin_iter)
{
string image_path = begin_iter->path().string();
src = imread(image_path, 0);
if (src.empty())
{
cerr << "can not load image \n" << std::endl;
exit(0);
}
//更改尺寸
resize(src, resize_img, Size(image_rows, image_cols), (0, 0), (0, 0), INTER_AREA);
//二值化
threshold(resize_img, train_img, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
for (int j = 0; j < image_rows*image_cols; j++)
{
trainingData[i*images_sum + k][j] = (float)resize_img.data[j];
}
// 设置标签数据
for (int j = 0; j < class_sum; j++)
{
if (j == i)
{
labels[i*images_sum + k][j] = 1;
}
else
{
labels[i*images_sum + k][j] = 0;
}
}
k++;
}
Mat labelsMat(class_sum*images_sum, class_sum, CV_32FC1, labels);
}
//训练数据及标签
Mat trainingDataMat(class_sum*images_sum, image_rows*image_cols, CV_32FC1, trainingData);
Mat labelsMat(class_sum*images_sum, class_sum, CV_32FC1, labels);
//设置参数
Ptr<ANN_MLP>model = ANN_MLP::create();
Mat layerSizes = (Mat_<int>(1, 5) << image_rows*image_cols, 128, 128, 128, class_sum);
model->setLayerSizes(layerSizes);
//训练方法为反向传播(这个跟深度学习的反向转播是一个道理)
model->setTrainMethod(ANN_MLP::BACKPROP, 0.001, 0.1);
// 激活函数设置为 sigmoid
model->setActivationFunction(ANN_MLP::SIGMOID_SYM, 1.0, 1.0);
model->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER | TermCriteria::EPS, 10000, 0.0001));
cout << "开始训练!" << endl;
Ptr<TrainData> trainData = TrainData::create(trainingDataMat, ROW_SAMPLE, labelsMat);
model->train(trainData);
//保存模型
model->save(model_path);
cout << "训练完成" << endl;
}
//得到路径下第一层的文件夹的绝对路径
void getFileNameFromDir(string &root_path,vector<string> &dir_path, int &class_sum)
{
class_sum = 0;
vector<string> dir_name;
fs::path dir(root_path);
// 判断路径是否存在
if (fs::exists(dir))
{
fs::directory_iterator itEnd;
fs::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()))
{
dir_path.push_back(file_name);
class_sum++;
}
}
}
}
训练代码调用方式:
//保存字符集的主目录
string char_path = "C:/code/DigitalRecognition/DigitalRecognition/numberChar/";
//训练好的模型保存的路径和文件名
string model_path = "C:/code/DigitalRecognition/DigitalRecognition/numberCharModel.xml";
trainChar(char_path,model_path);
2.测试代码
//测试模型
//src是要识别的图像
//model_path模型的路径
void useModel(Mat &src, string &model_path)
{
if (src.empty())
{
cout << "当前传入的图像为空!" << endl;
return;
}
if (src.channels() > 1)
{
cvtColor(src, src, CV_BGR2GRAY);
}
Mat dst;
//图像的行
const int image_rows = 8;
//图像的列
const int image_cols = 16;
Ptr<ANN_MLP>model = ANN_MLP::create();
model = cv::Algorithm::load<cv::ml::ANN_MLP>(model_path);
//将测试图像转化为1*128的向量
resize(src, src, Size(image_rows, image_cols), (0, 0), (0, 0), INTER_AREA);
threshold(src, src, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
Mat_<float> testMat(1, image_rows*image_cols);
for (int i = 0; i < image_rows*image_cols; i++)
{
testMat.at<float>(0, i) = (float)src.at<uchar>(i / 8, i % 8);
}
//使用训练好的MLP model预测测试图像,把预测到的值放到dst里面
model->predict(testMat, dst);
cout << "dst:" << dst << endl;
//选出最大值
double maxVal = 0;
Point maxLoc;
minMaxLoc(dst, NULL, &maxVal, NULL, &maxLoc);
cout << "测试结果:" << maxLoc.x << "置信度:" << maxVal * 100 << "%" << endl;
}
测试代码调用方式:
//训练好的模型保存的路径和文件名
string model_path = "C:/code/DigitalRecognition/DigitalRecognition/numberCharModel.xml";
//读取图像
Mat src = imread("src.png");
useModel(src,model_path);
运行结果:
结语
1.上面的所用到的样本,可以从我的资源上传那里得到。
2.如果在运行中有什么bug,可以找我之前的博客,下面有推荐的群,互相讨论学习。