python基于OpenCV模板匹配识别图片中的数字


Posted in Python onMarch 31, 2021

前言

本博客主要实现利用OpenCV的模板匹配识别图像中的数字,然后把识别出来的数字输出到txt文件中,如果识别失败则输出“读取失败”。

操作环境:

  • OpenCV - 4.1.0
  • Python 3.8.1

程序目标

单个数字模板:(这些单个模板是我自己直接从图片上截取下来的)

python基于OpenCV模板匹配识别图片中的数字

要处理的图片:

python基于OpenCV模板匹配识别图片中的数字

终端输出:

python基于OpenCV模板匹配识别图片中的数字

文本输出:

python基于OpenCV模板匹配识别图片中的数字

思路讲解

python基于OpenCV模板匹配识别图片中的数字

代码讲解

首先定义两个会用到的函数

第一个是显示图片的函数,这样的话在显示图片的时候就比较方便了

def cv_show(name, img):
 cv2.imshow(name, img)
 cv2.waitKey(0)
 cv2.destroyAllWindows()

第二个是图片缩放的函数

def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
 dim = None
 (h, w) = image.shape[:2]
 if width is None and height is None:
  return image
 if width is None:
  r = height / float(h)
  dim = (int(w * r), height)
 else:
  r = width / float(w)
  dim = (width, int(h * r))
 resized = cv2.resize(image, dim, interpolation=inter)
 return resized

先把这个代码贴出来,方便后面单个函数代码的理解。

if __name__ == "__main__":
 # 存放数字模板列表
 digits = []
 # 当前运行目录
 now_dir = os.getcwd()
 print("当前运行目录:" + now_dir)
 numbers_address = now_dir + "\\numbers"
 load_digits()
 times = input("请输入程序运行次数:")
 for i in range(1, int(times) + 1):
  demo(i)
 print("输出成功,请检查本地temp.txt文件")
 while True:
  if input("输入小写‘q'并回车退出") == 'q':
   break

接下来是第一个主要函数,功能是加载数字模板并进行处理。

这个函数使用到了os模块,所以需要在开头import os

def load_digits():
 # 加载数字模板
 path = numbers_address # 这个地方就是获取当前运行目录 获取函数在主函数里面
 filename = os.listdir(path) # 获取文件夹文件
 for file in filename:
  img = cv2.imread(numbers_address + "\\" + file) # 读取图片
  img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 灰度处理
  # 自动阈值二值化 把图片处理成黑底白字
  img_temp = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
  # 寻找数字轮廓
  cnt = cv2.findContours(img_temp, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]
  # 获取数字矩形轮廓
  x, y, w, h = cv2.boundingRect(cnt[0])
  # 将单个数字区域进行缩放并存到列表中以备后面使用
  digit_roi = cv2.resize(img_temp[y:y+h, x:x+w], (57, 88))
  digits.append(digit_roi)

最后一个函数是程序的重点,实现功能就是识别出数字并输出。

不过这里把这个大函数分开两部分来讲解。

第一部分是对图片进行处理,最终把图片中的数字区域圈出来。

