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$
经过此方法长度和宽度都变成原来的一半,大小变成原来的四分之一
向上采样方法(放大)
- 将每个图像在每个方向扩大为原来的两倍,新增的行和列以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}
$$
- 使用先前同样的内核(乘以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
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)
|
绘制轮廓(原图
)
绘制轮廓(处理后图像
)
轮廓特征
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$
方法
模板匹配(相关方法
)
-
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)
|