银行卡识别

环境

  • Python3.6

  • opencv-python 3.4.1.15

    1
    pip install opencv-python==3.4.1.15
  • opencv-contrib-python 3.4.1.15

    1
    pip install opencv-contrib-python==3.4.1.15
  • matplotlib

1
2
3
4
#引用
import cv2 #opencv读取的格式是RGB
import matplotlib.pyplot as plt
import numpy as np

常用函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
cv2.imread(src) #读取图片
cv2.imread(src,cv2.IMREAD_GRAYSCALE) #读取灰度图片
cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #将图片转换为灰度图
b,g,r = cv2.split(img) #将图片拆分为三个通道
cv2.resize(img,(width,height),[(fx= ,fy=)])
cv2.imshow(title,img) #显示图片
cv2.imwrite(name,img) #保存图片
cv2.waitKey() #图片展示时间,0表示任意键终止
cv2.destroyAllWindows() #关闭所有窗口
img.shape #查看图片数组

#对展示图片进行封装
#cv2原生显示图片
def cv_show(title,img)
cv2.imshow(title,img)
cv2.waitKey()
cv2.destroyAllWindows()
#plt显示彩色图片,不会创建新的窗口
def plt_show(img)
b,g,r = cv2.split(img)
img = cv2.merge([r,g,b])
plt.imshow(img)
plt.show()
#plt显示灰度图片
def plt_show_bw(img):
plt.imshow(img,cmap='gray')
plt.show()

图像阈值

1
2
3
4
5
6
7
8
9
10
11
ret,dst = cv2.threshold(src,thresh,maxval,type)
# src : 输入图,只能是单通道,通常来说为灰度图
# dst : 输出图
# thresh : 阈值
# maxval : 当像素超过了阈值(或者小于阈值,根据type来决定)所赋予的值
# type :
# cv2.THRESH_BINARY 超过阈值部分取maxval,否则取0
# cv2.THRESH_BIN_INV 小于阈值部分取maxval,否则取0
# cv2.THRESH_TRUNC 大于阈值设为阈值,否则不变
# cv2.THRESH_TOZERO 大于阈值部分不改变,否则为0
# cv2.THRESH_TOZERO_INV THRESH_TOZERO的反转

平滑处理

1
2
3
4
5
6
7
8
#均值滤波
cv2.blur(img,(widthSize,heightSize))
#方框滤波,基本和均值滤波一样,可以选择归一化
cv2.boxFilter(img,-1,(widthSize,heightSize),normalize=True)
#高斯滤波
cv2.GaussianBlur(img,(widthSize,heightSize),1)
#中值滤波
cv2.medianBlur(img,size)

形态学

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#核
kernel = np.ones((widthSize,heightSize),np.uint8)
#腐蚀
#iterations是迭代次数
erosion = cv2.erode(img,kernel,iterations = times)
#膨胀
expansion = cv2.dilate(img,kernel,iterations = times)
#开运算:先腐蚀,再膨胀
opening = cv2.morphologyEx(img,cv2.MORPH_OPEN,kernel)
#闭运算:先膨胀,在腐蚀
closing = cv2.morphologyEx(img,cv2.MORPH_CLOSE,kernel)
#梯度运算:膨胀 - 腐蚀
gradient = cv2.morphologyEx(img,cv2.MORPH_GRADIENT,kernel)
#礼帽运算: 原始输入 - 开运算结果
tophat = cv2.morphologyEx(img,cv2.MORPH_TOPHAT,kernel)
#黑帽运算: 闭运算 - 原始输入
blackhat = cv2.morphologyEx(img,cv2.MORPH_BLACKHAT,kernel)

图像梯度 - Sobel算子

1
2
3
4
5
6
7
8
9
10
sobelx = cv2.Sobel(img,cv2.CV_64,1,0,ksize = size)
# 此时只显示左侧边缘,此时要显示右侧边缘要对右侧结果取绝对值
sobelx = cv2.convertScaleAbs(sobelx)
# 计算上下边界
sobely = cv2.Sobel(img,cv2.CV_64,0,1,ksize = size)
sobely = cv2.convertScaleAbs(sobely)
#分别计算x,y求和
sobelxy = cv2.addWeighted(sobelx,0.5,sobely,0.5,0)
#不建议直接计算
sobelxy = cv2.Sobel(img,cv2.CV_64,1,1,ksize = size)

图像梯度 - Scharr算子

1
2
3
4
5
scharrx = cv2.Scharr(img,cv2.CV_64,1,0,ksize = size)
scharrx = cv2.convertScaleAbs(scharrx)
scharry = cv2.Scharr(img,cv2.CV_64,0,1,ksize = size)
scharry = cv2.convertScaleAbs(scharry)
scharrxy = cv2.addWeighted(scharrx,0.5,scharry,0.5,0)

