【OpenCV-Python】——单/多模板匹配&分水岭算法图像分割&图像金字塔&交互式前景提取

目录

前言:

1、模板匹配

1.1 单目标匹配

1.2 多目标匹配

2、图像分割

2.1 分水岭算法分割图像

2.2 图像金字塔

3、交互式前景提取

总结:


前言:

模板匹配是指当前图像中查找的目标图像最相近的部分。图像分割是指将前景对象从图像中分割和提取出来。

1、模板匹配

让模板图像在输入图像中滑动,逐像素遍历整个图像进行比较,查找出与模板图像最匹配的部分。

1.1 单目标匹配

即输入图像中只存在一个可能匹配结果,用cv2.matchTemplate()函数:

result=cv2.matchTemplate(image,templ,method)

image输入图像必须是8位或32位浮点类型;templ是模板图像,不可大于image,且数据类型要与image一致;method匹配方法cv2.TM_SQDIFF(以方差结果为依据进行匹配)、SQDIFF_NORMED(标准归一化方差匹配)、CCORR(相关匹配)、CCORR_NORMED(标准归一化相关匹配)、CCOEFF(相关系数匹配)、CCOEFF_NORMED(标准归一化相关系数匹配)。

result返回结果是一个numpy.ndarray对象,若image大小是WxH,templ的大小是wxh,则result大小是(W-w+1)x(H-h+1),其中每个值都表示对应位置的匹配结果。使用前两个method时,匹配结果越小说明匹配度越高;后四个方法匹配结果越小说明匹配度越低。

cv2.minMaxLoc()函数用于处理匹配结果:

minVal,maxVal,minLoc,minLoc=cv2.minMaxLoc(src)

src即cv2.matchTemplate()函数返回的结果。minVal和maxVal是src中的最小值和最大值,不存在时可以为NULL;minLoc和maxLoc是src中最小值和最大值的位置,不存在时为NULL。

import cv2
import numpy as np
import matplotlib.pyplot as plt
img=cv2.imread('cat.png',0)
template=cv2.imread('catface.png',0)
h,w=template.shape[:2]  #获取模板图的高度和宽度
methods=['cv2.TM_CCOEFF','cv2.TM_CCOEFF_NORMED',
        'cv2.TM_CCORR','cv2.TM_CCORR_NORMED',
        'cv2.TM_SQDIFF','cv2.TM_SQDIFF_NORMED']#六种匹配method,定义一个数组
for meth in methods:#六种method对比
    img2 = img.copy()
    method = eval(meth)       # 匹配方法的真值
    res = cv2.matchTemplate(img, template, 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, 255, 2)     # 画矩形
    plt.subplot(121), plt.imshow(res, cmap='gray')
    plt.xticks([]), plt.yticks([])  # 隐藏坐标轴
    plt.subplot(122), plt.imshow(img2, cmap='gray')
    plt.xticks([]), plt.yticks([])  # 隐藏坐标轴
    plt.suptitle(meth)
    plt.show()

 上面只列出了其中三种方法的结果,左侧为(直接)匹配结果,右侧是获取结果位置后再利用矩形绘制函数绘制的一幅可视图。

1.2 多目标匹配

输入图像里有多个可能的匹配结果,还是cv2.matchTemplate()函数执行匹配操作后,根据匹配方法设置阈值,匹配结果中低于或高于阈值的就是符合条件的匹配对象。

import cv2
import numpy as np
import matplotlib.pyplot as plt
img_rgb=cv2.imread('mali.png')
img_gray=cv2.cvtColor(img_rgb,cv2.COLOR_BGR2GRAY)
template=cv2.imread('mali_coin.png',0)
h,w=template.shape[:2]
res=cv2.matchTemplate(img_gray,template,cv2.TM_CCOEFF_NORMED)
threshold=0.65#设定合适阈值
#取匹配程度大于80%的坐标
loc = np.where(res >=threshold)
for pt in zip(*loc[::-1]):#*表示可选参数
    bottom_right=(pt[0]+w,pt[1]+h)
    cv2.rectangle(img_rgb,pt,bottom_right,(0,0,255),2)
