数字图像处理——信用卡识别

在数字支付时代,信用卡处理自动化技术日益重要。本文介绍如何利用Python和OpenCV实现信用卡数字的自动识别,结合图像处理与模式识别技术,具有显著实用价值。

系统概述与工作原理

信用卡数字识别系统包含两大核心模块:模板数字预处理和信用卡图像识别。系统先处理标准数字模板(0-9),再提取信用卡图像中的数字区域进行匹配识别。

识别流程采用多种图像处理技术,包括灰度转换、阈值处理、形态学操作和轮廓检测。通过分析数字形状特征和空间关系,系统能精确定位并识别信用卡数字序列。

环境配置与依赖安装

确保安装以下Python库:

pip install opencv-python numpy

Anaconda用户可使用:

conda install -c conda-forge opencv

验证模块导入:

import cv2
import numpy as np

模板数字处理

处理标准数字模板作为识别基准:

# 读取模板图像
# 使用cv2.imread函数从文件路径'number.png'读取图像[1,2](@ref)
# 返回值img是一个numpy数组,表示图像的像素数据,默认以BGR格式存储[1,2](@ref)
img = cv2.imread('number.png')# 将图像转换为灰度图
# 使用cv2.cvtColor将BGR格式的彩色图像转换为单通道灰度图像[2,3](@ref)
# 灰度化是许多图像处理任务(如二值化、轮廓检测)常见的预处理步骤,能减少计算复杂度[2,3,4](@ref)
ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 对灰度图像进行二值化处理
# 使用cv2.threshold函数进行阈值处理[4,5](@ref)
# 参数10是阈值:像素值大于10的将被设置为255(白色),小于等于10的将被设置为0(黑色)[5](@ref)
# cv2.THRESH_BINARY_INV 表示采用反向二值化模式,即通常的黑底白字变为白底黑字,便于轮廓检测[4](@ref)
# 函数返回一个元组,我们通过[1]索引取第二个元素,即二值化后的图像矩阵[4](@ref)
ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1]# 在二值图像中查找轮廓
# 使用cv2.findContours函数检测ref图像中的轮廓[4](@ref)
# ref.copy() 使用图像的副本进行操作,避免修改原始图像
# cv2.RETR_EXTERNAL 表示只检测最外层轮廓,忽略内部嵌套的轮廓(如数字0的内部)[4](@ref)
# cv2.CHAIN_APPROX_SIMPLE 压缩水平、垂直和对角方向的冗余点,仅保留轮廓的端点,节省内存[4](@ref)
# 函数返回两个值:轮廓列表(refCnts)和层次信息(此处用_忽略)[4](@ref)
refCnts, _ = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 创建一个空字典用于存储标准化后的数字模板
digits = {}# 遍历检测到的所有轮廓(refCnts),i是索引,c是每个轮廓的点集
for i, c in enumerate(refCnts):# 获取每个轮廓的外接矩形[4](@ref)# cv2.boundingRect(c) 返回一个元组 (x, y, w, h)# (x, y) 是矩形左上角的坐标,w是宽度,h是高度[4](@ref)x, y, w, h = cv2.boundingRect(c)# 从二值化图像ref中提取出当前轮廓所包围的区域(Region of Interest, ROI)# 使用数组切片操作,取y到y+h行,x到x+w列的区域roi = ref[y:y+h, x:x+w]# 将提取出的ROI(数字区域)缩放到统一尺寸 (57, 88)# 标准化模板尺寸对于后续的模板匹配或机器学习步骤至关重要,可以消除原始图像中数字大小不一的影响[6](@ref)# 将缩放后的模板存储在字典digits中,键为数字的索引i,值为对应的图像矩阵digits[i] = cv2.resize(roi, (57, 88))

