13- 信用卡账号识别 (OpenCV基础) (项目十三) *

项目要点

  • _, ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)   二值化处理图片, 黑白化图片
  • ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY灰度化处理
  • ref_contours, _ = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)  计算轮廓
  • cv2.drawContours(img, ref_contours, -1, (0, 0, 255), 2)  画出外轮廓
  • image = cv2.resize(image, (300, int(h * r)))   等比例缩放原图   # r = width / w
  • 顶帽运算: tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rect_kernel)  顶帽 = 原图 - 开运算  开运算的效果是去除图像外的噪点, 原图 - 开运算就得到了去掉的噪点.
  • 索贝尔算子: grad_x = cv2.Sobel(tophat, ddepth = cv2.CV_32F, dx = 1, dy = 0, ksize = -1)  边缘检测, 一般先分别求x, y 方向上的检测结果, 再进行合并 .
  • 闭操作: grad_x = cv2.morphologyEx(grad_x, cv2.MORPH_CLOSE, rect_kernel)  闭运算 = 膨胀 + 腐蚀  填充图像内部的噪点 .
  • _, thresh = cv2.threshold(grad_x, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU大津算法找到合适的阈值进行全局二值化  主要是通过阈值进行前后背景分割 .
  • 模板匹配: result = cv2.matchTemplate(roi, digit_roi, cv2.TM_CCOEFF)


信用卡识别项目

基本思路:

总体思路就是取出信用卡中每一个数字作为去和模板中的10个数字进行模板匹配操作.

  1. 先对模板处理, 获取每个数字的模板及其对应的数字标识.

  2. 再对信用卡处理, 通过一系列预处理操作, 取出信用卡数字区域.

  3. 然后再取出每一个数字去和模板中的10个数字进行匹配.

1  先对模板处理

1.1 显示原始图片

# 先处理数字模板的图片
import cv2
import numpy as np

# 模板图片
img = cv2.imread('./ocr_a_reference.png')
def cv_show(name, img):    # 封装显示的函数
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

print(img.shape)    # (126, 800, 3)
cv_show('img', img)

1.2 灰度化处理

ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)   # 灰度化处理
cv_show('ref', ref)
print(ref.shape)   # (126, 800)

 1.3 二值化图片

# 二值化处理
_, ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)
cv_show('ref', ref)

 1.4 描绘轮廓

# 计算轮廓
ref_contours,_=cv2.findContours(ref.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
# 画出外轮廓
cv2.drawContours(img, ref_contours, -1, (0, 0, 255), 2)
cv_show('img', img)
print(np.array(ref_contours).shape)   # (10,)

1.5 外接矩形

# 对轮廓进行排序, 按照数字大小进行排序, 方便使用
# 排序思路:根据每个数字的最大外接矩形的X轴坐标进行排序
# 计算每个轮廓的外接矩形
bounding_boxes = [cv2.boundingRect(c) for c in ref_contours]
print(len(bounding_boxes))   # 10
sorted(bounding_boxes, key = lambda b : b[0])
print('---------\n', bounding_boxes[0][:2])   # (15, 20)
cv2.rectangle(img, bounding_boxes[0][:2], (bounding_boxes[0][0]+bounding_boxes[0][2],
                                           bounding_boxes[0][1]+bounding_boxes[0][3]), (255, 0, 0), 2)
# * 去中括号                                            
(ref_contours, bounding_boxes) = zip(*sorted(zip(ref_contours, bounding_boxes), key = lambda b : b[1][0]))
digits = {}
for (i, c) in enumerate(ref_contours):
    # 重新计算外接矩形
    (x, y, w, h) = cv2.boundingRect(c)
    # region of interest 感兴趣的区域,取出每个数字
    roi = ref[y: y+h, x: x+ w]
    # resize 成合适的图像
    roi = cv2.resize(roi, (57, 88))
    digits[i] = roi

print(len(digits))   # 10
cv_show('img', img)
  • digits 是一个数字和特征对应的字典 .

2 信用卡处理

2.1 信用卡图片

# 对信用卡图片进行处理
image = cv2.imread('./credit_card_01.png')
print(image.shape)   # (368, 583, 3)
cv_show('img',image)

 2.2 调整图片尺寸

# 对信用卡图片resize
# 保证原图不拉伸, 计算原图的长宽比
h, w = image.shape[:2]
width = 300
r = width / w
image = cv2.resize(image, (300, int(h * r)))  # 等比例缩放原图
print(image.shape)  # (189, 300, 3)
cv_show('image', image)

 2.3 灰度化处理图片

# 灰度化处理
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
print(gray.shape)    # (189, 300)
cv_show('image', gray)

 2.4 顶帽操作

# 形态学操作
# 顶帽操作,突出明亮区,初始化卷积核, 去除图像外的噪点
rect_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rect_kernel)
print(tophat.shape)   # (189, 300)
cv_show('tophat', tophat)

 2.5 索贝尔算子

