系列内容:OpenCV概述与环境配置,OpenCV基础知识和绘制图形,图像的算数与位运算,图像视频的加载和显示,图像基本变换,滤波器,形态学,图像轮廓,图像直方图,车辆统计项目,特征检测和匹配,图像查找和拼接,虚拟计算器项目,信用卡识别项目,图像的分割与修复,人脸检测与车牌识别,目标追踪,答题卡识别判卷与文档ocr扫描识别,光流估计

(一)模板匹配

模板匹配和卷积原理很像,模板在原图像上从原点开始滑动,计算模板与(图像被模板覆盖的地方)的差别程度,这个差别程度的计算方法在opencv里有6种,然后将每次计算的结果放入一个矩阵里,作为结果输出。假如原图形是AxB大小,而模板是axb大小,则输出结果的矩阵是(A - a + 1)x(B - b + 1)

TM_SQDIFF:计算平方不同,计算出来的值越小,越相关

R(x, y) = \sum_{x', y'} \left( T(x', y') - I(x + x', y + y') \right)^2

TM_CCORR:计算相关性,计算出来的值越大,越相关

R(x, y) = \frac{\sum_{x', y'} \left( T(x', y') - I(x + x', y + y') \right)^2}{\sqrt{\sum_{x', y'} T(x', y')^2 \cdot \sum_{x', y'} I(x + x', y + y')^2}}

TM_CCOEFF:计算相关系数,计算出来的值越大,越相关

R(x, y) = \sum_{x', y'} \left( T(x', y') \cdot I(x + x', y + y') \right)

TM_SQDIFF_NORMED:计算归一化平方不同,计算出来的值越接近0,越相关

R(x, y) = \frac{\sum_{x', y'} \left( T(x', y') \cdot I(x + x', y + y') \right)}{\sqrt{\sum_{x', y'} T(x', y')^2 \cdot \sum_{x', y'} I(x + x', y + y')^2}}

TM_CCORR_NORMED:计算归一化相关性,计算出来的值越接近1,越相关

R(x, y) = \sum_{x', y'} \left( T'(x', y') \cdot I'(x + x', y + y') \right)

其中:


T'(x', y') = T(x', y') - \frac{1}{w \cdot h} \cdot \sum_{x'', y''} T(x'', y'')

I'(x + x', y + y') = I(x + x', y + y') - \frac{1}{w \cdot h} \cdot \sum_{x'', y''} I(x + x'', y + y'')

TM_CCOEFF_NORMED:计算归一化相关系数,计算出来的值越接近1,越相关

R(x, y) = \frac{\sum_{x', y'} \left( T'(x', y') \cdot I'(x + x', y + y') \right)}{\sqrt{\sum_{x', y'} T'(x', y')^2 \cdot \sum_{x', y'} I'(x + x', y + y')^2}}

建议使用归一化的计算方法会相对公平一些.

(1)matchTemplate(image, templ, method[, result[, mask]]) 进行模板匹配:

  • image是要匹配的图片
  • templ是模板图片
  • method是计算方式
  • result是进行匹配计算后得到的矩阵.
  • mask是掩膜

(2)minMaxLoc(src[, mask]) 获取最大值和最小值的位置:

返回四个值,分别是最小值,最大值,最小值坐标,最大值坐标

(a)模板匹配,示例代码:

import cv2
import numpy as np
img=cv2.imread('./lena1.png')
template=cv2.imread('./face.png')
print(img.shape)
print(template.shape)

res=cv2.matchTemplate(img,template,cv2.TM_SQDIFF)
print(res.shape)

min_val,max_val,min_loc,max_loc=cv2.minMaxLoc(res)
print(min_val)
print(max_val)
print(min_loc)
print(max_loc)

#画出匹配到的位置,注意,这里要交叉写,template的(206,161)对应(161,206)
img=cv2.rectangle(img,min_loc,(min_loc[0]+161,min_loc[1]+206),(0,0,255),3)

cv2.imshow('template',template)
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

运行结果:

(512, 512, 3)

(206, 161, 3)

(307, 352)

0.0

724556672.0

(199, 184)

(298, 96)

lena的人脸模板匹配结果,如图14.1所示:

14.1-lena的人脸模板匹配结果

(b)六种匹配方式的不同,示例代码:

import cv2
import numpy as np
import matplotlib.pyplot as plt
img=cv2.imread('./lena1.png',0)
template=cv2.imread('./face.png',0)

#定义六种方法的列表
methods=['cv2.TM_SQDIFF','cv2.TM_CCORR','cv2.TM_CCOEFF','cv2.TM_SQDIFF_NORMED','cv2.TM_CCORR_NORMED',
        'cv2.TM_CCOEFF_NORMED']

w,h=template.shape[1],template.shape[0]