图像梯度 - laplacian算子

1
2
laplacian = cv2.Laplacian(img,cv2.CV_64F)
laplacian = cv2.convertScaleAbs(laplacian)

Canny边缘检测

  • 使用高斯滤波器,以平滑图像,滤除噪声
  • 计算图像中每个像素点的梯度强度和方向
  • 应用非极大值(Non-Maximum Suppression)抑制,以消除边缘检测带来的杂散相应
  • 应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘
  • 通过抑制孤立的弱边缘最终来完成边缘检测

高斯滤波器

梯度和方向

非极大值抑制

双阈值检测

openCV中Canny算子的使用

1
result = cv2.Canny(img,minVal,maxVal)

图像金字塔

高斯金字塔

向下采样方法(缩小)
  • 将图像与高斯内核卷积
  • 将所有偶数行和列去除
1
down = cv2.pyrDown(img)
向上采样方法(放大)
  • 将图像在每个方向扩大为原来的两倍,新增的行和列以0填充
  • 使用先前同样的内核(乘以4)与放大后的图像卷积,获得近似值
1
up = cv2.pyrUp(img)

拉普拉斯金字塔

轮廓检测

轮廓检测函数

findContours
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
binary, contours, hierarchy = cv2.findContours(img,mode,method)
# mode: 轮廓检索模式
# RETR_EXTERNAL: 只检索最外面的轮廓
# RETR_LIST: 检索所有的轮廓,并将其保存到一条链表当中
# RETR_CCOMP: 检索所有的轮廓,并将他们最值为两层 - 顶层是各部分的外部边界,第二层是空洞的边界
# RETR_TREE: 检索所有的轮廓,并重构嵌套轮廓的整个层次
# method: 轮廓笔记的方法
# CHAIN_APPROX_NONE: 以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)
# CHAIN_APPROX_SIMPLE: 压缩水平的、垂直的和斜的部分,也就是,函数值保留他们的终点部分
# binary: 和原图类似的二值图像
# contours: list结构,列表中每个元素代表一个边沿信息。每个元素是(x,1,2)的三维向量,x表示该条边沿里共有多少个像素点,第三维的那个“2”表示每个点的横、纵坐标;
# hierarchy: 包含4个值的数组:[Next, Previous, First Child, Parent]
# Next:与当前轮廓处于同一层级的下一条轮廓
# Previous:与当前轮廓处于同一层级的上一条轮廓
# First Child:当前轮廓的第一条子轮廓
# Parent:当前轮廓的父轮廓
drawContours
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cv2.drawContours(img, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset ]]]]])
# 第一个参数是指明在哪幅图像上绘制轮廓;
# 第二个参数是轮廓本身,在Python中是一个list。
# 第三个参数指定绘制轮廓list中的哪条轮廓,如果是-1,则绘制其中的所有轮廓。
# 第四个参数是边界宽度
img = cv2.imread(url)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, bw = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)

binary, contours, hierarchy = cv2.findContours(bw,RETR_TREE,CHAIN_APPROX_NONE)

#绘制轮廓会覆盖到图片本身,所以对原图拷贝,进行备份处理
draw_img = img.copy()
res = cv2.drawContours(draw_img,contours,-1,(0,0,255),2)
cv2.imshow("res",res)

识别结果

轮廓特征

1
2
cv2.contourArea(cnt) #面积
cv2.arcLength(cnt,Ture) #周长

轮廓近似

1
2
3
4
# 定义一个近似阈值 例如 standard = 0.1 * cv2.arcLength(cnt,Ture)
approximation = cv2.approxPolyDP(cnt,standard,Ture)
draw_img = img.copy()
res = cv2.drawContours(draw_img,[approximation],-1,(0,0,255),2)

边界矩形

1
2
3
x,y,w,h = cv2.boundingRect(cnt)
draw_img = img.copy()
res = cv2.rectangle(draw_img,(x,y),(x+w,y+h),(0,0,255),2)

外接圆

1
2
3
4
5
(x,y),radius = cv.minEnclosingCircle(cnt)
cent = (int(x),int(y))
radius = int(radius)
draw_img = img.copy()
res = cv2.circle(draw_img,center,radius,(0,0,255),2)

模板匹配

