目录
此章节主要讲解图像金字塔、轮廓检测方法、轮廓特征与近似、模板匹配方法。
一.图像金字塔
图像金字塔,首先什么是金字塔,面积最大在底层,面积第二大的在第二层依次往上堆积。变成如下这样:
明白了金字塔的形状,接下来我们来看文字来说明什么是图像金字塔。顾名思义图像金字塔就是由将一个图像从最大的像素面积到依次累积减低从底层最大图像到顶层最小的图像堆积成的一个金字塔。
这样做的它用处是:单一尺度的特征检测器无法覆盖所有情况。图像金字塔通过生成不同分辨率的图像序列,逐层检测不同尺度的特征。
以后我们在做图像特征提取的时候,不仅仅要对图像的原始输入进行提取,也许我们可能也需要一些在图像金字塔特征提取图像的数据,降低图像的面积的数据提取,每一层提取的数据可能不一样。然后我们再把特征提取结果总结在一起。
图像金字塔主要有:高斯金字塔和拉普拉斯金字塔。
高斯金字塔:通过高斯模糊+下采样生成降尺度图像,抑制噪声但保留主要结构。
拉普拉斯金字塔:通过差值保留高频细节,用于图像重建。
1.高斯金字塔
高斯金字塔(向下采样方法)-缩小
向下采样如图:
第四章 OpenCV篇—图像梯度与边缘检测—Python
高斯金字塔处理方式:
第一步:
例如:
将卷积核的各个数相加/个数总和得到值×卷积核 = 归一化矩阵
第二步:
将所有偶数行和列去除。然后就将原先的矩阵压缩,达到图像金字塔逐层递减,将图像压缩。
高斯金字塔(向上采样方法)-放大
例如:
变化的过程步骤如下:
第一步:将图像在每个方向扩大为原来的两倍,新增的行和列以0填充。从图可以看出,我第一个数是10,我需要在它的右边和下面还有右下角都填充1个0。同理反复这个过程,30、56、96都是这样在各个方向填充0。这样将扩大形成放大的作用。
第二步:使用先前同样的内核(乘以4)与放大后的图像卷积,获得近似值。
利用代码实现的上述功能如下:
首先原本图像的shape值:
原图:
向上采样图像呈现如下:
import cv2
import numpy as np
img = cv2.imread('6.jpg')
cv2.imshow('img',img)
print("图像原本的shape值:")
print(img.shape)
# 向上采样开始
up = cv2.pyrUp(img)
cv2.imshow('up',up)
print("图像经过向上采样后的shape值:")
print(up.shape)
cv2.waitKey(0)
cv2.destroyAllWindows()
注:这个shape值的结果和原始数据是不是在经过向上采样的时候数据扩大了二倍了。
向下采样的演示如下:
向下采样的图像呈现如下:
import cv2
import numpy as np
img = cv2.imread('6.jpg')
cv2.imshow('img',img)
print("图像原本的shape值:")
print(img.shape)
# 向下采样开始
down = cv2.pyrDown(img)
cv2.imshow('down',down)
print("图像经过向下采样后的shape值:")
print(down.shape)
cv2.waitKey(0)
cv2.destroyAllWindows()
注:这个结果和原始数据是不是在经过向上采样的时候数据缩小了二倍了。
这是一次进行向上采样和向下采样,接下来是多次向上(下)进行采样。
多次向上采样代码如下:
import cv2
import numpy as np
img = cv2.imread('6.jpg')
cv2.imshow('img',img)
print("图像原本的shape值:")
print(img.shape)
# 向上采样开始
up1 = cv2.pyrUp(img)
cv2.imshow('up1',up1)
print("第一次图像经过向上采样后的shape值:")
print(up1.shape)
up2 = cv2.pyrUp(up1)
cv2.imshow('up2',up2)
print("第二次图像经过向上采样后的shape值:")
print(up2.shape)
cv2.waitKey(0)
cv2.destroyAllWindows()
图像如下:
如果我们做一次向上和做一次向下得到的图像是否会一致?答案是否定的,因为我们第一次向上采样的时候是以0填充,而再次做一次向下则丢失原本图像的一些数据,使得图像变得比原本要模糊一点。
代码如下:
import cv2
import numpy as np
img = cv2.imread('6.jpg')
up = cv2.pyrUp(img)
up_down = cv2.pyrDown(up)
cv2.imshow('up_down',up_down)
res = np.hstack((img,up_down))
cv2.imshow('res',res)
cv2.waitKey(0)
cv2.destroyAllWindows()
图像呈现如下:
2.拉普拉斯金字塔
拉普拉斯金字塔做法和公式如下:
代码如下:
import cv2
import numpy as np
img = cv2.imread('6.jpg')
down = cv2.pyrDown(img)
down_up = cv2.pyrUp(down)
laplacian = img - down_up
cv2.imshow('laplacian',laplacian)
cv2.waitKey(0)
cv2.destroyAllWindows()
图像呈现如下:
二.轮廓检测方法
cv2.findContours(img,mode,method)
img:当前输入的图像
mode:轮廓检索模式
- RETR_EXTERNAL:只检索最外面的轮廓;
- RETR_LIST:检索所有的轮廓,并将其保存到一条链表当中;
- RETR_CCOMP:检索所有的轮廓,并将他们组织为两层:顶层是各部分的外部边界,第二层是空洞的边界;
- RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次;
注:轮廓检索模式最常用的是RETR_TREE
method:轮廓逼近方法(最常用)
- CHAIN_APPROX_NONE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)
- CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,也就是,函数只保留他们的终点部分。
为了更高的准确率,通常使用二值图像。
代码如下:
import cv2
import numpy as np
# 第一步:读取图片
img = cv2.imread('11.png')
# 第二步:转换灰度化
gary = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 第三步:二值化
# threshold的第一个参数是灰度图,第二个参数是阈值,第三个参数是二值化后的最大值,第四个参数是二值化方法
ret, thresh = cv2.threshold(gary, 127,255,cv2.THRESH_BINARY)
# 二值方法原理是:如果像素值大于阈值,则置为最大值,否则置为0
# 第四步:获取轮廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
# contours是轮廓,hierarchy是层级关系
# 第五步:绘制轮廓
# 传入绘制图像,轮廓,轮廓索引,颜色模式,线条宽度
# 注意需要先拷贝一份,避免修改原图
img_copy = img.copy()
# drawContours函数的参数是:
# 第一个参数绘制图像
# 第二个参数是轮廓,各个数值代表轮廓的索引,-1表示绘制所有轮廓
# 第三个参数是轮廓索引
# 第四个参数是轮廓颜色模式(RGB)
# 第五个参数是线条宽度
# drawContours函数返回值是绘制后的图像
res = cv2.drawContours(img_copy, contours, -1, (0,0,255), 2)
cv2.imshow('res', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
图像轮廓如下:
注:
二值化语法格式如下:
cv2.threshold(灰度图,阈值,二值化后的最大值,二值化方法)
二值方法原理是:如果像素值大于阈值,则置为最大值,否则置为0
contours是轮廓,hierarchy是层级关系
绘制图像需要先拷贝一份,避免修改原图。
获取轮廓的语法格式如下:
cv2.drawContours(需要绘制的图像,获取的轮廓,轮廓的索引,轮廓的颜色模式,线条宽度),返回值是绘制后的图像
轮廓:各个数值代表轮廓的索引,-1表示绘制所有轮廓
三.轮廓特征与近似
轮廓面积和周长计算如下:
面积:cv2.contourArea(指定轮廓)
周长:cv2.arcLength(指定轮廓,True或False)
代码如下:
import cv2
import numpy as np
img = cv2.imread('12.png')
gary = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gary, 127,255,cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
# 获取特定轮廓
cnt = contours[0]
# 计算轮廓面积,需要传入特定的轮廓
print(cv2.contourArea(cnt))
# 计算轮廓周长,第一个参数是需要传入特定的轮廓,最后一个参数表示是否闭合
print(cv2.arcLength(cnt,True))
轮廓特征代码演示如下:
原图像
import cv2
import numpy as np
img = cv2.imread('13.png')
gary = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gary, 127,255,cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cnt = contours[0]
draw_img = img.copy()
res = cv2.drawContours(draw_img, [cnt], 0, (0,0,255), 2)
cv2.imshow('draw_img',draw_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
代码运行后图像如下:
轮廓近似代码如下:
import cv2
import numpy as np
img = cv2.imread('13.png')
gary = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gary, 127,255,cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cnt = contours[0]
# arcLength第一个参数是轮廓,第二个参数是闭合
epsilon = 0.1*cv2.arcLength(cnt,True)
# approxPolyDP第一个参数是轮廓,第二个参数是精度,第三个参数是闭合
# 获取轮廓的近似值
approx = cv2.approxPolyDP(cnt,epsilon,True)
draw_img = img.copy()
res = cv2.drawContours(draw_img, [approx], 0, (0,0,255), 2)
cv2.imshow('draw_img',draw_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
注:
获取轮廓近似值的语法格式如下:cv2.approxPolyDP(轮廓, 精度, 闭合)
轮廓以外界矩形代码如下:
import cv2
import numpy as np
img = cv2.imread('14.png')
gary = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gary, 127,255,cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cnt = contours[3]
# boundingRect函数的作用是获取轮廓的外界边界矩形
# 返回值是(x,y,w,h)
# 其中x,y是矩形的左上角坐标,w,h是矩形的宽高
# boundingRect的参数是轮廓
x,y,w,h = cv2.boundingRect(cnt)
draw_img = img.copy()
# rectangle函数作用是绘制矩形
# 第一个参数是绘制的图像,第二个参数是左上角坐标,第三个参数是右下角坐标,第四个参数是颜色,第五个参数是线宽
res = cv2.rectangle(draw_img,(x,y),(x+w,y+h),(0,255,0),2)
cv2.imshow('res',res)
area = cv2.contourArea(cnt)
rect_area = w*h
extent = float(area)/rect_area
print(f"轮廓面积与边界矩形之比:{extent}")
cv2.waitKey(0)
cv2.destroyAllWindows()
注:
获取轮廓的外界边界矩形的语法格式如下:boundingRect(轮廓)
boundingRect函数的作用是获取轮廓的外界边界矩形,它的返回值是(x,y,w,h)其中x,y是矩形的左上角坐标,w,h是矩形的宽高
rectangle函数作用是绘制矩形,它的语法格式如下:
cv2. rectangle(图像, 左上角坐标, 右下角坐标, 颜色, 轮廓线宽)
外接圆轮廓代码演示如下:
import cv2
import numpy as np
img = cv2.imread('14.png')
gary = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gary, 127,255,cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cnt = contours[0]
# minEnclosingCircle函数作用是获取轮廓的最小外接圆,返回值是圆心坐标和半径,参数是轮廓
(x,y),radius = cv2.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
draw_img = img.copy()
# circle函数作用是绘制圆,参数是绘制的图像,圆心坐标,半径,颜色,线宽
res = cv2.circle(draw_img,center,radius,(0,255,0),2)
cv2.imshow('res',res)
cv2.waitKey(0)
cv2.destroyAllWindows()
图像如下:
注:
cv2.minEnclosingCircle函数作用是获取轮廓的最小外接圆,返回值是圆心坐标和半径。它的语法格式如下:cv2.minEnclosingCircle(轮廓)
cv2. circle函数作用是绘制圆,它的语法格式如下:
cv2. circle(绘制的图像,圆心坐标,半径,颜色,轮廓线宽)
四.模板匹配
模板匹配和卷积原理很像,模板在原图像上从原点开始滑动,计算模板与(图像被模板獲盖的地方)的差别程度,这个差别程度的计算方法在opencv里有6种,然后将每次计算的结果放入一个矩阵里,作为结果输出。假如原图形是AxB大小,而模板是axb大小,则输出结果的矩阵是(A-a+1)x(B-b+1)
模板匹配计算的六种方法如下:
- TM_SQDIFF:计算平方不同,计算出来的值越小,越相关(相近)
- TM_CCORR:计算相关性,计算出来的值越大,越相关(相近)
- TM_CCOEFF:计算相关系数,计算出来的值越大,越相关(相近)
- TM_SQDIFF_NORMED:计算归一化平方不同,计算出来的值越接近0,越相关(相近)
- TM_CCORR_NORMED:计算归一化相关性,计算出来的值越接近1,越相关(相近)
- TM_CCOEFF_NORMED:计算归一化相关系数,计算出来的值越接近1,越相关(相近)
六种模板匹配公式原理网站如下:https://docs.opencv.org/3.3.1/df/dfb/group__imgproc__object.html#ga3a7850640f1fe1f58fe91a2d7583695d
原图像的大小和模板的大小如图:
模板匹配出来图像的大小如下:
注:
matchTemplate的作用是进行模板匹配,它的格式如下:
cv2. matchTemplate(模板匹配的图片,模板图片,匹配方法)
匹配方法:
0:cv2.TM_CCOEFF:相关系数
1:cv2.TM_CCOEFF_NORMED:相关系数归一化
2:cv2.TM_CCORR:卷积
3:cv2.TM_CCORR_NORMED:卷积归一化
4:cv2.TM_SQDIFF:平方差
5:cv2.TM_SQDIFF_NORMED:平方差归一化
参数3可以写0,1,2,3,4,5,分别代表上述方法
寻找匹配位置代码如下:
注:
minMaxLoc的作用是寻找匹配位置,它的格式如下:
cv2.minMaxLoc(模板匹配的结果矩阵)它的返回值是:最小值,最大值,最小值位置,最大值位置
模板六种匹配方法代码如下:
import cv2
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('TkAgg')
methods = ['cv2.TM_CCOEFF','cv2.TM_CCOEFF_NORMED','cv2.TM_CCORR','cv2.TM_CCORR_NORMED','cv2.TM_SQDIFF','cv2.TM_SQDIFF_NORMED']
# 第一步:读取图片
img = cv2.imread('15.png',0)
template = cv2.imread('16.png',0)
h,w = template.shape[:2]
for meth in methods:
img2 = img.copy()
method = eval(meth)
print(method)
# 第二步:模板匹配
res = cv2.matchTemplate(img2,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()
图像呈现如下:
模板匹配多个方法代码如下:
import cv2
import numpy as np
img = cv2.imread('17.png')
img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
template = cv2.imread('18.png',0)
h,w = template.shape[:2]
res = cv2.matchTemplate(img_gray,template,cv2.TM_CCOEFF_NORMED)
threshold = 0.8
loc = np.where(res>=threshold)
for pt in zip(*loc[::-1]):# *号表示可选参数,**表示关键字参数
bottom_right = (pt[0]+w,pt[1]+h)
cv2.rectangle(img,pt,bottom_right,(0,0,255),2)
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()