for method in methods:
    img2=img.copy()

    #匹配
    res=cv2.matchTemplate(img,template,eval(method))
    min_val,max_val,min_loc,max_loc=cv2.minMaxLoc(res)

    if method in[cv2.TM_SQDIFF,cv2.TM_SQDIFF_NORMED]:
        top_left=min_loc
    else:
        top_left=max_loc

    bottom_right=(top_left[0]+w,top_left[1]+h)

    #画矩形
    cv2.rectangle(img2,top_left,bottom_right,(0,0,255),2)
    #画子图,都画在一起
    plt.subplot(121),plt.imshow(res,cmap='gray')
    plt.axis('off')
    plt.subplot(122),plt.imshow(img2,cmap='gray')
    plt.axis('off')
    plt.suptitle(method)
    plt.show()

六种匹配方式的不同,运行结果:

匹配结果1,如图14.2(1)所示:

14.2(1)-匹配结果1

匹配结果2,如图14.2(2)所示:

14.2(2)-匹配结果2

匹配结果3,如图14.2(3)所示:

14.2(3)-匹配结果3

匹配结果4,如图14.2(4)所示:

14.2(4)-匹配结果4

匹配结果5,如图14.2(5)所示:

14.2(5)-匹配结果5

匹配结果6,如图14.2(6)所示:

14.2(6)-匹配结果6

(二)匹配多个对象

import cv2
img = cv2.imread('./cd.jpg')  # 读取原始图像
templ = cv2.imread('./cd_temp.png')  # 读取模板图像
width, height, c = templ.shape  # 获取模板图像的宽度、高度和通道数
results = cv2.matchTemplate(img, templ, cv2.TM_CCOEFF_NORMED)  # 按照标准相关系数匹配
for y in range(len(results)):  # 遍历结果数组的行
    for x in range(len(results[y])):  # 遍历结果数组的列
        if results[y][x] > 0.99:  # 如果相关系数大于0.99则认为匹配成功
            # 在最佳匹配结果位置绘制红色方框
            cv2.rectangle(img, (x, y), (x + width, y + height), (0, 0, 255), 2)
cv2.imshow("img", img)  # 显示匹配的结果
cv2.waitKey()  
cv2.destroyAllWindows()

cd(多物体)的原图,如图14.3(1)所示:

14.3(1)-cd(多物体)的原图

cd的模版,如图14.3(2)所示:

14.3(2)-cd的模版

运行结果如图14.3所示:

14.3-cd盘匹配结果

(三)信用卡数字识别

(1)数字模版图片的处理

#先处理数字模版图片
import cv2
import numpy as np

#封装显示图片的函数
def cv_show(name,img):
    cv2.imshow(name, img) 
    cv2.waitKey()  
    cv2.destroyAllWindows()

#读取模版图片
img=cv2.imread('./card_temp.png')
# print(img.shape)
# cv_show('img',img)
#灰度化
ref=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# cv_show('ref',ref)

#二值化处理
_,ref=cv2.threshold(ref,10,255,cv2.THRESH_BINARY_INV)
# cv_show('ref',ref)

