Opencv+Pyqt5实现银行卡检测

目录

实训目的

实训内容

项目基本要求:

设计方法和基本原理:

2)读入信用卡图像;

实现思路

算法原理

     2.程序流程图

详细实现

 对模板图像处理部分: 

对信用卡处理部分

运行结果:

整体代码: 

detect.py

train.py


实训目的

1、加深对数字图像处理基本理论和常用算法的理解和掌握;培养学生培养独立思考、分工协作、团结奋进的意识;培养不怕困难、勇于开拓的创新精神和求真务实、追求卓越的科学家精神;

2、针对给定的复杂工程问题,培养学生选择合适的数学模型和算法策略,设计高效的技术方案并开发实现,及分析、解释实验结果,判断程序结果是否符合预期,给出合理有效的结论的能力;

3、培养学生针对实际图像处理任务独立开展工作、胜任个体角色的能力;

4、培养学生在完成具体图像处理任务的过程中,能正确运用工程管理方法进行项目的时间管理、质量管理、沟通管理和风险管理等,并能对项目的成本及预期收益进行分析的能力。

实训内容

      

 

选择合适的算法,编写程序进行信用卡号识别;制作交互式用户界面,实现一个能读入信用卡图片并识别信用卡号的系统,系统界面上需要对输入图像、识别结果等进行展示。并对实验结果进行分析,评价算法性能。

项目基本要求:

(1)复习并深入理解数字图像处理基本理论,选用合适算法和工具进行开发;

(2)独立设计方案,按照数字图像处理系统的开发流程进行开发;

(3) 使用OpenCV及其它常用的数字图像处理库编写代码,熟悉相关函数的调用;

(4)制作交互式用户界面;

(5)对实验结果进行比较、分析,并总结设计过程中所遇到的问题。

设计方法和基本原理:

1、问题描述(功能要求):

根据实训要求,完成指定系统的开发,要求独立实现,条理清晰,主要(关键代码)须有详细注释,实训报告写清楚实现思路、过程及实验结果,并对实验结果进行分析和评价,图文并茂。

1)选择合适算法对信用卡图片进行预处理,定位卡号区域;

2)使用合适算法对信用卡字符进行分割;

3)使用模板匹配对信用卡数字进行识别;

4)记录相关实验数据。

2、问题的解决方案:

根据任务要求,可以将问题解决分为以下步骤:

1)完成相关模块和第三方库的安装配置;

2)读入信用卡图像;

3)对图像进行降噪、灰度化、二值化、边缘检测、形态学等处理,并通过一定方法对卡号区域进行定位;

4)对信用卡字符进行分割;

5)可采用模板匹配方法,对数字进行识别,并输出识别后的结果;

6)设计信用卡识别的软件界面;

实现思路

  1. 算法原理

        银行卡识别通常是通过图像处理和计算机视觉技术来实现的,主要包括以下几个步骤:

       图像预处理部分。将原始图像进行灰度化、滤波、边缘检测和二值化等操作,以便于后续的图像处理。

       分割图像区域部分。使用形态学处理和轮廓分割等技术,提取银行卡区域和卡号区域。其中银行卡区域可以采用模板匹配或者基于机器学习的图像分割技术实现。

        提取特征部分。针对银行卡区域和卡号区域,采用特征提取技术,提取出关键特征,包括银行卡号码、有效期、持卡人姓名、卡片背面的CVC等信息。

        特征匹配和判定部分。将银行卡区域和卡号区域中的特征与预设阈值相匹配,确定是否属于目标银行卡,判定银行卡卡片所属的银行等信息。

输出结果部分。输出识别结果,包括银行卡的基本信息、识别结果的置信度等。