​关键处理步骤与技术要点:​

  1. 1​​图像读取与色彩空间转换​​:OpenCV 默认使用 BGR 格式读取图像,这与许多其他库(如 matplotlib 的 RGB)不同,在处理颜色时需要注意。转换为灰度图是简化后续处理的关键步骤。
  2. ​二值化(Thresholding)​​:通过设定一个阈值(此代码中为 10),将灰度图像转换为仅包含纯黑(0)和纯白(255)像素的二值图像。使用 THRESH_BINARY_INV进行反向二值化,使得数字本身变为白色(前景),背景变为黑色,这通常是轮廓检测所期望的输入。
  3. 轮廓检测(findContours)​​:在二值图像上,轮廓检测能够找出所有白色区域的边界RETR_EXTERNAL模式只检索最外层的轮廓,这对于提取独立的数字块非常有效,避免了将数字内部(如08)的空洞也作为轮廓检出。CHAIN_APPROX_SIMPLE通过压缩冗余点来节省内存。
  4. ​提取与标准化(ROI & Resize)​​:通过 boundingRect获取每个轮廓(即每个数字)的精确位置和大小,然后从原图中截取出该区域(ROI)。最后,将所有数字 ROI 缩放到统一的尺寸(57x88 像素),这是为了确保后续进行模板匹配或特征提取时,所有模板都具有相同的大小,从而保证比较的公平性和准确性。

信用卡图像预处理

关键预处理步骤突出数字区域:

# 使用OpenCV的imread函数读取信用卡图像文件
# 'picture/credit1.png' 是图像文件的路径
# 返回值image是一个包含图像像素数据的numpy数组,默认以BGR格式存储[3,12](@ref)
image = cv2.imread('picture/credit1.png')# 调用自定义的resize函数调整图像尺寸,保持宽高比
# 这里将图像的宽度调整为300像素,高度会按比例自动计算
# 缩小图像可以减少后续处理的计算量,提高处理速度[1,5](@ref)
image = resize(image, width=300)# 将BGR格式的彩色图像转换为灰度图像
# 灰度化是许多图像处理任务的基础步骤,能减少计算复杂度和通道数[1,2](@ref)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# 定义形态学操作中使用的结构元素(核)
# 第一个是矩形核,尺寸为(9, 3),宽度较大,适合用于连接水平方向的数字[5,6](@ref)
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
# 第二个是方形核,尺寸为(5, 5),用于后续的闭操作以填充小孔洞和连接区域[5](@ref)
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))# 应用顶帽(礼帽)形态学操作
# 顶帽操作是原图像与开操作结果的差,能突出比周围更亮的细小区域
# 在这里用于增强信用卡上明亮的数字区域,同时抑制暗背景[1,5](@ref)
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel)# 应用闭操作(先膨胀后腐蚀)
# 使用相同的矩形核进行闭操作,有助于连接数字中间断开的区域(如数字4、8等)
# 使数字区域更加连贯,便于后续的轮廓检测[2,6](@ref)
closeX = cv2.morphologyEx(tophat, cv2.MORPH_CLOSE, rectKernel)# 对图像进行二值化处理
# 使用OTSU's方法自动计算最佳阈值,适用于处理灰度分布不均的图像
# cv2.THRESH_BINARY | cv2.THRESH_OTSU 组合表示使用OTSU算法进行自动阈值二值化
# 函数返回一个元组,[1]获取二值化后的图像[7](@ref)
thresh = cv2.threshold(closeX, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]# 再次应用闭操作,这次使用方形核
# 目的是进一步填充数字内部可能存在的微小孔洞,并增强数字区域的连通性
# 使数字轮廓更加完整,为后续的轮廓检测做好准备[6](@ref)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel)

代码功能总结:​

这段代码是信用卡数字识别流程中的​​图像预处理部分​​,主要目的是从信用卡图像中突出并增强数字区域,同时抑制背景干扰。其处理流程包括:

  1. 图像读取与尺寸调整​​:读取原始图像并调整到合适尺寸,平衡处理速度与细节保留。
  2. 色彩空间转换​​:将彩色图像转换为灰度图像,简化后续处理。
  3. 形态学核定义​​:定义不同形状和尺寸的结构元素,用于后续的形态学操作。
  4. ​图像增强​​:
    • 顶帽操作​​:突出明亮的数字区域。
    • ​闭操作​​:连接数字中断开的部分,增强其连通性。
  5. ​二值化​​:将图像转换为黑白二值图,便于轮廓检测。
  6. ​进一步形态学处理​​:使用不同核进行闭操作,优化二值化结果,填充空洞,使数字轮廓更完整。

