OpenCV4入门137:SVM+MNIST训练与测试

MNIST数据集来自美国国家标准与技术研究所, National Institute of Standards and Technology (NIST)。训练集 (training set) 由来自 250 个不同人手写的数字构成,其中50%是高中学生,50%来自人口普查局 (the Census Bureau) 的工作人员。测试集(test set)也是同样比例的手写数字数据。

MNIST数据集已经是一个被”嚼烂”了的数据集,很多教程都会对它”下手”,几乎成为一个 “典范”。不过有些人可能对它还不是很了解, 下面来介绍一下.

MNIST 数据集可在http://yann.lecun.com/exdb/mnist/获取, 它包含了四个部分:

  • Training set images: train-images-idx3-ubyte.gz (9.9 MB, 解压后 47 MB, 包含 60,000 个样本)
  • Training set labels: train-labels-idx1-ubyte.gz (29 KB, 解压后 60 KB, 包含 60,000 个标签)
  • Test set images: t10k-images-idx3-ubyte.gz (1.6 MB, 解压后 7.8 MB, 包含 10,000 个样本)
  • Test set labels: t10k-labels-idx1-ubyte.gz (5KB, 解压后 10 KB, 包含 10,000 个标签)

图片是以字节的形式进行存储,下面是官网的数据集说明

1
2
3
4
5
6
7
8
9
10
11
12
TRAINING SET IMAGE FILE (train-images-idx3-ubyte):
[offset] [type] [value] [description]
0000 32 bit integer 0x00000803(2051) magic number
0004 32 bit integer 60000 number of images
0008 32 bit integer 28 number of rows
0012 32 bit integer 28 number of columns
0016 unsigned byte ?? pixel
0017 unsigned byte ?? pixel
........
xxxx unsigned byte ?? pixel

像素是按行组织的,像素值为0到255。0表示背景(白色),255表示前景(黑色)。

就按照字节排序方式,对于图像数据而言,存储是:

1
10101011111111000001110001...

如果图像是长为8,宽为8,那么处理成图片是:

1
2
3
4
10101011
11111100
00011100
01...

MNIST数据集的图片是28 * 28的,也就是说,连续28 * 28=784个二进制值组成一幅28 * 28的二值图像。

对于数据集,处理流程是:

  • 读取训练图像数据
  • 读取训练标签数据
  • 训练
  • 保存训练结果
  • 读取测试图像数据
  • 读取测试标签数据
  • 对每一个测试图像数据进行预测
  • 将每个预测结果与图像对应的标签进行对比
  • 根据对比结果输出准确率

图像显示的时候是将连续二进制数据转换为指定长宽的图片数据,但是训练的时候不能用图片数据。OpenCV的处理是将图片数据转换为一行的向量来进行预测,这样的话一张图片是一行,对应一个标签。

1
2
3
11111111111111 1
11111111111110 0
...

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#include <opencv2/opencv.hpp>

#include <ctime>
#include <fstream>
#include <iostream>
#include <ostream>
#include <string>
#include <vector>

using namespace std;
using namespace cv;
using namespace cv::ml;

//将大端数值转换为小端数值
int reverseDigit(int num) {
unsigned char c1, c2, c3, c4;
c1 = num & 255;
c2 = (num >> 8) & 255;
c3 = (num >> 16) & 255;
c4 = (num >> 24) & 255;

return ((int)c1 << 24) + ((int)c2 << 16) + ((int)c3 << 8) + c4;
}

//读取图像数据
Mat readImagesData(int mode) {
ifstream f;

switch (mode) {
case 0:
f.open("train-images.idx3-ubyte", ios::binary);
cout << "读取训练图像数据" << endl;
break;
case 1:
f.open("t10k-images.idx3-ubyte", ios::binary);
cout << "读取测试图片数据" << endl;
break;
default:
break;
}

if (!f.is_open()) {
cout << "无法读取图像数据" << endl;
exit(-1);
}
/*
byte 0 - 3 : Magic Number(Not to be used)
byte 4 - 7 : Total number of images in the dataset
byte 8 - 11 : rows of each image in the dataset
byte 12 - 15 : cols of each image in the dataset
*/
int magic_number = 0;
int number_of_images = 0;
int height = 0;
int width = 0;

f.read((char *)&magic_number, sizeof(magic_number));
magic_number = reverseDigit(magic_number);

f.read((char *)&number_of_images, sizeof(number_of_images));
number_of_images = reverseDigit(number_of_images);
cout << "图像数量是:" << number_of_images;

f.read((char *)&height, sizeof(height));
height = reverseDigit(height);

f.read((char *)&width, sizeof(width));
width = reverseDigit(width);

Mat train_images = Mat(number_of_images, height * width, CV_8UC1);
for (int i = 0; i < number_of_images; i++) { //第几张图
for (int r = 0; r < height; ++r) {
for (int c = 0; c < width; ++c) {
unsigned char temp = 0;
f.read((char *)&temp, sizeof(temp));
train_images.at<uchar>(i, r * width + c) = (int)temp;
if (i == 0) {
Mat digit = Mat(height, width, CV_8UC1);
digit.at<uchar>(r, c) = (int)temp;
imwrite("digit.png", digit); //输出第一张图片
}
}
}
}

train_images.convertTo(train_images, CV_32F);
f.close();
return train_images;
}

