OpenCV-字典法实现数字识别(尺寸归一化+图像差值)

简介: OpenCV-字典法实现数字识别(尺寸归一化+图像差值)

实现原理

      数字识别是图像处理学在现实生活中较多的应用之一,比如车牌识别、银行卡号识别、执照识别、文档文字OCR识别等等。如何实现数字识别呢?目前最为流行的就是运用AI、机器学习等技术结合图像处理学,大量训练数据集,以实现智能且精确的识别。说到人工智能,很多人可能觉得它非常深奥和复杂,其实说白了它最底层的识别逻辑还是基于普通的图像分析,像特征提取、轮廓分析、比对分析等等,再在庞大的数据集中按照相似程度,分析出一个最可能的结果。


      本文提供了一种相对简单的思路来实现数字识别,适合初学图像处理的新人研究参考。该方法为字典法:首先对图像进行阈值二值化处理,并适当清理微小噪声区;再结合轮廓分析,分割识别图像中的图形区域(不一定全是数字,也可能有其他的图形),并将图形区域尺寸归一化;对字典数字图同样进行分割和尺寸归一化;运用图像差值法,将识别图像的子区域分别比对字典中所有的数字子图,取其中差值最低的数字作为识别数字,若识别完成所有的差值均高于阈值,则识别错误;最后,在原图中给识别成功的子区域绘制最小包围矩形,并在上方书写所识别的数字。

      下方介绍具体流程。

具体流程

1)读取识别图像的原图和灰度图,读取字典图(灰度图)。将灰度图和字典图进行二值化处理。

cv::Mat src = imread("number.jpg");
cv::Mat gray = imread("number.jpg",0);
cv::Mat Template= imread("num.jpg", 0);
// 二值化
cv::Mat thresh,thresh_t;
cv::threshold(gray, thresh, 200, 255, THRESH_BINARY);
cv::threshold(Template, thresh_t,200, 255, THRESH_BINARY);

图1 识别图像二值图

图2 字典二值图


2)若识别图像中数字为深色,背景为白色,则反相;若同字典类似,数字为白,背景为黑,则不动。

// 反相
vector<Point> points;
findNonZero(thresh, points);
if (points.size() > (gray.rows*gray.cols / 2))
{
  thresh = 255 - thresh;
}

3)Clear_MicroConnected_Areas函数清除二值图中的微小噪声区,函数具体介绍见:

OpenCV-清除小面积连通域_翟天保的博客-CSDN博客

4)确认识别图像和字典图的轮廓区。

// 寻找轮廓
vector<vector<Point>> contour,contour_t;
vector<Vec4i> hierarchy, hierarchy_t;
findContours(thresh, contour, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE);
findContours(thresh_t, contour_t, hierarchy_t, RETR_EXTERNAL, CHAIN_APPROX_NONE);

5)用GetSubarea函数实现子区域提取。

// 获取子区域集合
vector<cv::Mat> GetSubarea(cv::Mat src, vector<vector<Point>> contour, vector<Vec4i> hierarchy)
{
  vector<cv::Mat> result;
  if (!contour.empty() && !hierarchy.empty())
  {
    std::vector<std::vector<cv::Point> >::const_iterator itc = contour.begin();
    // 遍历所有轮廓
    while (itc != contour.end())
    {
      // 定位当前轮廓所在位置
      cv::Rect rect = cv::boundingRect(cv::Mat(*itc));
      cv::Mat temp = src(rect).clone();
      result.push_back(temp);
      itc++;
    }
  }
  return result;
}

6)用UniformSize函数实现子区域尺寸归一化。

// 统一尺寸
vector<cv::Mat> UniformSize(vector<cv::Mat> in)
{
  vector<cv::Mat> result;
  for (auto it = in.begin(); it != in.end(); ++it)
  {
    cv::Mat temp;
    resize(*it,temp,Size(48,48));
    result.push_back(temp);
  }
  return result;
}