数字区域定位与提取

筛选有效数字区域:

# 在二值图像thresh中查找轮廓
# thresh.copy(): 使用二值图像的副本进行操作,避免修改原始图像
# cv2.RETR_EXTERNAL: 只检测最外层轮廓,忽略内部嵌套的轮廓(如数字内部的孔洞)
# cv2.CHAIN_APPROX_SIMPLE: 压缩水平、垂直和对角方向的冗余点,仅保留轮廓的端点
# 函数返回两个值:轮廓列表(threshCnts)和层次结构信息(此处用_忽略)
threshCnts, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 初始化一个空列表,用于存储符合筛选条件的数字区域位置信息
locs = []# 遍历检测到的所有轮廓,i是索引,c是每个轮廓的点集
for i, c in enumerate(threshCnts):# 获取每个轮廓的外接矩形# cv2.boundingRect(c) 返回一个元组 (x, y, w, h)# (x, y) 是矩形左上角的坐标,w是宽度,h是高度x, y, w, h = cv2.boundingRect(c)# 计算轮廓的宽高比(Aspect Ratio)# 信用卡数字通常具有特定的宽高比特征[4](@ref)ar = w / float(h)# 基于宽高比和物理尺寸筛选可能是数字的轮廓# 2.5 < ar < 4.0: 典型的信用卡数字宽高比范围(数字通常比高度宽)# 40 < w < 55: 数字宽度的合理范围(基于图像缩放后的尺寸)# 10 < h < 20: 数字高度的合理范围# 这些阈值需要根据实际图像尺寸和数字字体进行调整[16](@ref)if 2.5 < ar < 4.0 and 40 < w < 55 and 10 < h < 20:# 如果轮廓符合筛选条件,将其位置信息(坐标和尺寸)添加到locs列表中locs.append((x, y, w, h))# 对筛选出的数字区域按照x坐标(水平位置)进行排序
# 使用Python内置的sort方法,key=lambda x: x[0]表示按照元组的第一个元素(x坐标)排序
# 这确保了数字按照从左到右的正确顺序排列,为后续的数字识别提供正确的顺序[5,6](@ref)
locs.sort(key=lambda x: x[0])

代码功能总结:​

这段代码是信用卡数字识别流程中的​​关键步骤​​,主要完成以下任务:

  1. ​轮廓检测​​:在预处理后的二值图像上查找所有可能包含数字的区域轮廓
  2. 2​​轮廓筛选​​:基于数字的​​几何特征​​(宽高比和物理尺寸)从所有轮廓中筛选出最可能是数字的区域
    • 宽高比(Aspect Ratio)​​:信用卡上的数字通常具有特定的宽高比(此代码中为2.5到4.0之间),这是一个非常重要的筛选条件,可以有效地排除许多非数字的干扰轮廓
    • 宽度和高度​​:同时限制数字的绝对尺寸(宽度40-55像素,高度10-20像素),确保筛选出的区域大小符合预期。这些具体的阈值需要根据图像的实际分辨率和预处理时的缩放比例进行调整
  3. ​位置排序​​:将筛选出的数字区域按照它们的水平位置(x坐标)进行排序,确保数字按照从左到右的正确顺序排列,这对于后续的数字识别和拼接成完整的卡号至关重要

数字识别与匹配

单个数字识别过程:

