OpenCV计算机视觉(2)——信用卡数字识别

信用卡数字识别-流程预览

使用模板匹配数字检测

1、图像转灰度图

2、对模板进行轮廓检测,得到的多个轮廓(内轮廓和外轮廓),计算外轮廓的外接矩形

3、对信用可图像做同样的操作后,由于有其他字体或数字的干扰,需要做形态学操作,礼帽+闭操作可以突出明亮区域(为了过滤背景)。x方向的Sobel算子,实验表明,加y的效果的并不好,x方向取绝对值 -> 归一化。通过闭操作(先膨胀,再腐蚀)将数字连在一起.  将本是4个数字的4个框膨胀成1个框,就腐蚀不掉了。。。。等操作(得到字体区域)。

4、车牌识别,银行卡数字识别,数字 长宽比例固定。根据长宽比过滤掉不相关的轮廓。信用卡识别首先要得到4组数字轮廓,一组由4位数组成,后续再分割成单字符

5、再进行匹配,得到结果。

一、基础配置

# 导入工具包
from imutils import contours
import numpy as np
import argparse
import cv2
import myutils

# 设置参数
ap = argparse.ArgumentParser()
ap.add_argument("-i","--image",default='./images/credit_card_01.png',help="path to input image")
ap.add_argument("-t","--template",default='./ocr_a_reference.png',help="path to template OCR image")
args = vars(ap.parse_args())
# 指定信用卡类型
FIRST_NUMBER = {"3": "American Express","4": "Visa","5": "MasterCard","6": "Discover Card"}
# 绘图展示
def cv_show(name,img):
	cv2.imshow(name, img)
	cv2.waitKey(0)
	cv2.destroyAllWindows()

二、模板处理

模板处理流程: 轮廓检测, 外接矩形, 抠出模板, 让模板对应每个数值
字典digits = {} # 存模板的单个数字

# 读取一个模板图像
img = cv2.imread(args["template"])
cv_show('template',img)
# 灰度图
ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv_show('template_gray',ref)
# 二值图像
ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1]
cv_show('template_bi',ref)

# 1.计算轮廓
# cv2.findContours()函数接受的参数为二值图,即黑白的(不是灰度图),cv2.RETR_EXTERNAL只检测外轮廓,cv2.CHAIN_APPROX_SIMPLE只保留终点坐标
# 返回的list中每个元素都是图像中的一个轮廓
refCnts, hierarchy = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)	# cv版本大于3.8的,只有两个返回值

cv2.drawContours(img,refCnts,-1,(0,0,255),3) 	# 轮廓在二值图上得到, 画要画在原图上
cv_show('template_Contours',img)
print (np.array(refCnts).shape)
refCnts = myutils.sort_contours(refCnts, method="left-to-right")[0] #排序,从左到右,从上到下
digits = {}	# 存模板的单个数字

# 2.遍历每一个轮廓,外接矩形
for (i, c) in enumerate(refCnts):	# c是每个轮廓的终点坐标
	# 计算外接矩形并且resize成合适大小
	(x, y, w, h) = cv2.boundingRect(c)
	# 3.抠出模板
	roi = ref[y:y + h, x:x + w]		# 每个roi对应一个数字 
	roi = cv2.resize(roi, (57, 88))	# 太小,调大点

	# 4.每一个数字对应每一个模板
	digits[i] = roi

注:在自己动手实践过程中,发现有以下几个点需要注意(顺一遍没有大问题的同学, 可先跳过此处)

  • Line 8:因findCoutours 检测黑底白字的物体,所以要选择反转的二值化THRESH_BINARY_INV
  • Line 14:我们需要外轮廓,画外接矩形,所以用cv2.RETR_EXTERNAL
  • Line 23:enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列

seq = [‘one’, ‘two’, ‘three’]
for i, element in enumerate(seq):
… print i, element

0 one
1 two
2 three

  • Line 27:侧重于抠出单个数值的模板,而不是画个rectangle
  • Line 20,31:digits设置为字典,第i个健对应的第i个模板数值roi
  • Line 19:对轮廓进行排序,并且返回两个值,只需要轮廓[0]
    refCnts = myutils.sort_contours(refCnts, method=“left-to-right”)[0]

排序前

排序后

 也可以不排序,在后面数值与模板数值匹配时需要做相应改动,感兴趣的同学自行研究