7)用NumberRecognition函数实现数字识别。该函数输入四个参数,分别为识别图像原图、识别图像尺寸归一后子区域容器、字典图尺寸归一后子区域容器和阈值参数。其中阈值参数用来判断两图的差异性。

// 数字识别
cv::Mat NumberRecognition(cv::Mat Thresh, vector<cv::Mat> sub_size, vector<cv::Mat> sub_t_size, vector<vector<Point>> contour,int thresh)
{
  cv::Mat result = Thresh.clone();
  for (int i = 0; i < sub_size.size(); ++i)
  {
    int min = 999999;
    int idx = 0;
    for (int j = 0; j < sub_t_size.size(); ++j)
    {
      int d = calcDiff(sub_size[i], sub_t_size[j]);
      if (d < min)
      {
        min = d;
        idx = j;
      }
    }
    if (min < thresh)
    {
      cout << "第" << i + 1 << "个轮廓数字识别为:" << a[idx] << endl;
      cv::Rect rect = cv::boundingRect(contour[i]);
      rectangle(result, rect, Scalar(255,0,0), 1, 8);
      putText(result, to_string(a[idx]), Point(rect.x, rect.y - 20), 1, 2, Scalar(0,0,255));
    }
    else {
      cout << "第" << i + 1 << "个轮廓数字识别为: 空" << endl;
    }
  }
  return result;
}
// 对比图像差值
int calcDiff(cv::Mat src1, cv::Mat src2)
{
  CV_Assert(src1.size() == src2.size());
  cv::Mat dif;
  cv::absdiff(src1, src2, dif);
  Scalar Sum=cv::sum(dif);
  return int(Sum[0] / 255);
}

C++测试代码