下面是代码中常用到OpenCV的函数和模块:

        1. 读取和保存图像:cv2.imread()、cv2.imwrite()。

        2. 图像预处理:cv2.cvtColor()、cv2.GaussianBlur()、cv2.Canny()、cv2.threshold()等。

        3. 形态学处理:cv2.erode()、cv2.dilate()、cv2.morphologyEx()等。

        4. 目标检测和分割:cv2.findContours()、cv2.drawContours()、cv2.minAreaRect()等。

        5. 特征提取和匹配:cv2.SIFT()、cv2.SURF()、cv2.ORB()、cv2.matchTemplate()等。

        6. 机器学习:cv2.ml.SVM()、cv2.ml.KNearest()等。

        在实现银行卡识别算法时,需要根据实际情况进行各种参数的设置,例如二值化阈值、轮廓检测算法、特征匹配算法等。也需要进行数据集的标注和预处理,以便于机器学习算法的训练。通过优化图像处理的流程和算法,可以提高银行卡识别的准确率和速度,实现高效自动化的银行卡识别系统。

     2.程序流程图

        整个代码主要包含两部分:数字模板的获取和银行卡号识别。其中,数字模板的获取是用户自己准备的,银行卡号识别是在 GUI 界面上进行的,具体流程如下:

        1. 用户在 GUI 界面上点击按钮选择数字模板图片和待识别的银行卡图片。

        2. 用户选择数字模板图片后,程序将其进行处理,提取出数字轮廓并存储在 `digits` 字典中。同时,数字模板的一些处理结果被显示在 GUI 界面上。

        3. 用户选择待识别的银行卡图片后,程序将其进行处理,提取出银行卡轮廓,然后进行数字区域的筛选和识别,并将识别结果显示在 GUI 界面上。

具体的数字模板处理流程包括:

        1. 用户选择数字模板图片,程序加载选择的图片文件并显示在 GUI 界面上。

        2. 对原始图片进行灰度化处理,然后进行二值化处理,得到二值化图像。

        3. 对二值化图像进行轮廓提取,得到数字模板的轮廓,并将其保存在 `digits` 字典中。

        4. 在 GUI 界面上显示数字模板的二值化轮廓图像和二值化后的数字区域图像。

具体的银行卡号识别流程包括:

        1. 用户选择待识别的银行卡图片,程序加载选择的图片文件并显示在 GUI 界面上。

        2. 对原始图片进行处理,提取出银行卡轮廓、数字区域轮廓等图像信息,并将不同的处理结果在 GUI 界面上显示出来。

        3. 根据识别模板和阈值等参数设置,对数字区域的轮廓进行筛选,得到符合条件的数字区域轮廓。

        4. 对每个数字轮廓进行处理和识别,得到银行卡上的卡号。

        5. 在 GUI 界面上显示识别结果,将识别出来的银行卡号码输出到文本标签上。

详细实现

 对模板图像处理部分: 

        def gray_processing(image):该函数的功能是将彩色图像转换为灰度图像,并对灰度图像进行二值化处理。具体步骤如下:

1. 使用OpenCV库的`cvtColor`函数将输入的彩色图像转换为灰度图像。`cv2.COLOR_BGR2GRAY`表示将BGR格式的图像转换为灰度图像。

2. 对灰度图像进行二值化处理。使用`cv2.threshold`函数对灰度图像进行阈值处理,将像素值小于10的像素变成白色(255),大于等于10的像素变成黑色(0)。`cv2.THRESH_BINARY_INV`表示二值化后将像素值大于10的像素设置为黑色。

3. 返回阈值(分割阈值)和处理后的结果图像。需要注意的是,该函数中的阈值10是我调试后得到的,不同的图像可能需要不同的阈值来进行二值化处理。因此,在实际应用中,需要根据具体的场景和数据进行调整。

def gray_processing(image):
    gray = cv2.cvtColor(image, code=cv2.COLOR_BGR2GRAY)
    ret_, threshold_ = cv2.threshold(src=gray, thresh=10, maxval=255, type=cv2.THRESH_BINARY_INV)
    # show_img(threshold_)
    return ret_,threshold_

        def draw_outline(image,threshold_):该函数的主要作用是绘制轮廓,并按照从左到右的顺序提取每个轮廓中的数字图像。具体步骤如下:

1. 使用OpenCV库的`findContours`函数提取输入图像中的轮廓。其中`cv2.RETR_EXTERNAL`表示只检测外层轮廓,`cv2.CHAIN_APPROX_SIMPLE`表示对轮廓进行压缩。

2. 使用`drawContours`函数在输入图像上绘制轮廓。其中`contourIdx=-1`表示绘制所有轮廓,`color=(0,255,0)`表示绘制轮廓的颜色为绿色,`thickness=3`表示绘制轮廓线条的厚度为3。