对轮廓进行排序的代码如下:

def sort_contours(cnts, method="left-to-right"):
    reverse = False
    i = 0

    if method == "right-to-left" or method == "bottom-to-top":
        reverse = True

    if method == "top-to-bottom" or method == "bottom-to-top":
        i = 1

    # 计算外接矩形 boundingBoxes是一个元组
    boundingBoxes = [cv2.boundingRect(c) for c in cnts] #用一个最小的矩形,把找到的形状包起来x,y,h,w
    # sorted排序
    (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
                                        key=lambda b: b[1][i], reverse=reverse))

    return cnts, boundingBoxes  # 轮廓和boundingBoxess

三、输入图像处理

在这里插入图片描述

# 1.初始化卷积核,根据实际任务指定大小,不一定非要3x3
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))

# 2.读取输入图像,预处理
image = cv2.imread(args["image"])
cv_show('Input_img',image)
image = myutils.resize(image, width=300)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv_show('Input_gray',gray)

# 3.礼帽操作,突出更明亮的区域 
# 形态学操作,礼帽+闭操作可以突出明亮区域,但并不是非得礼帽+闭操作
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel) 
cv_show('Input_tophat',tophat) 
# 4.x方向的Sobel算子,实验表明,加y的效果的并不好
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0,ksize=-1) #ksize=-1相当于用3*3的

# x方向取绝对值 -> 归一化
gradX = np.absolute(gradX)	# absolute: 计算绝对值
(minVal, maxVal) = (np.min(gradX), np.max(gradX))
gradX = (255 * ((gradX - minVal) / (maxVal - minVal)))
gradX = gradX.astype("uint8")

print (np.array(gradX).shape)
cv_show('Input_Sobel_gradX',gradX)

# 5.通过闭操作(先膨胀,再腐蚀)将数字连在一起.  将本是4个数字的4个框膨胀成1个框,就腐蚀不掉了
gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel) 
cv_show('Input_CLOSE_gradX',gradX)

# 6.THRESH_OTSU会自动寻找合适的阈值,适合双峰,需把阈值参数设置为0
thresh = cv2.threshold(gradX, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] 
cv_show('Input_thresh',thresh)

# 7.再来一个闭操作
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel) # 填补空洞
cv_show('Input_thresh_CLOSE',thresh)

# 8.计算轮廓
threshCnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
	cv2.CHAIN_APPROX_SIMPLE)

cnts = threshCnts
cur_img = image.copy()
cv2.drawContours(cur_img,cnts,-1,(0,0,255),3) 
cv_show('Input_Contours',cur_img)

在这里插入图片描述

在这里插入图片描述

注:

  • Line 20-23:绝对值+归一化
    归一化 x’ = (x-min)/(max-min)
  • Line 36:第二个闭操作换个9x9的核
    onekernel = np.ones((9,9), np.uint8)
    thresh = cv2.morphologyEx(thresh,cv2.MORPH_CLOSE,onekernel)

在这里插入图片描述

四、遍历轮廓

# 1.遍历轮廓
locs = []	# 存符合条件的轮廓
for i,c in enumerate(threshCnts):
	# 计算矩形
	x,y,w,h = cv2.boundingRect(c)

	ar = w / float(h)
	# 选择合适的区域,根据实际任务来,这里的基本都是四个数字一组
	if ar > 2.5 and ar < 4.0:
		if (w > 40 and w < 55) and (h > 10 and h < 20):
			#符合的留下来
			locs.append((x, y, w, h))	

# 将符合的轮廓从左到右排序
locs = sorted(locs,key=lambda x:x[0])

注:

  • Line 6-11:根据w和h的比例,选出包括4个数字的区域,视实际情况判定
  • Line 14:
    key=lambda 元素: 元素[字段索引]
    C = (sorted(C, key=lambda x: x[0]))
    x:x[]字母可以随意修改,排序方式按照中括号[]里面的维度,[0]按照第一维,[1]按照第二维。

五、遍历数字