cv2.imshow('img_rgb',img_rgb)
cv2.waitKey(0)
cv2.destroyAllWindows()


2、图像分割

下面使用分水岭算法和图像金字塔对图像进行分割。

2.1 分水岭算法分割图像

原理:将任意灰度图像视为地形图表面,灰度高的表示山峰或丘陵,灰度低的表示山谷。用不同颜色的水(标签)填充每个独立的山谷(局部最小值);随着水平面的上升,来自不同山谷(具有不同颜色)的水将开始合并。为了避免这种情况,需要在水的汇合位置建造水坝;持续填充水和建造水坝,直到所有山峰和丘陵都在水下。整个过程中建造的水坝将作为图像分割的依据。

步骤:

将原图像转换为灰度图像;

应用形态变换中的开运算和膨胀操作,去除图像的噪声,获得图像边缘信息,确定图像背景;

进行距离转换,再进行阈值处理,确定图像前景;

确定图像的未知区域(用图像的背景减去前景的剩余部分);

标记背景图像;

执行分水算法分割图像。

①cv2.distanceTransform()函数用于计算非0值像素点到0值(背景)像素点的距离,其基本格式:

dst=cv2.distanceTransform(src,distanceType,maskSize,dstType)

dst是返回的距离转换图像。src必须是8位单通道二值图像;distanceType是距离类型;maskSize是掩模大小,可设置1,3或5。最后是可选参数dstType是返回图像类型,默认为CV_32F(32位浮点数)。

import cv2
import numpy as np
img=cv2.imread('qizi.png')
cv2.imshow('img',img)
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)  #转换为灰度图
ret,thresh=cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)  #Otsu算法阈值处理
kernel=np.ones((3,3),np.uint8)    #定义形态变换卷积核
imgopen=cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel,iterations=2)  #形态变换:开运算
imgdist=cv2.distanceTransform(imgopen,cv2.DIST_L2,5)   #距离转换
cv2.imshow('distance',imgdist)    #显示距离转换结果
cv2.waitKey(0)
cv2.destroyAllWindows()

②cv2.connectedComponents()函数用于将图像中的背景标记为0,将其他图像编辑为从1开始的整数,基本格式:

ret,labels=cv2.connectedComponents(image,connectivity,ltype)

labels是返回的标记结果图像,和image大小相同;image是要标记的8位单通道图像;后两个是可选参数,connectivity为4或8(默认为8),表示连接性;ltype是返回标记结果图像的类型。

ret,imgfg=cv2.threshold(imgdist,0.7*imgdist.max(),255,2)  #对距离转换结果进行阈值处理
imgfg=np.uint8(imgfg)   #转换为整数
ret,markers=cv2.connectedComponents(imgfg)  #标记阈值处理结果

③cv2.watershed()函数用于执行分水岭算法分割图像,基本格式:

ret=cv2.watershed(image,markers)

ret是返回的8位或32位单通道图像;image是输入的8位3通道图像;markers是输入的32位单通道图像。

import cv2
import numpy as np
import matplotlib.pyplot as plt
img=cv2.imread('qizi.png')
cv2.imshow('img',img)
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)  #转换为灰度图
ret,thresh=cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)  #Otsu算法阈值处理
kernel=np.ones((3,3),np.uint8)    #定义形态变换卷积核
imgopen=cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel,iterations=2)  #形态变换:开运算
imgbg=cv2.dilate(imgopen,kernel,iterations=3)        #膨胀操作,确定背景
imgdist=cv2.distanceTransform(imgopen,cv2.DIST_L2,0)   #距离转换
ret,imgfg=cv2.threshold(imgdist,0.7*imgdist.max(),255,2)  #对距离转换结果进行阈值处理
imgfg=np.uint8(imgfg)   #转换为整数,获得前景图像
ret,markers=cv2.connectedComponents(imgfg)  #标记阈值处理结果
unknown=cv2.subtract(imgbg,imgfg)   #确定位置位置区域
markers=markers+1  #加1使背景不为0
markers[unknown==255]=0    #将未知区域设置为0
imgwater=cv2.watershed(img,markers)   #执行分水岭算法分割图像
plt.imshow(imgwater)   #以灰度图像格式显示匹配结果
plt.title('watershed')
plt.axis('off')
plt.show()
img[imgwater==-1]=[0,255,0]   #将原图中被标记点设置为绿色
cv2.imshow('wartershed',img)    #显示距离转换结果
cv2.waitKey(0)
cv2.destroyAllWindows()

