OpenCV实现手写体数字训练与识别

简介: OpenCV实现手写体数字训练与识别机器学习(ML)是OpenCV模块之一,对于常见的数字识别与英文字母识别都可以做到很高的识别率,完成这类应用的主要思想与方法是首选对训练图像数据完成预处理与特征提取,根据特征数据组成符合OpenCV要求的训练数据集与标记集,然后通过机器学习的KNN、SVM、ANN等方法完成训练,训练结束之后保存训练结果,对待检测的图像完成分割、二值化、ROI等操作之后,加载训练好的分类数据,就可以预言未知分类。

OpenCV实现手写体数字训练与识别

机器学习(ML)是OpenCV模块之一,对于常见的数字识别与英文字母识别都可以做到很高的识别率,完成这类应用的主要思想与方法是首选对训练图像数据完成预处理与特征提取,根据特征数据组成符合OpenCV要求的训练数据集与标记集,然后通过机器学习的KNN、SVM、ANN等方法完成训练,训练结束之后保存训练结果,对待检测的图像完成分割、二值化、ROI等操作之后,加载训练好的分类数据,就可以预言未知分类。

一:数据集

这里使用的数据集是mnist 手写体数字数据集、关于数据集的具体说明如下:

数据集名称 说明
train-images-idx3-ubyte.gz 训练图像28x28大小,6万张
train-labels-idx1-ubyte.gz 每张图像的数字标记,6万条
t10k-images-idx3-ubyte.gz 测试数据集、1万张图像28x28
t10k-labels-idx1-ubyte.gz 测试数据集标记,表示图像数字

上述数据集数据组成内部结构,图像是以灰度每个字节表示一个像素点的灰度值,图像的总数、宽与高的大小从开始位置读取,说明如下:

开始移位 类型 描述
0000 4字节int类型 0x00000803(2051) 魔数
0004 4字节int类型 60000 图像数目
0008 4字节int类型 28 图像高度
00012 4字节int类型 28 图像宽度

标记部分数据组成如下:

开始移位 类型 描述
0000 4字节int类型 0x00000801(2049) 魔数
0004 4字节int类型 60000 标记数目
0008 1字节ubyte ?? 对应图像数字
0009 1字节ubyte ?? 对应图像数字

- 读取图像数据集

