opencv有多强?看它如何识别你的银行卡并提取出卡号!

前言

一个好的深度学习算法工程师,从来就是和opencv、pilow、Matplotlib一起混合用,由于这个代码不是我写的,我就不上传代码了。

opencv的安装

这里我们使用python版opnecv,c++版本的基础算法也差不多。

pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple

 

预备知识

1.二值化

ret, dst = cv2.threshold(src, thresh, maxval, type)
src: 输入图,只能输入单通道图像,通常来说为灰度图
thresh:一般取127和255
maxval: 当像素值超过了阈值(或者小于阈值,根据type来决定),所赋予的值
type:二值化操作的类型,包含以下5种类型:
1.cv2.THRESH_BINARY 超过阈值部分取maxval(最大值),否则取0
2.cv2.THRESH_BINARY_INV THRESH_BINARY的反转
3.cv2.THRESH_TRUNC 大于阈值部分设为阈值,否则不变
4.cv2.THRESH_TOZERO 大于阈值部分不改变,否则设为0
5.cv2.THRESH_TOZERO_INV THRESH_TOZERO的反转
常用
THRESH_BINARY_INV
自动找到阙值
cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第1张图片

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第2张图片

import cv2
import matplotlib.pyplot as plt
img = cv2.imread('cat.jpg')
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret,img_bi = cv2.threshold(img_gray,127,255,cv2.THRESH_BINARY)
ret,img_bi_inv = cv2.threshold(img_gray,127,255,cv2.THRESH_BINARY_INV)
ret,img_tr = cv2.threshold(img_gray,127,255,cv2.THRESH_TRUNC)
ret,img_zero = cv2.threshold(img_gray,127,255,cv2.THRESH_TOZERO)
ret,img_zero_inv = cv2.threshold(img_gray,127,255,cv2.THRESH_TOZERO_INV)

titles = ['Original','Binary','Binary_INV','TRUNC','ZERO','ZERO_INV']
images = [img,img_bi,img_bi_inv,img_tr,img_zero,img_zero_inv]

for i in range(6):
    plt.subplot(2,3,i+1),plt.imshow(images[i],'gray'),plt.title(titles[i])
    plt.xticks([]),plt.yticks([])   # 不显示坐标轴