3. 对绘制的轮廓进行排序,得到0-9的数字,并将每个数字对应的图像存储在一个字典中。具体步骤如下:

   a. 使用`sort_contours`函数对轮廓进行排序。`method='left-to-right'`表示按照从左到右的顺序对轮廓进行排序。

   b. 对排序之后的每个轮廓,使用`cv2.boundingRect`函数得到包围轮廓的矩形,并裁剪矩形内的数字图像。

   c. 对裁剪出的数字图像进行大小调整,统一调整为57×88的大小,并将调整后的数字图像存储在字典中,键为数字的序号,值为数字图像。

4. 返回绘制轮廓后的图像和存储每个数字图像的字典。

需要注意的是,该函数中提取数字的大小调整和裁剪的操作仅满足我这个项目场景,需要根据具体的识别任务进行调整,以确保得到高质量的数字图像,从而提高识别的准确率。

 

def draw_outline(image,threshold_):
    contours_1_, hierarch = cv2.findContours(image=threshold_, mode=cv2.RETR_EXTERNAL, method=cv2.CHAIN_APPROX_SIMPLE)
    cv2.drawContours(image=image, contours=contours_1_, contourIdx=-1,
                     color=(0, 255, 0), thickness=3) # 绘制轮廓
    # show_img(image)
    # 根据绘制的轮廓的坐标对其进行排序,得到0-9的数字,返回排序之后的每个数字的左上角坐标
    contours_2, ret_2 = contours.sort_contours(contours_1_, method='left-to-right') # 绘制的矩形的长宽
    # 利用字典的形式存储数字对应的模版图像中的数字
    digits_ = {}
    for step, c in enumerate(contours_2):
        (x, y, w, h) = cv2.boundingRect(c)
        roi = threshold_[y:y + h, x:x + w]
        roi = cv2.resize(src=roi, dsize=(57, 88))
        digits_[step] = roi
    return image,digits_

对信用卡处理部分

        

        def draw_card_outline(image,threshold_):该函数的主要功能是检测银行卡轮廓,具体步骤如下:

1. 对传入的图像进行缩放。使用辅助函数`ScalePic()`按长宽比缩放图片,以保证后续处理的准确性。

2. 对缩放后的图像进行灰度处理,得到灰度图像。

3. 对灰度图像进行二值化处理。使用`cv2.threshold`函数对灰度图像进行阈值处理,将灰度值过高(暗的区域)的像素变成白色(255),灰度值过低(亮的区域)的像素变成黑色(0)。

4. 对阈值图像进行Top Hat操作。使用`cv2.morphologyEx`函数进行morphology操作,从而提取出信用卡区域。

5. 对提取的信用卡区域进行索贝尔算子的处理。通过`cv2.Sobel`函数进行算子计算,得到图像的梯度。

6. 对梯度图像进行闭运算操作。使用`cv2.morphologyEx`函数进行morphology操作,从而将数字之间的空隙连接为一整块。

7. 对连接之后的图像进行轮廓查找。使用`cv2.findContours`函数提取输入图像中的轮廓,其中`cv2.RETR_EXTERNAL`表示只检测外层轮廓,`cv2.CHAIN_APPROX_SIMPLE`表示对轮廓进行压缩。

8. 在轮廓上绘制矩形。使用`cv2.drawContours`函数在输入图像上绘制轮廓。

9. 返回经过各种处理后的图像及银行卡轮廓的坐标。

需要注意的是,对缩放后的图像和梯度图像进行处理的参数,尤其是核的大小和形状、阈值等

