分为三大步:
1、处理数字模板
2、处理信用卡上的数字
3、将信用卡上的数字与模板进行匹配
数字模板如图(要与信用卡上的数字相像)
信用卡如图
导入工具包
import numpy as np
import cv2
import myutils
import myutils as contours
myutils就是包含定义排序函数 和 按比例resize图像大小的函数,在下面介绍
一、处理数字模板
分为三步:
1、对图像进行预处理(灰度,二值,)
2、计算找到每个数字的轮廓并排序
3、将每个数字的小照片剪切后依次保存到一个字典中(键=值)
1、对图像进行预处理
# 定义函数方便调用,绘图展示
def cv_show(name,img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
img = cv2.imread(args["template"]) # 读取一个模板图像
cv_show('img',img)
ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转化为灰度图
cv_show('ref',ref)
ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1] # 再转化为二值图像
cv_show('ref',ref)
2、找到数字轮廓并排序
定义排序函数 和 按比例resize图像大小的函数,此部分由工具包引入使用便捷
import cv2
#定义轮廓排序函数
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 = [cv2.boundingRect(c) for c in cnts] #用一个最小的矩形,把找到的形状包起来x,y,h,w
(cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
key=lambda b: b[1][i], reverse=reverse))
#b:b[1][i]是按boundingBoxes中x值排序
return cnts, boundingBoxes
#定义按比例放大缩小图像的函数
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
boundingBoxes = [cv2.boundingRect(c) for c in cnts]
等价于 boundingBoxes=[]
for c in cnts:
boundingBoxes.append(cv2.boundingRect(c))
画出轮廓外接矩形,返回矩形左上角数据,利用横坐标x大小排序
# 计算轮廓
#cv2.findContours()函数接受的参数为二值图,黑白的(不是灰度图),cv2.RETR_EXTERNAL只检测外轮廓,cv2.CHAIN_APPROX_SIMPLE只保留终点坐标
#返回的list中每个元素都是图像中的一个轮廓
refCnts, hierarchy = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img,refCnts,-1,(0,0,255),3) #画轮廓
cv_show('img',img)
#print (np.array(refCnts).shape)
refCnts = myutils.sort_contours(refCnts, method="left-to-right")[0] #排序,从左到右,从上到下
3、将每个数字的小照片剪切后依次保存到一个字典中
digits = {} #定义一个空字典
# 遍历每一个轮廓
for (i, c) in enumerate(refCnts): # 数字的下标和数值是一样的
# 计算外接矩形并且resize成合适大小
(x, y, w, h) = cv2.boundingRect(c)
roi = ref[y:y + h, x:x + w] #轮廓i的外接矩形
roi = cv2.resize(roi, (57, 88)) #将外接矩形大小调整为一固定大小
#cv_show('roi',roi)
# 每一个数字对应每一个模板
digits[i] = roi #i是键,模板是值,并且二者大小相同,添加到字典中
至此,数字模板处理完成
二、处理信用卡上的数字
分为三步
1、对图像进行灰度化并且调整大小
2、对图像进行处理形态学处理和其他操作,使图像中字符连成块状
3、找到卡号所在位置,并提取轮廓
1、对图像进行灰度化并且调整大小
# 初始化卷积核
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
#读取输入图像,预处理
image = cv2.imread('Card.jpg')
cv_show('image',image)
image = myutils.resize(image, width=300) #调整大小
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) #灰度化
cv_show('gray',gray)
2、使图像中字符连成块状
礼帽操作、水平梯度、绝对值,归一化、去掉小数、闭操作、二值化、闭操作
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel) #礼帽操作,突出更明亮的区域
cv_show('tophat',tophat)
#求水平方向的梯度,检测边界
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1) #ksize=-1相当于用3*3的Scharr滤波器
gradX = np.absolute(gradX) #求绝对值
(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('gradX',gradX)
gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel) #通过闭操作(先膨胀,再腐蚀)将数字连在一起(用较大的核)
cv_show('gradX',gradX)
thresh = cv2.threshold(gradX, 0, 255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] #THRESH_OTSU会自动寻找合适的阈值,适合双峰,需把阈值参数设置为0
cv_show('thresh',thresh)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel) #再来一个闭操作,去除杂质
cv_show('thresh',thresh)
3、找到卡号所在位置,并提取轮廓
# 计算轮廓
threshCnts,herarchy= 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('img',cur_img)
locs = [] #定义一个空列表
# 遍历所有轮廓
for (i, c) in enumerate(cnts):
(x, y, w, h) = cv2.boundingRect(c) # 得到轮廓信息
ar = w / float(h) #计算宽与长的比例(不是卡号的话,长宽比一般不一样,可以进行筛选)
# 选择合适的区域,根据实际任务来,这里的基本都是四个数字一组
if ar > 2.5 and ar < 4.0 and (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]) # 将符合的轮廓按横坐标大小从左到右排序这样才和卡号一样
至此,银行卡号四部分轮廓提取完毕。
输入图像 灰度化并调整大小
礼帽操作 水平图像梯度,绝对值,归一化处理 闭操作处理(填充小洞)
二值处理 闭操作处理(填充小洞,>_< 好像没什么用) 画出轮廓
三、将信用卡上的数字与数字模板进行匹配
分为三步
1、对得到的数字块轮廓遍历,利用其位置信息得到数字块轮廓在原图像灰度图中的区域截取下来
2、得到每个小区域中的单个数字
3、将单个数字与模板中10个数字匹配,得到匹配得分最高的模板图像的索引值
1、对得到的数字块轮廓遍历,将数字块轮廓在原图像灰度图中的区域截取下来
output = [] #定义一个列表,将最终结果依次添加到里面
for (i, (gX, gY, gW, gH)) in enumerate(locs): # 遍历每一个轮廓中的数字
groupOutput = [] #将每个轮廓中的数字存储起来,存储完添加到最终结果列表中
# 根据坐标提取每一个组
group = gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]
cv_show('group',group)
group = cv2.threshold(group, 0, 255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] #二值处理
cv_show('group',group)
2、得到每个小区域中的单个数字
(对这些区域进行二值化处理,对区域轮廓按横坐标大小排序,在每个区域里计算每个数字的轮廓信息,得到图像并且resize成数字模板大小)
# 计算每一组数字的轮廓
digitCnts,hierarchy = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
digitCnts = contours.sort_contours(digitCnts,method="left-to-right")[0] #对这几组数字轮廓按横坐标大小排序
# 计算每一组中的每一个数值
for c in digitCnts:
(x, y, w, h) = cv2.boundingRect(c)
roi = group[y:y + h, x:x + w]
roi = cv2.resize(roi, (57, 88)) #找到当前数值的轮廓,resize成数字模板大小
cv_show('roi',roi)
3、将单个数字与模板中10个数字匹配,得到匹配得分最高的模板图像的索引值
scores = [] # 计算匹配得分
# 在模板中计算每一个得分
for (digit, digitROI) in digits.items():
result = cv2.matchTemplate(roi, digitROI,cv2.TM_CCOEFF) # 模板匹配
(_, score, _, _) = cv2.minMaxLoc(result) #数字越大,分值越高,越匹配
scores.append(score)
groupOutput.append(str(np.argmax(scores))) # 返回scores[]里面最大值的索引添加到列表
cv2.rectangle(image, (gX - 5, gY - 5),(gX + gW + 5, gY + gH + 5), (0, 0, 255), 1) #画出外接矩形
cv2.putText(image, "".join(groupOutput), (gX, gY - 15),cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2) #在外接矩形上面写上匹配的数字
output.extend(groupOutput) # 得到最终结果
# 打印结果
print("Credit Card #: {}".format("".join(output)))
cv2.imshow("Image", image)
cv2.waitKey(0)
完成。
区域数字轮廓 二值 单个数字+resize
最终图像