需求
根据数字模板,自动读取银行卡中的银行卡号码。
总体流程
流程
- 获取数字模板。从数字图片中,读取每个数字的模板
- 导入数字模板图片
- 对图像进行灰度转换
- 对图像进行二值化处理
- 提取数字轮廓
- 获取数字的矩形外轮廓。
- 模板匹配。将数字模板与银行卡图片中的模板进行匹配。
- 导入银行卡图片
- 对银行卡图片进行灰度转换,重新计算图片宽高减小数据
- 通过形态学操作,提取数字组
- 对数字组进行拆分,将每个数字与数字模板进行模板匹配,识别数字
- 输出银行卡号码
实现
获取数字模板
import cv2
import matplotlib.pyplot as plt
'''获取图像数字模板'''
# 导入数字模板图片
ref = cv2.imread('../images/ocr_a_reference.png')
# 对图像进行灰度转换
ref_gray = cv2.cvtColor(ref,cv2.COLOR_BGR2GRAY)
# 对图像进行二值化处理
_,ref_threshold = cv2.threshold(ref_gray,10,255,cv2.THRESH_BINARY_INV)
plt.subplot(231),plt.imshow(ref),plt.title('ORIGIN')
plt.subplot(232),plt.imshow(ref_gray,'gray'),plt.title('GRAY')
plt.subplot(233),plt.imshow(ref_threshold,'gray'),plt.title('THRESH')
plt.show()
二值化处理函数
函数原型:retval, dst = cv2.threshold(src, thresh, maxval, type)
src
:输入图像,需要是灰度图像。
thresh
:用于对像素值进行分类的阈值。如果像素值大于或等于阈值,则将其设置为最大值,否则设置为最小值。
maxval
:最大值,用于将大于阈值的像素值赋值为的最大值。type
:二值化的类型1。OpenCV提供了以下几种类型:
cv2.THRESH_BINARY
:如果像素值大于阈值,则将其设置为最大值,否则设置为。cv2.THRESH_BINARY_INV
:如果像素值大于阈值,则将其设置为0,否则设置为最大值。cv2.THRESH_TRUNC
:如果像素值大于阈值,则将其设置为阈值,否则保持不变。cv2.THRESH_TOZERO
:如果像素值小于阈值,则将其设置为0,否则保持不变。cv2.THRESH_TOZERO_INV
:如果像素值大于阈值,则将其设置为0,否则保持不变。返回值:
retval
:使用的阈值。dst
:已二值化的输出图像。
# 提取数字轮廓,因版本问题findContours有三个参数,一般为两个参数
ref_, ref_contours,hierarchy = cv2.findContours(ref_threshold,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
# 绘画数字轮廓
ref_outline = ref.copy()
cv2.drawContours(ref_outline,ref_contours,-1,(0,0,255),3)
cv2.imshow('outline',ref_outline)
cv2.waitKey(0)
cv2.destroyAllWindows()
边缘提取函数
函数原型:contours, hierarchy = cv2.findContours(image, mode, method)
image
:输入图像,需要是二值图像。mode
:轮廓检索模式。OpenCV提供了以下几种模式:
cv2.RETR_EXTERNAL
:只检测外轮廓。cv2.RETR_LIST
:检测所有轮廓,但不建立轮廓间的等级关系。cv2.RETR_CCOMP
:检测所有轮廓并按从左到右的顺序排列。cv2.RETR_TREE
:检测所有轮廓并建立轮廓树。
method
:轮廓近似方法。常用的有:
cv2.CHAIN_APPROX_NONE
:存储所有顶点。cv2.CHAIN_APPROX_SIMPLE
:仅存储水平、垂直方向上的顶点。cv2.CHAIN_APPROX_TC89_L1
:使用TEH_CHAIN近似算法。返回值:
contours
:输出的轮廓集合,每个轮廓由一系列点组成。
hierarchy
:输出的轮廓层次结构,用于表示轮廓之间的父子关系。
使用cv2.boundingRect函数提取出矩形的外轮廓。但由于我们需要的是每个数字对应的矩形的矩形轮廓,可以针对矩形的横坐标按从大到小进行排序,则可将轮廓和数字一一对应。
# 计算轮廓外接矩形和排序
ref_boundingRects = []
ref_outline = ref.copy()
for cont in ref_contours:
# 外接矩形
ref_boundingRects.append(cv2.boundingRect(cont))
x, y, w, h = cv2.boundingRect(cont)
# 在原图上画出预测的矩形
cv2.rectangle(ref_outline, (x, y), (x+w, y+h), (0, 0, 255), 3)
# 对矩形框按左上角横坐标排序,从小到大依次为123....
ref_boundingRects = sorted(ref_boundingRects,key=lambda boundingRect: boundingRect[0])
# 按照矩形框的顺序,从原图像中提取数字影像
digits = []
for index,cout in enumerate(ref_boundingRects):
x,y,w,h = cout
roi = ref_gray[y:y+h,x:x+w]
roi = cv2.resize(roi,(57,88))
digits.append(roi)
cv_show('outline',ref_outline)
提取外接矩形函数
函数原型:x, y, w, h = cv.boundingRect(array)array
:输入的二维点集,可以是vector或Mat类型。一般输入已经提取好的轮廓信息。 返回值:x, y
:矩阵左上点的坐标。
w, h
:矩阵的宽和高。
信用卡图像模糊匹配
我们可以通过形态学操作,单独将银行号码以四个为一组,提取出来,一组银行卡号码会被分为四个部分;然后对每个部分在进行边缘提取,提取出每个数字,对每个数字进行模糊匹配即可得该数字所的值。
形态学操作
# 对图像进行形态学处理
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT,(9,3))
img_close = cv2.morphologyEx(img_thresh,cv2.MORPH_CLOSE,rectKernel)
cv_show('img',img_close)
形态学操作 函数原型:
dst = cv.morphologyEx(src, op, kerne)
src
:输入图像,图像数据类型必须为CV_8U, CV_16U, CV_16S, CV_32F 或 CV_64F中的一种。
op
:形态学处理的类型。OpenCV提供了以下几种类型:
cv2.MORPH_ERODE
:腐蚀处理。cv2.MORPH_DILATE
:膨胀处理。cv2.MORPH_OPEN
:开运算处理。cv2.MORPH_CLOSE
:闭运算处理。cv2.MORPH_GRADIENT
:形态学梯度。cv2.MORPH_TOPHAT
:顶帽变换。cv2.MORPH_BLACKHAT
:黑帽变换。cv2.MORPH_HITMISS
:击中-击不中变换。
kernel
:结构元矩阵。 返回值:dst
:输出图像,即进行形态学操作后的图像。函数原型:
retval = cv.getStructuringElement(shape, ksize[, anchor])
shape
:元素的形状。OpenCV提供了以下几种形状:
cv2.MORPH_RECT
:矩形元素。cv2.MORPH_CROSS
:十字形元素。cv2.MORPH_ELLIPSE
:椭圆形元素。
ksize
:元素的大小。anchor
:锚点的位置,默认值为Point(-1,-1),表示锚点位于元素的中心。 返回值:
retval
:返回指定大小和形状的结构元素(MAT类型)。
识别数字组位置
# 识别数字组位置
# 检测外矩形轮廓
_,img_contours,_ = cv2.findContours(img_close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
img_boundingRects = []
img_outline = img_resize.copy()
for cont in img_contours:
# 筛选符合条件的矩形框,如果长宽比在某个阈值之内,则认为符合条件
boundingRect = cv2.boundingRect(cont) # x, y, w, h
x, y, w, h = boundingRect
ds = w/float(h)
if((ds > 3.2)&(ds < 3.6)):
img_boundingRects.append(boundingRect)
# 在原图上画出预测的矩形
cv2.rectangle(img_outline, (x, y), (x+w, y+h), (0, 0, 255), 1)
# 按照左上角坐标排序,按横轴从小到大排序
img_boundingRects = sorted(img_boundingRects,key=lambda boundingRect: boundingRect[0])
# 添加到数字组中
card_digits =[]
for boundingRect in img_boundingRects:
x, y, w, h = boundingRect
img_ = img_thresh[y-5:y+h+5,x-5:x+w+5]
card_digits.append(img_)
cv_show('img_outline',img_outline)
for card_digit in card_digits:
cv_show('img',card_digit)
数字模糊匹配
获得到的数字组是一个二值图像,可以直接数字组进行边缘匹配,获取数字的矩形边缘,切割出数字图片。在此之前,我们已经获取了所有的数字模板,将切割出的数字与每个数字模板进行模糊匹配,取出最合适的匹配结果,就可以的到数字图片所对应的数字。
# 对每个数字进行模糊匹配
card_num = ''
for card_digit in card_digits:
# cv_show('roi',card_digit)
card_cont_boundingRects = []
# 边缘提取,提取数字边缘
_,card_cont,_ = cv2.findContours(card_digit,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
# 提取矩形边缘,并排序
for cont in card_cont:
card_cont_boundingRects.append(cv2.boundingRect(cont))
card_cont_boundingRects = sorted(card_cont_boundingRects,
key=lambda boundingRect: boundingRect[0])
# 根据排序后的矩形边缘,切割出图片中数字部分
for cont in card_cont_boundingRects:
x,y,w,h = cont
roi = card_digit[y:y+h,x:x+w]
roi = cv2.resize(roi,(57,88))
#cv_show('roi',roi)
# 根据切割出的数字部分,可以对数字进行模糊匹配
result = []
for index,digit in enumerate(digits):
res = cv2.matchTemplate(roi,digit,cv2.TM_CCOEFF_NORMED)
result.append(np.abs(res[0,0]))
# 根据的到的数据,取绝对值最大的为正确的匹配结果
num = np.where(result==np.max(result))[0][0]
card_num += str(num)
card_num += ' '
print(card_num)