def draw_card_outline(image):
    kernel = cv2.getStructuringElement(shape=cv2.MORPH_RECT, ksize=(7, 1))
    imgCardSize_ = ScalePic(image) # 按长宽比缩放图片
    # show_img(imgCardSize_)
    imgCardGray_ = cv2.cvtColor(src=imgCardSize_, code=cv2.COLOR_BGR2GRAY) # 灰度处理
    # show_img(imgCardGray_)
    thresh = cv2.threshold(imgCardGray_, 0, 255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    # 进行顶帽操作
    imgToPhat = cv2.morphologyEx(src=thresh, op=cv2.MORPH_TOPHAT,kernel=kernel, iterations=1)
    # show_img(imgToPhat)
    # 进行索贝尔算子
    imgCardGradX = cv2.Sobel(src=imgToPhat, ddepth=-1, dx=1, dy=0, ksize=-1)
    # show_img(imgCardGradX)
    # 进行闭运算操作,主要是为了将数字之间的空隙连成一片
    imgCardClosed = cv2.morphologyEx(src=imgCardGradX, op=cv2.MORPH_CLOSE, kernel=kernel, iterations=2)
    # show_img(imgCardClosed)
    # 对信用卡号进行轮廓查找
    contours_3_, hierarchy = cv2.findContours(image=imgCardClosed, mode=cv2.RETR_EXTERNAL,method=cv2.CHAIN_APPROX_SIMPLE)
    cv2.drawContours(image=imgCardSize_, contours=contours_3_, contourIdx=-1,
                     color=(0, 255, 0), thickness=3)
    # show_img(imgCardSize_)
    ret =np.hstack((imgCardGray_,imgToPhat,imgCardGradX,imgCardClosed))
    # show_img(ret)
    return imgCardGray_,imgToPhat,imgCardGradX,imgCardClosed,imgCardSize_,contours_3_

        def filter_Outline(contours_3_): 该函数的主要功能是从银行卡轮廓中筛选出卡号轮廓,具体步骤如下:

1. 遍历所有银行卡轮廓。使用`cv2.boundingRect`函数得到每个轮廓的坐标和大小。

2. 计算轮廓的长度宽度比。根据实际情况,筛选出长度宽度比在2.5到4.0之间的轮廓。

3. 对筛选出的轮廓进行进一步的限制。宽度在40到55个像素之间,高度在10到20个像素之间。

4. 对筛选出的卡号轮廓按照坐标进行排序,返回结果。

该函数中的参数阈值也是调参得到的,确保得到高质量的卡号轮廓,从而提高识别的准确率。

 

def filter_Outline(contours_3_): # 筛选轮廓
    # 筛选出卡号轮廓
    LocateCard_ = []
    for step, c in enumerate(contours_3_):
        (x, y, w, h) = cv2.boundingRect(c)
        # 根据绘制的卡号轮廓,可以看到宽度与长度比有一定的规律
        rate = w / float(h)
        # 进行筛选
        if 2.5 < rate < 4.0:
            if (40 < w < 55) and (10 < h < 20):
                LocateCard_.append((x, y, w, h))
    LocateCard_ = sorted(LocateCard_, key=lambda x: x[0])
    return LocateCard_

        def identify_data(LocateCard_,digits_,imgCardGray_,imgCardSize_):该函数的主要功能是识别银行卡中的卡号,具体步骤如下:

1. 遍历每个卡号轮廓。对于每个轮廓,提取其所包含的数字图像,将数字图像放入一个列表中。

2. 对于每个数字图像,使用模板匹配的方法计算与预先保存的数字模板图像的相似性得分,并记录得分最高的数字。

3. 将得分最高的数字放入列表中,得到该卡号的所有数字列表。

4. 对于每个卡号轮廓,将识别结果画在输入图像上,并将结果放入一个输出列表中。

5. 返回识别结果列表和在输入图像上画好识别结果的图像。

该函数中,这里的模板的数量和质量对识别结果的影响比较大。根据使用,调整数字模板的质量,并采用更完备的数字模板,以提高识别的准确率。

def identify_data(LocateCard_,digits_,imgCardGray_,imgCardSize_):
    Output = []
    # 遍历每个轮廓中的数字
    print(len(LocateCard_))
    for (step, (gX, gY, gW, gH)) in enumerate(LocateCard_):
        groupOutput = []

        # 提取每一个卡号的轮廓
        group = imgCardGray_[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]
        # 预处理
        group = cv2.threshold(group, 0, 255,
                              cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
        # 计算每一组的轮廓
        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:
            # 找到当前数值的轮廓,resize成合适的的大小
            (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)
                (_, score, _, _) = cv2.minMaxLoc(result)
                scores.append(score)

            # 得到最合适的数字
            groupOutput.append(str(np.argmax(scores)))
        # 画出来
        print('count:{}'.format(step))
        cv2.rectangle(imgCardSize_, (gX - 5, gY - 5),
                      (gX + gW + 5, gY + gH + 5), (0, 0, 255), 1)
        cv2.putText(imgCardSize_, "".join(groupOutput), (gX, gY - 15),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)
        # 得到结果
        Output.extend(groupOutput)
    return imgCardSize_, Output

运行结果:

模板图片:

 待处理的图

 

 

if __name__ == '__main__':
    img = cv2.imread('13.png') # 读取模版图像
    ret,threshold=gray_processing(img)
    _,digits=draw_outline(img,threshold)
    # print(digits_)
    test_img =cv2.imread('b_1.jpg')
    imgCardGray,_1,_2,_3,imgCardSize,contours_3 = draw_card_outline(test_img)
    LocateCard =filter_Outline(contours_3)
    result_img,out = identify_data(LocateCard,digits,imgCardGray,imgCardSize)
    # show_img(result_img)

文件结构

整体代码: 

detect.py

from PyQt5.QtGui import QPainter
import sys  # 引用sys库
from PyQt5 import QtWidgets  # 引用PyQt5库里QtWidgets类
from PyQt5.QtWidgets import *  # 导入PyQt5.QtWidgets里所有的方法
from PyQt5.QtGui import *  # 导入PyQt5.QtGui里所有的方法
import os  # 引用os库
from train import *
from PyQt5.QtCore import Qt


class Qt_Window(QWidget):  # 定义一个类,继承于QWidget
    def __init__(self):  # 构建方法
        self.digits = None
        self._app = QtWidgets.QApplication([])  # 创建QApplication示例
        super(Qt_Window, self).__init__()  # 固定格式
        self.count = None

        # 定义组件
        self.win = QMainWindow()
        self.img_Button =QPushButton(self.win) # 选择图片按钮
        self.img_model =QPushButton(self.win) # 选择模板按钮
        self.open_Button = QPushButton(self.win)  # 退出按钮

        self.image_label = QLabel(self.win)  # 显示选择的图片
        self.model_label =QLabel(self.win) # 显示选择的模板
        self.result_label =QLabel(self.win) # 显示最终结果
        self.label_1 =QLabel(self.win)
        self.label_2 = QLabel(self.win)
        self.label_3 = QLabel(self.win)
        self.label_4 = QLabel(self.win)
        self.label_5 = QLabel(self.win)

        self.translate_label =QLabel(self.win) # 转化图片过程
        self.translate_model =QLabel(self.win) # 处理模板过程
        self.final_result=QLabel(self.win) # 最终处理过程

        self.ret_label =QLabel(self.win)
        self.ret =QLabel(self.win)
        self.pre_img = QLabel(self.win)

    def init_ui(self):
        # 设置窗口背景
        palette = QPalette()
        palette.setBrush(QPalette.Background, QBrush(QPixmap("back1.jpg")))
        self.win.setPalette(palette)

        # 设置控件
        self.img_Button.resize(100, 25)
        self.img_Button.move(150, 400)
        self.img_Button.setText("选择图片")
        self.img_Button.clicked.connect(self.select_image)

        self.img_model.resize(100,25)
        self.img_model.move(450,400)
        self.img_model.setText("选择模板")
        self.img_model.clicked.connect(self.select_mode)

        self.open_Button.resize(100, 25)
        self.open_Button.move(750, 400)
        self.open_Button.setText("退出")
        # self.open_Button.clicked.connect(self.exit)

        self.image_label.resize(221, 142)
        self.image_label.move(100, 150)
        self.image_label.setStyleSheet('''
            background-color: #f0f0f0;
            border: 5px solid black;
        ''')

        self.model_label.resize(221, 50)
        self.model_label.move(100, 50)
        self.model_label.setStyleSheet('''
                    background-color: #f0f0f0;
                    border: 5px solid black;
                ''')

        self.translate_label.resize(221, 142)
        self.translate_label.move(400, 150)
        self.translate_label.setStyleSheet('''
            background-color: #f0f0f0;
            border: 5px solid black;
        ''')

        self.translate_model.resize(221, 50)
        self.translate_model.move(400, 50)
        self.translate_model.setStyleSheet('''
                    background-color: #f0f0f0;
                    border: 5px solid black;
                ''')

        self.final_result.resize(221, 142)
        self.final_result.move(700,150)
        self.final_result.setStyleSheet('''
                            background-color: #f0f0f0;
                            border: 5px solid black;
                        ''')

        self.label_1.setGeometry(150,325,100,25)
        self.label_1.setText("初始图片显示")
        self.label_1.setAlignment(Qt.AlignCenter)
        self.label_1.setFont(QFont("宋体", 10))
        self.label_1.setStyleSheet('''background-color: blue;color:red;''')

        self.label_2.setGeometry(450, 325, 100, 25)
        self.label_2.setText("转化图片显示")
        self.label_2.setAlignment(Qt.AlignCenter)
        self.label_2.setFont(QFont("宋体", 10))
        self.label_2.setStyleSheet('''background-color: blue;color:red;''')

        self.label_3.setGeometry(750, 325, 100, 25)
        self.label_3.setText("最终结果展示")
        self.label_3.setAlignment(Qt.AlignCenter)
        self.label_3.setFont(QFont("宋体", 10))
        self.label_3.setStyleSheet('''background-color: blue;color:red;''')

        self.label_4.setGeometry(0 ,60, 100, 25)
        self.label_4.setText("模板:")
        self.label_4.setAlignment(Qt.AlignCenter)
        self.label_4.setFont(QFont("宋体", 10))
        self.label_4.setStyleSheet('''color:red;''')

        self.label_5.setGeometry(0, 200, 100, 25)
        self.label_5.setText("待处理图片:")
        self.label_5.setAlignment(Qt.AlignCenter)
        self.label_5.setFont(QFont("宋体", 10))
        self.label_5.setStyleSheet('''color:red;''')


        # resize为设置大小,move为设置x与y坐标,setText为设置控件文本内容
        self.win.resize(1000, 500)
        self.win.move(300, 300)
        self.win.show()
        sys.exit(self._app.exec_())  # sys.exit()退出程序机制 app.exec_()的作用是运行主循环,开始进行事件处理,直到结束

    def BIN_pix(self, image_array):
        height, width = image_array.shape
        qImg = QImage(image_array.data, width, height, width, QImage.Format_Grayscale8)
        pixmap = QPixmap(qImg)
        return pixmap

    # def RGB_pix(self, image_array):
    #     height, width, channel = image_array.shape
    #     bytesPerLine = 3 * width
    #     qImg = QImage(image_array.data, width, height, bytesPerLine, QImage.Format_RGB888)
    #     pixmap = QPixmap(qImg)
    #     return pixmap

    def RGB_pix(self, image_array):
        height, width, channel = image_array.shape
        bytesPerLine = 3 * width
        if channel == 4:
            # 如果原始图像是带有Alpha通道的RGBA格式,则需要手动移除Alpha通道
            image_array = image_array[:, :, 0:3]
        elif channel == 1:
            # 如果原始图像是单通道的灰度图像,则将其转换为RGB格式
            image_array = cv2.cvtColor(image_array, cv2.COLOR_GRAY2RGB)
        qImg = QImage(image_array.data, width, height, bytesPerLine, QImage.Format_RGB888)
        pixmap = QPixmap(qImg)
        return pixmap

    def select_mode(self):
        file_dialog = QFileDialog(self)
        # 图片格式为bmp、png和jpg
        file_dialog.setNameFilter("Image files (*.bmp *.png *.jpg)")
        file_dialog.setFileMode(QFileDialog.ExistingFile)

        if file_dialog.exec_():
            # 加载选择的图片文件
            image_file_path = file_dialog.selectedFiles()[0]
            # print(self.count)
            model_img = cv2.imread(image_file_path, cv2.IMREAD_COLOR)
            print('选择模板图片:', image_file_path)
            pixmap = QPixmap(image_file_path)
            self.model_label.setScaledContents(True)
            self.model_label.setPixmap(pixmap)

            _, threshold_ = gray_processing(model_img)
            pixmap = self.BIN_pix(threshold_)
            self.translate_model.setScaledContents(True)
            self.translate_model.setPixmap(pixmap)
            cv2.waitKey(1000)

            deal_img,self.digits = draw_outline(model_img, threshold_)
            pixmap =self.RGB_pix(deal_img)
            self.translate_model.setPixmap(QPixmap())
            self.translate_model.setPixmap(pixmap)
            cv2.waitKey(1000)



    def select_image(self):
        # 打开文件对话框以选择一个图片文件
        file_dialog = QFileDialog(self)
        # 图片格式为bmp、png和jpg
        file_dialog.setNameFilter("Image files (*.bmp *.png *.jpg)")
        file_dialog.setFileMode(QFileDialog.ExistingFile)

        if file_dialog.exec_():
            # 加载选择的图片文件
            image_file_path = file_dialog.selectedFiles()[0]
            # print(self.count)
            test_img = cv2.imread(image_file_path, cv2.IMREAD_COLOR)
            print('选择银行卡图片:', image_file_path)
            pixmap = QPixmap(image_file_path)
            self.image_label.setScaledContents(True)
            self.image_label.setPixmap(pixmap)

            imgCardGray,_1,_2,_3,imgCardSize,contours_3 = draw_card_outline(test_img)

            pixmap =self.BIN_pix(imgCardGray)
            self.translate_label.setScaledContents(True)
            self.translate_label.setPixmap(pixmap)
            cv2.waitKey(1000)

            pixmap = self.BIN_pix(_1)
            self.translate_label.setPixmap(QPixmap())
            self.translate_label.setPixmap(pixmap)
            cv2.waitKey(1000)

            pixmap = self.BIN_pix(_2)
            self.translate_label.setPixmap(QPixmap())
            self.translate_label.setPixmap(pixmap)
            cv2.waitKey(1000)

            pixmap = self.BIN_pix(_3)
            self.translate_label.setPixmap(QPixmap())
            self.translate_label.setPixmap(pixmap)
            cv2.waitKey(1000)

            pixmap = self.RGB_pix(imgCardSize)
            self.translate_label.setPixmap(QPixmap())
            self.translate_label.setPixmap(pixmap)
            cv2.waitKey(1000)

            LocateCard = filter_Outline(contours_3)
            result_img, out = identify_data(LocateCard, self.digits, imgCardGray, imgCardSize)
            pixmap =self.RGB_pix(result_img)
            self.final_result.setScaledContents(True)
            self.final_result.setPixmap(pixmap)

            self.result_label.setGeometry(650,50,221,50)
            self.result_label.setText("Credit Card #: {}".format("".join(out)))
            self.result_label.setAlignment(Qt.AlignCenter)
            self.result_label.setFont(QFont("宋体",10))
            self.result_label.setStyleSheet('''background-color: blue;color:red;''')



if __name__ == '__main__':
    s = Qt_Window()  # 调用对象和方法
    s.init_ui()  # 调用对象和方法

train.py

import os
import cv2
import cvzone
import numpy as np
from imutils import contours
from PyQt5.QtWidgets import *  # 导入PyQt5.QtWidgets里所有的方法
from PyQt5.QtGui import *  # 导入PyQt5.QtGui里所有的方法

def show_img(image):
    cv2.imshow('',image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

# 按比例缩放图像
def ScalePic(image):
    height,width,channel=image.shape
    height=int(height*(300/width))
    image=cv2.resize(image,dsize=(300,height))
    return image

# 对模版图进行灰度处理和二值化处理
def gray_processing(image):
    gray = cv2.cvtColor(image, code=cv2.COLOR_BGR2GRAY)
    ret_, threshold_ = cv2.threshold(src=gray, thresh=10, maxval=255, type=cv2.THRESH_BINARY_INV)
    # show_img(threshold_)
    return ret_,threshold_

# 查找模版中数字的轮廓和绘制轮廓
def draw_outline(image,threshold_):
    contours_1_, hierarch = cv2.findContours(image=threshold_, mode=cv2.RETR_EXTERNAL, method=cv2.CHAIN_APPROX_SIMPLE)
    cv2.drawContours(image=image, contours=contours_1_, contourIdx=-1,
                     color=(0, 255, 0), thickness=3) # 绘制轮廓
    # show_img(image)
    # 根据绘制的轮廓的坐标对其进行排序,得到0-9的数字,返回排序之后的每个数字的左上角坐标
    contours_2, ret_2 = contours.sort_contours(contours_1_, method='left-to-right') # 绘制的矩形的长宽
    # 利用字典的形式存储数字对应的模版图像中的数字
    digits_ = {}
    for step, c in enumerate(contours_2):
        (x, y, w, h) = cv2.boundingRect(c)
        roi = threshold_[y:y + h, x:x + w]
        roi = cv2.resize(src=roi, dsize=(57, 88))
        digits_[step] = roi
    return image,digits_

def draw_card_outline(image):
    kernel = cv2.getStructuringElement(shape=cv2.MORPH_RECT, ksize=(7, 1))
    imgCardSize_ = ScalePic(image) # 按长宽比缩放图片
    # show_img(imgCardSize_)
    imgCardGray_ = cv2.cvtColor(src=imgCardSize_, code=cv2.COLOR_BGR2GRAY) # 灰度处理
    # show_img(imgCardGray_)
    thresh = cv2.threshold(imgCardGray_, 0, 255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    # 进行顶帽操作
    imgToPhat = cv2.morphologyEx(src=thresh, op=cv2.MORPH_TOPHAT,kernel=kernel, iterations=1)
    # show_img(imgToPhat)
    # 进行索贝尔算子
    imgCardGradX = cv2.Sobel(src=imgToPhat, ddepth=-1, dx=1, dy=0, ksize=-1)
    # show_img(imgCardGradX)
    # 进行闭运算操作,主要是为了将数字之间的空隙连成一片
    imgCardClosed = cv2.morphologyEx(src=imgCardGradX, op=cv2.MORPH_CLOSE, kernel=kernel, iterations=2)
    # show_img(imgCardClosed)
    # 对信用卡号进行轮廓查找
    contours_3_, hierarchy = cv2.findContours(image=imgCardClosed, mode=cv2.RETR_EXTERNAL,method=cv2.CHAIN_APPROX_SIMPLE)
    cv2.drawContours(image=imgCardSize_, contours=contours_3_, contourIdx=-1,
                     color=(0, 255, 0), thickness=3)
    # show_img(imgCardSize_)
    ret =np.hstack((imgCardGray_,imgToPhat,imgCardGradX,imgCardClosed))
    # show_img(ret)
    return imgCardGray_,imgToPhat,imgCardGradX,imgCardClosed,imgCardSize_,contours_3_

def filter_Outline(contours_3_): # 筛选轮廓
    # 筛选出卡号轮廓
    LocateCard_ = []
    for step, c in enumerate(contours_3_):
        (x, y, w, h) = cv2.boundingRect(c)
        # 根据绘制的卡号轮廓,可以看到宽度与长度比有一定的规律
        rate = w / float(h)
        # 进行筛选
        if 2.5 < rate < 4.0:
            if (40 < w < 55) and (10 < h < 20):
                LocateCard_.append((x, y, w, h))
    LocateCard_ = sorted(LocateCard_, key=lambda x: x[0])
    return LocateCard_

def identify_data(LocateCard_,digits_,imgCardGray_,imgCardSize_):
    Output = []
    # 遍历每个轮廓中的数字
    print(len(LocateCard_))
    for (step, (gX, gY, gW, gH)) in enumerate(LocateCard_):
        groupOutput = []

        # 提取每一个卡号的轮廓
        group = imgCardGray_[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]
        # 预处理
        group = cv2.threshold(group, 0, 255,
                              cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
        # 计算每一组的轮廓
        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:
            # 找到当前数值的轮廓,resize成合适的的大小
            (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)
                (_, score, _, _) = cv2.minMaxLoc(result)
                scores.append(score)

            # 得到最合适的数字
            groupOutput.append(str(np.argmax(scores)))
        # 画出来
        print('count:{}'.format(step))
        cv2.rectangle(imgCardSize_, (gX - 5, gY - 5),
                      (gX + gW + 5, gY + gH + 5), (0, 0, 255), 1)
        cv2.putText(imgCardSize_, "".join(groupOutput), (gX, gY - 15),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)
        # 得到结果
        Output.extend(groupOutput)
    return imgCardSize_, Output


if __name__ == '__main__':
    img = cv2.imread('13.png') # 读取模版图像
    ret,threshold=gray_processing(img)
    _,digits=draw_outline(img,threshold)
    # print(digits_)
    test_img =cv2.imread('b_1.jpg')
    imgCardGray,_1,_2,_3,imgCardSize,contours_3 = draw_card_outline(test_img)
    LocateCard =filter_Outline(contours_3)
    result_img,out = identify_data(LocateCard,digits,imgCardGray,imgCardSize)
    # show_img(result_img)

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阳阳小可爱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值