plt.show(

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第3张图片

2.轮廓检测

contours,hierarchy=cv2.findContours(image, mode, method[, offset])
image:8位单通道图像。视为二值图像
contours:轮廓点。列表格式,每一个元素为一个3维数组(其形状为(n,1,2),其中n表示轮廓点个数,2表示像素点坐标),表示一个轮廓
hierarchy:轮廓间的层次关系,为三维数组,形状为(1,n,4),其中n表示轮廓总个数,4指的是用4个数表示各轮廓间的相互关系。第一个数表示同级轮廓的下一个轮廓编号,第二个数表示同级轮廓的上一个轮廓的编号,第三个数表示该轮廓下一级轮廓的编号,第四个数表示该轮廓的上一级轮廓的编号
mode:轮廓检索的方式
1.cv2.RETR_EXTERNAL:只检索外部轮廓
2.cv2.RETR_LIST: 检测所有轮廓且不建立层次结构
3.cv2.RETR_CCOMP: 检测所有轮廓,建立两级层次结构
4.cv2.RETR_TREE: 检测所有轮廓,建立完整的层次结构
method:轮廓近似的方法
1.cv2.CHAIN_APPROX_NONE:存储所有的轮廓点
2.cv2.CHAIN_APPROX_SIMPLE:压缩水平,垂直和对角线段,只留下端点。
常用
cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

3.形态学

腐蚀与膨胀属于形态学操作,所谓的形态学,就是改变物体的形状,形象理解一些:腐蚀=变瘦(白色部分) 膨胀=变胖(白色部分)
主要是采用 cv2.erode() 和 cv2.dilate()

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第4张图片

主要针对二值化图像的白色部分
腐蚀:是一种消除边界点,使边界向内部收缩的过程.到亮区(背景)变细,而黑色区域(字母)则变大了。

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第5张图片

通俗讲法:在原图的每一个小区域里取最小值,由于是二值化图像,只要有一个点为0,则都为0,来达到瘦身的目的
算法:用 3x3 的 kernel,扫描图像的每一个像素;用 kernel 与其覆盖的二值图像做 “与” 操作;若都为1,则图像的该像素为1;否则为0. 最终结果:使二值图像减小一圈.
膨胀:是将与物体接触的所有背景点合并到该物体中,使边界向外部扩张的过程,可以用来填补物体中的空洞。背景(白色)膨胀,而黑色字母缩小了。

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第6张图片

算法:用 3x3 的 kernel,扫描图像的每一个像素;用 kernel 与其覆盖的二值图像做 “与” 操作;若都为0,则图像的该像素为0;否则为1. 最终结果:使二值图像扩大一圈
1.先腐蚀后膨胀的过程称为 开运算。用来消除小物体、在纤细点处分离物体、平滑较大物体的边界的同时并不明显改变其面积
【cv2.morphologyEx(img,cv2.MORPH_OPEN,kernel)】

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第7张图片

2.先膨胀后腐蚀的过程称为 闭运算。用来填充物体内细小空洞、连接邻近物体、平滑其边界的同时并不明显改变其面积.
【cv2.morphologyEx(img,cv2.MORPH_CLOSE,kernel)】

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第8张图片

3.膨胀 减去 腐蚀的过程称为 梯度运算。用来计算轮廓【cv2.morphologyEx(pie,cv2.MORPH_GRADIENT,kernel)】

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第9张图片

4.顶帽:原始输入 减去 开运算结果 【cv2.morphologyEx(img,cv.MORPH_TOPHAT,kernel)】

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第10张图片

5.黑帽:闭运算 减去 原始输入 【cv2.morphologyEx(img,cv.MORPH_BLACKHAT,kernel)】

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第11张图片

3.sobel算子

cv2.Sobel(src, ddepth, dx, dy, ksize) 进行sobel算子计算
src表示当前图片,
ddepth表示图片深度,这里使用cv2.CV_64F使得结果可以是负值,
dx表示x轴方向,
dy表示y轴方向, ksize表示移动方框的大小
cv2.convertScalerAbs(src) 将像素点进行绝对值计算
src表示当前图片

import cv2
import matplotlib.pyplot as plt
img = cv2.imread('pie.png',cv2.IMREAD_GRAYSCALE)
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)# 1,0: 表示只算水平的,不算竖直的
sobelxx = cv2.convertScaleAbs(sobelx)# 

sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3)
sobelyy = cv2.convertScaleAbs(sobely)

# 分别计算x和y,再求和,融合的较好
sobelxy_1 = cv2.addWeighted(sobelxx,0.5,sobelyy,0.5,0)
# 不建议直接计算,融合的不好
sobelxy_2 = cv2.Sobel(img,cv2.CV_64F,1,1,ksize=3)

# cv_show('pie',img)
cv2.imshow('sobelx',sobelx)
cv2.imshow('sobelxx',sobelxx)
cv2.imshow('sobely',sobely)
cv2.imshow('sobelyy',sobelyy)
cv2.imshow('sobelxy_1',sobelxy_1)
cv2.imshow('sobelxy_2',sobelxy_2)
cv2.waitKey(0)
cv2.destroyAllWindows()

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第12张图片

 

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第13张图片

 

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第14张图片

 

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第15张图片

4.模版匹配

模板匹配和卷积原理很像,模板在原图像上从原点开始滑动,计算模板与(图像被模板覆盖的地方)的差别程度,这个差别程度的计算方法在opencv里有6种,然后格每次计算的結果放入一个矩阵里,作为結果输出.假如原图形是AxB大小,而模板是axb大小,则输出结果的矩阵是(A-a+1)x(B-b+1)

简单来说,模板匹配就是在整个图像区域发现与给定子图像匹配的小块区域。

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第16张图片


cv2.matchTemplate(image, templ, method, result=None, mask=None)
image:待搜索图像
templ:模板图像
method:计算匹配程度的方法
返回参数res:是一个结果矩阵,假设待匹配图像为 I,宽高为(W,H),模板图像为 T,宽高为(w,h)。那么result的大小就为(W-w+1, H-h+1)
其中method:
TM-SQDIFF:计算平方不同,计算出来的值越小,越相关
TM_CCORR:计算相关性,计算出来的值越大,越相关
TM_CCOEFF:计算相关系数,计算出来的值越大,越相关
TM SQDIFF-NORMED: 计算归一化平方不同,计算出来的值越接近0,越相关
TM_CCORR-NORMED: 计t算归一化相关性,计算出来的值越接近1,越相关
TM-CCOEFF-NORMED:计算归一化相关系数,计算出来的值越接近1,越相关