# 索贝尔算子, 计算图像轮廓
grad_x = cv2.Sobel(tophat, ddepth = cv2.CV_32F, dx = 1, dy = 0, ksize = -1)
grad_x = np.absolute(grad_x)
# 再把grad_x 变成0 到255 之间的整数
min_val, max_val = np.min(grad_x), np.max(grad_x)
grad_x = ((grad_x - min_val)/ (max_val - min_val)) * 255   # 归一化 ?
# 修改 数据类型
grad_x = grad_x.astype('uint8')
cv_show('grad_x', grad_x)  # 只用X方向的值

# 计算y轴梯度
grad_y = cv2.Sobel(tophat, ddepth = cv2.CV_32F, dx = 0, dy = 1, ksize = -1)
grad_y = np.absolute(grad_y)
# 再把grad_y 变成0 到255 之间的整数
min_val, max_val = np.min(grad_y), np.max(grad_y)
grad_y = ((grad_y - min_val)/ (max_val - min_val)) * 255
# 修改 数据类型
grad_y = grad_y.astype('uint8')

cv_show('grad_y', grad_y)
cv_show('gray', grad_x + grad_y)

. y轴的索贝尔算子梯度                                     . 索贝尔算子合并后效果

 2.6 闭操作

# 闭操作 ,先膨胀,再腐蚀, 填充内部噪点
grad_x = cv2.morphologyEx(grad_x, cv2.MORPH_CLOSE, rect_kernel)
cv_show('grad_x', grad_x)

 2.7 大津算法

# 大津算法找到合适的阈值进行全局二值化, 区分前景背景
_, thresh = cv2.threshold(grad_x, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
cv_show('thresh', thresh)

 2.8 继续闭运算

# 再来闭操作, 中间还有空洞  5 * 5 的卷积核放入, 减少内部噪点
sq_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sq_kernel)
cv_show('thresh', thresh)

 2.9 寻找轮廓

# 找轮廓
thresh_contours, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 在原图画轮廓
image_copy = image.copy()
cv2.drawContours(image_copy, thresh_contours, -1, (0, 0, 255), 3)
cv_show('image', image_copy)

2.10 查找信用卡数字区

  • 根据信用卡数字区的长宽特征, 找出数字区域 .

# 定义每一组匹配到的值的列表
# 遍历轮廓, 计算外接矩形,然后根据信用卡的长宽比,找到数字区
locs = []
for c in thresh_contours:
    # 计算外接矩形
    (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))

print(locs)  
# [(224, 102, 52, 14), (157, 102, 51, 14), (90, 102, 51, 14), (24, 102, 50, 14)]

3  模板匹配