#include <opencv2/opencv.hpp>
#include <iostream>
#include <algorithm>
#include <time.h>
using namespace cv;
using namespace std; 
vector<cv::Mat> UniformSize(vector<cv::Mat> in);
vector<cv::Mat> GetSubarea(cv::Mat src,vector<vector<Point>> contour, vector<Vec4i> hierarchy);
int calcDiff(cv::Mat src1, cv::Mat src2);
void Clear_MicroConnected_Areas(cv::Mat src, cv::Mat &dst, double min_area);
cv::Mat NumberRecognition(cv::Mat Thresh, vector<cv::Mat> sub_size, vector<cv::Mat> sub_t_size, vector<vector<Point>> contour, int thresh);
int a[10] = { 7,0,9,8,6,5,4,3,2,1 };
int main()
{
  cv::Mat src = imread("number.jpg");
  cv::Mat gray = imread("number.jpg",0);
  cv::Mat Template= imread("num.jpg", 0);
  // 二值化
  cv::Mat thresh,thresh_t;
  cv::threshold(gray, thresh, 200, 255, THRESH_BINARY);
  cv::threshold(Template, thresh_t,200, 255, THRESH_BINARY);
  // 反相
  vector<Point> points;
  findNonZero(thresh, points);
  if (points.size() > (gray.rows*gray.cols / 2))
  {
    thresh = 255 - thresh;
  }
  // 清除噪声区
  Clear_MicroConnected_Areas(thresh, thresh, 200);
  // 寻找轮廓
  vector<vector<Point>> contour,contour_t;
  vector<Vec4i> hierarchy, hierarchy_t;
  findContours(thresh, contour, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE);
  findContours(thresh_t, contour_t, hierarchy_t, RETR_EXTERNAL, CHAIN_APPROX_NONE);
  // 分割子区域
  vector<cv::Mat> sub = GetSubarea(thresh, contour, hierarchy);
  vector<cv::Mat> sub_t = GetSubarea(thresh_t, contour_t, hierarchy_t);
  // 子区域尺寸统一化
  vector<cv::Mat> sub_size = UniformSize(sub);
  vector<cv::Mat> sub_t_size = UniformSize(sub_t);
  // 数字识别(字典法比对)
  int t = 400;
  cv::Mat result = NumberRecognition(src, sub_size, sub_t_size, contour, t);
  imshow("original", src);
  imshow("thresh", thresh);
  imshow("result", result);
  waitKey(0);
  return 0;
}
// 获取子区域集合
vector<cv::Mat> GetSubarea(cv::Mat src, vector<vector<Point>> contour, vector<Vec4i> hierarchy)
{
  vector<cv::Mat> result;
  if (!contour.empty() && !hierarchy.empty())
  {
    std::vector<std::vector<cv::Point> >::const_iterator itc = contour.begin();
    // 遍历所有轮廓
    while (itc != contour.end())
    {
      // 定位当前轮廓所在位置
      cv::Rect rect = cv::boundingRect(cv::Mat(*itc));
      cv::Mat temp = src(rect).clone();
      result.push_back(temp);
      itc++;
    }
  }
  return result;
}
// 统一尺寸
vector<cv::Mat> UniformSize(vector<cv::Mat> in)
{
  vector<cv::Mat> result;
  for (auto it = in.begin(); it != in.end(); ++it)
  {
    cv::Mat temp;
    resize(*it,temp,Size(48,48));
    result.push_back(temp);
  }
  return result;
}
// 对比图像差值
int calcDiff(cv::Mat src1, cv::Mat src2)
{
  CV_Assert(src1.size() == src2.size());
  cv::Mat dif;
  cv::absdiff(src1, src2, dif);
  Scalar Sum=cv::sum(dif);
  return int(Sum[0] / 255);
}
// 清除小区域
void Clear_MicroConnected_Areas(cv::Mat src, cv::Mat &dst, double min_area)
{
  // 备份复制
  dst = src.clone();
  std::vector<std::vector<cv::Point> > contours;  // 创建轮廓容器
  std::vector<cv::Vec4i>  hierarchy;
  // 寻找轮廓的函数
  // 第四个参数CV_RETR_EXTERNAL,表示寻找最外围轮廓
  // 第五个参数CV_CHAIN_APPROX_NONE,表示保存物体边界上所有连续的轮廓点到contours向量内
  cv::findContours(src, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE, cv::Point());
  if (!contours.empty() && !hierarchy.empty())
  {
    std::vector<std::vector<cv::Point> >::const_iterator itc = contours.begin();
    // 遍历所有轮廓
    while (itc != contours.end())
    {
      // 定位当前轮廓所在位置
      cv::Rect rect = cv::boundingRect(cv::Mat(*itc));
      // contourArea函数计算连通区面积
      double area = contourArea(*itc);
      // 若面积小于设置的阈值
      if (area < min_area)
      {
        // 遍历轮廓所在位置所有像素点
        for (int i = rect.y; i < rect.y + rect.height; i++)
        {
          uchar *output_data = dst.ptr<uchar>(i);
          for (int j = rect.x; j < rect.x + rect.width; j++)
          {
            // 将连通区的值置0
            if (output_data[j] == 255)
            {
              output_data[j] = 0;
            }
          }
        }
      }
      itc++;
    }
  }
}
// 数字识别
cv::Mat NumberRecognition(cv::Mat Thresh, vector<cv::Mat> sub_size, vector<cv::Mat> sub_t_size, vector<vector<Point>> contour,int thresh)
{
  cv::Mat result = Thresh.clone();
  for (int i = 0; i < sub_size.size(); ++i)
  {
    int min = 999999;
    int idx = 0;
    for (int j = 0; j < sub_t_size.size(); ++j)
    {
      int d = calcDiff(sub_size[i], sub_t_size[j]);
      if (d < min)
      {
        min = d;
        idx = j;
      }
    }
    if (min < thresh)
    {
      cout << "第" << i + 1 << "个轮廓数字识别为:" << a[idx] << endl;
      cv::Rect rect = cv::boundingRect(contour[i]);
      rectangle(result, rect, Scalar(255,0,0), 1, 8);
      putText(result, to_string(a[idx]), Point(rect.x, rect.y - 20), 1, 2, Scalar(0,0,255));
    }
    else {
      cout << "第" << i + 1 << "个轮廓数字识别为: 空" << endl;
    }
  }
  return result;
}