项目步骤

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第17张图片

1.准备识别模版

准备好识别模版,对它进行opencv操作,拆分为单个图0,1,2,3,4,5…并按从左到右排序。

2.对银行卡操作

对银行卡部分进行opencv的一些列操作,选取银行卡卡号的部分,进行拆分,并排序。

 

3.模版匹配

利用模版匹配,计算得分最高的,银行卡号进行识别。

具体代码

文件配置

ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", default='./image/credit_card_01.png',required=True,
	help="path to input image")
ap.add_argument("-t", "--template",default='./image/ocr_a_reference.png', required=True,
	help="path to template OCR-A 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()
print(args["template"])

识别模版读取与裁剪

img=cv2.imread(args["template"])
cv_show("tem",img)
#灰度图
ref=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
cv_show("GRAY",ref)
#二值化
ref=cv2.threshold(ref,10,255,cv2.THRESH_BINARY_INV)[1]
cv_show("GRAY1",ref)
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("GRAY1",img)
refCnts = myutils.sort_contours(refCnts, method="left-to-right")[0]
digits = {}	# 存模板的单个数字
# 2.遍历每一个轮廓,外接矩形
for (i, c) in enumerate(refCnts):
	# 计算外接矩形并且resize成合适大小
	(x, y, w, h) = cv2.boundingRect(c)
	# 3.抠出模板
	roi = ref[y:y + h, x:x + w]		# 每个roi对应一个数字
	roi = cv2.resize(roi, (57, 88))	# 太小,调大点
	cv_show("waijie",roi)
	# 4.每一个数字对应每一个模板
	digits[i] = roi

代码拆分:

img=cv2.imread(args["template"])
cv_show("tem",img)

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第18张图片

#灰度图
ref=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
cv_show("GRAY",ref)

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第19张图片

#二值化
ref=cv2.threshold(ref,10,255,cv2.THRESH_BINARY_INV)[1]
cv_show("GRAY1",ref)
123

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第20张图片

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("GRAY1",img)
12345

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第21张图片

for (i, c) in enumerate(refCnts):
	# 计算外接矩形并且resize成合适大小
	(x, y, w, h) = cv2.boundingRect(c)
	# 3.抠出模板
	roi = ref[y:y + h, x:x + w]		# 每个roi对应一个数字
	roi = cv2.resize(roi, (57, 88))	# 太小,调大点
	cv_show("waijie",roi)
	# 4.每一个数字对应每一个模板
	digits[i] = roi
123456789

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第22张图片

 

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第23张图片

#初始化卷积核的大小
#################################################
# kernel = cv2.getStructuringElement(shape, ksize, anchor)
# shape: 核的形状
# cv2.MORPH_RECT: 矩形
# cv2.MORPH_CROSS: 十字形(以矩形的锚点为中心的十字架)
# cv2.MORPH_ELLIPSE: 椭圆(矩形的内切椭圆)
#
# ksize: 核的大小,矩形的宽,高格式为(width, height)
# anchor: 核的锚点,默认值为(-1, -1), 即核的中心点
#################################################
#卷积核的大小根据项目设定
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('tophat',tophat)

# 4.x方向的Sobel算子,实验表明,加y的效果的并不好
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0,ksize=-1) #ksize=-1相当于用3*3的
# sobelyy = cv2.convertScaleAbs(gradX)
# cv_show('gradXt',sobelyy)

# x方向取绝对值 -> 归一化
# 绝对值+归一化
# 归一化 x’ = (x-min)/(max-min
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)
#通过闭操作(先膨胀,再腐蚀)将数字连在一起.变亮
gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel)
cv_show('Input_CLOSE_gradX',gradX)
# THRESH_OTSU会自动寻找合适的阈值,适合双峰,需把阈值参数设置为0
thresh = cv2.threshold(gradX, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
cv_show('Input_thresh',thresh)
#再来一个闭操作,填补空洞
#两段代码雷同
# onekernel = np.ones((9,9), np.uint8)
# thresh = cv2.morphologyEx(thresh,cv2.MORPH_CLOSE,onekernel)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel) # 填补空洞
cv_show('Input_thresh_CLOSE',thresh)
#计算轮廓
threshCnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
	cv2.CHAIN_APPROX_SIMPLE)