下图依次是原图、分水岭算法返回的结果图像、在原图中标记的分割结果(绿色):

2.2 图像金字塔

图像金字塔从分辨率的角度分析处理图像。金字塔底部使原始图像,对原始图像进行梯次向下采样得到各层图像。层次越高,分辨率越低,图像越小。通常每上一层的图像的宽度和高度就为下一层的一半。

常见的有高斯金字塔和拉普拉斯金字塔。高斯金字塔有向下和向上两种采样方式,向下采样时,原图像是0层,第1次向下采样的结果是第1层,以此类推。每次采样图像的高度和宽度都减小为原来的一半,所有的图层构成高斯金字塔。向上采样的过程与此相反。

①高斯金字塔向下采样

cv2.pyrDown()函数用于执行高斯金字塔构造的向下采样步骤,基本格式:

ret=cv2.pyrDown(image,dstsize,borderType)

ret是返回的图像,类型与输入图像相同。后两个都是可选参数,前者是结果图像的大小1;后者是边界值类型。

import cv2
import numpy as np
import matplotlib.pyplot as plt
img=cv2.imread('qizi.png')
down1=cv2.pyrDown(img)  #第1次采样
down2=cv2.pyrDown(down1) #第2次采样
cv2.imshow('img',img)
cv2.imshow('down1',down1)
cv2.imshow('down2',down2)
print('0层形状',img.shape)
print('1层形状',down1.shape)
print('2层形状',down2.shape)
cv2.waitKey(0)
cv2.destroyAllWindows()

 ②高斯金字塔向上采样

cv2.pyrUp()函数用于执行高斯金字塔构造的向下采样步骤,基本格式形如向下采样。

代码也是把Down换为Up即可。

 ③拉普拉斯金字塔

拉普拉斯金字塔的n层是该层高斯金字塔图像减去第n+1层向上采样结果获得的图像。

下面代码运行报错各位懂的小伙伴看看哪里有问题:

import cv2
import numpy as np
import matplotlib.pyplot as plt
img0=cv2.imread('qizi.png')
down1=cv2.pyrDown(img0)  #第1次采样
down2=cv2.pyrDown(down1) #第2次采样
down3=cv2.pyrDown(down2) #第3次采样
imglap0=cv2.subtract(img0,cv2.pyrUp(down1))  #拉普拉斯金字塔第0层
imglap1=cv2.subtract(down1,cv2.pyrUp(down2))  #拉普拉斯金字塔第1层
imglap2=cv2.subtract(down2,cv2.pyrUp(down3))  #拉普拉斯金字塔第2层
cv2.imshow('imglap0',imglap0)
cv2.imshow('imglap1',imglap1)
cv2.imshow('imglap2',imglap2)
cv2.waitKey(0)
cv2.destroyAllWindows()

 ④应用图像金字塔实现图像的分割和融合

直接拼接过渡不够自然,利用金字塔拼接,拼接处过渡更加自然。

此处略。


3、交互式前景提取

基本原理(过程):

①用一个矩形指定要提取的前景的大致范围,然后执行前景提取算法,得到初步结果。初步结果存在前景提取不完整或者背景被处理为前景的问题。

②此时需要人工干预(交互),复制原图像作为掩膜图像,并用白色标注要提取的前景区域,用黑色标注背景区域,标注不需要很精确。

