目录
一、自定义命令行参数
1、什么是自定义命令行参数
自定义命令行参数是指在编写命令行程序时,由程序开发者定义并期望用户通过命令行界面(CLI)输入的参数。这些参数允许用户向程序传递额外的信息或配置,以便程序能够根据这些信息执行不同的操作或行为。
在Python中,使用argparse模块可以方便地定义和解析自定义命令行参数。argparse模块提供了丰富的功能来定义参数的类型、默认值、是否必需等属性,并能够在用户没有按预期提供参数时给出友好的帮助信息。
2、自定义命令行参数的类型
1)位置参数(Positional Arguments)
位置参数是按照位置顺序提供的参数。在命令行中,它们紧跟在程序名称之后,不需要使用--
或-
前缀。位置参数的数量和顺序通常是固定的,用户必须按照程序的要求提供它们。
2)可选参数(Optional Arguments)
可选参数,也称为选项或开关,是可选提供的参数。它们通常使用--
(长选项)或-
(短选项)前缀来标识。可选参数可以指定默认值,这样即使用户没有提供它们,程序也能正常运行。可选参数还可以指定为必需的,这样如果用户没有提供它们,程序就会报错或显示帮助信息。
3)子命令(Sub-commands)
子命令允许程序具有多个相关的功能,每个功能都通过一个特定的子命令来调用。子命令是可选参数的一种特殊形式,它们通常用于实现类似git
这样的命令行工具的复杂功能。
3、定义参数时注意
- 参数名称:选择一个简短、直观且易于理解的参数名称。
- 参数类型:确定参数应该接受的数据类型(如字符串、整数、浮点数等)。
- 默认值:为可选参数指定一个默认值,以便在用户没有提供参数时程序能够正常运行。
- 是否必需:确定参数是否必需提供。对于必需参数,如果用户没有提供,程序应该报错或显示帮助信息。
- 帮助信息:为每个参数提供清晰的帮助信息,以便用户了解参数的作用和用法。
4、代码案例
import argparse
# 创建ArgumentParser对象
parser = argparse.ArgumentParser(description='This is a sample script.')
# 添加位置参数
parser.add_argument('name', type=str, help='Your name')
# 添加可选参数
parser.add_argument('--age', type=int, default=25, help='Your age')
parser.add_argument('-c', '--city', type=str, help='Your city')
# 解析命令行参数
args = parser.parse_args()
# 使用解析后的参数
print(f'Hello, {args.name}!')
print(f'You are {args.age} years old.')
if args.city:
print(f'You live in {args.city}.')
运行方法:
1)pycharm中右击鼠标,打开修改运行配置
输入参数,格式与定义时一致:
2)终端运行
打开终端,输入python + 代码地址,再输入参数内容,注意参数顺序
二、项目实施
1、案例图片
2、自定义参数
定义参数内容为待识别的信用卡号,以及0-1的数字模版
import numpy as np
import argparse # py内置参数,用于自定义命令行参数
import cv2
import myutils # 自定义的函数用法
#
"""
-i card1.png
-t kahao.png
"""
ap = argparse.ArgumentParser() # 创建一个自定义命令行参数对象,其中存放参数的内容
ap.add_argument("-i","--image",required=True,help="path to input image") # 定义一个参数,命名为i或image,require=True表示参数是必须参数,help表示提示作用
ap.add_argument('-t',"--template",required=True,help="path to template OCA-A image") # 同样定义一个必须参数t或template
args = vars(ap.parse_args()) # parse_args表示解析命令行参数,默认从sys.argv读取,得到一个命名空间,vars表示将这个空间转换为字典类型
FIRST_NUMBER = { # 定义一个字典,用于存放信用卡类型的标识
"3":"American Express"
,"4":"Visa"
,"5":"MasterCard"
,"6":"Discover Card"
}
3、处理模版图像
img = cv2.imread(args["template"]) # 读取图片,args["template"]为上述定义的参数用法的字典
cv_show('img',img) # 调用自定义函数用法,展示原图
ref = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) # 灰度图
cv_show('ref',ref)
# 对灰度图进行二值化处理,灰度值大于10转变为255,小于10转变为0,cv2.THRESH_BINARY_INV表示反二值化,即黑白颠倒(小于10的转变为255,大于10的转变为0)
# 返回两个参数,第一个是阈值10,一个是二值化图像,使用索引[1]只返回后者
ref = cv2.threshold(ref,10,255,cv2.THRESH_BINARY_INV)[1]
cv_show('ref_thr',ref)
运行结果:
4、识别数字轮廓
# 使用cv2.findContours识别轮廓,ref.copy()表示生成图像副本其为待识别图像
# cv2.RETR_EXTERNAL只检索最外层的轮廓
# cv2.CHAIN_APPROX_SIMPLE表示使用轮廓近似法,只保留轮廓端点
_,refCnts,hierarchy = cv2.findContours(ref.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) # 返回三个参数,一个是原图,一个是轮廓的列表(全是点),以及轮廓的层次结构
cv2.drawContours(img,refCnts,-1,(0,0,255),3) # 绘制轮廓,在原图上绘制轮廓,轮廓集为refCnts,-1表示绘制所有轮廓
cv_show('img',img)
refCnts = myutils.sort_contours(refCnts,method="left-to-right")[0] # 调用自定义模块中的方法sort_contours对轮廓图进行排序返回索引对应的值和,利用索引[0],返回排序后的轮廓和轮廓的边界信息
digits = {} # 定义一个空字典,保存模板中每个数字对应的像素值
for (i,c) in enumerate(refCnts): # 使用numerate将每一个轮廓组成一个索引序列,返回轮廓索引和轮廓,此时i值表示轮廓中的值0-9
(x,y,w,h) = cv2.boundingRect(c) # 计算轮廓外接矩形,返回坐标和高宽
roi = ref[y:y + h,x:x + w] # 裁剪出每个数字对应的轮廓
roi = cv2.resize(roi,(57,88)) # 缩放轮廓到指定的大小
digits[i] = roi # 每一个数字对应每一个模板,将他们存入字典
运行结果:
5、处理信用卡图片
1)读取图片
image = cv2.imread(args['image']) # 读取图像
cv_show('image',image) # 展示图像
image = myutils.resize(image,width=300) # 缩放图像,将原始图像的宽度变为300像素值,高度根据比例自动改变
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) # 灰度图
cv_show('gray',gray) # 展示灰度图
运行结果:
2)顶帽操作突出亮区域
# 顶帽操作,实出更明亮的区域,清除背景图,原因是背景颜色变化小,不被腐蚀掉。
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT,(9,3)) # 初始化卷积核,创建一个9x3的卷积核用于腐蚀膨胀等操作,cv2.MORPH_RECT表示结构为矩形
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
# cv2.morphologyEx表示图像形态学操作
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel) # 对信用卡的灰度图进行顶帽操作,cv2.MORPH_TOPHAT表示顶帽,突出亮色,rectKernel表示卷积核,顶帽 = 厡始图像 - 开运算结果(先腐蚀后膨胀)
cv_show('tophat',tophat) # 展示顶帽后的图片
运行结果:
6、定位卡号轮廓
# """ 找到数字边框- """
# 图像形态学处理,通过闭操作(先膨胀,再腐蚀)将数字连在一起
closeX =cv2.morphologyEx(tophat, cv2.MORPH_CLOSE, rectKernel)
cv_show('gradX',closeX)
# 将灰度图转变为二值化图像,cv2.THRESH_BINARY | cv2.THRESH_OTSU为阈值类型和方法的组合,前者指定二值化,后者自动寻找合适阈值
thresh = cv2.threshold(closeX,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
cv_show('thresh',thresh)
# 再来一个闭操作,sqKernel为上述定义的卷积核
thresh = cv2.morphologyEx(thresh,cv2.MORPH_CLOSE,sqKernel)
cv_show('thresh',thresh)
# 检索最外层的轮廓,使用轮廓近似法,只保留轮廓端点,返回原图,轮廓的列表(全是点),以及轮廓的层次结构
t_,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) # 绘制所有轮廓的外接矩形
cv_show('img',cur_img)
# 遍历轮廓,找到数字部分像素区域
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)) # 将符合条件的轮廓存放进列表
运行结果:
7、识别卡号的每个数字
# # 将符合的轮廓从左到右排序
locs = sorted(locs,key=lambda x:x[0])
output =[]
# 遍历每一个对应的索引和轮廓边界信息
for (i,(gX,gY,gW,gH)) in enumerate(locs): # enumerate用于返回一个可迭代对象的索引和数据,这里是轮廓索引、x坐标、y坐标、宽高
groupOutput =[]
group = gray[gY - 5:gY + gH + 5 , gX - 5:gX + gW + 5] # 适当加一点边界,用于增大识别准确率
cv_show('group',group)
# 预处理
group =cv2.threshold(group,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] # 将每个轮廓进行二值化处理(黑白),返回两个值,一个是阈值,一个是图像,索引[1]表示图像
cv_show('group',group)
# 计算每一组的轮廓
group_, digitCnts, hierarchy = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) # 查找每个轮廓中的数字轮廓,返回三个值,一个是每个轮廓,一个是轮廓数,以及轮廓的参层次信息
digitCnts = myutils.sort_contours(digitCnts, method="left-to-right")[0] # 调用自定义模块中的函数将识别的数字轮廓进行排序,排序依据为x轴大小,即卡面上的先后顺序,返回排序后的轮廓和轮廓边界
# 计算每一组中的每一个数值
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)) # 缩放每个数字轮廓
cv_show('roi', roi)
运行结果:
8、使用模板匹配,计算匹配得分,并绘制
(此处需缩进,在上述for循环下)
""" 使用模板匹配,计算匹配得分 """
scores = []
# 在模板中计算每一个得分
for (digit,digitROI) in digits.items(): # 此处digits为上述定义并存放这数字模版中数字轮廓及其对应的数字代号
# 模板匹配
result = cv2.matchTemplate(roi,digitROI,cv2.TM_CCOEFF) # 对卡号数字轮廓和数字对应的轮廓进行模板匹配,使用相关系数匹配法
(_,score,_,_) = cv2.minMaxLoc(result) # 找到上述模板匹配相关系数最大值,只要score,其他返回值忽略
scores.append(score)
# 得到最合适的数字
groupOutput.append(str(np.argmax(scores))) # 返回scores中最大相关系数的索引,然后转换为字符串类型增加到上述定义的空列表
# 画出来
cv2.rectangle(image,(gX-5,gY-5),(gX+gW+5,gY+gH+5),(0,0,255),1) # 在image图像上绘制矩形,其后参数表示左上角坐标、右下角坐标
# cv2.putText()是OpenCV库中的一个函数,用于在图像上添加文本
cv2.putText(image,''.join(groupOutput),(gX,gY-15),cv2.FONT_HERSHEY_SIMPLEX,0.65,(0,0,255),2)
output.extend(groupOutput) # 得到结果,将一个列表的元素添加到两一个列表的末尾
print("Credit Card Type: {}".format(FIRST_NUMBER[output[0]]))
print("Credit Card #:{}".format("".join(output)))
cv2.imshow("Image", image)
cv2.waitKey(0)
运行结果:
9、自定义模块myutils.py代码
import cv2
def sort_contours(cnts ,method='left-to-right'): # 输入参数轮廓列表,以及method,默认排序方式为由左到右
reverse = False # 布尔值,用于控制排序的方向
i = 0
if method == 'right-to-left' or method == 'bottom-to-top': # 判断排序方式,以此来更改reverse
reverse=True
if method == 'top-to-bottom' or method == 'bottom-to-top':
i = 1
boundingBoxes = [cv2.boundingRect(c) for c in cnts] # 遍历每一个轮廓图,取出轮廓图的x、y、w、h,将这些信息存放到空列表中
# 将列表轮廓和轮廓信息组合成一个元组的列表,再通过匿名函数排序这个元组列表,排序依据为轮廓数据的第一位x大小,降序方式,返回两个元素,一个是排序后的轮廓列表,一个是轮廓的边界框
(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) # 默认为cV2.INTER_AREA,即面积插值,适用于缩放图像。
return resized