信用卡/银行卡识别(OpenCV)

本文详细描述了使用Python和OpenCV进行信用卡/银行卡识别的过程,包括读取模板图片、预处理目标图片、模板制作与保存、目标图片的轮廓检测和模板匹配,最终实现数字识别。
摘要由CSDN通过智能技术生成

记录一下第一次手动'机器学习'过程

源项目来自B站Python-木子的视频

信用卡/银行卡识别

主体思路

  1. 指定模板

    1. 读入模板图片以及该模板图片的灰度图

    2. 灰度图进行阈值处理

    3. 查找轮廓并绘制(绘制轮廓只是方便我们用户查看是否查找轮廓正确)

    4. 按轮廓制作数字模板并排序保存

  2. 目标图片

    1. 读入目标图片以及该目标图片的灰度图片

    2. 对图片进行预处理操作(阈值处理,梯度计算,形态学处理)

    3. 查找轮廓并绘制(绘制轮廓只是方便我们用户查看是否查找轮廓正确)

    4. 按轮廓筛选并分割成大模板并排序

    5. 大模板继续分割成一个个单独的数字图片

    6. 进行模板匹配

    7. 输出结果

具体代码

头文件

import numpy as np
import cv2
import myutils

预设函数

def show(img, name='demo'):
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

对模板图片进行一系列操作

读入模板
template = cv2.imread('../res/template.png')#python读取文件的方式
template_gray = cv2.imread('../res/template.png', 0)#转换为灰度图,当然你也可以使用cv2.cvtColor()进行灰度处理
show(template_gray)
对灰度图进行阈值处理

原图的作用:绘制模板查看具体情况

灰度图的作用:图像处理

template_gray = cv2.threshold(template_gray, 10, 255, cv2.THRESH_BINARY_INV)[1]#注意这个[1],因为该函数返回2个值
show(template_gray)
查找轮廓
template_cnts = cv2.findContours(template_gray.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
绘制轮廓
cv2.drawContours(template, template_cnts, -1, (0, 0, 255), 2)
定义字典
template_dic = {}#该字典用于存放 每一个轮廓对应的(x,y,w,h)
模板制作与保存
template_cnts = myutils.sort_contours(template_cnts)[0]#自定义sort函数,将轮廓图从左到右排序
# 遍历每一个轮廓,找到每一个模板,并将模板装入到 template_dic 字典中
for (i, template_single_contour) in enumerate(template_cnts):#推荐使用enumerate()将下标值标识为i
    (x, y, w, h) = cv2.boundingRect(template_single_contour)#boundingRect()函数基于轮廓得到其外围矩形的基本信息
    single_template = template_gray[y:y + h, x:x + w]#截图,一般都是处理灰度图
    single_template = cv2.resize(single_template, (57, 88))
    template_dic[i] = single_template#字典中存放的是 基于轮廓得到的矩形 所绘制的灰度图的部分截图,而不是存放轮廓!

对目标图片进行处理

读入图片
image = cv2.imread('../res/card.png')
image = cv2.resize(image, (400, 300))#根据个人喜好进行设置
image_gray = cv2.imread('../res/card.png', 0)
image_gray = cv2.resize(image_gray, (400, 300))
自定义卷积核

方便后续图像处理涉及的卷积计算

rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (12, 4))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
#为什么不用rectKernel = np.ones((12, 4), np.uint8)作为卷积?
#np.ones()得到的卷积 按行排列
#cv2.getStructuringElement()得到的卷积 按列排列,一般涉及OpenCV我们只需要使用cv2库里面的卷积
#由于它们的排列方式不同,因此产生了不同的效果。为了避免这种情况,建议始终使用cv2.getStructuringElement()函数来创建卷积核,这样可以确保得到的卷积核是按列优先存储的,与OpenCV中其他函数的默认行为相匹配
图片礼帽操作
#对图片进行礼帽处理:
#礼帽: 原始输入 - 开运算结果
#目的: 保留并突出 图片中 较为明亮的部分
tophat = cv2.morphologyEx(image_gray, cv2.MORPH_TOPHAT, rectKernel)

梯度计算

计算梯度 类似 查找边缘 , 方便我们后续进行阈值处理

一般用Sobel算子就好了

Scharr算子更加细腻,能捕捉更多细节,更加丰富,计算规则同Sobel

gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=3)
gradX = cv2.convertScaleAbs(gradX)  # 这里不能直接np.abs,凡是涉及CV的,一律用CV的库函数,避免出现莫名其妙的BUG
gradY = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=0, dy=1, ksize=3)
gradY = cv2.convertScaleAbs(gradY)
gradXY = cv2.addWeighted(gradX, 0.5, gradY, 0.5, 0)#分开计算X,Y的梯度,再加起来,处理效果最好

