6.1 图像金字塔
高斯金字塔
向下采样(缩小图像):
将图像区域与高斯内核卷积以减少图像噪声
A = 1 16 [ 1 4 6 4 1 4 16 24 16 4 6 24 36 24 6 4 16 24 16 4 1 4 6 4 1 ] A = \frac{1}{16} \begin{bmatrix} 1 & 4 & 6 & 4 & 1 \\ 4 & 16 & 24 & 16 & 4 \\ 6 & 24 & 36 & 24 & 6 \\ 4 & 16 & 24 & 16 & 4 \\ 1 & 4 & 6 & 4 & 1 \\ \end{bmatrix} A=161 1464141624164624362464162416414641 将所有偶数行和列去除
向上采样(放大图像)
将图像在每个方向扩大为原来的两倍,新增的行和列以0填充
原始矩阵: [ 10 30 56 96 ] 经过扩展后的矩阵: [ 10 0 30 0 0 0 0 0 56 0 96 0 0 0 0 0 ] 原始矩阵: \begin{bmatrix} 10 & 30 \\ 56 & 96 \\ \end{bmatrix} \\ 经过扩展后的矩阵: \begin{bmatrix} 10 & 0 & 30 & 0 \\ 0 & 0 & 0 & 0 \\ 56 & 0 & 96 & 0 \\ 0 & 0 & 0 & 0 \\ \end{bmatrix} 原始矩阵:[10563096]经过扩展后的矩阵: 10056000003009600000 使用先前同样的内核(乘以4)与放大后的图像卷积,获得近似值
def cv_show(img,name):
cv2.imshow(name,img)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 原始图像
img = cv2.imread('nailong.png')
cv_show(img, 'img')
print(img.shape) # (222, 152, 3)
up = cv2.pyrUp(img)
cv_show(up, 'up')
print(up.shape) # (444, 304, 3)
上采样后图像变大,图像模糊
down = cv2.pyrDown(img)
cv_show(down, 'down')
print(down.shape) # (111, 76, 3)
下采样后图像变小,图像模糊
图像经过上采样然后下采样后会发生什么?
up = cv2.pyrUp(img)
up_down = cv2.pyrDown(up)
cv_show(up_down, 'up_down')
# 与原图像进行比对
cv_show(np.hstack((img, dp_down)), 'up_down')
由于上采样时像素值被周围平均分,下采样时又损失了一些信息,所以图像处理后会模糊。
用原始图像减去上下采样后的结果来显示采样过程中丢失的信息:
cv_show(img - up_down, 'img-up_down')
- 拉普拉斯金字塔
首先对原始图像进行高斯平滑操作,然后将平滑后的图像进行下采样得到一个较小尺寸的图像,再将下采样后的图像进行上采样得到一个较大尺寸的图像,最后将上采样得到的图像与原始图像进行差分,得到一个称为拉普拉斯图像的结果。
down = cv2.pyrDown(img)
down_up = cv2.pyrUp(down)
cv_show(img - down_up, 'img-down_up')
- 两者用途
- 高斯金字塔主要用于图像的多分辨率(不同尺寸)分析,常用于图像压缩、特征提取和图像分割等任务。
- 拉普拉斯金字塔每一岑都包含高频细节信息,且每一层都是上一层的差分结果,因此能够有效保留图像的边缘和细节。主要用于图像重建、边缘检测和增强等任务。
6.2 图像轮廓
cv2.findContours(img, mode, method)
mode:轮廓检索模式
- RETR_EXTERNAL:只检索最外面的轮廓
- RETR_LIST:检索所有的轮廓,并将其保存到一条链表中
- RETR_CCOMP:检索所有的轮廓,并将他们组织为两层:顶层是各部分的外部边界,第二层是空洞的边界。
- RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次;
method:轮廓逼近方法
CHAIN_APPROX_NONE:以以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)。
CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,也就是,函数只保留他们的终点部分。
轮廓检测通常使用二值图像。
二值图像只有两种像素值(0、255),大大简化了轮廓检测问题,且像素值少,处理速度更快,
且大多数轮廓检测算法都是基于二值图像设计的,cv.findContours只能输入二值图像数据。
img = cv2.imread('contours.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
cv_show(thresh, 'thresh')
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
返回contours,是一个列表,其中每个元素都是一个轮廓,每个轮廓都是一个点的像素。
hierarchy:与contours同样长度的列表,每个元素都是一个数组,描述了相应轮廓的层次关系。
6.3 绘制轮廓
# 绘制轮廓时需要传入绘制图像、轮廓、轮廓索引、颜色模式、线条厚度
draw_img = img.copy() # 需要copy,因为绘制轮廓会在原图上进行绘制
res = cv2.drawContours(draw_img, contours, -1, (0, 0, 255), 2)
# -1表示对所有轮廓进行绘制
cv_show(res, 'res')
也可以选择对哪个轮廓进行绘制
draw_img = img.copy()
res = cv2.drawContours(draw_img, contours, 0, (0, 0, 255), 2)
cv_show(res, 'res')
6.4 轮廓特征
cnt = contours[0]
# 轮廓面积
cv2.contourArea(cnt) # 8500.5
# 周长,True表示计算闭合的轮廓
cv.arcLength(cnt, True) # 437.9482651948929
6.5 轮廓近似
轮廓近似是在轮廓检测过程中对检测到的轮廓点进行简化处理的一种方法。
这个过程就像不断地二分一样
img = cv2.imread('contours2.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 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], -1, (0, 0, 255), 2)
cv_show(res, 'res')
接下来对轮廓求近似
# 求出轮廓的边长,再乘以小数
epsilon = 0.15 * cv2.arcLength(cnt, True)
# 对轮廓进行多边形近似,将复杂的轮廓简化为多变形
approx = cv2.approxPolyDP(cnt, epsilon, True)
draw_img = img.copy()
res = cv2.drawContours(draw_img, [approx], -1, (0, 0, 255), 2)
cv_show(res, 'res')
也可以自己去调整上边epsilon的参数,看看乘的小数越小轮廓会发生什么变化。
我们也可以画出轮廓的边界矩形(最小外接矩形)
img = cv2.imread('contours.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
# 返回最小外接矩形的左上角坐标、宽和高
x, y, w, h = cv2.boundingRect(cnt)
# 绘制矩形
img = cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv_show(img, 'img')
# 计算轮廓面积与边界矩形比
area = cv2.contourArea(cnt)
x, y, w, h = cv2.boundingRect(cnt)
rect_area = w * h
extent = float(area) / rect_area
print (extent) # 0.5154317244724715
画出轮廓的外接圆
(x,y),radius = cv2.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
img = cv2.circle(img,center,radius,(0,255,0),2)
cv_show(img,'img')
6.6 模板匹配
模板匹配是一种在图像中查找与给定模板最相似区域的方法,模板在原图像上从原点开始滑动,计算模板与(图像被模板覆盖的地方)的差别程序。
这个差别程度的计算方法在opencv里有6种,然后将每次计算的结果放入一个矩阵里,作为结果输出。假如原图形大小为A×B,模板大小是a×b,则输入结果的矩阵大小是(A-a+1)x(B-b+1)
模板宽为b,从原点像右边移动时,能移动的步数只有B-b格,加上在原点时区域的输出,则输出结果的矩阵的宽度为B-b+1,同理高也是如此。
# 模板匹配
img = cv2.imread('lena.jpg', 0) # 0表示以灰度模式读取图像
cv_show(img, 'img')
template = cv2.imread('face.jpg', 0)
cv_show(template, 'template')
print(img.shape) # (263, 263)
print(template.shape) # (110, 85)
模板匹配的计算方法有多种:
- TM_SQDIFF:计算平方不同,计算出来的值越小,越相关
- TM_CCORR:计算相关性,计算出来的值越大,越相关
- TM_CCOEFF:计算相关系数,计算出来的值越大,越相关
- TM_SQDIFF_NORMED:计算归一化平方不同,计算出来的值越接近0,越相关
- TM_CCORR_NORMED:计算归一化相关性,计算出来的值越接近1,越相关
- TM_CCOEFF_NORMED:计算归一化相关系数,计算出来的值越接近1,越相关
res = cv2.matchTemplate(img, template, cv2.TM_SQDIFF)
res.shape() # (154, 179)
# 263-110+1=154,263-85+1=179
# cv2.minMaxLoc用于计算二维数组的最大值和最小值
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
print(min_val) # 39168.0
print(max_val) # 74403584.0
print(min_loc) # (107, 89)
print(max_loc) # (159, 62)
6.7 匹配多个对象
img_rgb = cv2.imread('mario.jpg')
cv_show(img_rgb, 'img_rgb')
template = cv2.imread('mario_coin.jpg')
cv_show(template, 'template')
img_rgb = cv2.imread('mario.jpg')
template = cv2.imread('mario_coin.jpg')
h, w = template.shape[:2]
res = cv2.matchTemplate(img_rgb, template, cv2.TM_CCOEFF_NORMED)
threshold = 0.8
# 取匹配程序大于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)
cv_show(img_rgb, 'img_rgb')