# 对符合的轮廓进行排序
# 遍历每个外接矩形,通过外接矩形可以把原图中的数字抠出来
sorted(locs, key = lambda x : x[0])
out_put = []
for (i , (gx, gy, gw, gh)) in enumerate(locs):
    # 扣除数字,外轮廓留预值
    # 根据坐标调出在原图的位置截图
    group = gray[gy - 5 : gy + gh + 5, gx - 5: gx+ gw + 5]   # gray 是灰度化处理后的图片
    
    # 对取出的灰色group做全局二值化处理, 黑白图
    group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    
    # 计算轮廓
    digit_contours, _ = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    # 对轮廓进行排序
    bounding_boxes = [cv2.boundingRect(c) for c in digit_contours]
    (digit_contours, _) = zip(*sorted(zip(digit_contours, bounding_boxes), key = lambda b : b[1][0]))
    
    # print(len(digit_contours))   # 4 每次是四个数字, 每个数字的轮廓
    group_output = []
    # 遍历排好序的轮廓, 每个digit_contours 为每个数字的元组
    for c in digit_contours:
        # 找到当前的轮廓,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', roi)   # 显示每个数字
        
        # 进行模板匹配, 定义保存匹配得分的列表
        scores = []
        for (digit, digit_roi) in digits.items():
            result = cv2.matchTemplate(roi, digit_roi, cv2.TM_CCOEFF)
            # 只要最大值分数
            (_, score, _, _) = cv2.minMaxLoc(result)
            scores.append(score)

        # 找到分数最高的数字, 即匹配到的数字
        group_output.append(str(np.argmax(scores)))
        # print(scores)    # 第一列在 9位置的值: -5058846.5
        # print(group_output)  # 第一个值是 ['9']

    out_put = group_output + out_put
    # 画出轮廓显示数字
    cv2.rectangle(image, (gx - 5, gy - 5), (gx + gw + 5, gy + gh + 5), (0, 0, 255), 1)
    cv2.putText(image, ''.join(group_output), (gx, gy - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
cv_show('image', image)
print(out_put)    # ['4', '0', '0', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '0']
car_num = ''
for i in out_put:
    car_num += str(i)
print('信用卡账号:',car_num)    # 信用卡账号: 4000123456789010

http://www.niftyadmin.cn/n/92504.html

相关文章

Elasticsearch 基础(三)之相关术语概念及原理

目录前言一、集群1、相关术语概念1.1、集群&#xff08;Cluster&#xff09;1.2、节点&#xff08;Node&#xff09;1.3、角色&#xff08;Roles&#xff09;1.4、分片&#xff08;Shard&#xff09;2、集群场景及原理2.1、集群健康2.2、空节点2.3、单节点2.4、集群追加节点2.5…

37-Golang中的封装

封装介绍 封装就是把抽象出的字段和对字段的操作封装在一起&#xff0c;数据被保护在内部&#xff0c;程序的其他包只有通过被授权的操作(方法)&#xff0c;才能对字段进行操作 封装的理解和好处 1.隐藏实现细节 2.可以对数据进行验证&#xff0c;保证安全合理 如何体现封…

位运算笔记

1. 为什么要学位运算 因为这是计算机内部运算的语言&#xff0c;所以会非常快。 本人是因为学习算法经常遇见一些求二进制中的0和1的各种操作&#xff0c;好多都不知道所以特此整理一下&#xff0c;如有不对&#xff0c;烦请指正。 2. 什么是位运算 程序中的所有数在计算机内存…

JavaWeb13-线程休眠和指定唤醒:LockSupport

目录 1.LockSupport.park()&#xff1a;休眠当前线程 2.LockSupport.unpark(线程对象)&#xff1a;唤醒某一个指定的线程 3.扩展&#xff1a;LockSupport.parkUntil(long)等待最大时间是一个固定时间 4.LockSupport和Interrupt 5.LockSupport VS wait 相同点&#xff1a;…

蓝桥杯的比赛流程和必考点

蓝桥杯的比赛流程和必考点 距省赛仅1个多月&#xff01;蓝桥杯的比赛流程和必考点&#xff0c;你还不清楚&#xff1f; “巷子里的猫很自由&#xff0c;却没有归宿&#xff1b;围墙里的狗有归宿&#xff0c;终身都得低头。人生这道选择题&#xff0c;怎么选都会有遗憾。” 但不…

day03 表

文章目录1、查询每一个员工的所在部门名称&#xff1f;要求显示员工名和部门名。2、insert语句可以一次插入多条记录吗&#xff1f;【掌握】3、快速创建表&#xff1f;【了解内容】4、将查询结果插入到一张表当中&#xff1f;insert相关的&#xff01;&#xff01;&#xff01;…

DCL单例模式是如何保证数据安全的?

承接上文证明CPU指令是乱序执行的DCL单例&#xff08;Double Check Lock&#xff09;到底需不需要volatile&#xff1f;new对象这一步&#xff0c;对应着汇编层面的这3个指令&#xff0c;指令0是申请空间&#xff0c;设置默认值&#xff1b;指令7是执行构造方法&#xff0c;设置…

利用STM32的LR寄存器调试HardFault错误

R14 or LR(Link Register) HardFault调试的思路 先在出错误的地方打断点&#xff0c;让程序的状态固定下来&#xff1b;由于HardFault属于异常&#xff0c;所以出现HardFault后&#xff0c;LR的值一定是0xFFFFFFFx&#xff0c;这样就可以根据其值&#xff0c;判断程序进入这个…