Mat readImages(int opt) {
    int idx = 0;
    ifstream file;
    Mat img;
    if (opt == 0)
    {
        cout << "\n Training...";
        file.open("D:/vcprojects/images/mnist/train-images.idx3-ubyte", ios::binary);
    }
    else
    {
        cout << "\n Test...";
        file.open("D:/vcprojects/images/mnist/t10k-images.idx3-ubyte", ios::binary);
    }
    // check file
    if (!file.is_open())
    {
        cout << "\n File Not Found!";
        return img;
    }
    /*
    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;

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

    file.read((char*)&number_of_images, sizeof(number_of_images));
    number_of_images = reverseDigit(number_of_images);

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

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

    Mat train_images = Mat(number_of_images, height*width, CV_8UC1);
    cout << "\n No. of images:" << number_of_images <<endl;
    Mat digitImg = Mat::zeros(height, width, CV_8UC1);
    for (int i = 0; i < number_of_images; i++) {
        int index = 0;  
        for (int r = 0; r<height; ++r) {
            for (int c = 0; c<width; ++c) {
                unsigned char temp = 0;
                file.read((char*)&temp, sizeof(temp));
                index = r*width + c;
                train_images.at<uchar>(i, index) = (int)temp;
                digitImg.at<uchar>(r, c) = (int)temp;
            }
        }
        if (i < 100) {
            imwrite(format("D:/vcprojects/images/mnist/images/digit_%d.png", i), digitImg);
        }
    }
    train_images.convertTo(train_images, CV_32FC1);
    return train_images;
}
  • 读取标记数据集
Mat readLabels(int opt) {
    int idx = 0;
    ifstream file;
    Mat img;
    if (opt == 0)
    {
        cout << "\n Training...";
        file.open("D:/vcprojects/images/mnist/train-labels.idx1-ubyte");
    }
    else
    {
        cout << "\n Test...";
        file.open("D:/vcprojects/images/mnist/t10k-labels.idx1-ubyte");
    }
    // check file
    if (!file.is_open())
    {
        cout << "\n File Not Found!";
        return img;
    }
    /*
    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;

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

    cout << "\n No. of labels:" << 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;
        file.read((char*)&temp, sizeof(temp));
        //printf("temp : %d\n ", temp);
        labels.at<uchar>(i, 0) = temp;
    }
    labels.convertTo(labels, CV_32SC1);
    return labels;
}

二:训练与测试

对上述数据集,我们不使用提取特征方式,而是采用纯像素数据作为输入,分别使用KNN与SVM对数据集进行训练与测试,比较他们最终的识别率。

KNN方式
KNN是最简单的机器学习方法、主要是计算目标与模型之间的空间向量距离得到最终预测分类结果。训练的代码如下:

void knnTrain() {
    Mat train_images = readImages(0);
    Mat train_labels = readLabels(0);
    printf("\n read mnist train dataset successfully...\n");
    Ptr<ml::KNearest> knn = ml::KNearest::create();
    knn->setDefaultK(5);
    knn->setIsClassifier(true);
    Ptr<ml::TrainData> tdata = ml::TrainData::create(train_images, ml::ROW_SAMPLE, train_labels);
    knn->train(tdata);
    knn->save("D:/vcprojects/images/mnist/knn_knowledge.yml");
}
  • 测试代码如下:
void testMnist() {
    //Ptr<ml::SVM> svm = Algorithm::load<ml::SVM>("D:/vcprojects/images/mnist/knn_knowledge.yml"); // SVM-POLY - 98%
    Ptr<ml::KNearest> knn = Algorithm::load<ml::KNearest>("D:/vcprojects/images/mnist/knn_knowledge.yml"); // KNN - 97%
    Mat train_images = readImages(1);
    Mat train_labels = readLabels(1);
    printf("\n read mnist test dataset successfully...\n");

    float total = train_images.rows;
    float correct = 0;
    Rect rect;
    rect.x = 0;
    rect.height = 1;
    rect.width = (28 * 28);
    for (int i = 0; i < total; i++) {
        int actual = train_labels.at<int>(i);
        rect.y = i;
        Mat oneImage = train_images(rect);
        //int digit = svm->predict(oneImage);
        Mat result;
        float predicted = knn->predict(oneImage, result);
        int digit = static_cast<int>(predicted);
        if (digit == actual) {
            correct++;
        }
    }
    printf("\n recognize rate : %.2f \n", correct / total);
}

SVM方式
SVM的全称是支掌向量机,本来是用来对数据进行二分类的预测与分析、后来扩展到可以对数据进行回归与多分类预测与分析,主要是把数据映射到高维数据空间、把靠近高维数据的部分称为支掌向量(SV)。SVM根据使用的核不同、参数不同,可以得到不同的分类与预测结果、所以在OpenCV中使用SVM做分类的时候,尽量推荐大家使用train_auto方法来训练、但是train_auto运行时间一般都会比较久,有时候可能长达数天。

三:应用

训练好的数据保存在本地,初始化加载,使用对象的识别方法就可以预测分类、进行对象识别。当然这么做,还需要对输入的手写数字图像进行二值化、分割、调整等预处理之后才可以传入进行预测。完整的步骤如下:
这里写图片描述

  • 以下是两个测试图像识别结果:
    这里写图片描述

这里写图片描述

- 注意点:
最终要把图像Mat对象转换为CV_32FC1的灰度,否则可能报错!

欢迎继续关注本人博客,只分享干货!

目录
相关文章
|
C++ Python
Python+OpenCV人行道盲道边缘侦测识别
Python+OpenCV人行道盲道边缘侦测识别
215 1
Python+OpenCV人行道盲道边缘侦测识别
|
监控 计算机视觉 Python
用Python和OpenCV库实现识别人物出现并锁定
用Python和OpenCV库实现识别人物出现并锁定
265 0
|
计算机视觉 Python
最快速度写出一个识别效果——OpenCV模板匹配(含代码)
最快速度写出一个识别效果——OpenCV模板匹配(含代码)
456 0
|
7月前
|
算法 计算机视觉 开发者
OpenCV中使用Eigenfaces人脸识别器识别人脸实战(附Python源码)
OpenCV中使用Eigenfaces人脸识别器识别人脸实战(附Python源码)
362 0
|
计算机视觉 Python
OpenCV完成面部情绪识别
OpenCV完成面部情绪识别
217 0
|
5月前
|
计算机视觉 Python
opencv识别颜色
opencv识别颜色
|
5月前
|
机器学习/深度学习 数据采集 算法
Python基于OpenCV和卷积神经网络CNN进行车牌号码识别项目实战
Python基于OpenCV和卷积神经网络CNN进行车牌号码识别项目实战
378 19
|
6月前
|
移动开发 算法 计算机视觉
技术笔记:openCV特征点识别与findHomography算法过滤
技术笔记:openCV特征点识别与findHomography算法过滤
106 0
|
7月前
|
计算机视觉 开发者 Python
OpenCV中Fisherfaces人脸识别器识别人脸实战(附Python源码)
OpenCV中Fisherfaces人脸识别器识别人脸实战(附Python源码)
254 0
|
7月前
|
算法 计算机视觉
基于opencv的指针式仪表的识别与读数
基于opencv的指针式仪表的识别与读数