声明
声明:本系列博客是我在学习OpenCV官方教程中文版(For Python)(段力辉 译)所做的笔记。所以,其中的绝大部分内容引自这本书,博客中的代码也是其配套所附带的代码或书中的代码,侵删。其中部分代码可能会因需要而改动。在本系列博客中,其中包含书中的引用,也包括我自己对知识的理解,思考和总结。本系列博客的目的主要有两个,一个是可以作为我自己的学习笔记,时常复习巩固。第二个是可以为想学习基于python的opencv 3 相关知识的朋友提供一些参考。
正文
一、关于角点
首先,图像特征类型可分为以下三种:
- 边缘
- 角点(感兴趣关键点)
- 斑点(感兴趣区域)
角点所具有的特征:
- 轮廓之间的交点
- 对于同一场景,即使视角发生变化,通常具备稳定性质的特征
- 该点附近区域的像素点无论在梯度方向上还是其梯度幅值上有着较大变化
二、Harris角点检测原理
角点:如果存在任意方向上的滑动,都有着较大灰度变化,那么我们可以认为该窗口中存在角点。
边界:仅在水平、或者仅在竖直方向有较大的变化量,另一个没有什么变化.
平坦区域:在水平、竖直方向的变化量比较小。
将窗口向各个方向移动(u,v)然后计算所有差异的总和。表达式为:
E ( u , v ) = ∑ x , y w ( x , y ) [ I ( x + u , y + v ) − I ( x , y ) ] 2 E(u,v)=\sum_{x,y} w(x,y)[I(x+u,y+v)-I(x,y)]^2 E(u,v)=x,y∑w(x,y)[I(x+u,y+v)−I(x,y)]2
其中w(x,y)是窗口函数,它可以是正常的矩形窗口也可以是对每一个像素给予不同权重的高斯窗口。
角点检测中要使E(u,v)的值最大,对上面的等式进行泰勒级数展开然后再通过几步数学换算,可以得到下面的等式:
E ( u , v ) ≈ [ u v ] M [ u v ] E(u,v)\approx \left[ \begin{matrix} u&v\\ \end{matrix} \right] M\left[ \begin{matrix} u \\v \end{matrix} \right] E(u,v)≈[uv]M[uv]
其中:
M = ∑ x , y w ( x , y ) [ I x I x I x I y I x I y I y I y ] M=\sum_{x,y}w(x,y)\left[ \begin{matrix} I_xI_x&IxI_y\\I_xI_y&I_yI_y \end{matrix} \right] M=x,y∑w(x,y)[IxIxIxIyIxIyIyIy]
这里 I x I_x Ix和 I y I_y Iy是图像在x和y方向的导数。
然后就是主要部分了,根据一个用来判定窗口内是否包含角点的等式进行打分。
R = d e t ( M ) − k [ t r a c e ( M ) ] 2 R=det(M)-k[trace(M)]^2 R=det(M)−k[trace(M)]2
其中:
d
e
t
(
M
)
=
λ
1
λ
2
det(M)=\lambda_1\lambda_2
det(M)=λ1λ2
t
r
a
c
e
(
M
)
=
λ
1
+
λ
2
trace(M)=\lambda_1+\lambda_2
trace(M)=λ1+λ2
λ
1
和
λ
2
\lambda_1和\lambda_2
λ1和λ2是矩阵M的特征值
根据这些特征值我们可以判断一个区域是否是角点,边界或者是平面。
- 特征值都比较大时,即窗口中含有角点
- 特征值一个较大,一个较小,窗口中含有边缘
- 特征值都比较小,窗口处在平坦区域
可以用一幅图来形象地表示:
- 角点:R为大数值正数
- 边缘:R为大数值负数
- 平坦区:|R|是小数值
所以 Harris角点检测的结果是一个由角点分数构成的灰度图像。选取适当的阈值对结果图像进行二值化我们就检测到了图像中的角点。
三、opencv中的Harris角点检测
opencv中的函数**cv2.cornerHarris()**可以用来进行角点检测,这个函数有这么几个参数:
- img:数据类型为float32的输入图像
- blockSize:角点检测中要考虑的领域大小
- ksize:Sobel求导中使用的窗口大小
- k:Harris角点检测方程中的自由参数,取值参数为[0.04 , 0.06]
举个例子:
import cv2 as cv
import numpy as np
def harris_demo(image):
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
gray = np.float32(gray)
dst = cv.cornerHarris(gray, 2, 3, 0.04)
dst = cv.dilate(dst, None) # 交点膨胀,使其突出
print(dst.max())
# 取出膨胀后dst.max()的1%以上交点,标记为红色
image[dst>0.01*dst.max()] = [0, 0, 255]
cv.namedWindow('harris corner', 0)
cv.imshow('harris corner',image)
def main():
src = cv.imread("../images/blox.jpg")
harris_demo(src)
cv.waitKey(0) # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
cv.destroyAllWindows() # 关闭所有窗口
if __name__ == '__main__':
main()
四、亚像素级精确度的角点
有时我们需要最大精度的角点检测。OpenCV 为我们提供了函数 cv2.cornerSubPix(),它可以提供亚像素级别的角点检测。
下面是一个例子。首先我们要找到 Harris角点,然后将角点的重心传给这个函数进行修正。Harris 角点用红色像素标出,绿色像素是修正后的像素。在使用这个函数时我们要定义一个迭代停止条件。当迭代次数达到或者精度条件满足后迭代就会停止。我们同样需要定义进行角点搜索的邻域大小。
cornerSubPix(image, corners, winSize, zeroZone, criteria)
- corners: 检测到的角点,既是输入也是输出。
- winSize: 计算亚像素角点时考虑的区域的大小,大小为NXN; N=(winSize*2+1)
- zeroZone: 作用类似于winSize,但是总是具有较小的范围,通常忽略(即Size(-1, -1))。
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
def sub_pixel(image):
# 找出Harris 角点
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
gray = np.float32(gray)
dst = cv.cornerHarris(gray, 2, 3, 0.04)
dst = cv.dilate(dst, None)
ret, dst = cv.threshold(dst, 0.01 * dst.max(), 255, 0)
dst = np.uint8(dst)
# 用connectedComponentsWithStats()找角点重心centroids
ret, labels, stats, centroids = cv.connectedComponentsWithStats(dst)
print(ret)
# 定义迭代停止条件,提取角点
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 100, 0.001)
corners = cv.cornerSubPix(gray, np.float32(centroids), (5,5), (-1,-1), criteria)
# 将角点重心和角点水平堆砌,例如
# >>> a = np.array([[1],[2],[3]])
# >>> b = np.array([[2],[3],[4]])
# >>> np.hstack((a,b))
# array([[1, 2],
# [2, 3],
# [3, 4]])
res = np.hstack((centroids, corners))
# np.int0 可以用来省略小数点后面的数字(非四五入)
res = np.int0(res)
# 画出角点颜色
image[res[:, 1], res[:, 0]] = [255, 0, 0]
image[res[:, 3], res[:, 2]] = [0, 255, 0]
cv.imwrite('blox_3.jpg', image)
plt.figure(figsize=(6,6))
plt.imshow(image)
plt.show()
def main():
src = cv.imread("../images/blox.jpg")
sub_pixel(src)
cv.waitKey(0) # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
cv.destroyAllWindows() # 关闭所有窗口
if __name__ == '__main__':
main()
放大看:
五、Shi-Tomasi角点检测
对Harris角点检测算法做一点小小的修改,可以得到更好的结果,这就是Shi-Tomasi角点检测。Harris角点检测的打分公式为 R = λ 1 λ 2 − k [ λ 1 + λ 2 ] 2 R=\lambda_1\lambda_2-k[\lambda_1+\lambda_2]^2 R=λ1λ2−k[λ1+λ2]2,但Shi-Tomasi使用的打分公式为: R = m i n ( λ 1 , λ 2 ) R=min(\lambda_1,\lambda_2) R=min(λ1,λ2)
如果打分超过阈值,我们就认为它是一个角点。可以把它绘制到
λ
1
∼
λ
2
\lambda_1\sim\lambda_2
λ1∼λ2空间中,就会得到下图:
从这幅图中,我们可以看出来只有当 λ 1和 λ2 都大于最小值时,才被认为是角点(绿色区域)。
OpenCV提供了函数:cv2.goodFeatureToTrack(),这个函数可以帮我们使用 Shi-Tomasi 方法获取图像中 N 个最好的角点,通常情况下,输入的应该是灰度图像。然后确定你想要检测到的角点数目。再设置角点的质量水平,0 到 1 之间。它代表了角点的最低质量,低于这个数的所有角点都会被忽略。最后再设置两个角点之间的最短欧式距离。根据这些信息,函数就能在图像上找到角点。所有低于质量水平的角点都会被忽略。然后再把合格角点按角点质量进行降序排列。函数会采用角点质量最高的那个角点(排序后的第一个),然后将它附近(最小距离之内)的角点都删掉。按着这样的方式最后返回 N 个最佳角点。
举个例子:我设置成找到40个最佳角点
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
def shi_tomasi(image):
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
# 取出40个最佳角点
corners = cv.goodFeaturesToTrack(gray, 40, 0.01, 10) # 返回结果是[[311., 250.]] 两层括号数组
# 取整
corners = np.int0(corners)
for i in corners:
x, y = i.ravel() # 分解
cv.circle(image, (x, y), 3, 255, -1)
plt.imshow(image), plt.show()
def main():
src = cv.imread("../images/blox.jpg")
shi_tomasi(src)
cv.waitKey(0) # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
cv.destroyAllWindows() # 关闭所有窗口
if __name__ == '__main__':
main()
感谢观看
如有错误,欢迎批评指正!