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共享引用(多个变量引用)示例代码
Dec 04 Python
详解Python中的__getitem__方法与slice对象的切片操作
Jun 27 Python
Python操作SQLite数据库的方法详解
Jun 16 Python
python中的tcp示例详解
Dec 09 Python
Python根据欧拉角求旋转矩阵的实例
Jan 28 Python
Python可迭代对象操作示例
May 07 Python
Python对列表的操作知识点详解
Aug 20 Python
Python中的sys.stdout.write实现打印刷新功能
Feb 21 Python
自学python用什么系统好
Jun 23 Python
Python3爬虫关于代理池的维护详解
Jul 30 Python
Python制作简单的剪刀石头布游戏
Dec 10 Python
python爬虫智能翻页批量下载文件的实例详解
Feb 02 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 MSSQL 存储过程的方法
2008/12/24 PHP
php图像处理函数大全(推荐收藏)
2013/07/11 PHP
帝国CMS留言板回复后发送EMAIL通知客户
2015/07/06 PHP
两种php去除二维数组的重复项方法
2015/11/04 PHP
php自动提交表单的方法(基于fsockopen与curl)
2016/05/09 PHP
php中this关键字用法分析
2016/12/07 PHP
使用dynatrace-ajax跟踪JavaScript的性能
2010/04/12 Javascript
autoPlay 基于jquery的图片自动播放效果
2011/12/07 Javascript
jQuery 1.7.2中getAll方法的疑惑分析
2012/05/23 Javascript
DIV+CSS+JS不间断横向滚动实现代码
2013/03/19 Javascript
浅析$.getJSON异步请求和同步请求
2016/06/06 Javascript
Vue.js基础知识小结
2017/01/13 Javascript
关于AngularJs数据的本地存储详解
2017/01/20 Javascript
纯JS实现轮播图
2017/02/22 Javascript
vue自定义底部导航栏Tabbar的实现代码
2018/09/03 Javascript
vue.js 2.*项目环境搭建、运行、打包发布的详细步骤
2019/05/01 Javascript
koa2的中间件功能及应用示例
2020/03/05 Javascript
vue 项目软键盘回车触发搜索事件
2020/09/09 Javascript
javascript实现简单页面倒计时
2021/03/02 Javascript
[13:16]INFAMOUS vs VGJ T BO3
2018/06/07 DOTA
[01:51]2018年度CS GO最具人气外援-完美盛典
2018/12/16 DOTA
[45:52]完美世界DOTA2联赛PWL S3 Forest vs INK ICE 第二场 12.09
2020/12/12 DOTA
django-csrf使用和禁用方式
2020/03/13 Python
Anconda环境下Vscode安装Python的方法详解
2020/03/29 Python
Windows+Anaconda3+PyTorch+PyCharm的安装教程图文详解
2020/04/03 Python
django rest framework serializers序列化实例
2020/05/13 Python
Python+kivy BoxLayout布局示例代码详解
2020/12/28 Python
英国网上超市:Ocado
2020/03/05 全球购物
社团文化节策划书
2014/02/01 职场文书
聚美优品广告词改编
2014/03/14 职场文书
财政局个人总结
2015/03/04 职场文书
2015年维修电工工作总结
2015/04/25 职场文书
2016学习全国教书育人楷模先进事迹心得体会
2016/01/21 职场文书
python某漫画app逆向
2021/03/31 Python
Linux安装Nginx步骤详解
2021/03/31 Servers
Java实现给Word文件添加文字水印
2022/02/15 Java/Android