# 初始化一个空列表output,用于存储最终识别出的信用卡数字序列
output = []# 遍历之前筛选出的四个数字组区域(locs中的每个元素代表一个数字组的坐标和尺寸:gX, gY, gW, gH)
for gX, gY, gW, gH in locs:# 从灰度图像gray中提取当前数字组的感兴趣区域(ROI)# 为了确保完整捕获数字,在原始坐标基础上上下左右各扩展5个像素group = gray[gY-5:gY+gH+5, gX-5:gX+gW+5]# 对提取出的数字组区域进行二值化处理# 使用OTSU's方法自动计算最佳阈值,适用于处理灰度分布不均的图像# cv2.THRESH_BINARY | cv2.THRESH_OTSU 组合表示使用OTSU算法进行自动阈值二值化# 函数返回一个元组,[1]获取二值化后的图像矩阵group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]# 在二值化的数字组图像中查找轮廓# group.copy(): 使用图像的副本进行操作,避免修改原始图像# cv2.RETR_EXTERNAL: 只检测最外层轮廓,忽略内部嵌套的轮廓# cv2.CHAIN_APPROX_SIMPLE: 压缩冗余点,仅保留轮廓端点# 返回轮廓列表(digitCnts)和层次结构信息(此处用_忽略)digitCnts, _ = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 对检测到的数字轮廓进行排序,确保按从左到右的正确顺序排列# sort_contours函数返回排序后的轮廓和边界框,这里取[0]即排序后的轮廓列表digitCnts = sort_contours(digitCnts, method="left-to-right")[0]# 遍历当前数字组中的每个数字轮廓for c in digitCnts:# 获取当前数字轮廓的外接矩形# cv2.boundingRect(c) 返回一个元组 (x, y, w, h)# (x, y) 是矩形左上角在group图像中的坐标,w是宽度,h是高度x, y, w, h = cv2.boundingRect(c)# 从数字组图像group中提取出当前数字的区域# 使用数组切片操作,取y到y+h行,x到x+w列的区域# 然后将该数字区域缩放到与模板相同的尺寸(57, 88)像素# 标准化尺寸对于后续的模板匹配至关重要,确保公平比较[1,9,18](@ref)roi = cv2.resize(group[y:y+h, x:x+w], (57, 88))# 模板匹配:将当前数字ROI与模板字典digits中的所有数字模板进行匹配# 计算与每个模板数字的匹配得分# 使用列表推导式遍历digits字典中的所有模板数字(d)# cv2.matchTemplate(roi, d, cv2.TM_CCOEFF): 使用相关系数法进行模板匹配# cv2.minMaxLoc(...)[1]: 获取匹配结果中的最大值(对于TM_CCOEFF,最大值表示最佳匹配)# 最终scores列表包含当前数字ROI与0-9每个模板数字的匹配得分scores = [cv2.minMaxLoc(cv2.matchTemplate(roi, d, cv2.TM_CCOEFF))[1] for d in digits.values()]# 找出得分最高的索引,即识别出的数字# np.argmax(scores)返回scores列表中最大值的索引# 由于digits字典的键是0-9(按模板中的顺序),索引即对应数字本身# 将识别出的数字转换为字符串并添加到output列表中output.append(str(np.argmax(scores)))

代码功能总结:​

这段代码是信用卡数字识别流程中的​​核心识别部分​​,主要完成以下任务:

  1. ​数字组提取与预处理​​:从已定位的四个数字组区域中提取ROI,并进行二值化处理,为单个数字的分割和识别做准备
  2. ​单个数字分割​​:在每个数字组内部进行轮廓检测,找到并分离出各个独立的数字
  3. ​数字标准化​​:将每个分割出的数字区域缩放到与模板相同的尺寸(57x88像素),消除大小差异对匹配的影响
  4. ​模板匹配与识别​​:将待识别的数字与预先准备好的0-9模板库进行相似度比较(使用相关系数法 TM_CCOEFF),选择相似度最高的模板所对应的数字作为识别结果。这是一种直接利用图像相似性进行识别的方法。
  5. 结果存储​​:将识别出的数字按顺序存储在列表 output中,最终 output列表将包含信用卡上所有识别出的数字序列。

工具函数

辅助功能实现:

import cv2def sort_contours(cnts, method='left-to-right'):"""对轮廓进行排序。该函数根据指定的方向方法对找到的轮廓进行排序,常用于OCR等需要确定轮廓顺序的场景。参数:cnts (list): 待排序的轮廓列表,通常由cv2.findContours函数得到。method (str): 排序方法,可选:'left-to-right'(默认):从左到右'right-to-left':从右到左'top-to-bottom':从上到下'bottom-to-top':从下到上返回:tuple: 返回一个元组,包含两个元素:- 排序后的轮廓列表 (cnts)- 对应的边界框列表 (boundingBoxes),每个边界框格式为 (x, y, w, h)"""# 初始化排序方向和索引reverse = False  # 默认升序(例如,左到右,上到下)i = 0            # 默认按x坐标排序(用于水平方向排序)# 判断是否需要反向排序(从右到左或从下到上)if method == 'right-to-left' or method == 'bottom-to-top':reverse = True# 判断是否按y坐标排序(用于垂直方向排序:从上到下或从下到上)if method == 'top-to-bottom' or method == 'bottom-to-top':i = 1  # 将索引改为1,表示对边界框的y坐标进行排序# 为每个轮廓计算其外接矩形(边界框)# cv2.boundingRect(c) 返回一个元组 (x, y, w, h),表示能包围轮廓的最小矩形的左上角坐标(x,y)和宽高(w,h)boundingBoxes = [cv2.boundingRect(c) for c in cnts]# 将轮廓(cnts)和其对应的边界框(boundingBoxes)打包成元组列表,然后根据边界框的特定坐标(x或y)进行排序# key=lambda b: b[1][i]:b是 (轮廓, 边界框) 的元组,b[1]是边界框,b[1][i] 是边界框的第i个坐标(i=0为x,i=1为y)# reverse 参数控制升序或降序sorted_list = sorted(zip(cnts, boundingBoxes), key=lambda b: b[1][i], reverse=reverse)# 使用zip(*...)将排序后的元组列表解压回两个独立的列表(轮廓和边界框)cnts, boundingBoxes = zip(*sorted_list)# 返回排序后的轮廓列表和边界框列表return cnts, boundingBoxesdef resize(image, width=None, height=None, inter=cv2.INTER_AREA):"""按比例缩放图像,保持原始图像的宽高比。参数:image (numpy.ndarray): 输入图像。width (int, optional): 目标宽度。如果为None,则根据高度计算。height (int, optional): 目标高度。如果为None,则根据宽度计算。inter (int, optional): 插值方法,默认为cv2.INTER_AREA(用于缩小图像)。返回:numpy.ndarray: 缩放后的图像。注意:必须指定width或height中的一个(但不能同时为None)。"""# 获取图像原始高度(h)和宽度(w)h, w = image.shape[:2]# 如果宽高都未指定,直接返回原图if width is None and height is None:return imagedim = None  # 初始化目标尺寸 (width, height)# 如果指定了宽度,但高度为None,则根据宽度和原始宽高比计算新高度if width is not None:r = width / float(w)  # 计算宽度缩放比例dim = (width, int(h * r))  # 新尺寸为 (指定宽度, 按比例计算的高度)# 否则,如果指定了高度,则根据高度和原始宽高比计算新宽度elif height is not None:r = height / float(h)  # 计算高度缩放比例dim = (int(w * r), height)  # 新尺寸为 (按比例计算的宽度, 指定高度)# 使用cv2.resize函数进行图像缩放,inter参数指定插值方法resized = cv2.resize(image, dim, interpolation=inter)return resized

关键点说明:​​

.​​sort_contours函数​​:

​​核心思想​​:通过计算每个轮廓的边界框(cv2.boundingRect),然后根据边界框的特定坐标(x 或 y)进行排序,从而实现轮廓的排序。
​​应用场景​​:在OCR(光学字符识别)项目中非常常见,例如对银行卡号数字、印刷文本的轮廓进行排序,以确保正确的读取顺序(如从左到右)。


​​zip和 sorted的妙用​​:zip(cnts, boundingBoxes)将轮廓和其边界框一一对应打包。sorted(...)根据指定的坐标排序。zip(*sorted_list)则将排序后的结果重新解包成两个独立的列表。


​​resize函数​​:
​​核心思想​​:在改变图像尺寸时,始终保持图像的原始宽高比,避免图像变形。
​​插值方法​​:cv2.INTER_AREA插值方法通常用于图像缩小,它能产生较好的结果。
结果输出与信用卡类型识别

最终识别输出:

CARD_TYPE = {"3": "American Express","4": "Visa","5": "MasterCard","6": "Discover Card"
}print(f"Credit Card Type: {CARD_TYPE[output[0]]}")
print(f"Credit Card #: {''.join(output)}")# 可视化结果
cv2.rectangle(image, (gX-5, gY-5), (gX+gW+5, gY+gH+5), (0, 0, 255), 1)
cv2.putText(image, "".join(output), (gX, gY-15), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)
cv2.imshow("Result", image)
cv2.waitKey(0)
cv2.destroyAllWindows()

