图像金字塔
图像金字塔是有一副图像的多个不同分辨率的子图构成的图像集合。这组图像是由单个图像通过不断的降采样所产生的,最小的图像可能仅仅有一个像素点。金字塔从低向上分辨率逐渐降低
通常,图像金字塔底部是待处理的高分辨率图像(原始图像),顶部为低分辨率的近似图像,通常来说每向上移动一级,图像的高和宽都降低为原来的一半,大小变为原来的 1/4
理论基础
图像金字塔是通过原图像不断向下采样而产生的,即由高分辨率的图像产生低分辨率的近似图像。
简单的图像金字塔可以通过不断的删除图像的偶数行和偶数列获得。也可以对原图滤波,得到原始图像的近似图像然后删除近似图像的偶数行偶数列。滤波器可以选择:
- 邻域滤波器,使用邻域平均技术,可以产生平均金字塔
- 高斯滤波器:得到高斯金字塔,就是 cv2.pyrDown() 采用的方式
向上采用中,大小要变为原来的四倍,就需要插值处理,最邻近插值方法等。常见的是补零的方法,通常再每列像素点的右边插入为0的列,每行下方插入。
如果再使用高斯滤波器对补零的图像进行滤波处理,来向下采样,但是图像是扩大了四倍的,四分之三的像素点的值都是0,如果直接高斯滤波得到值范围就是[0, 255*1/4],所以需要将高斯滤波器的系数乘以4,或图像值乘4,,,保证像素值的范围与原始图像一致。。怎么理解呢,,
另外向上,向下采样两种操作不可逆,他们得到的图像也不能恢复原始状态了,
pyrDown 函数及应用
dst = cv2.pyrDown( src[, dstsize[, borderType]] )
这个实现了向下采样
- dstsize 目标图像的大小
默认,输出的图像大小为Size((src.cols+1)/2, (src.rows+1)/2)
经过向下采样后,图像的分辨率会降低,也就是模糊了。。
pyrUp函数及应用
dst = cv2.pyrUp( src[, dstsize[, borderType]] )
实现图像金字塔的向上采样
默认情况下,目标图像的大小为 Size(src.cols2, src.rows2),虽然要将高斯滤波器的系数乘以4,但是Opencv 库的目的就是要我们忽略这些细节,,所以不用太纠结为什么。。
o=cv2.imread('5.jpg')
r1=cv2.pyrUp(o)
r2=cv2.pyrDown(r1)
diff=r2-o # 构造 diff 图像,查看 down 与 o 的区别
cv2.imshow("original",o)
cv2.imshow("r1",diff)
cv2.imshow('r2',r2)
cv2.waitKey()
cv2.destroyAllWindows()
当然放大后也会变模糊,并且放大再缩小,也回不到原图的分辨率了。右图显示的是放大缩小后与原图像素值的插值。
拉普拉斯金字塔
这个就是通过金字塔的小图像进行向上采样获取完整的大尺寸高分辨率图像
定义
一个图像经过向下采样,必然会失去一些信息,再向上采样就不会恢复,为了再向上采样时可以恢复,就要获取再采样过程中丢失的信息,这些丢失的信息就构成了拉普拉斯金字塔Li = Gi - pyrUp(Gi + 1)
Li Gi 分别表示拉普拉斯金字塔和高斯金字塔。
拉普拉斯金字塔中的第 i 层,等于“高斯金字塔中的第 i 层”与“高斯金字塔中的第 i+1 层的向上采样结果”之差。
问题是图像的尺寸问题,如果不是偶数最后就减不了,,比如(707,500,3) 经过一次向下一次向上就变成了 (708,500,3) 。。这是无法相减的,只能修修尺寸了吧。。
应用
拉普拉斯金字塔的作用在于可以恢复高分辨率的图像,将G1 与 L0 相加就可以获得高分辨率的G0
o=cv2.imread('14.jpg')
G0=o
G1=cv2.pyrDown(G0)
G2=cv2.pyrDown(G1) # 构建高斯金字塔
G3=cv2.pyrDown(G2)
L0=G0-cv2.pyrUp(G1)
L1=G1-cv2.pyrUp(G2) # 拉普拉斯金字塔
L2=G2-cv2.pyrUp(G3)
RG0=L0+cv2.pyrUp(G1) # 其实就是上面式子的移项
print('G0.shape=',G0.shape)
print('RG0.shape=',RG0.shape)
result=RG0-G0 #将 RG0 和 G0 相减 # 获得的result 是一个数组
result=abs(result)
print("原始图像 G0 与恢复图像 RG0 差值的绝对值和:",np.sum(result)) # 结果是0,表示两个图像是一样的。
其他的层也一样。。使用拉普拉斯金字塔恢复的图像与原始图像完全一致。
图像轮廓
边缘检测虽然可以检测出边缘 但边缘是不连续的,检测到的边缘不一定是一个整体,图像轮廓是指将边缘连接起来后形成的一个整体,用于后续的计算。使用 cv2.findCountours() 来查找轮廓, cv2.drawContours() 绘制轮廓。图像轮廓是图像中非常重要的一个特征信息,通过对图像轮廓的操作,可以获取目标图像的大小,位置方向等信息。、
查找并绘制图像
一个轮廓对应一系列的点,这些点以某种方式表示图像的一条曲线,findContours() 用于查找图像的轮廓,并可以根据参数返回特定表示方式的轮廓,drawContours() 可以将查找的轮廓绘制到图像上,可以根据参数绘制不同样式的轮廓(实心空心,线条粗细颜色等),可以绘制全部轮廓也可以绘制部分。
查找图像轮廓:findContours函数
contours, hierarchy = cv2.findContours( image, mode, method)
- image 返回的原始图像
- contours 返回的轮廓
- hierarchy 图像的拓扑信息(轮廓层次)
- image 原始图像,8位单通道,要处理为二值图
- mode 轮廓检索模式
- method 轮廓的近似方法
- 返回值contours 返回一组轮廓信息,每个轮廓都是由若干个点构成,例如contours[i] 是第i个轮廓,contours[i][j] 是第i 个轮廓的第J 个点
contours的基本属性:
- type 返回值contours 的type属性是列表,每一个元素都是一个轮廓,用Numpy 的 ndarray 结构表示
- len(contours) 获得轮廓个数
- (len(contours[0]) 第零个轮廓右多少点数,(contours[0].shape 获得每个轮廓内点的shape 属性 ,比如那个正方形的shape 属性就是 (4,1,2) print(contours[0]) 打印第 0 个轮廓的像素点
[[[ 79 270]]
[[ 79 383]]
[[195 383]]
[[195 270]]] 只有四个点
- 返回值 hierarchy ,图像内的轮廓可能位于不同的位置,比如一个轮廓再另一个轮廓的内部。外轮廓称为父轮廓,内部的就是子轮廓,根据轮廓之间的关系就可以确定一个轮廓和其他轮廓是如何连接的,比如,确定一个轮廓是某个轮廓的子轮廓,或是某个轮廓的父轮廓,上述关系称为层次(组织结构),返回值 hierarchy 就包含上述层次关系。
每个轮廓 contours[i] 对应4个元素来说明当前轮廓的层次关系[Next,Previous,First_Child,Parent]
- Next ,后一个轮廓索引编号
- Previous 前一个轮廓索引编号
- First_Child 第一个子轮廓的索引编号
- Parent 父轮廓的索引编号,,如果各个参数没有对应的关系,就将参数设置为-1.
print(hierarchy) 来查看hierarchy 的值,需要注意轮廓的层次结构是由参数 mode 决定的,不同的 mode 得到的轮廓的编号不一样,hierachy 也不一样。
- image 输入图像,必须是8位单通道二值图
- mode 决定了轮廓的提取方式
-
cv2.RETR_EXTERNAL:只检测外轮廓。
输出值 [1,-1,-1,-1] 表示第零个轮廓的层次,后一个轮廓是第一个轮廓,前一个轮廓不存在所以两个 -1,不存在父轮廓,第四个参数也是 -1. [ -1 0 -1 -1] 同理,后一个轮廓不存在,前一个轮廓是0,不存在父子轮廓。 -
cv2.RETR_LIST:对检测到的轮廓不建立等级关系。
这里就检测出来三个轮廓,由于不建立等级关系,所以没有父子轮廓。 -
cv2.RETR_CCOMP:检索所有轮廓并将它们组织成两级层次结构。上面的一层为外边界,下面的一层为内孔的边界。如果内孔内还有一个连通物体,那么这个物体的边界仍然位于顶层。
额,这前一个轮廓就很迷,应该是说同一个层级的前一个轮廓吧,,上面的都是一个层级,这有两个了、
- cv2.RETR_TREE:建立一个等级树结构的轮廓。
由于这个图像就有两层轮廓,所以 cv2.RETR_CCOMP 和 cv2.RETR_TREE 得到的层级结构是一样的,但有多层级时,前者就会得到两层,后者可以获得多层。
- 参数method 这个决定了如果表达轮廓,
- cv2.CHAIN_APPROX_CONE 存储所有轮廓点,相邻两个点的像素位置差不超过1,
- cv2.CHAIN_APPROX_SIMPLE 压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,极端情况下,四个点来保存一个矩形的轮廓信息
- cv2.CHAIN_APPROX_TC89_L1:使用 teh-Chinl chain 近似算法的一种风格。
- cv2.CHAIN_APPROX_TC89_KCOS:使用 teh-Chinl chain 近似算法的一种风格。
细看,,蓝色的边就是保存的轮廓,使用第一个方法保留所有的点,第二个参数就保留边界上的四个点。
使用 cv2.findContours() 查找图像轮廓时,需要注意:
- 处理的必须时灰度二值图
- 再Oepncv中都是从黑色背景中查找白色对象,因此对象必须是白色的,背景必须是黑色的
- 4.X 版本中,函数仅返回两个个值。
绘制图像轮廓:drawContours函数
image=cv2.drawContours( image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]] )
- image 待绘制轮廓的图像,注意函数会在图像上直接绘制轮廓,也就是函数执行完后,image 不再是原始图像了,而是包含了轮廓的图像。所有记得复制一份图像
- contours 需要绘制的轮廓,该参数的类型与函数 cv2.findContours()的输出 contours 相同,都是 list 类型
- contourIdx 需要绘制的边缘索引,告诉函数是要绘制一条轮廓还是所有轮廓,如果是个整数或0,表示绘制对应索引号的轮廓,如果值为-1 ,绘制全部轮廓
- color 绘制的颜色,RGB 格式表示,十六机制的嘛?
- thickness 可选,表示绘制轮廓使用的画笔粗细,,,-1表示绘制实心轮廓
- lineType 可选,绘制轮廓的线类型,具体介绍以后再说,,
- hierarchy 对应函数 cv2.findContours()所输出的层次信息
- maxLevel 控制绘制的轮廓层次的深度,如果为0,仅绘制第零层的轮廓,如果是其他正数,绘制最高层以下的层级轮廓。
- offset:偏移参数。该参数使轮廓偏移到不同的位置展示出来
- 由于直接作用再原图上,所有不写返回值也是可以的。
实例
import cv2
o = cv2.imread('12.jpg')
cv2.imshow("original",o)
gray = cv2.cvtColor(o,cv2.COLOR_BGR2GRAY) # 转换为灰度二值图
ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary, # 找边界
cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
o=cv2.drawContours(o,contours,-1,(0,0,255),5) # 绘制边界,红色,画笔粗细为5
cv2.imshow("result",o)
cv2.waitKey()
cv2.destroyAllWindows()
太厉害了吧///
import numpy as np
o = cv2.imread('16.jpg')
cv2.imshow("original",o) # 逐个显示一副图像内的边缘信息。
gray = cv2.cvtColor(o,cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary,
cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
n=len(contours)
contoursImg=[]
for i in range(n):
temp=np.zeros(o.shape,np.uint8)
contoursImg.append(temp)
contoursImg[i]=cv2.drawContours(contoursImg[i],contours,i,(255,255,255),5)
cv2.imshow("contours[" + str(i)+"]",contoursImg[i])
cv2.waitKey()
cv2.destroyAllWindows()
o = cv2.imread('6.jpg')
cv2.imshow("original",o)
gray = cv2.cvtColor(o,cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
mask=np.zeros(o.shape,np.uint8)
mask=cv2.drawContours(mask,contours,-1,(255,255,255),-1) # thickness设置为-1,绘制前景对象的实心轮廓
cv2.imshow("mask" ,mask)
loc=cv2.bitwise_and(o,mask) # 实现轮廓与原始图像进行按位与,可以将前景对象从原始图像中提取出来
cv2.imshow("location" ,loc)
cv2.waitKey()
cv2.destroyAllWindows()
就这个效果,,,感觉跟掩模一样。。。。