归一化操作

为什么要归一化操作?

归一化是一种常见的操作,通常用于将图像的像素值范围调整到特定的区间内。

这是因为在进行图像处理算法时,不同的像素值范围可能会导致算法的不稳定性或不准确性。

(minVal, maxVal) = (np.min(gradXY), np.max(gradXY))
gradXY = (255 * ((gradXY - minVal) / (maxVal - minVal)))#这个计算规则常用~
gradXY = cv2.convertScaleAbs(gradXY)#取绝对值
show(gradXY)

闭运算

闭运算:填充图片中的黑色,目的是将目标区域连成一块,方便后续进行轮廓查找

image_close = cv2.morphologyEx(gradXY, cv2.MORPH_CLOSE, rectKernel)

阈值处理
image_threshold = cv2.threshold(image_close, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]#让CV算法自己去识别处理

再次闭运算
# 若第一次闭运算填充效果不好,可以再进行几次闭运算,直到目标区域连在一起,方便后续轮廓查找与切割
image_close2 = cv2.morphologyEx(image_threshold, cv2.MORPH_CLOSE, sqKernel)
查找轮廓
image_contours = cv2.findContours(image_close2.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
cv2.drawContours(image, image_contours, -1, (0, 0, 255), 2)
show(image)#检查轮廓是否正确

自定义存储变量并保存
locs = [] #用于存放后续的目标矩形(x,y,w,h)
for c in image_contours:
    (x, y, w, h) = cv2.boundingRect(c)
    ar = w / float(h) #带一个float 可以直接让 ar 也变为 float类型,更加精确
    #经测量得目标区域轮廓的基本范围,我们只需要提取出我们需要的那4个矩形轮廓区域即可
    if 2.25 < ar < 3.0:
        if 25 < h < 30 and 65 < w < 80:
            locs.append((x, y, w, h))
locs.sort()#内置的sort也会根据位置进行排序,这里是一种取巧的做法,保险起见还是调用myutils里面的sort()方法
图片分割处理与模板匹配
locs_final = []
result = []
temp_max = 0
temp_num = 0
for (i, (gx, gy, gw, gh)) in enumerate(locs):#拿到存放的矩形的信息
    group = image[gy - 5:gy + gh + 5, gx - 5:gx + gw + 5]#截图常用语法~
    group_gray = image_gray[gy - 5:gy + gh + 5, gx - 5:gx + gw + 5]
    group_gray = cv2.threshold(group_gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    show(group_gray)#不出错即为 '4000'
    group_cnts = cv2.findContours(group_gray.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
    cv2.drawContours(group, group_cnts, -1, (0, 0, 255), 2)
    show(group)#不出意外为 彩图,且'4000'已被识别 并红笔描边~
    for c in group_cnts:#对于 '4000' 处理后得到的 4个轮廓 依次处理
        (x, y, w, h) = cv2.boundingRect(c)#得到每一个轮廓的外接矩形的信息并用locs_final存储起来
        locs_final.append((x, y, w, h))
    locs_final.sort()#取巧的排序
    for (fx, fy, fw, fh) in locs_final:#这里我们得到了 '4','0','0','0'四个轮廓的外置矩形信息,那么依据它进行模板匹配
       ROI = group_gray[fy:fy+fh,fx:fx+fw]#截图 一般是截灰度图,因为我们上面制作的模板截图也是灰度图
       ROI = cv2.resize(ROI,(57,88))#模板匹配必须要 统一大小!
       show(ROI)#逐个展示字符'4','0','0','0'
       for j in range(10):#这里我们进行模板匹配,由于此前template的模板制备符合数字排序,我们只需要range(10)即可
           match = cv2.matchTemplate(ROI,template_dic[j],cv2.TM_CCOEFF)#CCOEFF计算相关系数,越大越好
           score = cv2.minMaxLoc(match)[1]#提取两图匹配后的最大值
           if score > temp_max:
               temp_max = score
               temp_num = j#若从0~9之间哪一个匹配度最高,那么我们就可以确定其为源目标图片该区域的数字
       print(temp_num)
       result.append(temp_num)#这里result列表用来存放得到的结果
       temp_max = temp_num = 0
    locs_final.clear()#一次模板匹配完后清空换人~

最后识别结果: 4 0 0 0

myutils附录

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))

    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
  • 18
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值