给定一个信用卡,最终要输出上面的卡号,且需要在原图中把卡号的位置圈出来。这是一个模板匹配任务,如果想让计算机认识数字,我们需要给定一个模板。
这样, 我们只要找到信用卡上的数字区域,然后拿着数字区域的数字一一与模板进行匹配,看看到底是啥数字,就能识别出来了。 但是,对于信用卡来说我们需要找到它的数字区域呀,对于给定的模板,我们虽然有它的数字区域,但是也得分割成一个个的数字,才能进行匹配工作,所以该任务,就转成了处理信用卡, 处理模板以及模板匹配三个子问题。
大致上思路如下:
使用轮廓检测算法,找到每个对象的大致轮廓以及外接矩形,即先定位到各个对象。找到对象轮廓之后,根据外接矩形的长宽比例,找到中间的这一长串数字部分,由于这个轮廓比较长比较窄,所以还是比较好找的。对于这一长串数字,用形态学操作使其更加突出,让这部分更加精准。接下来,对于这一部分,再次进行轮廓检测,分割成了四个小块,对于每个小块再进行轮廓检测,就能得到每个具体的数字了。对于每个数字,与模板进行匹配(直接有函数可用),就知道是几了。
下面进行代码展示:
import cv2
import numpy as np
# 读取模板图片
img = cv2.imread("./model.png")
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 反二值化处理,常见的二值图像一般前景为白色,背景为黑色
img_gray = cv2.threshold(img_gray, 0, 255, cv2.THRESH_OTSU|cv2.THRESH_BINARY_INV)[1]
# 查找图像的轮廓
# CHAIN_APPROX_NONE保存所有轮廓上的点
# RETR_EXTERNAL=0表示值检测外围轮廓
img_g_con = cv2.findContours(img_gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]
img_g_dst = cv2.drawContours(img.copy(), img_g_con, -1, (0, 0, 255), 2)
# 画模板图片的矩形包围盒
lst_1 = []
for con in img_g_con:
#最大矩形
(x, y, w, h) = cv2.boundingRect(con)
#(x,y)起始坐标
lst_1.append(x)
# 将其包围盒按照x坐标排序,这样就得到了按序排列的0~9模板数字图片,
# (因为查找得到的模板是无序的,排序后可以确认每个模板对应的数字)
(img_g_con, lst_1) = zip(*sorted(zip(img_g_con, lst_1), key=lambda x: x[1], reverse=False))
# 将模板数字图像分割开
digits = {}
# enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标
# sequence -- 一个序列、迭代器或其他支持迭代对象。
# start -- 下标起始位置的值。
for i, con in enumerate(img_g_con):
(x, y, w, h) = cv2.boundingRect(con)
roi = img_gray[y:y + h, x:x + w]
# (57,88)为调整后的模板图像的大小,也可以设置为其他大小
digits[i] = cv2.resize(roi, (57, 88))
# 至此,从0到9的模板图像已经保存在了digits中,模板图像就处理完了。
img_card = cv2.imread("./Card.png")
#灰度化
img_card_gray = cv2.cvtColor(img_card, cv2.COLOR_BGR2GRAY)
# 顶帽操作,原始图像-开运算图像,作用:提取噪音、高亮的区域。
kernel = np.ones((5, 5))
tophat = cv2.morphologyEx(img_card_gray, cv2.MORPH_TOPHAT, kernel)
#求图像梯度
grady = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=0, dy=1, ksize=3)
# Sobel计算出来的像素值类似为float,需要将其规范到(0~255),并且将其格式转为Uint8
grady = cv2.convertScaleAbs(grady)
gradx = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=3)
gradx = cv2.convertScaleAbs(gradx)
# 将X方向的梯度和Y方向的梯度融合。
# 只用单方向梯度也可以达到目的
grad = cv2.addWeighted(gradx, 0.5, grady, 0.5, 0)
# 使用Canny边缘检测,也可以得到类似的效果
# 接下来执行闭操作(先腐蚀后膨胀)一是为了去除一些白色小点噪音,二是将单独的数字给连接成一个小区域
kernel = np.ones((3, 3))
grad_close = cv2.morphologyEx(grad, cv2.MORPH_CLOSE, kernel, iterations=1)
grad_dst = cv2.threshold(grad_close, 0 , 255, cv2.THRESH_OTSU)[1]
# 注意到第三个区域还没有连通,继续进行闭操作
grad_close = cv2.morphologyEx(grad_dst, cv2.MORPH_CLOSE, kernel, iterations=8)
# 目标区域已经完全连通,接下来进行轮廓检测,画包围盒,然后再进行筛选
gradcon = cv2.findContours(grad_close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]
dst = cv2.drawContours(img_card.copy(), gradcon, -1, (0, 0, 255), 2)
# 筛选合适的轮廓,筛选参数值是通过经验获取的,可以将各个轮廓的w,h打印后观察。
losc = []
for con in gradcon:
(x, y, w, h) = cv2.boundingRect(con)
ar = w/h
#print(x,y,w,h)
if ar > 2.4 and ar < 3.5:
if w > 155 or w < 85:
continue
losc.append((x,y,w,h))
# 对矩形包围框按照坐标进行排序,排序目的和前面对模板数字图片的排序目的相同。
sort_losc = sorted(losc, key=lambda x: x[0], reverse=False)
img_part = []
for i in sort_losc:
x, y, w, h = i[0], i[1], i[2], i[3]
con = np.array([[[x, y],[x+w, y], [x+w, y+h], [x, y+h]]])
img_part.append(img_card[y:y+h, x:x+w])
# cv2.imshow('7', img_card[y:y+h, x:x+w])
# 接下来获取每个数字。
digital = []
def f(img):
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_2 = cv2.threshold(img_gray, 0, 255, cv2.THRESH_OTSU)[1]
# 获取轮廓可以进行先Canny检测,再画轮廓,也可以之前求梯度的方法
# 在这里可以直接进行轮廓查找,因为我们需要查找的是每个数字的轮廓,每个数字都是连通的
imgcon = cv2.findContours(img_2, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]
new_con = []
# 筛选
for con in imgcon:
(x, y, w, h) = cv2.boundingRect(con)
if h > 35 or w < 15:
continue
new_con.append((x,y,w,h))
# 排序,目的和前面的类似
sort_con = sorted(new_con,key = lambda x:x[0])
for i in sort_con:
x, y, w, h = i[0], i[1], i[2], i[3]
con = np.array([[[x, y], [x+w, y], [x+w, y+h], [x, y+h]]])
roi = img_2[y-1:y+h+1, x-1:x+w+1]
# 注意数字图片区域和模板图片区域要大小要保持一致
digital.append(cv2.resize(roi, (57, 88)))
for i in img_part:
f(i)
# 接下来进行模板匹配
lst_2 = []
for img_dig in digital:
now = []
for (dig, digroi) in digits.items():
res = cv2.matchTemplate(img_dig, digroi, cv2.TM_CCOEFF_NORMED)
now.append(res.item())
lst_2.append(now.index(max(now)))
now.clear()
print(lst_2)