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

前言

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

操作环境:

  • OpenCV - 4.1.0
  • Python 3.8.1

程序目标

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

要处理的图片:

终端输出:

文本输出:

思路讲解

代码讲解

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

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

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))

部分步骤的效果图:

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

轮廓效果:

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

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

这个函数的使用需要导入imutils,这个模块具体使用方法可以浏览我的另一篇博客OpenCV学习笔记

函数的最后一部分就是对每个数字轮廓进行分割,取出单个数字的区域然后进行模板匹配。

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 识别图片中的数字的资料请关注我们其它相关文章!

(0)

相关推荐

  • python中的opencv和PIL(pillow)转化操作

    opencv > pil import cv2 from PIL import Image img = cv2.imread("test.png") image = Image.fromarray(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) pil > opencv import cv2 from PIL import Image image = Image.open("test.png") img = cv2.cvtCol

  • Python+Opencv实现数字识别的示例代码

    一.什么是数字识别?   所谓的数字识别,就是使用算法自动识别出图片中的数字.具体的效果如下图所示: 上图展示了算法的处理效果,算法能够自动的识别到LCD屏幕上面的数字,这在现实场景中具有很大的实际应用价值.下面我们将对它的实现细节进行详细解析. 二.如何实现数字识别?   对于数字识别这个任务而言,它并不是一个新的研究方向,很久之前就有很多的学者们在关注这个问题,并提出了一些可行的解决方案,本小节我们将对这些方案进行简单的总结. 方案一:使用现成的OCR技术. OCR,即文字识别,它是一个比较

  • python+opencv实现车道线检测

    python+opencv车道线检测(简易实现),供大家参考,具体内容如下 技术栈:python+opencv 实现思路: 1.canny边缘检测获取图中的边缘信息: 2.霍夫变换寻找图中直线: 3.绘制梯形感兴趣区域获得车前范围: 4.得到并绘制车道线: 效果展示: 代码实现: import cv2 import numpy as np def canny(): gray = cv2.cvtColor(lane_image, cv2.COLOR_RGB2GRAY) #高斯滤波 blur = c

  • Python OpenCV高斯金字塔与拉普拉斯金字塔的实现

    基础知识铺垫 学习图像金字塔,发现网上的资料比较多,检索起来比较轻松. 图像金字塔是一张图像多尺度的表达,或者可以理解成一张图像不同分辨率展示. 金字塔越底层的图片,像素越高,越向上,像素逐步降低,分辨率逐步降低. 高斯金字塔 我们依旧不对概念做过多解释,第一遍学习应用,应用,毕竟 365 天的周期,时间长,后面补充理论知识. 高斯金字塔用于向下采样,同时它也是最基本的图像塔. 在互联网检索原理,得到最简单的说明如下: 将图像的最底层(高斯金字塔的第 0 层),例如高斯核(5x5)对其进行卷积操

  • Python OpenCV 基于图像边缘提取的轮廓发现函数

    基础知识铺垫 在图像中,轮廓可以简单的理解为连接具有相同颜色的所有连续点(边界)的曲线,轮廓可用于形状分析和对象检测.识别等领域. 轮廓发现的原理:先通过阈值分割提取目标物体,再通过边缘检测提取目标物体轮廓. 一个轮廓就是一系列的点(像素),这些点构成了一个有序的点集合. 使用 cv2.findContours 函数可以用来检测图像的边缘. 函数原型说明 contours, hierarchy = cv2.findContours(image, mode, method[, contours[,

  • Python opencv操作深入详解

    直接读取图片 def display_img(file="p.jpeg"): img = cv.imread(file) print (img.shape) cv.imshow('image',img) cv.waitKey(0) cv.destroyAllWindows() 读取灰度图片 def display_gray_img(file="p.jpeg"): img = cv.imread(file,cv.IMREAD_GRAYSCALE) print (img

  • python opencv实现图像配准与比较

    本文实例为大家分享了python opencv实现图像配准与比较的具体代码,供大家参考,具体内容如下 代码 from skimage import io import cv2 as cv import numpy as np import matplotlib.pyplot as plt img_path1 = '2_HE_maxarea.png' img_path2 = '2_IHC_maxarea.png' img1 = io.imread(img_path1) img2 = io.imre

  • python利用opencv实现颜色检测

    本文实例为大家分享了python利用opencv实现颜色检测的具体代码,供大家参考,具体内容如下 需要实现倒车辅助标记检测的功能,倒车辅助标记颜色已经确定了,所以不需要使用深度学习的方法,那样成本太高了,直接可以使用颜色检测的方法. 1.首先需要确定待检测目标的HSV值 import cv2 img = cv2.imread('l3.png') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) hsv = cv2.cvtColor(img, cv2.COL

  • OpenCV+Python几何变换的实现示例

    几何变换 图像的几何变换是指将一幅图像映射到另一幅图像内.有缩放.翻转.仿射变换.透视.重映射等操作. 1 缩放 使用cv2.resize()函数实现对图像的缩放,但要注意cv2.resize()函数内的dsize参数与原图像的行列属性是相反的,也就是:目标图像的行数是原始图像的列数,目标图像的列数是原始图像的行数. 下面举例说明cv2.resize()函数的用法: import cv2 img=cv2.imread('E:/python_opencv/tupian.jpg') rows,col

  • python OpenCV学习笔记

    图像翻转 使用Python的一个包,imutils.使用下面的指令可以安装. pip install imutils imutils包的Github地址:https://github.com/jrosebr1/imutils CSDN镜像:https://codechina.csdn.net/mirrors/jrosebr1/imutils 可以在上面这个地址里面学习更多的使用方式. import cv2 import imutils ''' imutils.rotate 第一个参数是翻转的图像

随机推荐