1
2
3
4
5
6
7
8
9
10
11
12
res = cv2.matchTemplate(img,template,Method)
# TM_SQDIFF: 计算平方不同,计算出来的值越小,越相关
# TM_CCORR: 计算相关性,计算出来的值越大,越相关
# TM_CCOEFF: 计算相关系数,计算出来的值越大,越相关
# TM_SQDIFF_NORMED: 计算归一化平方不同,计算出来的值越接近0,越相关
# TM_CCORR_NORMED: 计算归一化相关性,计算出来的值越接近1,越相关
# TM_CCOEFF_NORMED: 计算归一化相关系数,计算出来的值越接近1,越相关
min_val,max_val,min_loc,max_loc = cv2.minMaxLoc(res)

# 获取多个匹配的区域
threshold = 0.8
locs = np.where(res >= threshold)
1
2
3
4
5
6
7
8
img = cv2.imread('./img/all.png')
template = cv2.imread('./img/girl.png')
h,w = template.shape[:2]
res = cv2.matchTemplate(img, template, cv2.TM_SQDIFF)
min_val,max_val,min_loc,max_loc = cv2.minMaxLoc(res)
img_copy = img.copy()
res = cv2.rectangle(img_copy,min_loc,(min_loc[0]+w,min_loc[1]+h),(0,0,255),2)
cv_show("res",res)

银行卡识别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import cv2
import numpy as np
from User.Code import MyTools
from User.Code.MyTools import plt_show, cv_show, sort_contours


#读取模板
Template = cv2.imread('./img/Template.png')
Template_gray = cv2.cvtColor(Template,cv2.COLOR_BGR2GRAY)
Template_bw = cv2.threshold(Template_gray,10,255,cv2.THRESH_BINARY_INV)[1]

#获取轮廓
binary, contours, hierarchy = cv2.findContours(Template_bw.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#对轮廓按照x位置进行排序
contours_sorted = sort_contours(contours,method="left-to-right")[0]

digits = {}
for (i,c) in enumerate(contours_sorted): #遍历每一个轮廓,计算外界矩形并且resize成合适大小
(x,y,w,h) = cv2.boundingRect(c)
roi = Template_bw[y:y+h,x:x+w]
roi = cv2.resize(roi,(57,88))
#为每一个数字对应一个模板
digits[i] = roi
#初始化卷积核
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT,(9,3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))

#读取图片
img = cv2.imread('./img/card3.png')
img = MyTools.resize(img,300)
img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

#礼貌操作,突出更明亮的区域
tophat = cv2.morphologyEx(img_gray,cv2.MORPH_TOPHAT,rectKernel)


#Sober显示边缘,归一化
sobelx = cv2.Sobel(tophat,cv2.CV_32F,1,0,ksize = -1)
sobelx = np.absolute(sobelx)
(minVal,maxVal) = (np.min(sobelx),np.max(sobelx))
sobelx = (255*((sobelx - minVal)/(maxVal - minVal)))
sobelx = sobelx.astype("uint8")

#闭操作,将数字连在一起
sobelx = cv2.morphologyEx(sobelx,cv2.MORPH_CLOSE,rectKernel)

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


#再次闭操作
thresh = cv2.morphologyEx(thresh,cv2.MORPH_CLOSE,rectKernel)

#计算轮廓
binary_thresh, contours_thresh, hierarchy_thresh = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# img_copy = img.copy()
# cv2.drawContours(img_copy,contours_thresh,-1,(0,0,255),3)

#获取有用轮廓
locs = []
for (i,c) in enumerate(contours_thresh): #遍历每一个轮廓,计算外界矩形并且resize成合适大小
(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])

#定义一个结果
output = []
#对每一个轮廓进行处理
for (i,(gX,gY,gW,gH)) in enumerate(locs):
groupOutput = []
#根据坐标提取每一个组
group = img_gray[gY-5:gY+gH+5,gX-5:gX+gW+5]
group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
#每一个小矩形轮廓
binary_group, contours_group, hierarchy_group = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
contours_group = sort_contours(contours_group,method="left-to-right")[0]

#计算每一组中的每一个数值
for c in contours_group:
(x,y,w,h) = cv2.boundingRect(c)
roi = group[y:y + h, x:x + w]
roi = cv2.resize(roi, (57, 88))
#每个数对模板进行匹配,并保存结果
scores = []
for (digit,digitROI) in digits.items():
result = cv2.matchTemplate(roi,digitROI,cv2.TM_CCOEFF)
(min_val, max_val, min_loc, max_loc) = cv2.minMaxLoc(result)
score = max_val
scores.append(score)
#对每个数匹配后找到最匹配的那一个
groupOutput.append(str(np.argmax(scores)))
output.extend(groupOutput)
result = "".join(output)
print(result)

实验用的银行卡

获取银行卡数字轮廓

识别结果