# 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))

# 将符合的轮廓从左到右排序
#x:x[]字母可以随意修改,排序方式按照中括号[]里面的维度,[0]按照第一维,[1]按照第二维。
locs = sorted(locs,key=lambda x:x[0])



分解代码:

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv_show('Input_gray',gray)

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第24张图片

# 3.礼帽操作,突出更明亮的区域 # 形态学操作,礼帽+闭操作可以突出明亮区域,但并不是非得礼帽+闭操作 tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel) cv_show('tophat',tophat)

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第25张图片

# 4.x方向的Sobel算子,实验表明,加y的效果的并不好
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0,ksize=-1) #ksize=-1相当于用3*3的

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第26张图片

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

 

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第27张图片

#通过闭操作(先膨胀,再腐蚀)将数字连在一起.变亮
gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel)
cv_show('Input_CLOSE_gradX',gradX)

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第28张图片

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

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第29张图片

进行模版匹配

遍历每一个轮廓中的数字
output = []	# 存正确的数字
for (i,(gx,gy,gw,gh)) in enumerate(locs):
	groupOutput = []
	# 根据坐标提取每一个组(4个值)
	group = gray[gy-5:gy+gh+5, gx-5:gx+gw+5]	# 往外扩一点
	#cv_show('group_' + str(i), group)
	# 二值化预处理
	group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]  # 二值化的group
	#cv_show('group2_' + str(i), group)
	# 把每个轮廓分成小轮廓
	digitCnts = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
	#排序
	digitCnts = myutils.sort_contours(digitCnts, method="left-to-right")[0]
	for c in digitCnts:
		z = 0
		(x, y, w, h) = cv2.boundingRect(c)  # 外接矩形
		roi = group[y:y + h, x:x + w]  # 在原图中取出小轮廓覆盖区域,即数字
		roi = cv2.resize(roi, (57, 88))
        #cv_show('roi' , roi )
		scores = []
		#进行遍历模版匹配
		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画出来
		cv2.rectangle(image, (gx - 5, gy - 5), (gx + gw + 5, gy + gh + 5), (0, 0, 255), 1)  # 左上角,右下角
		# putText参数:图片,添加的文字,左上角坐标,字体,字体大小,颜色,字体粗细
		cv2.putText(image, "".join(groupOutput), (gx, gy - 15),
					cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)

		# 得到结果
		output.extend(groupOutput)
		print("groupOutput:", groupOutput)
	cv_show("result",image)

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第30张图片

进行模版匹配

遍历每一个轮廓中的数字
output = []	# 存正确的数字
for (i,(gx,gy,gw,gh)) in enumerate(locs):
	groupOutput = []
	# 根据坐标提取每一个组(4个值)
	group = gray[gy-5:gy+gh+5, gx-5:gx+gw+5]	# 往外扩一点
	#cv_show('group_' + str(i), group)
	# 二值化预处理
	group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]  # 二值化的group
	#cv_show('group2_' + str(i), group)
	# 把每个轮廓分成小轮廓
	digitCnts = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
	#排序
	digitCnts = myutils.sort_contours(digitCnts, method="left-to-right")[0]
	for c in digitCnts:
		z = 0
		(x, y, w, h) = cv2.boundingRect(c)  # 外接矩形
		roi = group[y:y + h, x:x + w]  # 在原图中取出小轮廓覆盖区域,即数字
		roi = cv2.resize(roi, (57, 88))
        #cv_show('roi' , roi )
		scores = []
		#进行遍历模版匹配
		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画出来
		cv2.rectangle(image, (gx - 5, gy - 5), (gx + gw + 5, gy + gh + 5), (0, 0, 255), 1)  # 左上角,右下角
		# putText参数:图片,添加的文字,左上角坐标,字体,字体大小,颜色,字体粗细
		cv2.putText(image, "".join(groupOutput), (gx, gy - 15),
					cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)

		# 得到结果
		output.extend(groupOutput)
		print("groupOutput:", groupOutput)
	cv_show("result",image)

opencv有多强?看它如何识别你的银行卡并提取出卡号!_第31张图片

你学会了吗?如需获取系列教程加群:1136192749

 

你可能感兴趣的:(Python,卷积,python,opencv,计算机视觉,cv)