测试效果

图3 原图

图4 阈值图

图5 识别结果

      不难看出,字典的丰富性影响了识别的准确程度,当字体同字典数字字体类似,即使数字压缩变形也能识别出来,反之则不行。画了个圆识别为了0,emm哈哈哈。手写体用字典法很难做到,毕竟千人千样,字典量过大,程序计算时间就飞上去了。所以,该方法适合标准数字类型,即使是车牌或者信用卡卡号这种,只要提前做好字典都是可以实现的。


      如果函数有什么可以改进完善的地方,非常欢迎大家指出,一同进步何乐而不为呢~


      如果文章帮助到你了,可以点个赞让我知道,我会很快乐~加油!

相关文章
|
1月前
|
计算机视觉
Opencv学习笔记(三):图像二值化函数cv2.threshold函数详解
这篇文章详细介绍了OpenCV库中的图像二值化函数`cv2.threshold`,包括二值化的概念、常见的阈值类型、函数的参数说明以及通过代码实例展示了如何应用该函数进行图像二值化处理,并展示了运行结果。
322 0
Opencv学习笔记(三):图像二值化函数cv2.threshold函数详解
|
2月前
|
算法 计算机视觉
opencv图像形态学
图像形态学是一种基于数学形态学的图像处理技术,它主要用于分析和修改图像的形状和结构。
49 4
|
2月前
|
存储 计算机视觉
Opencv的基本操作(一)图像的读取显示存储及几何图形的绘制
本文介绍了使用OpenCV进行图像读取、显示和存储的基本操作,以及如何绘制直线、圆形、矩形和文本等几何图形的方法。
Opencv的基本操作(一)图像的读取显示存储及几何图形的绘制
|
3月前
|
算法 计算机视觉 Python
python利用opencv进行相机标定获取参数,并根据畸变参数修正图像附有全部代码(流畅无痛版)
该文章详细介绍了使用Python和OpenCV进行相机标定以获取畸变参数,并提供了修正图像畸变的全部代码,包括生成棋盘图、拍摄标定图像、标定过程和畸变矫正等步骤。
python利用opencv进行相机标定获取参数,并根据畸变参数修正图像附有全部代码(流畅无痛版)
WK
|
3月前
|
编解码 计算机视觉 Python
如何在OpenCV中进行图像转换
在OpenCV中,图像转换涉及颜色空间变换、大小调整及类型转换等操作。常用函数如`cvtColor`可实现BGR到RGB、灰度图或HSV的转换;`resize`则用于调整图像分辨率。此外,通过`astype`或`convertScaleAbs`可改变图像数据类型。对于复杂的几何变换,如仿射或透视变换,则可利用`warpAffine`和`warpPerspective`函数实现。这些技术为图像处理提供了强大的工具。
WK
108 1
|
5月前
|
算法 计算机视觉
【Qt&OpenCV 图像的感兴趣区域ROI】
【Qt&OpenCV 图像的感兴趣区域ROI】
165 1
|
5月前
|
运维 算法 计算机视觉
【Qt&OpenCV 图像的模板匹配 matchTemplate/minMaxLoc】
【Qt&OpenCV 图像的模板匹配 matchTemplate/minMaxLoc】
77 1
|
5月前
|
存储 编解码 算法
【Qt&OpenCV 检测图像中的线/圆/轮廓 HoughLinesP/HoughCircles/findContours&drawContours】
【Qt&OpenCV 检测图像中的线/圆/轮廓 HoughLinesP/HoughCircles/findContours&drawContours】
89 0
|
4月前
|
机器学习/深度学习 XML 计算机视觉
OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习库,它提供了大量的函数和工具,用于处理图像和视频数据。
OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习库,它提供了大量的函数和工具,用于处理图像和视频数据。
|
5月前
|
计算机视觉
OpenCV中图像算术操作与逻辑操作
OpenCV中图像算术操作与逻辑操作
66 1