//读取标记数据集
Mat readLabelsData(int mode) {
ifstream f;

switch (mode) {
case 0:
f.open("train-labels.idx1-ubyte");
cout << "读取训练标签" << endl;
break;
case 1:
f.open("t10k-labels.idx1-ubyte");
cout << "读取测试标签" << endl;
break;
default:
break;
}

if (!f.is_open()) {
cout << "无法读取标签数据" << endl;
exit(-1);
}
/*
byte 0 - 3 : Magic Number(Not to be used)
byte 4 - 7 : Total number of labels in the dataset
*/
int magic_number = 0;
int number_of_labels = 0;

f.read((char *)&magic_number, sizeof(magic_number));
magic_number = reverseDigit(magic_number);

f.read((char *)&number_of_labels, sizeof(number_of_labels));
number_of_labels = reverseDigit(number_of_labels);
cout << "标签数量为:" << number_of_labels << endl;

Mat labels = Mat(number_of_labels, 1, CV_8UC1);
for (long int i = 0; i < number_of_labels; i++) {
unsigned char temp = 0;
f.read((char *)&temp, sizeof(temp));
labels.at<uchar>(i, 0) = temp;
}
labels.convertTo(labels, CV_32S);
f.close();
return labels;
}

int main() {
double time_start = (double)clock();

//先训练数据
Mat train_images = readImagesData(0);
if (train_images.size == 0)
return -1;

Mat train_labels = readLabelsData(0);
if (train_labels.size == 0)
return -1;
cout << "成功读取图像和标签" << endl;

Ptr<ml::SVM> svm = ml::SVM::create();
svm->setType(ml::SVM::C_SVC);
svm->setKernel(ml::SVM::LINEAR);
// svm->setDegree(5);
// svm->setGamma(0.01);
// svm->setCoef0(1.0);
// svm->setC(10.0);
// svm->setNu(0.5);
// svm->setP(0.1);
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 1000, FLT_EPSILON));

cout << "SVM训练开始。。。" << endl;
Ptr<ml::TrainData> tdata = ml::TrainData::create(train_images, ml::ROW_SAMPLE, train_labels);
svm->train(tdata);
svm->save("svm.xml");
double time_end = (double)clock();
cout << "SVM训练数据已保存" << endl;
cout << "SVM训练耗时:" << (time_end - time_start) / 1000.0 << "ms" << endl;

cout << "开始导入测试数据" << endl;
Mat tData = readImagesData(1);
if (tData.size == 0)
return -1;
Mat tLabel = readLabelsData(1);
if (tLabel.size == 0)
return -1;
cout << "已成功导入测试数据" << endl;

float count = 0;
for (int i = 0; i < tData.rows; i++) {
Mat sample = tData.row(i);
float res = svm->predict(sample);
res = std::abs(res - tLabel.at<unsigned int>(i, 0)) <= FLT_EPSILON ? 1.0f : 0.0f;
count += res;
}
double _rate = (count + 0.0) / 10000 * 100.0;
cout << "准确率为:" << _rate << "%..." << endl;

return 0;
}

测试输出为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
读取训练图像数据
图像数量是:60000
读取训练标签
标签数量为:60000
成功读取图像和标签
SVM训练开始。。。
SVM训练数据已保存
SVM训练耗时:99225.3ms
开始导入测试数据
读取测试图片数据
图像数量是:10000
读取测试标签
标签数量为:10000
已成功导入测试数据
准确率为:79.02%...

输出的第一张图像是:

digit


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!