③使用掩膜图像执行前景提取算法,获得理想结果。

利用cv2.grabCut()函数实现:

mask2,bgdModel,fgdModel=cv2.grabCut(img,mask1,rect,bgdModel,fgdModel,iterCount,model)

img是输入的8位3通道图像;mask1是输入的8位单通道掩膜图像,用于指定图像哪些区域可能是背景或前景;rect是矩形坐标,格式是(左上角坐标,右下角坐标,宽度,高度),mode设置矩形模板时,rect才有效;bgdModel和fgdModel用于内部计算的临时数组,需定义为大小是1x65的np.float64类型的数组,数组元素均为0;iterCount是迭代次数;mode是可选参数提取模式,cv2.GC_INIT_WITH_TECT矩形模板、cv2.GC_INIT_WITH_MASK自定义模板、cv2.GC_EVAL修复模式、cv2.GC_EVAL_FREEZE_MODEL固定模式等。

import cv2
import numpy as np
img=cv2.imread('hehua.png')
cv2.imshow('img',img)
mask=np.zeros(img.shape[:2],np.uint8)  #定义与原图大小相同的掩模图像
bg=np.zeros((1,65), np.float64)
fg=np.zeros((1,65), np.float64)
rect=(50,50,400,300)   #根据原图设置包含前景的矩形大小
cv2.grabCut(img,mask,rect,bg,fg,5,cv2.GC_INIT_WITH_RECT)  #提取前景
#将返回的掩模图像中像素值为0或2的像素设置为0(确认为背景)
#将所有像素值是1或3的像素设置为1(确认为前景)
mask2=np.where((mask==2)|(mask==0),0,1).astype('uint8')
img=img*mask2[:,:,np.newaxis]   #将掩模图像与原图像相乘,获得分割出来的前景图像
cv2.imshow('grabcut',img)   #显示获取的前景
cv2.waitKey(0)
cv2.destroyAllWindows()

 为了使效果更好,在原图像上先使用绘图工具在其中用黑白两种颜色标注背景和前景,再稍微修改代码,执行两次前景提取:

import cv2
import numpy as np
img=cv2.imread('hehua.png')
mask=np.zeros(img.shape[:2],np.uint8)  #定义与原始图(未黑白标注)大小相同的原始掩模图像
bg=np.zeros((1,65), np.float64)
fg=np.zeros((1,65), np.float64)
rect=(50,50,400,300)   #根据原图设置包含前景的矩形大小
cv2.grabCut(img,mask,rect,bg,fg,5,cv2.GC_INIT_WITH_RECT)  #第1次提取前景,矩形模式
imgmask=cv2.imread('hehua2.png')   #读取已经黑白标注的掩模图像
cv2.imshow('imgmask',imgmask)
mask2=cv2.cvtColor(imgmask,cv2.COLOR_BGR2GRAY,dstCn=1)  #转换为单通道灰度图
mask[mask2==0]=0  #将掩模图像mask2中黑色像素对应的原始掩模mask像素设置为0
mask[mask2==255]=1  #将掩模图像mask2中白色像素对应的原始掩模mask像素设置为1
cv2.grabCut(img,mask,None,bg,fg,5,cv2.GC_INIT_WITH_MASK)  #第1次提取前景,自定义模式
#将返回的掩模图像中像素值为0或2的像素设置为0(确认为背景)
#将所有像素值是1或3的像素设置为1(确认为前景)
mask2=np.where((mask==2)|(mask==0),0,1).astype('uint8')
img=img*mask2[:,:,np.newaxis]   #将掩模图像与原图像相乘,获得分割出来的前景图像
cv2.imshow('grabcut',img)   #显示获取的前景
cv2.waitKey(0)
cv2.destroyAllWindows()


总结:

由于是初学者可能很多地方没有总结完全或者有误,后续深入学习后会不断回来该删,也欢迎各位朋友指正!下次学习特征检测

猜你喜欢

转载自blog.csdn.net/weixin_51658186/article/details/130413422