任务要求
学了这么久opencv,不做个实战项目怎么行(手动狗头)😏
对于所给的一张信用卡图片,目的是要识别出信用卡的卡号
图片如下:
任务分析
这个任务是opencv学习过程中的一个阶段性的总结,而为了完成它,我们必须将其割裂成几步来分析它
一、首先调一下包
import cv2
import numpy as np
from imutils import contours
import imutils
import main
其中有几个常用的库就不在此赘述了,唯一要说的是main库,这个库并不是python自带的,而是根据任务的要求写出的库,读者在尝试时可以直接通过写函数的方式来实现,没有必要另写一个库,为了方便读者阅读,特将此库内容放入文章
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对外接矩形取左上点的横坐标,通过对横坐标的排序即可实现对数字的排序
# boundingboxes是一个元组,其中包含xyzw四个值
(cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
key=lambda b: b[1][i], reverse=reverse))#使用其X进行判断
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
首先,我们的目的是识别数字,那么我们首先需要一些数字的模板,然后将信用卡上的数字与模板上的数字进行比对,返回匹配程度最高的数字,即可近似给出准确答案
# 灰度图转化()
ref=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#改变颜色通道
对灰度图我们可以进行二值化处理,突出我们需要的部分
# 二值图像
ref=cv2.threshold(ref,10,225,cv2.THRESH_BINARY_INV)[1]
接着将所有的区域轮廓画出
refCnts,hierarchy=cv2.findContours(ref.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img,refCnts,-1,(0,0,255),3)#-1表示画出所有的轮廓
已经画完轮廓后,需要判断模板中数字的位置,我们需要得到每个轮廓的的大致位置,那么我们可以通过外接矩形左上角的坐标来代表一个轮廓的相对位置
refCnts=main.sort_contours(refCnts,method="left-to-right")[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))
digits[i]=roi#字典中每一个值都有其对应的模板
模板中的数字已经识别完毕并储存在字典中了,那我们现在开始对信用卡进行处理吧
首先我们还是为了减轻影响先转为灰度图
image=cv2.imread("credit_card_03.jpg")
cv_show("image",image)
image=main.resize(image,width=300)
gray=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
考虑到与我们使用的比照图差别较大,故考虑使用礼帽操作来突出更明亮的区域
tophat=cv2.morphologyEx(gray,cv2.MORPH_TOPHAT,rectKernel)
然后使用Sobel算子计算梯度并进行归一化处理
gradx=cv2.Sobel(tophat,cv2.CV_32F,1,0,ksize=-1)
gradx=np.absolute(gradx)
(minval,maxval)=(np.min(gradx),np.max(gradx))
gradx=(255*((gradx-minval)/(maxval-minval)))#归一化
gradx=gradx.astype("uint8")
通过闭运算先膨胀后腐蚀将数字连在一起以得到更准确的区域来锁定信用卡上四组数字每组数字的轮廓
gradx=cv2.morphologyEx(gradx,cv2.MORPH_CLOSE,rectKernel)
cv_show("gradx",gradx)
# 对于双峰形式问题,我们在二值处理过程中可以使用如下操作
thresh=cv2.threshold(gradx,0,255,cv2.THRESH_BINARY|cv2.THRESH_OTSU)[1]#cv2.THRESH_OTSU在阈值设为0是会自动帮你设置阈值
cv_show("thresh",thresh)
thresh=cv2.morphologyEx(thresh,cv2.MORPH_CLOSE,sqKernel)#再次执行闭操作
cv_show("thresh",thresh)
threshCnts,hierarchy=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)
但是还有部分区域会对我们的识别进行干扰,那么我们该如何避免呢,我们知道这四组数字的长宽比都是固定的,那么我们只要人工计算出长宽比,再进行计算即可得到正确区域
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))
#将符合的留下
接着有了四片区域了,那么我们可以对每块区域进行轮廓检测得到每个数字的区域,再对每个数字进行模式匹配就很可,是吧😅
for(i,(gX,gY,gW,gH))in enumerate(locs):
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]#二值化
cv_show("group",group)
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:
(x,y,w,h)=cv2.boundingRect(c)
roi=group[y:y+h,x:x+w]
roi=cv2.resize(roi,(57,88))#获得每个数字的轮廓并将其resize为和模板一样大小的图片以便于模式匹配
创建一个列表scores用于存储模式匹配时与不同模板的匹配程度,随之进行模板比配,将结果存储
for (digit, digitROI) in digits.items():
# 进行模板匹配, res是结果矩阵
res = cv2.matchTemplate(roi, digitROI, cv2.TM_CCOEFF) # 此时roi是X digitROI是0 依次是1,2.. 匹配10次,看模板最高得分多少
Max_score = cv2.minMaxLoc(res)[1] # 返回4个,取第二个最大值Maxscore
scores.append(Max_score) # 10个最大值
# print("scores:",scores)
# 得到最合适的数字
groupOutput.append(str(np.argmax(scores)))
将所给图像绘出
cv2.rectangle(image, (gX - 5, gY - 5), (gX + gW + 5, gY + gH + 5), (0, 0, 255), 1) # 左上角,右下角
# 2.4 putText参数:图片,添加的文字,左上角坐标,字体,字体大小,颜色,字体粗细
cv2.putText(image, "".join(groupOutput), (gX, gY - 15),
cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)
# 2.5 得到结果
output.extend(groupOutput)
print("groupOutput:", groupOutput)
最后展示图像
print("Credit Card #: {}".format("".join(output)))
cv2.imshow("Output_image", image)
cv2.waitKey(0)
最终的识别结果
嘿,换张图照样可
再多试几次
虽然是实现了目的,其实很多东西都欠缺了考虑,比如说现实中对信用卡进行拍照处理很可能会有光的干扰,那么数字的颜色可能会与底色无法区分开,所以需要进行更细微的操作来应对这种情况,读者大可自行尝试,设计出能识别更苛刻情况下的信用卡卡号。
终末
opencv学习的资源来自视频.
如果需要相关图片资料,可以私信博主。