完整代码

工具函数

myutils.py

import cv2def sort_contours(cnts, method='left-to-right'):reverse = Falsei=0if method == 'right-to-left' or method == 'bottom-to-top':reverse = Trueif method == 'top-to-bottom' or method == 'bottom-to-top':i=1boundingBoxes = [cv2.boundingRect(c) for c in cnts](cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),key=lambda x: x[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 imageif 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

主代码

import cv2
import numpy as np
import argparse
import myutils# ap = argparse.ArgumentParser()
# ap.add_argument("-i", "--image", required=True,help="path to input image")
#
# ap.add_argument("-t", "--output", 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"}# img = cv2.imread(args["templater"])
img = cv2.imread('number.png')
# cv2.imshow("templater", img)
# cv2.waitKey(0)ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow("ref", ref)
cv2.waitKey(0)
ref = cv2.threshold(ref,10,255,cv2.THRESH_BINARY_INV)[1]
# cv2.imshow("ref", ref)
# cv2.waitKey(0)refCnts,hierarchy = cv2.findContours(ref,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)cv2.drawContours(img, refCnts, -1, (0,0,255), 3)
cv2.imshow("img", img)
cv2.waitKey(0)digits = {}
for (i,c) in enumerate(refCnts):(x,y,w,h) = cv2.boundingRect(c)roi = ref[y:y+h,x:x+w]roi = cv2.resize(roi, (57,88))# cv2.imshow("ROI", roi)# cv2.waitKey(0)digits[i] = roi
print(digits)
# cv2.destroyAllWindows()'''信用卡的图像处理'''
image = cv2.imread('picture/credit1.png')
cv2.imshow("image", image)
cv2.waitKey(0)
image = myutils.resize(image, width=300)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT,(9,3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
tophat = cv2.morphologyEx(gray,cv2.MORPH_TOPHAT,rectKernel)
cv2.imshow("tophat", tophat)
cv2.waitKey(0)closeX = cv2.morphologyEx(gray,cv2.MORPH_CLOSE,rectKernel)
# cv2.imshow("closeX", closeX)
# cv2.waitKey(0)
thresh = cv2.threshold(closeX,0,255,cv2.THRESH_BINARY|cv2.THRESH_OTSU)[1]
# cv2.imshow("thresh", thresh)
# cv2.waitKey(0)
thresh = cv2.morphologyEx(thresh,cv2.MORPH_CLOSE,sqKernel)
# cv2.imshow("thresh1", thresh)
# cv2.waitKey(0)threshCnts,h =cv2.findContours(thresh.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = threshCnts
cur_img = image.copy()
cv2.drawContours(cur_img,cnts,-1,(0,0,255),3)
cv2.imshow("cur_img", cur_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
locs = []
for (i,c) in enumerate(cnts):(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 = gray[gY-5:gY+gH+5, gX-5:gX+gW+5]cv2.imshow("group", group)cv2.waitKey(0)group = cv2.threshold(group,0,255,cv2.THRESH_BINARY|cv2.THRESH_OTSU)[1]cv2.imshow("group", group)cv2.waitKey(0)digitCnts,hierarchy = cv2.findContours(group.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)digitCnts = myutils.sort_contours(digitCnts, method="left-to-right")[0]for c in digitCnts:(x,y,w,h) = cv2.boundingRect(c)roi = group[y:y+h,x:x+w]roi = cv2.resize(roi, (57,88))cv2.imshow("roi", roi)cv2.waitKey(0)score = []for (digit, digitROI) in digits.items():result = cv2.matchTemplate(roi,digitROI,cv2.TM_CCOEFF)scores = cv2.minMaxLoc(result)[1]score.append(scores)groupOutput.append(str(np.argmax(score)))cv2.rectangle(cur_img,(gX-5,gY-5),(gX+gW+5,gY+gH+5),(0,0,255),1)cv2.putText(image,"".join(groupOutput),(gX,gY-15),cv2.FONT_HERSHEY_SIMPLEX,0.65,(0,0,255),2 )output.extend(groupOutput)
print(output[0])
# print("Credit Card Types:{}".format(FIRST_NUMBER[output[0]]))
print("Credit Card #:{}".format("".join(output)))
cv2.imshow("results", image)
cv2.waitKey(0)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/98424.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/98424.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式ARM64 基于RK3588原生SDK添加用户配置选项./build lunch debian

1 背景 在我们正常拿到SDK后会有一些配置选项&#xff0c;在使用./build.sh lunch之后会输出一些defautconfig让我们选择&#xff0c;瑞芯微的原厂sdk会提供一些主板的配置选项&#xff0c;但是我们的如果是一块新的主板就需要添加自己的配置选项&#xff0c;本文就讨论如何来添…

专为石油和天然气检测而开发的基于无人机的OGI相机

专为石油和天然气检测而开发的基于无人机的OGI相机基于无人机的 OGI 相机:&#xff08;Optical Gas Imaging&#xff0c;光学气体成像&#xff09;其实是近几年油气、电力、化工等行业里非常热门的应用方向。什么是 OGI 相机OGI&#xff08;Optical Gas Imaging&#xff09;&am…

iPhone17全系优缺点分析,加持远程控制让你的手机更好用!

知名数码厂商苹果&#xff0c;不久前已官宣将于北京时间9月10日凌晨1点开启发布会&#xff0c;主打对于iPhone 17系列产品介绍&#xff0c;并且和以往不同的是&#xff0c;今年会在购物平台上开启线上直播&#xff0c;还是很有新意的。9.13全平台渠道将开启预售模式&#xff0c…

人工智能-python-深度学习-神经网络VGG(详解)

LeNet 系列之后 —— VGG&#xff08;详解&#xff09;&#xff1a;从原理到 PyTorch 实现 文章目录LeNet 系列之后 —— **VGG&#xff08;详解&#xff09;**&#xff1a;从原理到 PyTorch 实现1. VGG 的发展历史与意义&#xff08;一句话&#xff0b;背景&#xff09;2. VGG…

光伏运维迎来云端革命!AcrelCloud-1200如何破解分布式光伏四大痛点?

在国家“双碳”目标推动下&#xff0c;分布式光伏正迎来爆发式增长&#x1f31e;。甘肃、吉林、云南等多地政策接连落地&#xff0c;整县推进屋顶光伏试点如火如荼&#xff01;然而&#xff0c;快速发展的背后&#xff0c;你是否也遇到过这些“光伏运维之痛”&#xff1f;✨【痛…

将 maven 集成到 idea 后出现 向项目创建模块时出错:null 的问题

1.出现的问题今天想将maven继承到idea出现了一下问题&#xff1a;用生成器里面的也会报错&#xff0c;找了找帖子并没有哪位大佬出现类似错误&#xff0c;于是我解决完想分享一下&#xff0c;如果有不对&#xff0c;请指正。2.解决办法很可能是java 的 版本 与 maven 版本有问题…

类似于 Progress Telerik Fiddler Classic 的 免费 或 开源 HTTP/HTTPS 抓包与调试工具推荐

以下是一些 类似于 Progress Telerik Fiddler Classic 的 免费 或 开源 HTTP/HTTPS 抓包与调试工具推荐&#xff1a;免费 / 开源替代工具推荐 1. Wireshark 免费且开源的网络协议分析工具&#xff0c;支持 Windows、macOS、Linux 等平台。可捕获并深入分析网络流量&#xff0c;…

7.0 热电偶的工作原理

在工业生产过程中&#xff0c;温度是需要测量和控制的重要参数之一。在温度测量中&#xff0c;热电偶的应用极为广泛&#xff0c;它具有结构简单、制造方便、测量范围广、精度高、惯性小和输出信号便于远传等许多优点。另外&#xff0c;由于热电偶是一种无源传感器&#xff0c;…

commons-lang3

概述 提供了许多帮助程序实用程序&#xff0c;特别是字符串操作方法&#xff0c;基本数值方法&#xff0c;对象反射&#xff0c;并发&#xff0c;创建和序列化以及系统属性。maven依赖<dependency><groupId>org.apache.commons</groupId><artifactId>c…

vue-amap组件呈现的效果图如何截图

我们用amap呈现了几个图层后&#xff0c;用户觉得效果很好&#xff0c;想点个按钮直接将这个画面截图下来。 首先我们用Canvas的toDataURL方法可以直接获取图像数据&#xff0c;但是实践发现截图后是空白的。 原因在警告中&#xff1a; 地图的WebGL context 的preserveDrawin…

杰理烧录ERROR: Data error after erasing, address = 0x430000

把CONFIG_BOARD_DEV_KIT关闭&#xff0c;打开CONFIG_BOARD_DEVELOP

超越自动化:为什么说供应链的终局是“AI + 人类专家”的混合智能?

摘要&#xff1a;当前&#xff0c;围绕AI赋能供应链的讨论&#xff0c;大多聚焦于“自动化”带来的降本增效。然而&#xff0c;这仅仅是第一层。当我们的系统面对“黑天鹅”事件时&#xff0c;一个过度依赖自动化的“脆弱”系统可能会瞬间崩溃。本文旨在深入探讨供应链演进的下…

Spine文件导入Unity流程

1、转为Json文件导出 2、对文件进行处理 3、添加Spine的Package包 一、Spine文件导出设置 1、选择Json文件 2、选择导出所在路径 3、点击打包设置 更改图集扩展名 二、文件导出后的设置 1、修改Json的Spine版本 这里必须是3.8 三、下载Unity支持包 1、链接 spine-unit…

Docker Compose healthcheck介绍(监控容器中服务的实际健康状态)数据库健康检查pg_isready

文章目录**功能概述****核心参数详解****配置示例****1. 基础用法****2. 使用数据库健康检查****3. 结合 depends_on 控制启动顺序****高级用法****1. 自定义健康检查脚本****2. 多种健康检查类型**- **HTTP 检查**&#xff1a;- **TCP 端口检查**&#xff1a;- **Redis 检查**…

算法之双指针

在算法设计中&#xff0c;双指针是一种高效优化工具&#xff0c;主要用于线性数据结构&#xff08;如数组&#xff08;数组划分和数组分块常用&#xff09;、链表、字符串&#xff09;&#xff0c;通过控制两个指针的移动轨迹&#xff0c;将原本需要 O (n) 时间复杂度的问题优化…

幂等性、顺序性保障以及消息积压

幂等性 概念 在应用程序中&#xff0c;幂等性就是指对一个系统进行重复调用&#xff08;相同参数&#xff09;&#xff0c;不论请求多少次&#xff0c;这些请求对系统的影响都是相同的效果. 比如数据库的select操作.不同时间两次查询的结果可能不同&#xff0c;但是这个操作…

算法训练营DAY58 第十一章:图论part08

拓扑排序精讲 卡码网&#xff1a;117. 软件构建(opens new window) 题目描述&#xff1a; 某个大型软件项目的构建系统拥有 N 个文件&#xff0c;文件编号从 0 到 N - 1&#xff0c;在这些文件中&#xff0c;某些文件依赖于其他文件的内容&#xff0c;这意味着如果文件 A 依…

如何在Python中使用正则表达式?

在Python中使用正则表达式主要通过内置的re模块实现。正则表达式用于匹配、查找、替换字符串中的特定模式&#xff0c;是处理文本的强大工具。以下是使用正则表达式的核心方法和示例&#xff1a; 一、基本用法步骤 导入re模块&#xff1a;import re定义正则表达式模式&#xff…

用 Trae 玩转 Bright Data MCP 集成

引言 在自动化与智能体浪潮中&#xff0c;Trae 以“开箱即用、所见即所得”的工具编排体验&#xff0c;成为个人与团队落地 AI 工作流的高效选择。本篇将以 Trae 为主角&#xff0c;展示如何通过最少配置完成与 Bright Data MCP 的对接&#xff0c;并快速构建一个可用、可观测…

大数据Spark(六十三):RDD-Resilient Distributed Dataset

文章目录 RDD-Resilient Distributed Dataset 一、RDD五大特性 二、RDD创建方式 RDD-Resilient Distributed Dataset 在 Apache Spark 编程中&#xff0c;RDD&#xff08;Resilient Distributed Dataset&#xff0c;弹性分布式数据集&#xff09;是 Spark Core 中最基本的数…