# 这两个都是核,参数可以改变
 rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 25))
 sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
 # 这个就是读取图片的,可以暂时不理解
 target_path = now_dir + "\\" + "demo_" + str(index) + ".png"
 img_origin = cv2.imread(target_path)
 # 对图片进行缩放处理
 img_origin = resize(img_origin, width=300)
 # 灰度图
 img_gray = cv2.cvtColor(img_origin, cv2.COLOR_BGR2GRAY)
 # 高斯滤波 参数可以改变,选择效果最好的就可以
 gaussian = cv2.GaussianBlur(img_gray, (5, 5), 1)、
 # 自动二值化处理,黑底白字
 img_temp = cv2.threshold(
  gaussian, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
 # 顶帽操作
 img_top = cv2.morphologyEx(img_temp, cv2.MORPH_TOPHAT, rectKernel)
 # sobel操作
 img_sobel_x = cv2.Sobel(img_top, cv2.CV_64F, 1, 0, ksize=7)
 img_sobel_x = cv2.convertScaleAbs(img_sobel_x)
 img_sobel_y = cv2.Sobel(img_top, cv2.CV_64F, 0, 1, ksize=7)
 img_sobel_y = cv2.convertScaleAbs(img_sobel_y)
 img_sobel_xy = cv2.addWeighted(img_sobel_x, 1, img_sobel_y, 1, 0)
 # 闭操作
 img_closed = cv2.morphologyEx(img_sobel_xy, cv2.MORPH_CLOSE, rectKernel)
 # 自动二值化
 thresh = cv2.threshold(
  img_closed, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
 # 闭操作
 img_closed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel)
 # 寻找数字轮廓
 cnts = cv2.findContours(
  img_closed.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]
 # 轮廓排序
 (cnts, boundingBoxes) = contours.sort_contours(cnts, "top-to-bottom")
 # 存放正确数字序列(包含逗号)的轮廓,即过滤掉不需要的轮廓
 right_loc = []
 # 下面这个循环是对轮廓进行筛选,只有长宽比例大于2的才可以被添加到列表中
 # 这个比例可以根据具体情况来改变。除此之外,还可以通过轮廓周长和轮廓面积等对轮廓进行筛选
 for c in cnts:
  x, y, w, h = cv2.boundingRect(c)
  ar = w/float(h)
  if ar > 2:
   right_loc.append((x, y, w, h))

部分步骤的效果图:

python基于OpenCV模板匹配识别图片中的数字

可以看到在进行完最后一次闭操作后,一串数字全部变成白色区域,这样再进行轮廓检测就可以框出每一行数字的大致范围,这样就可以缩小数字处理的范围,可以在这些具体的区域内部对单个数字进行处理。

轮廓效果:

python基于OpenCV模板匹配识别图片中的数字

在这样进行以上步骤之后,就可以确定一行数字的范围了,下面就进行轮廓筛选把符合条件的轮廓存入列表。

注意:在代码中使用了(cnts, boundingBoxes) = contours.sort_contours(cnts, "top-to-bottom")

这个函数的使用需要导入imutils,函数的最后一部分就是对每个数字轮廓进行分割,取出单个数字的区域然后进行模板匹配。

for (gx, gy, gw, gh) in right_loc:
  # 用于存放识别到的数字
  digit_out = []
  # 下面两个判断主要是防止出现越界的情况发生,如果发生的话图片读取会出错
  if (gy-10 < 0):
   now_gy = gy
  else:
   now_gy = gy-10
  if (gx - 10 < 0):
   now_gx = gx
  else:
   now_gx = gx-10
  # 选择图片兴趣区域
  img_digit = gaussian[now_gy:gy+gh+10, now_gx:gx+gw+10]
  # 二值化处理
  img_thresh = cv2.threshold(
   img_digit, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
  # 寻找所有轮廓 找出每个数字的轮廓(包含逗号) 正确的话应该有9个轮廓
  digitCnts = cv2.findContours(
   img_thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]
  # 从左到右排列轮廓
  # 这样排列的好处是,正常情况下可以确定逗号的位置方便后面删除逗号
  (cnts, boundingBoxes) = contours.sort_contours(digitCnts, "left-to-right")
  # cnts是元组,需要先转换成列表,因为后面会对元素进行删除处理
  cnts = list(cnts)
  flag = 0
  # 判断轮廓数量是否有9个
  if len(cnts) == 9:
   # 删除逗号位置
   del cnts[1]
   del cnts[2]
   del cnts[3]
   del cnts[4]
   # 可以在转成元组
   cnts = tuple(cnts)
   # 存放单个数字的矩形区域
   num_roi = []
   for c in cnts:
    x, y, w, h = cv2.boundingRect(c)
    num_roi.append((x, y, w, h))
   # 对数字区域进行处理,把尺寸缩放到与数字模板相同
   # 对其进行简单处理,方便与模板匹配,增加匹配率
   for (rx, ry, rw, rh) in num_roi:
    roi = img_digit[ry:ry+rh, rx:rx+rw]
    roi = cv2.resize(roi, (57, 88))
    # 高斯滤波
    roi = cv2.GaussianBlur(roi, (5, 5), 1)
    # 二值化
    roi = cv2.threshold(
     roi, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
    # 用于存放匹配率
    source = []
    # 遍历数字模板
    for digitROI in digits:
     # 进行模板匹配
     res = cv2.matchTemplate(
      roi, digitROI, cv2.TM_CCOEFF_NORMED)
     max_val = cv2.minMaxLoc(res)[1]
     source.append(max_val)
    # 这个需要仔细理解 这个就是把0-9数字中匹配度最高的数字存放到列表中
    digit_out.append(str(source.index(max(source))))
   # 打印最终输出值
   print(digit_out)
  else:
   print("读取失败")
   flag = 1
  # 将数字输出到txt文本中
  t = ''
  with open(now_dir + "\\temp.txt", 'a+') as q:
   if flag == 0:
    for content in digit_out:
     t = t + str(content) + " "
    q.write(t.strip(" "))
    q.write('\n')
    t = ''
   else:
    q.write("读取失败")
    q.write('\n')

注意理解:digit_out.append(str(source.index(max(source))))

这个是很重要的,列表source存放模板匹配的每个数字的匹配率,求出其中最大值的索引值,因为数字模板是按照0-9排列的,索引source的匹配率也是按照0-9排列的,所以每个元素的索引值就与相匹配的数字相同。这样的话,取得最大值的索引值就相当于取到了匹配率最高的数字。

完整代码

from imutils import contours
import cv2
import os


def cv_show(name, img):
 cv2.imshow(name, img)
 cv2.waitKey(0)
 cv2.destroyAllWindows()


def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
 dim = None
 (h, w) = image.shape[:2]
 if width is None and height is None:
  return image
 if width is None:
  r = height / float(h)
  dim = (int(w * r), height)
 else:
  r = width / float(w)
  dim = (width, int(h * r))
 resized = cv2.resize(image, dim, interpolation=inter)
 return resized


def load_digits():
 # 加载数字模板
 path = numbers_address
 filename = os.listdir(path)
 for file in filename:
  # print(file)
  img = cv2.imread(
   numbers_address + "\\" + file)
  img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  img_temp = cv2.threshold(
   img_gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
  cnt = cv2.findContours(img_temp, cv2.RETR_EXTERNAL,
        cv2.CHAIN_APPROX_NONE)[0]
  x, y, w, h = cv2.boundingRect(cnt[0])
  digit_roi = cv2.resize(img_temp[y:y+h, x:x+w], (57, 88))
  # 将数字模板存到列表中
  digits.append(digit_roi)


def demo(index):
 rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 25))
 sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
 target_path = now_dir + "\\" + "demo_" + str(index) + ".png"
 img_origin = cv2.imread(target_path)
 img_origin = resize(img_origin, width=300)
 img_gray = cv2.cvtColor(img_origin, cv2.COLOR_BGR2GRAY)
 gaussian = cv2.GaussianBlur(img_gray, (5, 5), 1)
 img_temp = cv2.threshold(
  gaussian, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
 img_top = cv2.morphologyEx(img_temp, cv2.MORPH_TOPHAT, rectKernel)
 img_sobel_x = cv2.Sobel(img_top, cv2.CV_64F, 1, 0, ksize=7)
 img_sobel_x = cv2.convertScaleAbs(img_sobel_x)
 img_sobel_y = cv2.Sobel(img_top, cv2.CV_64F, 0, 1, ksize=7)
 img_sobel_y = cv2.convertScaleAbs(img_sobel_y)
 img_sobel_xy = cv2.addWeighted(img_sobel_x, 1, img_sobel_y, 1, 0)
 img_closed = cv2.morphologyEx(img_sobel_xy, cv2.MORPH_CLOSE, rectKernel)
 thresh = cv2.threshold(
  img_closed, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
 img_closed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel)
 cnts = cv2.findContours(
  img_closed.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]
 (cnts, boundingBoxes) = contours.sort_contours(cnts, "top-to-bottom")
 draw_img = img_origin.copy()
 draw_img = cv2.drawContours(draw_img, cnts, -1, (0, 0, 255), 1)
 cv_show("666", draw_img)

 # 存放正确数字序列(包含逗号)的轮廓,即过滤掉不需要的轮廓
 right_loc = []
 for c in cnts:
  x, y, w, h = cv2.boundingRect(c)
  ar = w/float(h)
  if ar > 2:
   right_loc.append((x, y, w, h))
 for (gx, gy, gw, gh) in right_loc:
  # 用于存放识别到的数字
  digit_out = []
  if (gy-10 < 0):
   now_gy = gy
  else:
   now_gy = gy-10
  if (gx - 10 < 0):
   now_gx = gx
  else:
   now_gx = gx-10
  img_digit = gaussian[now_gy:gy+gh+10, now_gx:gx+gw+10]
  # 二值化处理
  img_thresh = cv2.threshold(
   img_digit, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
  # 寻找轮廓 找出每个数字的轮廓(包含逗号) 正确的话应该有9个轮廓
  digitCnts = cv2.findContours(
   img_thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]
  # 从左到右排列
  (cnts, boundingBoxes) = contours.sort_contours(digitCnts, "left-to-right")
  cnts = list(cnts)
  flag = 0
  if len(cnts) == 9:
   del cnts[1]
   del cnts[2]
   del cnts[3]
   del cnts[4]
   cnts = tuple(cnts)
   num_roi = []
   for c in cnts:
    x, y, w, h = cv2.boundingRect(c)
    num_roi.append((x, y, w, h))
   for (rx, ry, rw, rh) in num_roi:
    roi = img_digit[ry:ry+rh, rx:rx+rw]
    roi = cv2.resize(roi, (57, 88))
    roi = cv2.GaussianBlur(roi, (5, 5), 1)
    roi = cv2.threshold(
     roi, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
    source = []
    for digitROI in digits:
     res = cv2.matchTemplate(
      roi, digitROI, cv2.TM_CCOEFF_NORMED)
     max_val = cv2.minMaxLoc(res)[1]
     source.append(max_val)
    digit_out.append(str(source.index(max(source))))
   cv2.rectangle(img_origin, (gx-5, gy-5),
       (gx+gw+5, gy+gh+5), (0, 0, 255), 1)
   print(digit_out)
  else:
   print("读取失败")
   flag = 1
  t = ''
  with open(now_dir + "\\temp.txt", 'a+') as q:
   if flag == 0:
    for content in digit_out:
     t = t + str(content) + " "
    q.write(t.strip(" "))
    q.write('\n')
    t = ''
   else:
    q.write("读取失败")
    q.write('\n')


if __name__ == "__main__":
 # 存放数字模板列表
 digits = []
 # 当前运行目录
 now_dir = os.getcwd()
 print("当前运行目录:" + now_dir)
 numbers_address = now_dir + "\\numbers"
 load_digits()
 times = input("请输入程序运行次数:")
 for i in range(1, int(times) + 1):
  demo(i)
 print("输出成功,请检查本地temp.txt文件")
 cv2.waitKey(0)
 cv2.destroyAllWindows()
 while True:
  if input("输入小写‘q'并回车退出") == 'q':
   break

整个文件下载地址:https://wwe.lanzous.com/iLSDunf850b

注意:如果想同时识别多个图片话,需要将图片统一改名为“demo_ + 数字序号.png” 例如:demo_1.png demo_2.png 同时在运行代码时输入图片个数即可。

总结

这个程序代码相对来说不算复杂,主要是对图像的一些基础处理需要注意。因为不同的图像想要识别成功需要进行不同程度的基础处理,所以在做的时候可以多输出几张图片检查一下那一步效果不太好并及时进行修改调整,这样才能达到最终比较好的效果。

以上就是python基于OpenCV模板匹配识别图片中的数字的详细内容,更多关于python 识别图片中的数字的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
python list使用示例 list中找连续的数字
Jan 27 Python
零基础写python爬虫之urllib2使用指南
Nov 05 Python
使用IronPython把Python脚本集成到.NET程序中的教程
Mar 31 Python
Python实现股市信息下载的方法
Jun 15 Python
Python函数式编程指南(三):迭代器详解
Jun 24 Python
解决Scrapy安装错误:Microsoft Visual C++ 14.0 is required...
Oct 01 Python
Python 实现12306登录功能实例代码
Feb 09 Python
《与孩子一起学编程》python自测题
May 27 Python
python实现抽奖小程序
Apr 15 Python
python pyenv多版本管理工具的使用
Dec 23 Python
基于TensorBoard中graph模块图结构分析
Feb 15 Python
Jupyter 无法下载文件夹如何实现曲线救国
Apr 22 Python
Python insert() / append() 用法 Leetcode实战演示
Mar 31 #Python
tensorflow学习笔记之tfrecord文件的生成与读取
Mar 31 #Python
Python中快速掌握Data Frame的常用操作
Mar 31 #Python
pycharm无法导入lxml的解决办法
python某漫画app逆向
python爬虫--selenium模块
Mar 31 #Python
【超详细】八大排序算法的各项比较以及各自特点
You might like
php高级编程-函数-郑阿奇
2011/07/04 PHP
php二维数组用键名分组相加实例函数
2013/11/06 PHP
php实现parent调用父类的构造方法与被覆写的方法
2015/02/11 PHP
SESSION存放在数据库用法实例
2015/08/08 PHP
PHP实现的mysql操作类【MySQL与MySQLi方式】
2017/10/07 PHP
yii2.0框架使用 beforeAction 防非法登陆的方法分析
2019/09/11 PHP
laravel 实现划分admin和home 模块分组
2019/10/15 PHP
JavaScript 事件属性绑定带参数的函数
2009/03/13 Javascript
jquery 可排列的表实现代码
2009/11/13 Javascript
JS+css 图片自动缩放自适应大小
2013/08/08 Javascript
jQuery用noConflict代替$的实现方法
2017/04/12 jQuery
彻底理解js面向对象之继承
2018/02/04 Javascript
说说node中的可读流和可写流的区别
2018/06/01 Javascript
解决vue js IOS H5focus无法自动弹出键盘的问题
2018/08/30 Javascript
基于Element封装一个表格组件tableList的使用方法
2020/06/29 Javascript
[00:37]食人魔魔法师轮盘吉兆顺应全新至宝将拥有额外款式
2019/12/19 DOTA
python字典序问题实例
2014/09/26 Python
利用Python中的输入和输出功能进行读取和写入的教程
2015/04/14 Python
详细介绍Python中的偏函数
2015/04/27 Python
对python中的logger模块全面讲解
2018/04/28 Python
对python3新增的byte类型详解
2018/12/04 Python
Python+OpenCV图片局部区域像素值处理改进版详解
2019/01/23 Python
解决python gdal投影坐标系转换的问题
2020/01/17 Python
Python datetime 格式化 明天,昨天实例
2020/03/02 Python
对python中return与yield的区别详解
2020/03/12 Python
Python3标准库之threading进程中管理并发操作方法
2020/03/30 Python
Python基于callable函数检测对象是否可被调用
2020/10/16 Python
浅谈Selenium 控制浏览器的常用方法
2020/12/04 Python
laravel使用redis队列实例讲解
2021/03/23 PHP
建筑工程自我鉴定
2013/10/18 职场文书
财务部出纳岗位职责
2013/12/22 职场文书
国际语言毕业生求职信
2014/07/08 职场文书
大学生档案自我鉴定(2篇)
2014/10/14 职场文书
java中重写父类方法加不加@Override详解
2021/06/21 Java/Android
Netty客户端接入流程NioSocketChannel创建解析
2022/03/25 Java/Android
SQL SERVER中的流程控制语句
2022/05/25 SQL Server