本片文章介绍的是利用OpenCV对信用卡的卡号进行一个简单的识别
核心用的是match的方法
先把图片读进来,然后转换为灰度图后做二值化处理
img = cv2.imread('/Users/qiaoye/Desktop/信用卡识别/images/ocr_a_reference.png')
#cv_show('moban',img)
ref = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv_show('gray',ref)
ref = cv2.threshold(ref,127,255,cv2.THRESH_BINARY_INV)[1]
cv_show('2',ref)
cv2.drawContours(image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]])
img - 目标图像
contours - 所有输入的轮廓
contourIdx - 指定轮廓列表的索引 ID(将被绘制),若为负数,则所有的轮廓将会被绘制。
color - 绘制轮廓的颜色。
thickness - 绘制轮廓线条的宽度,若为负值或CV.FILLED则将填充轮廓内部区域
lineType - Line connectivity,(有的翻译线型,有的翻译线的连通性)
hierarchy - 层次结构信息,与函数findcontours()的hierarchy有关
maxLevel - 绘制轮廓的最高级别。若为0,则绘制指定轮廓;若为1,则绘制该轮廓和所有嵌套轮廓(nested contours);若为2,则绘制该轮廓、嵌套轮廓(nested contours)/子轮廓和嵌套-嵌套轮廓(all the nested-to-nested contours)/孙轮廓,等等。该参数只有在层级结构时才用到。
offset - 按照偏移量移动所有的轮廓(点坐标)。
#计算轮廓,cv2.findContours 函数接受的参数为二值图,即黑白,不是黑就是白
cnts,hierarchy = cv2.findContours(ref.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img, cnts, -1, (0, 0, 255), 3)
cv_show('img', img)
cv2.boundingRect(c)
(1) 第一个参数,InputArray array,一般为findContours函数查找的轮廓,包含轮廓的点集或者Mat;
(2) 返回值,Rect,返回值为最小外接矩形的Rect,即左上点与矩形的宽度和高度;
OpenCV提供了一个函数getStructuringElement,可以获取常用的结构元素的形状:矩形(包括线形)、椭圆(包括圆形)及十字形。
矩形:MORPH_RECT;
交叉形:MORPH_CORSS;
椭圆形:MORPH_ELLIPSE;
boundingBoxes = [cv2.boundingRect(c) for c in cnts] #用一个最小的矩形,把找到的形状包起来x,y,h,w
(cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),key=lambda b: b[1][0], reverse=False))
digits = {}
for (i,c) in enumerate(cnts):
(x,y,h,w) = cv2.boundingRect(c)
roi = ref[y:y+h,x:x+w]
roi = cv2.resize(roi,(57,88))
digits[i] = roi
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT,(9,3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
对每个矩形框,找到他最小的外接矩形框 boundingBoxes
然后对这些矩形框进行排序,让他们分别对应自己的位置,也就是0对应0的框
随意要对他们进行排序,按照横坐标升序的方式进行排序
sorted(zip(cnts,boundingBoxes),key = lambda b:b[1][0],reverse=False )
先把 cnts
和 boundingBoxes
绑定为一个元素分别存入一个列表中,然后对两个分别进行升序排列
zip(*)
分别对数组进行解包,并返回cnts和boundingBoxes
对排序过的进行遍历
对每个包围,找到最小的框,并得到该坐标,适当调整后,存入roi中
第二步是设置卷积核大小和形状
image = cv2.imread('/Users/qiaoye/Desktop/信用卡识别/images/credit_card_01.png')
#cv_show('image',image)
image = myutils.resize(image, width=300)
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
cv_show('image',gray)
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel)# 礼帽操作 突出明亮的区域
cv_show('tophat', tophat)
礼帽操作:原图-开操作 得到的是噪声图像
可以返现礼帽操作后,背景板上很多的噪声都剔除掉了
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize = -1)
#ksize=-1相当于用3*3的
gradX = np.absolute(gradX)
(minVal, maxVal) = (np.min(gradX), np.max(gradX))
sobel 算子
Sobel算子依然是一种过滤器,只是其是带有方向的。在OpenCV-Python中,使用Sobel的算子的函数原型如下:
dst = cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])
前四个是必须的参数:
第一个是需要处理的图像
第二个参数是图像的深度,-1表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度;
dx和dy表示的是求导的阶数,0表示这个方向上没有求导,一般为0、1、2。
dx = 1 ,dy=0 表示对x方向进行求导
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize = 3) #核函数为3*3 现在算出的水平的情况
cv2.imshow(sobelx)
x方向是右边减去左边,会存在剪出的结果为负数的情况,为负数的时候图像进行显示的时候默认为0 所以在显示的时候只能显示出一边的情况
0,1 在y方向是同理
gradX = (255 * ((gradX - minVal) / (maxVal - minVal))) # 对他做归一化操作
# 归一化后要把数据改成uint8的形式
gradX = gradX.astype("uint8")
print(np.array(gradX).shape) #打印出gradx的shape
cv_show('gradx',gradX)
对比发现经过归一化后的操作,效果比较明显
对现在的图像进行闭操作处理,也就是先进行膨胀再进行腐蚀操作,目的是把相近的数字连在一起
# 通过闭操作(先膨胀,再腐蚀)将数字连在一起
gradx = cv2.morphologyEx(gradX,cv2.MORPH_CLOSE,sqKernel)
cv_show('gradx',gradx)
thres = cv2.threshold(gradX,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] #这个是希望电脑自己去找到自适应的阈值,0并不大是阈值
cv_show('thres',thres)
通过闭操作后的阈值操作,找出合适的阈值,并对图像进行处理
thres = cv2.threshold(gradX,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
THRESH_BINARY | THRESH_OTSH
是进行自适应的阈值操作
通过对比发现,进行过阈值操作后的图像,效果较为明显
#再来一个闭操作
thres = cv2.morphologyEx(thres,cv2.MORPH_CLOSE,sqKernel)
cv_show('thres2',thres)
接着闭操作再来一次,为了让相近的数字尽可能的靠在一起
从图中发现我们要找的区域基本上连在了一起
接下来我们开始计算轮廓
threshCnts, hierarchy = cv2.findContours(thresh.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
得到后在原图中进行展示
cnts = threscnts
cur_img = image
cv2.drawContours(cur_img,cnts,-1,(0,0,255),3)
cv_show('img', cur_img)
可以发现吧原图中的字符基本都标注出来了,接下来我们要识别哪些是需要的,哪些是不需要的
locs = []
# 遍历轮廓
for (i,c) in enumerate(cnts):
x,y,h,w = 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], reverse = False)
output = []
通过比例选择出合适的留下来
现在已经选出合适的轮廓了。需要把各个轮廓提出来,最后进行分别的识别操作
通过遍历轮廓中的每一个数字,寻找合适的参数
#遍历轮廓中的每个数字
for(i,(gx,gy,gw,gh)) in enumerate(locs):
groupOut = []
# 根据坐标提取每一个组
group = gray[gy - 5:gy + gh + 5, gx - 5:gx + gw + 5]
cv_show('group',group)
通过遍历一遍发现选出的轮廓不符合要求,随机对对参数进行调整
threscnts,hierarchy = cv2.findContours(thres.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = threscnts
cur_img = image
cv2.drawContours(cur_img,cnts,-1,(0,0,255),3)
#cv_show('img', cur_img)
locs = []
# 遍历轮廓
for (i, c) in enumerate(cnts):
#for (i, c) in enumerate(cnts):
(x,y,w,h) = cv2.boundingRect(c)
#(x,y,h,w) = cv2.boundingRect(c)
#计算这个框出来的矩形的比例,通过比例进行选择
ar = w / float(h)
# 选择合适的区域,根据实际任务来,这里的基本都是四个数字一组
if ar > 2.5 and ar < 3.5:
if (w > 50 and w < 60) and (h > 10 and h < 30):
# 符合的留下来
locs.append((x, y, w, h))
locs = sorted(locs,key=lambda x:x[0], reverse = False)
output = []
调整过程中发现在闭操作后 轮廓识别不是很好,自己又加上了一个膨胀操作,得到了较好的效果
又对参数进行调整得到较好效果 能比较好的识别
对截取的图像做阈值处理
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 = 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))
cv_show('roi',roi)
现在需要对每个轮廓中识别出每个数字,首先先框出每个轮廓,然后对框出来的轮廓按从左到右进行排序
然后对排序出来的x,y坐标进行处理,得出roi区域
cv2.matchTemplate(img1,img2,方法) 模板匹配
平方差匹配CV_TM_SQDIFF:用两者的平方差来匹配,最好的匹配值为0
归一化平方差匹配CV_TM_SQDIFF_NORMED
相关匹配CV_TM_CCORR:用两者的乘积匹配,数值越大表明匹配程度越好
归一化相关匹配CV_TM_CCORR_NORMED
相关系数匹配CV_TM_CCOEFF:用两者的相关系数匹配,1表示完美的匹配,-1表示最差的匹配
归一化相关系数匹配CV_TM_CCOEFF_NORMED
item()
Python 字典(Dictionary) items() 函数以列表返回可遍历的(键, 值) 元组数组。
dict.items()
匹配函数返回的是一副灰度图,最白的地方表示最大的匹配。使用cv2.minMaxLoc()
函数可以得到最大匹配值的坐标,以这个点为左上角角点,模板的宽和高画矩形就是匹配的位置了:
# 相关系数匹配方法:cv2.TM_CCOEFF
res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
left_top = max_loc # 左上角
right_bottom = (left_top[0] + w, left_top[1] + h) # 右下角
cv2.rectangle(img, left_top, right_bottom, 255, 2) # 画出矩形位置
所以为了找到最大的点位
#计算匹配的得分
scores = []
for (digits,digitROI) in digits.items():
result = cv2.matchTemplate(roi,digitROI,cv2.TM_CCOEFF)
(_, score, _, _) = cv2.minMaxLoc(result)
scores.append(score)
score 为最大得分
# 得到最合适的数字
groupOutput.append(str(np.argmax(scores)))
返回得分最大值的下标,就是最佳匹配数字
找到最佳数字后要画出来
# 画出来
cv2.rectangle(image, (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)
cv2.putText(img, str(i), (123,456)), font, 2, (0,255,0), 3)
各参数依次是:
图片,
添加的文字,
左上角坐标,
字体,
字体大小,
颜色,
字体粗细