OpenCV C++案例实战三十二《字符识别》

OpenCV C++案例实战三十二《字符识别》


前言

本案例将使用OpenCV C++ 进行字符识别。主要包括制作数据集、以及模型预测两部分。先看看效果如何吧。

一、结果演示

在这里插入图片描述

二、制作数据集

首先第一步,我们需要制作数据集。这里我的方法是,读取一张字符图像,然后通过提取字符轮廓找到字符ROI图像,利用键盘输入给字符打上相应的标签,即完成数据集制作。由于我这里的数据字符图像只包含数字以及大写英文字符,故只识别数字字符以及大写英文字符。如图所示,这是我使用的字符图像,下面需要进行图像预处理提取到字符轮廓。
在这里插入图片描述

//进行图像预处理,提取字符轮廓
Mat grayImg;
cvtColor(Train_Chars, grayImg, COLOR_BGR2GRAY);

Mat blurImg;
GaussianBlur(grayImg, blurImg, Size(3, 3), 0);

Mat binImg;
threshold(blurImg, binImg, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);

    在这里插入图片描述
    接下来需要提取字符轮廓,使用findContours提取最外轮廓。然后提取字符ROI图像,并通过键盘输入对应的ASCII码给其打上标签(注:区分大小写)。
    在这里插入图片描述

    Rect rect = boundingRect(contours[cnt]);
    rectangle(Train_Chars, rect, Scalar(0, 255, 0), 2);
    

    Mat ROI = binImg(rect);
    imshow(“ROI”, ROI);
    imshow(“Training_Chars”, Train_Chars);
    int charVal = waitKey(0); //将字符通过键盘输入给予标签

      进行KNN训练,具体看源码相应注释。

      	//由于我这里的数据字符图像只包含数字以及大写英文字符,只识别数字字符以及大写英文字符
      	vector<int>ValidChars =
      	{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
      	'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
      	'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
      	'U', 'V', 'W', 'X', 'Y', 'Z' };
      

      //进行图像预处理,提取字符轮廓
      Mat grayImg;
      cvtColor(Train_Chars, grayImg, COLOR_BGR2GRAY);
      Mat blurImg;
      GaussianBlur(grayImg, blurImg, Size(3, 3), 0);

      Mat binImg;
      threshold(blurImg, binImg, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
      imwrite(“result.jpg”, binImg);

      vector<vector<Point>>contours;
      findContours(binImg, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

      //准备训练所用数据集,将字符通过键盘输入给予标签
      Mat Train_Data, Train_Label;
      for (int cnt = 0; cnt < contours.size(); cnt++)
      {
      if (contourArea(contours[cnt]) > 10)
      {
      Rect rect = boundingRect(contours[cnt]);
      rectangle(Train_Chars, rect, Scalar(0, 255, 0), 2);

      Mat ROI = binImg(rect);
      imshow(“ROI”, ROI);
      imshow(“Training_Chars”, Train_Chars);
      int charVal = waitKey(0); //将字符通过键盘输入给予标签

      if (find(ValidChars.begin(), ValidChars.end(), charVal) != ValidChars.end())
      {
      //如果输入的字符在字符匹配表中,则进行存储

      //由于我们在识别字符时,会遇到各种尺寸的字符,故将所有的字符固定同一尺寸
      Mat resizeRoi;
      resize(ROI, resizeRoi, Size(ImgWidth, ImgHeight));

      //将图像转成浮点型,因为KNN训练数据集读取的是浮点型数据
      Mat RoiFloat;
      resizeRoi.convertTo(RoiFloat, CV_32FC1);

      Train_Data.push_back(RoiFloat.reshape(0,1));
      Train_Label.push_back(charVal);
      cout << charVal << endl;
      }
      }
      }

      //进行KNN训练
      Train_Data.convertTo(Train_Data, CV_32FC1);
      Train_Label.convertTo(Train_Label, CV_32FC1);
      const int k = 3;//k取值,基数
      Ptr<ml::KNearest>knn = ml::KNearest::create();//构造KNN模型
      knn->setDefaultK(k);//设定k值
      knn->setIsClassifier(true);//KNN算法可用于分类,回归
      knn->setAlgorithmType(ml::KNearest::BRUTE_FORCE);//字符匹配算法
      knn->train(Train_Data, ml::ROW_SAMPLE, Train_Label);//模型训练
      knn->save(filename);//模型保存

      cout << “Model training is complete!” << endl;

        三、字符识别

        当我们完成训练后,会保存相应的模型。这时我们仅需直接读取模型数据就可以了。

        	Ptr<ml::KNearest>knn = cv::Algorithm::load<cv::ml::KNearest>(filename);//加载KNN模型
        

        //图像预处理
        Mat grayImg;
        cvtColor(Test_Chars, grayImg, COLOR_BGR2GRAY);

        Mat blurImg;
        GaussianBlur(grayImg, blurImg, Size(3, 3), 0);

        Mat binImg;
        threshold(blurImg, binImg, 200, 255, THRESH_BINARY_INV);

        vector<vector<Point>>contours;
        findContours(binImg, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);//查找最外轮廓
        for (int cnt = 0; cnt < contours.size(); cnt++)
        {
        if (contourArea(contours[cnt]) > 10)
        {
        Rect rect = boundingRect(contours[cnt]);//轮廓外接矩形
        rectangle(Test_Chars, rect, Scalar(0, 0, 255), 2);

        Mat ROI = binImg(rect);//字符ROI图像

        Mat resizeRoi;//将字符resize成固定大小
        resize(ROI, resizeRoi, Size(ImgWidth, ImgHeight));

        Mat RoiFloat;//将图像转化成CV_32FC1
        resizeRoi.convertTo(RoiFloat, CV_32FC1);
        RoiFloat = RoiFloat.reshape(0, 1);

        float f = knn->predict(RoiFloat);//进行字符识别预测

        //结果显示
        char text[50];
        sprintf_s(text, “%c”,char(int(f)));
        cout << char(int(f)) << endl;//将字符结果float转成char
        double scale = rect.width * 0.02;
        putText(Test_Chars, text, rect.br(), FONT_HERSHEY_SIMPLEX, scale, Scalar(0, 255, 0), 2);
        imshow(“Test_Chars”, Test_Chars);
        waitKey(0);

        }
        }

          四、源码

          #include<iostream>
          #include<opencv2/opencv.hpp>
          #include<opencv2/ml.hpp>
          #include<fstream>
          using namespace std;
          using namespace cv;
          

          const int ImgWidth = 20;//图片宽
          const int ImgHeight = 30;//图片高

          int main()
          {
          //准备数据集
          Mat Train_Chars = imread(“training_chars.png”);//数据集图像
          Mat Test_Chars = imread(“test.jpg”);//测试图像
          if (Train_Chars.empty()|| Test_Chars.empty())
          {
          cout << “can not read the image…” << endl;
          system(“pause”);
          return -1;
          }

          string filename = “model.xml”;//模型文件
          fstream fin;
          fin.open(filename, ios::in);
          if (fin.is_open())
          {
          Ptr<ml::KNearest>knn = cv::Algorithm::load<cv::ml::KNearest>(filename);//加载KNN模型

          //图像预处理
          Mat grayImg;
          cvtColor(Test_Chars, grayImg, COLOR_BGR2GRAY);

          Mat blurImg;
          GaussianBlur(grayImg, blurImg, Size(3, 3), 0);

          Mat binImg;
          threshold(blurImg, binImg, 200, 255, THRESH_BINARY_INV);

          vector<vector<Point>>contours;
          findContours(binImg, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);//查找最外轮廓
          for (int cnt = 0; cnt < contours.size(); cnt++)
          {
          if (contourArea(contours[cnt]) > 10)
          {
          Rect rect = boundingRect(contours[cnt]);//轮廓外接矩形
          rectangle(Test_Chars, rect, Scalar(0, 0, 255), 2);

          Mat ROI = binImg(rect);//字符ROI图像

          Mat resizeRoi;//将字符resize成固定大小
          resize(ROI, resizeRoi, Size(ImgWidth, ImgHeight));

          Mat RoiFloat;//将图像转化成CV_32FC1
          resizeRoi.convertTo(RoiFloat, CV_32FC1);
          RoiFloat = RoiFloat.reshape(0, 1);

          float f = knn->predict(RoiFloat);//进行字符识别预测

          //结果显示
          char text[50];
          sprintf_s(text, “%c”,char(int(f)));
          cout << char(int(f)) << " ";//将字符结果float转成char
          double scale = rect.width * 0.02;
          putText(Test_Chars, text, rect.br(), FONT_HERSHEY_SIMPLEX, scale, Scalar(0, 255, 0), 2);
          imshow(“Test_Chars”, Test_Chars);
          waitKey(0);

          }
          }
          cout << endl;
          }
          else
          {
          //由于我这里的数据字符图像只包含数字以及大写英文字符,只识别数字字符以及大写英文字符
          vector<int>ValidChars =
          { ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’,
          ‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’, ‘G’, ‘H’, ‘I’, ‘J’,
          ‘K’, ‘L’, ‘M’, ‘N’, ‘O’, ‘P’, ‘Q’, ‘R’, ‘S’, ‘T’,
          ‘U’, ‘V’, ‘W’, ‘X’, ‘Y’, ‘Z’ };

          //进行图像预处理,提取字符轮廓
          Mat grayImg;
          cvtColor(Train_Chars, grayImg, COLOR_BGR2GRAY);

          Mat blurImg;
          GaussianBlur(grayImg, blurImg, Size(3, 3), 0);

          Mat binImg;
          threshold(blurImg, binImg, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);

          vector<vector<Point>>contours;
          findContours(binImg, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

          //准备训练所用数据集,将字符通过键盘输入给予标签
          Mat Train_Data, Train_Label;
          for (int cnt = 0; cnt < contours.size(); cnt++)
          {
          if (contourArea(contours[cnt]) > 10)
          {
          Rect rect = boundingRect(contours[cnt]);
          rectangle(Train_Chars, rect, Scalar(0, 255, 0), 2);

          Mat ROI = binImg(rect);
          imshow(“ROI”, ROI);
          imshow(“Training_Chars”, Train_Chars);
          int charVal = waitKey(0); //将字符通过键盘输入给予标签

          if (find(ValidChars.begin(), ValidChars.end(), charVal) != ValidChars.end())
          {
          //如果输入的字符在字符匹配表中,则进行存储

          //由于我们在识别字符时,会遇到各种尺寸的字符,故将所有的字符固定同一尺寸
          Mat resizeRoi;
          resize(ROI, resizeRoi, Size(ImgWidth, ImgHeight));

          //将图像转成浮点型,因为KNN训练数据集读取的是浮点型数据
          Mat RoiFloat;
          resizeRoi.convertTo(RoiFloat, CV_32FC1);
          Train_Data.push_back(RoiFloat.reshape(0,1));
          Train_Label.push_back(charVal);
          cout << charVal << endl;
          }
          }
          }

          //进行KNN训练
          Train_Data.convertTo(Train_Data, CV_32FC1);
          Train_Label.convertTo(Train_Label, CV_32FC1);
          const int k = 3;//k取值,基数
          Ptr<ml::KNearest>knn = ml::KNearest::create();//构造KNN模型
          knn->setDefaultK(k);//设定k值
          knn->setIsClassifier(true);//KNN算法可用于分类,回归
          knn->setAlgorithmType(ml::KNearest::BRUTE_FORCE);//字符匹配算法
          knn->train(Train_Data, ml::ROW_SAMPLE, Train_Label);//模型训练
          knn->save(filename);//模型保存

          cout << “Model training is complete!” << endl;
          }

          destroyAllWindows();
          system(“pause”);
          return 0;
          }


            总结

            本文使用OpenCV C++ 进行字符识别,主要操作有以下几点。
            1、制作数据集,利用键盘输入字符相应的ASCII码
            2、进行KNN训练,得到训练模型
            3、将预测结果转回相应的字符char类型

            • 0
              点赞
            • 0
              收藏
              觉得还不错? 一键收藏
            • 0
              评论
            评论
            添加红包

            请填写红包祝福语或标题

            红包个数最小为10个

            红包金额最低5元

            当前余额3.43前往充值 >
            需支付:10.00
            成就一亿技术人!
            领取后你会自动成为博主和红包主的粉丝 规则
            hope_wisdom
            发出的红包
            实付
            使用余额支付
            点击重新获取
            扫码支付
            钱包余额 0

            抵扣说明:

            1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
            2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

            余额充值