# 2.遍历每一个轮廓中的数字
output = []	# 存正确的数字
for (i,(gx,gy,gw,gh)) in enumerate(locs):	# 遍历每一组大轮廓(包含4个数字)
	# initialize the list of group digits
	groupOutput = []

	# 根据坐标提取每一个组(4个值)
	group = gray[gy-5:gy+gh+5, gx-5:gx+gw+5]	# 往外扩一点
	cv_show('group_'+str(i),group)
	# 2.1 预处理 
	group = cv2.threshold(group,0,255,cv2.THRESH_BINARY|cv2.THRESH_OTSU)[1]	# 二值化的group
	# cv_show('group_'+str(i),group)
		# 计算每一组的轮廓 这样就分成4个小轮廓了
	digitCnts = cv2.findContours(group.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[0]
		# 排序
	digitCnts = myutils.sort_contours(digitCnts,method="left-to-right")[0]

	# 2.2 计算并匹配每一组中的每一个数值
	for c in digitCnts:	# c表示每个小轮廓的终点坐标
		z = 0
		# 找到当前数值的轮廓,resize成合适的的大小
		(x,y,w,h) = cv2.boundingRect(c)	# 外接矩形
		roi = group[y:y+h,x:x+w]		# 在原图中取出小轮廓覆盖区域,即数字
		roi = cv2.resize(roi, (57, 88))
		# cv_show("roi_"+str(z),roi)
		
		# 计算匹配得分: 0得分多少,1得分多少...
		scores = []	# 单次循环中,scores存的是一个数值 匹配 10个模板数值的最大得分

		# 在模板中计算每一个得分
		# digits的digit正好是数值0,1,...,9;digitROI是每个数值的特征表示
		for (digit,digitROI) in digits.items():
			# 进行模板匹配, res是结果矩阵
			res = cv2.matchTemplate(roi,digitROI,cv2.TM_CCOEFF)	# 此时roi是X digitROI是0 依次是1,2.. 匹配10次,看模板最高得分多少
			Max_score = cv2.minMaxLoc(res)[1]	# 返回4个,取第二个最大值Maxscore
			scores.append(Max_score)	# 10个最大值
		print("scores:",scores)
		# 得到最合适的数字
		groupOutput.append(str(np.argmax(scores)))	# 返回的是输入列表中最大值的位置
		z = z+1
	# 2.3 画出来
	cv2.rectangle(image,(gx-5,gy-5),(gx+gw+5,gy+gh+5),(0,0,255),1)	# 左上角,右下角
	# 2.4 putText参数:图片,添加的文字,左上角坐标,字体,字体大小,颜色,字体粗细
	cv2.putText(image,"".join(groupOutput),(gx,gy-15),
				cv2.FONT_HERSHEY_SIMPLEX,0.65,(0,0,255),2)

	# 2.5 得到结果
	output.extend(groupOutput)
	print("groupOutput:",groupOutput)

在这里插入图片描述

注:

  • 二的digits[i] 是 模板中的单个数值(即五中的digitROI)
  • 五的digitCnts -> group -> roi 是 输入图中的单个数值
  • 输入图中的单个数值 和 模板中的单个数值 需进行相同的resize,否则无法进行匹配
    roi = cv2.resize(roi, (57, 88))
  • Line 11:对group的二值化预处理时(即 将一组轮廓group(4数字) 分为 4个小轮廓digitCnts前),必须加上cv2.THRESH_OTSU,否则检测不出4个小轮廓
  • Line 44:groupOutput中存的是每一组(4个)数值 如4000,因此在putText时,数值中间就不用空格或其他内容了,双引号""中间没有任何东西。
    putText参数:图片,添加的文字,左上角坐标,字体,字体大小,颜色,字体粗细
    putText的第3个参数是左上角的坐标,因此在打印下一组groupOutput,它会重新以这组的(gx,gy-15)为新左上角坐标

六、识别结果

# 打印结果
print("Credit Card Type: {}".format(FIRST_NUMBER[output[0]]))
print("Credit Card #: {}".format("".join(output)))
cv2.imshow("Output_image", image)
cv2.waitKey(0)

输出:

(10,)
(189, 300)
groupOutput: ['4', '0', '0', '0']
groupOutput: ['1', '2', '3', '4']
groupOutput: ['5', '6', '7', '8']
groupOutput: ['9', '0', '1', '0']
Credit Card Type: Visa
Credit Card #: 4000123456789010

在这里插入图片描述

附:测试另外几张信用卡数字识别的效果
在这里插入图片描述

 

 

  • 6
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值