目录

OpenCV Chapter4 图像金字塔,图像轮廓以及模板匹配


OpenCV

图像金字塔

通过不停的变换让图像越来越小

应用:图像特征提取

高斯金字塔

向下采样方法(缩小)

从金字塔底部往上走

方法 $G_i与高斯内核卷积\newline$ $$ \frac {1}{16}\begin{bmatrix} &1\ 4\ 6\ 4\ 1\newline &4\ 16\ 24\ 16\ 4\newline &6\ 24\ 36\ 24\ 6\newline &4\ 16\ 24\ 16\ 4\newline &1\ 4\ 6\ 4\ 1\newline \end{bmatrix} $$

$将所有偶数行和列去掉\newline$

经过此方法长度和宽度都变成原来的一半,大小变成原来的四分之一

向上采样方法(放大)

  1. 将每个图像在每个方向扩大为原来的两倍,新增的行和列以0填充

$$ \begin{bmatrix} &10\ 30\newline &56\ 96\newline \end{bmatrix}\rightarrow\begin{bmatrix} &10\ 0\ 30\ 0\newline &0\ 0\ 0\ 0\newline &56\ 0\ 96\ 0\newline &0\ 0\ 0\ 0\newline \end{bmatrix} $$

  1. 使用先前同样的内核(乘以4)与放大后的图像卷积,获得近似值(将像素点“均匀摊开”)

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import cv2


def show(image):
    cv2.imshow('result', image)
    cv2.waitKey()
    cv2.destroyAllWindows()


img1 = cv2.imread("img path")
print(img1.shape)  # (512, 512, 3)
show(img1)

up = cv2.pyrUp(img1)
print(up.shape)  # (1024, 1024, 3)
show(up)

down = cv2.pyrDown(img1)
print(down.shape)  # (256, 256, 3)
show(down)

拉普拉斯金字塔

$$ L_i=G_i-PyrUp(PyrDown(G_i))=G_i-PyrUp(G_{i+1}) $$

  1. 低通滤波
  2. 缩小尺寸
  3. 放大尺寸
  4. 图像相减
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import cv2


def show(image):
    cv2.imshow('result', image)
    cv2.waitKey()
    cv2.destroyAllWindows()


img1 = cv2.imread("img path")
show(img1)

down = cv2.pyrDown(img1)
down_up = cv2.pyrUp(down)
img = img1 - down_up
show(img)

图像轮廓

1
cv2.findContours(img, mode, method)

轮廓和边缘不一样的地方:边缘可以是零零散散的线段,轮廓是一个整体

mode:轮廓检索模式

  • RETR_EXTERNAL:只检索最外面的轮廓
  • RETR_LIST:检索所有的轮廓,并将其保存到一条链表当中
  • RETR_CCOMP:检索所有的轮廓,并将他们组织成两层。顶层是各部分的外部边界,第二层是空洞的边界
  • RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次(最常用)

method:轮廓逼近方法

  • CHAIN_APPROX_NONE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)
  • CHAIN_APPROX_SIMPLE:压缩水平的,垂直的和斜的部分,也就是,函数只保留他们的终点部分

notice:为了更高的准确率,使用二值图像

绘制轮廓

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import cv2


def show(image):
    cv2.imshow('result', image)
    cv2.waitKey()
    cv2.destroyAllWindows()


img1 = cv2.imread("img path")
show(img1)

img2 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)  # 转为灰度图
show(img2)

ret, thresh = cv2.threshold(img2, 127, 255, cv2.THRESH_BINARY)  # 阈值处理
show(thresh)

contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)

# 绘制轮廓
draw_img = img1.copy()  # drawContours会改变原有图像,所以这里需要copy一下
res = cv2.drawContours(draw_img, contours, -1, (255, 0, 0), 2)
# -> 绘图图像,轮廓,第几个轮廓, -1说明绘制所有轮廓,(R, G, B)说明绘制轮廓使用的颜色,最后一个是线条的宽度
show(res)
/img/Opencv/chapter4-1.png
绘制轮廓(原图)
/img/Opencv/chapter4-2.png
绘制轮廓(处理后图像)

轮廓特征

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import cv2


def show(image):
    cv2.imshow('result', image)
    cv2.waitKey()
    cv2.destroyAllWindows()


img1 = cv2.imread("img path")
show(img1)

img2 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)  # 转为灰度图
show(img2)

ret, thresh = cv2.threshold(img2, 127, 255, cv2.THRESH_BINARY)  # 阈值处理
show(thresh)

contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)

cnt = contours[0]
print(type(cnt))  # numpy.ndarray
print(cv2.contourArea(cnt))  # 面积
print(cv2.arcLength(cnt, True))  # 周长,True表示闭合

轮廓近似

“以直带曲”

寻找曲线上面距离直线距离最大的点,比较该距离与阈值的大小,如果小于阈值就可以用直线近似,反之则连接该点与曲线的两端,出现两条新的直线,重复上面的操作,直到符合条件

比较值越小,轮廓程度变化程度越小

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import cv2


def show(image):
    cv2.imshow('result', image)
    cv2.waitKey()
    cv2.destroyAllWindows()


img1 = cv2.imread("img path")
show(img1)

img2 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)  # 转为灰度图
show(img2)

ret, thresh = cv2.threshold(img2, 127, 255, cv2.THRESH_BINARY)  # 阈值处理
show(thresh)

contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
show(cv2.drawContours(img1.copy(), contours, -1, (255, 0, 0)))
cnt = contours[1]
epsilon = 0.3 * cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)  # -> 轮廓, 比较值(通常是周长的百分比)
show(cv2.drawContours(img1.copy(), cnt, -1, (255, 0, 0)))
show(cv2.drawContours(img1.copy(), approx, -1, (0, 0, 255), 2))

通过轮廓和相应的函数也能画边界矩形和外接圆

模板匹配

原理

模板匹配和卷积的原理很像,模板在原图像上从原点开始滑动,计算模板与(图像被模板覆盖的地方)的差别程度,然后将每次计算的结果放入一个矩阵里,作为结果输出。原图像大小$A\times B$,模板大小$a\times b$,输出结果的矩阵大小$(A-a+1)\times (B-b+1)\newline$

方法

/img/Opencv/chapter4-3.png
模板匹配(相关方法)
  • TM_SQDIFF:计算平方不同,计算出来的值越小,越相关

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

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

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

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

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

注:归一化的效果通常更好

使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import cv2


def show(image):
    cv2.imshow('result', image)
    cv2.waitKey()
    cv2.destroyAllWindows()


img1 = cv2.imread("img path")
show(img1)

img2 = cv2.resize(img1, (300, 300))
res = cv2.matchTemplate(img1, img2, cv2.TM_CCOEFF_NORMED)
print(img1.shape, img2.shape, res.shape)  # (512, 512, 3) (300, 300, 3) (213, 213)
1
2
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)  # 最小/最大值 + 坐标位置
print(f"min_val={min_val}, max_val={max_val}, min_loc={min_loc},max_loc={max_loc}")

通过最大/最小(与方法有关),可以定位到最匹配的区域(因为shape已知)

匹配多个对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import cv2
import numpy as np


def show(image):
    cv2.imshow('result', image)
    cv2.waitKey()
    cv2.destroyAllWindows()


img = cv2.imread("img path")
show(img)

img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

template = cv2.resize(img, (300, 300))
h, w = template.shape[:2]

res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED)
threshold = 0.4  # 取匹配程度大于40%的坐标
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)

show(img)