#计算轮廓
ref_,ref_contours,_=cv2.findContours(ref.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
#画出外轮廓
cv2.drawContours(img,ref_contours,-1,(0,0,255),3)
cv_show('img',img)
#表示数字的轮廓
print(np.array(ref_contours,dtype='object').shape)
#对轮廓进行排序,按照数字大小进行排序,方便后面使用
#排序思路:根据每个数字的最大外接矩形的x轴坐标进行排序
#计算每个轮廓的外接矩形
bounding_boxes=[cv2.boundingRect(c) for c in ref_contours]
print(bounding_boxes)
print(sorted(bounding_boxes,key=lambda b:b[0]))
#要把排序之后的外接矩形和轮廓建立对应的关系
print(sorted(bounding_boxes,key=lambda b:b[1]))
(ref_contours,bounding_boxes)=zip(*sorted(zip(ref_contours,bounding_boxes),key=lambda b:b[1][0]))
digits={}
for (i,c) in enumerate(ref_contours):
    #重新计算外接矩形
    (x,y,w,h)=cv2.boundingRect(c)
    #region of interest 感兴趣的区域
    #取出每个数字
    roi=ref[y:y+h,x:x+w]
    #resize成合适的大小
    roi=cv2.resize(roi,(57,88))
    digits[i]=roi
print(digits)

数字模版图片的处理结果,如图14.4所示:

14.4-数字模版图片的处理结果

(2)数字图像图片的匹配

#对信用卡进行处理
image=cv2.imread('./card02.jpg')
# image=cv2.imread('./card.png')
# cv_show('image',image)
#对信用卡进行resize
#为了保证原图不拉伸,需要计算原图的长宽比
h,w=image.shape[:2]
width=300
r=width/w
image=cv2.resize(image,(300,int(h*r)))
# cv_show('image',image)

#灰度化处理
gray=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
# cv_show('gray',gray)

#接下来是形态学的各种操作
#顶帽操作,突出更明亮的区域
#初始化卷积核
rect_kernel=cv2.getStructuringElement(cv2.MORPH_RECT,(9,3))
tophat=cv2.morphologyEx(gray,cv2.MORPH_TOPHAT,rect_kernel)
# cv_show('tophat',tophat)

#sobel算子
#只用x轴方向的梯度
grad_x=cv2.Sobel(tophat,ddepth=cv2.CV_32F,dx=1,dy=0,ksize=-1)
print(grad_x)
#对grad_x进行处理
grad_x=np.absolute(grad_x)
#再把grad_x变成0到255之间的整数
min_val,max_val=np.min(grad_x),np.max(grad_x)
grad_x=(grad_x-min_val)/(max_val-min_val)*255
#修改一下数据类型
grad_x=grad_x.astype('uint8')
# cv_show('grad_x',grad_x)

# grad_y=cv2.Sobel(tophat,ddepth=cv2.CV_32F,dx=0,dy=1,ksize=-1)
# print(grad_y)
# #对grad_y进行处理
# grad_y=np.absolute(grad_y)
# #再把grad_y变成0到255之间的整数
# min_val,max_val=np.min(grad_y),np.max(grad_y)
# grad_y=(grad_y-min_val)/(max_val-min_val)*255
# #修改一下数据类型
# grad_y=grad_y.astype('uint8')
# cv_show('grad_y',grad_y)

# cv_show('grad',grad_x+grad_y)


#闭操作,先膨胀,再腐蚀,可以把数字连在一起
cv2.morphologyEx(grad_x,cv2.MORPH_CLOSE,rect_kernel)
# cv_show('gradx',grad_x)

#通过大津(OTSU)算法找到合适的阈值,进行全局二值化操作
_,thresh=cv2.threshold(grad_x,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)
# cv_show('thresh',thresh)

#中间还有空洞,再来一个闭操作
sq_kernel=cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
thresh=cv2.morphologyEx(thresh,cv2.MORPH_CLOSE,sq_kernel)
# cv_show('thresh',thresh)

#找轮廓
thres_,thresh_contours,_=cv2.findContours(thresh.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
#在原图上画轮廓
image_copy=image.copy()
cv2.drawContours(image_copy,thresh_contours,-1,(0,0,255),3)
cv_show('img',image_copy)

#遍历轮廓,计算外接矩形,然后根据实际信用卡数字区域的长宽比,找到真正的数字区域
locs=[]
output=[]
for c in thresh_contours:
    #计算外接矩形
    (x,y,w,h)=cv2.boundingRect(c)
    #计算外接矩形的长宽比例
    ar=w/float(h)
    #选择合适的区域
    print(ar)
    if ar >2 and ar<3.5:
        #根据实际的长宽做进一步的筛选
        if(w>40 and w<55) and(h>10 and h<20):
            #符合条件的外接矩形留下来
            locs.append((x,y,w,h))

#对符合要求的轮廓进行从左到右的排序,
sorted(locs,key=lambda x:x[0])

#遍历每一个外接矩形,通过外接矩形把原图中的数字抠出来
for(i,(gx,gy,gw,gh))in enumerate(locs):
    #抠出数字,并加余量
    group=gray[gy-5:gy+gh+5,gx-5:gx+gw+5]
    # cv_show('group',group)
    #对取出的灰色group做全局二值化处理
    group=cv2.threshold(group,0,255,cv2.THRESH_BINARY|cv2.THRESH_OTSU)[1]
    # cv_show('group',group)

    #计算轮廓
    _,digit_contours,_=cv2.findContours(group.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    #对轮廓进行排序
    bounding_boxes=[cv2.boundingRect(c) for c in digit_contours]
    (digit_contours,_)=zip(*sorted(zip(digit_contours,bounding_boxes),key=lambda b:b[1][0]))
    
    #定义每一组匹配好的数字的存放列表
    group_output=[]
    #遍历排好序的轮廓
    for c in digit_contours:
        #找到当前数字的轮廓,resize成合适的大小,再进行模版匹配
        (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)

        #进行模版批评
        #定义保存匹配得分的列表
        scores=[]
        for (digit,digit_roi) in digits.items():
            result=cv2.matchTemplate(roi,digit_roi,cv2.TM_CCOEFF)
            #只要最大值,即分数
            (_,score,_,_)=cv2.minMaxLoc(result)
            scores.append(score)
        #找到分数最高的数字,即我们匹配到的数字
        group_output.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(group_output),(gx,gy-15),cv2.FONT_HERSHEY_SIMPLEX,0.6,(0,0,255),2)
    output.extend(group_output)
cv_show('image',image)

数字图像图片的匹配结果(1),如图14.5(1)所示:

14.5(1)-数字模版图片的处理结果(轮廓显示)

数字图像图片的匹配结果(2),如图14.5(2)所示:

14.5(2)-数字模版图片的处理结果(匹配标记)

注意:由于字体模板和选取银行卡字体模板不一致,导致匹配结果较差,如果选择轮廓清晰的字体模板且对应银行的字体的银行卡,效果应该不错。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