OpenCV4入门144:Qt+OpenCV+SVM+KNN的MNIST手写字符识别

手写字符识别:

  • 使用Qt/C++/Linux实现手写字符(主要是界面)
  • 使用MNIST手写字符集作为训练源
  • 使用OpenCV/SVM/KNN训练MNIST数据集

MNIST字符集读取与训练

MNIST介绍:SVM+MNIST

将代码简单修改就是本文使用的训练测试源码,在这里就不赘述了,具体可看源码。

手写字符界面

所谓手写其实是模拟手写,毕竟一般开发的笔记本和PC没有手写功能,就是用鼠标画图。

先定义两个点lastpoint,endpoint。

鼠标拖动的时候更新两个点数据

1
2
3
4
5
6
7
void Drawing::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton){
lastPoint = event->pos();
}
endPoint = lastPoint;
}

而Qt会自动调用paintEvent绘图,我们使用lastpoint,endpoint划线,同时两个点数据一直在更新,这样就模拟出手写的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void Drawing::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
//qDebug()<<this->size();

QPainter pp(&pix);
pp.setPen(pen);
pp.setFont(font);
pp.drawLine(lastPoint,endPoint);
pp.setRenderHint(QPainter::HighQualityAntialiasing,true);

lastPoint = endPoint;
QPainter painter(this);
painter.drawPixmap(0,0,pix);
}

这里为了效果使用缓冲技术,先将线画在一张图片上,在将图片绘制在界面上。

手写字符处理

鼠标模拟手写结束之后,需要将我们绘制的图片处理一下。我们绘制的图片显示是黑白的,但是实际上他是彩色的。而且训练的数据是1* 784的向量,我们的图片也要转换成这个尺寸,否则会报错。

处理流程为:

  • 保存界面图片
  • 图片缩放至28*28(这是字符集的尺寸)
  • 灰度化图片
  • 二值化图片
  • 将28 * 28转换为1784(2828)
  • 将图片的数据转换为CV_32F
  • 得到结果

源码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
QImage drawImg = ui->wgtDrawing->getImage();
QImage scaleImg = drawImg.scaled(28,28);
cv::Mat img = toMat(scaleImg);
//cv::imshow("drawImg",img);//correct
cv::Mat gray = getGrayImg(img);
//cv::imshow("gray",gray);//correct
cv::Mat bin = getBinImg(gray);
//cv::imshow("bin",bin);//correct

cv::Mat temp(1,28* 28, CV_8UC1);
for(int i=0;i<bin.rows;i++){
for(int j=0;j<bin.cols;j++){
uchar a=bin.at<uchar>(i,j);
temp.at<uchar>(0,i*28+j)=a;
}
}
temp.convertTo(temp,CV_32F);

然后就可以是这个图片进行识别了

结果预测

预测流程是:

  • 检查xml文件是否存在
  • 加载xml文件
  • 预测
  • 结果显示

代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
std::cout << "svm预测开始" << std::endl;
file.open("svm.xml");
if (!file.is_open())
{
std::cout << "->SVM训练结果文件svm.xml不存在" << std::endl;
}

std::cout << "->开始加载svm模型" << std::endl;
cv::Ptr<cv::ml::SVM> svm = cv::Algorithm::load<cv::ml::SVM>("svm.xml");
std::cout << "->svm模型加载完毕" << std::endl;

predicted = svm->predict(temp);
result = static_cast<int>(predicted);
std::cout << "svm预测结束" << std::endl;

源码使用

本文源码地址为:HandWriting

源码有三个文件夹:

  • data
  • src
  • tools

data是已经解压的mnist数据集,src是QT的手写字符识别软件,tools里面是SVM/KNN/OpenCV训练测试MNIST工具。

先到tools文件夹下,可以看到:

tools

将data文件夹中的数据集复制到此文件夹下:

data

然后编译:

1
2
cmake .
make

build

knnttsvmtt就是训练和测试工具(svmtraintest)。

训练SVM,执行:

1
./svmtt

输出为:

tt

svm.xml就是训练结果文件,将其复制到手写字符软件编译可执行文件文件夹下。

同理可得knn结果文件:

knntt

编译运行手写字符软件:

write

使用鼠标绘制字符(相当于手写),点击Type下拉框选择SVM/KNN模型(暂时只有这两个)点击GO就会从软件文件夹加载之前训练的*.xml文件然后预测结果:

predict

到此结束,源码注释都有,可自行优化。