【OpenCV 学习笔记】第二十章: 角点检测之:harris算法以及Shi-Tomasi算法

第二十章: 角点检测之:harris算法以及Shi-Tomasi算法

一张图像,我们可以用很多方法去处理它,就会得到很多不同的特征。比如基于梯度方法我们就能得到图像的边缘特征;比如基于直方图我们就得到图像的统计特征;比如基于傅里叶变换我们就得到图像频域特征;比如基于霍夫变换我们就可以得到图像的某些几何特征;比如通过形态变换、阈值处理、图像边缘处理等手段我们就可以得到图像的轮廓特征;再比如基于图像金字塔我们可以得到图像的尺度不变性特征。。。等等,这些都是图像的特征。不同的特征有自己独特的优势和用处。
除此之外,图像还有角点特征、斑点特征、脊向特征等等。本章讲角点特征,根据角点特征检测出图像中的角点区域

  • 1、什么是角点?
    角点(Corner Point),很多人都喜欢用下面这张图先感性的认识一下角点:

    图中:E和F是角点区域,C和D是边缘区域, A和B是平坦区域

    直观的看:图像中的E和F区域是图像中最独特的区域,几乎没有其他区域和它长得一样,C和D区域次之,A和B区域是最没有特征的区域。如果我拿一个A或B区域的小模板去图片中找模板的对应位置,我们可以找到很多和模板一样的位置,就是我们无法定位。如果我们拿一个E或F区域的模板去图片中找匹配,我们就可以准确的定位住模板的位置。所以角点区域很重要!角点区域对于图像精准识别、精准定位非常重要! 所以我们需要研究角点特征的特点,根据这个特点去检测图像中的角点区域。下面是两幅不同视角的图像,通过找出对应的角点进行匹配:
    所以,角点特征是图像特征中非常重要的一类特征,是机器视觉算法工作的基础。角点检测广泛应用于视频跟踪、三维建模、图像匹配、目标识别、运动分析、缺陷检测等领域。

  • 2、角点有什么特征(特点)?
    从图像上看,角点是图像中两条边的交点,或者说,角点的部分邻域应该具有两个不同范围和不同方向的边界。

    所以角点的特征有:
    (1)角点是轮廓之间的交点;
    (2)对于同一个场景,即使视角发生变化,角点区域的特征性质比较稳定;
    (3)角点附近区域的像素点无论在梯度大小还是在梯度方向上都有着较大的变化;
    为了方便检测,我们一般是先将图像转化为灰度图像再去检测角点特征。

  • 3、角点检测的方法有哪些?
    近年来学者们提出的角点检测方法有很多,整体上分两类:一类是用模板检测,一般是先建立一系列具有不同角度的角点模板,然后计算待测图像与标准模板之间的相似程度,以此来检测图像中的角点。另外一类是基于图像的梯度去检测角点区域,其中最著名的是Harris角点检测以及后来对Harris算法进行改进的Shi-Tomasi角点检测算法。

    当我们检测到图像角点后,可以用BRISK (Binary Robust Invariant Scalable Keypoints)或FREAK (FastRetina Keypoint)方法等将检测到的角点生成以二值方式编码的特征向量,也就是角点描述。而角点匹配则是计算两幅图像二值特征向量之间的汉明距离,并依据距离的大小来判断其是否匹配。

    本章只讲Harris角点检测算法和Shi-Tomasi角点检测算法。

一、Harris角点检测

极好的参考文章:怎么深入了解 Harris 角点检测? - 知乎
第十一节、Harris角点检测原理(附源码) - 大奥特曼打小怪兽 - 博客园

  • 1、Harris角点检测的思路
    我们是对一张图片的每个像素,逐个去判断它是否是角点像素的。但我们不是像机器学习中的那样,逐个像素去做二分类任务。当我们要判断某个像素点是否是角点像素,我们就以这个像素点为中心,判断以它为中心的一个小区域是否具有角点特征。具体操作就是通过一个小窗口去观察这个点是否是角点。所以通常的做法是:先定义一个尺寸大小恰当(比如5x5,7x7等)的滑动窗口(滑窗),让这个滑窗在被检测图像(的灰度图像)上,从左往右,从上到下,步长为1去滑动,每滑动一步,判断滑窗内的局部图像是否具有角点特征,如果有,这个滑窗内的中心像素点就是角点像素,如果没有就不是。从下面这张图直观感受一下:

     当滑窗滑到左图所示的位置时:滑窗向任意方向平移,平移前后局部图像的灰度值几乎没有变化,具有这种性质的区域就是平坦区域,此时这个区域的中心点就不是角点。
    当滑窗滑到中间图所示的位置时:滑窗向任意方向平移,平移前后局部图像的灰度值有所改变,那么这个区域就是边缘区域,此时这个区域的中心点就是边缘点。
    当滑窗滑到右图所示的位置时:滑窗向任意方向平移,平移前后局部图像的灰度值有很明显的改变,那么这个区域就是角点区域,这个区域的中心点就是角点。

  • 一句话:如果是角点区域,滑窗平移前后的局部图像的灰度值变化较大;如果是边缘区域,灰度值变化较小;如果是平坦区域,灰度值几乎没有变化。 返过来也对:如果滑窗平移前后的灰度值变化较大,就是角点区域;如果滑窗平移前后的灰度值变化较小,就是边缘区域;如果滑窗平移前后的灰度值没有变化,就是平坦区域。

  • 2、数学推导
    基于上述的检测思路和角点特征,用数学语言描述就是:当窗口向任意方向移动[u,v]时,那么移动前后对应的窗口中的像素点灰度变化计算公式如下:

     其中:
    (x,y)是窗口W所套住的原图的像素的坐标位置,假如我们规定窗口W是5x5的,那么(x,y)就有25个位置。
    w(x,y)是:滑窗(5x5)中每个格中的数值,我们也叫权重。滑窗可以是一个只有尺寸没有数值的滑窗(也等价于全是1的滑窗)也可以是一个高斯滑窗(这体现了视觉的注意力特征)。
    I(x,y)表示被滑窗套住的、被检测图像的、待判断区域的灰度值,也就是前面讲的窗口向任意方向移动前的灰度值(是一个5x5的区域)。
    I(x+u, y+v)表示窗口向任意方向移动了u、v的幅度后,还是一个5x5区域的,对应位置的灰度值。由于我们是在灰度图上任意角度的平移,所以这个平移可以理解成仿射变换,仿射变换就可以分解成x、y两个正交方向,两个方向上的伸缩量就是仿射变换矩阵的两个特征值。这点一定要理解!!!便于理解后面的数学推导结论!如果不理解可以看看我第五章的几何变换部分的内容可以帮助你理解。
    I(x+u, y+v)-I(x,y)外面加个平方是因为:在用减法计算差异的时候会出现负数,我们希望对差异的度量不要出现负值,通常的做法就是取平方。
    E(u,v)就是:被滑窗套住的区域,平移前后,区域中对应位置的灰度值差值的平方再加权求和后的结果。这个结果就是“图像局部特征”的量化度量。我们要找的就是E(u,v)最大的那些点,就是角点。

    但是从这个公式出发计算的结果都是带u、v的,也就是计算结果是一个自变量为u、v的二元函数,那么这个二元函数有什么特点呢,是什么决定了这个二元函数的形状呢?harris把上面的计算公式做了如下的推导:

     其中,约等于那步是一个二维的泰勒一阶展开:I(x+u, y+v)=I(x, y) + uIx + vIy + ...
    所以,E(u,v)的计算公式就可以变为:

     Ix,Iy分别是窗口内像素点(x,y)在x方向上和y方向上的梯度值,这个两个梯度值我们都可以通过之前讲的sobel算子计算得到。

    所以E(u,v)的大小就取决于M,我们要找的是E(u,v)最大的那些点。很多参考资料写到这里就开始用λ1和λ2为特征,来区分每个滑窗区域是角点区域还是边缘区域还是平坦区域,我个人觉得这样很牵强,没有讲清楚,所以,继续往下推导:
    由于M是一个实对称矩阵,所以M是可以正交相似对角化:

     这里为了说明问题,我们把w(x,y)看成都是1的窗口,先省略了。我们把M正交相似对角化后,其中P就是特征值λ1、λ2对应的相互正交的特征向量,所以这个P矩阵我们可以理解为梯度变化的方向,λ1和λ2为两个正交方向上的变化强度值。但是我们并不需要知道梯度到底是往哪个方向变化的,我们仅仅是想知道变化了多大?就是只要知道变化大不大即可,因为我们要找的是变化大的区域,变化大的区域是角点区域,变化小的区域是边缘区域,没有变化的区域是平坦区域,我们根本不用关心变化的方向!所以我们只要找λ1和λ2都很大的像素点,我们就认为是角点即可。

     如果有同学还没明白,我们也可以从函数E(u,v)的形状上去看:继续推导,E(u,v)函数本质上就是一个椭圆函数:

    所以,从这个角度看,u,v代表了E(u,v)的方向,λ1、λ2代表了椭圆E(u,v)的长短轴,也就是两个垂直方向的梯度大小。所以要使E(u,v)最大,就得使λ1、λ2同时都大。殊途同归,都是上面的那个图的结论。

    至此,问题就转化为求矩阵M的特征值λ1和λ2,找λ1和λ2都很大的点就是角点。但是,但是,又但是了,可能是因为,如果显式的去计算特征值计算开销会很大,而求M的行列式和迹比较简单吧,而且矩阵M的行列式等于λ1乘λ2,矩阵M的迹等于λ1+λ2,所以,harris又构建了一个角点响应函数:

     从函数的形式上看,R取决于M的特征值,如果是角点区域|R|就会很大,平坦区域|R|就会很小,边缘区域R为负值。 opencv是直接引入这个函数,其实我也没想明白Harris怎么就能想出这么一个函数!下面图片是到别的参考资料上找的一个解释,读者自行揣摩吧

     

     

     其中:detM是M的行列式;traceM是M的迹;k是Harris系数,根据经验一般取值为0.04~0.06,从上图看,k的存在只是调节函数的形状而已。从函数的形式上看,k值越大,会降低角点检测的灵敏度,减少被检测角点的数量;减少k值,会增加角点检测的灵敏度,增加被检测角点的数量。
    这里的难点不在于理解这个函数表达式,而在于harris是如何创造出这个函数表达式的。针对这个问题我找了很多参考资料,几乎都是直接略过没有任何解释,只看到一篇文章对此解释是:“Harris也许对很多函数模型非常了解,对于创造出这样的一个函数表达式,易如反掌,当然在我们看来感觉是很了不起的,那是因为我们见过的函数模型太少”。哈哈,只有这一个诡异的解释!!!

    至此,这就是harris角点检测的数学计算过程。
    至此,我们就计算出了每个像素点的R值,最后就是设定R的阈值,大于阈值的我们认为是角点,小于阈值的不是角点。
    此外,我们也可以进行非极大值抑制处理,即在一个窗口内,如果有很多大于阈值的角点,则用R值最大的那个角点,其他点都删除。

  • 3、API:ret = cornerHarris(image, blockSize, ksize, k[, dst[, borderType]])
    image:输入的单通道8位或者浮点图像;
    blockSize:滑窗的窗口尺寸;
    ksize:该参数是定义sobel算子的尺寸;就是上面我们数学推导中提到的sobel算子。该参数决定了角点检测的敏感度,其值的经验参考范围是介于3~31之间的奇数。
    k:harris在角点响应函数中用到的参数k,k一般取0.04-0.06;
    dst:用来存储检测结果R的。一般不用自己设定,默认是和image尺寸大小一样的一个float32类型的数组;
    borderType:边界处理方式。
    ret:输出是一幅浮点值图像,大小与输入图像大小相同,浮点值越高,表明越可能是特征角点(所以我们要对图像阈值化处理)。

个人总结:角点检测是需要滑窗大小和sobel算子共同搭配去检测角点的。如果说sobel算子是代表了梯度的大小和方向,这个参数越小,梯度值就越小,因为参数越小,表示越是两个位置更近的像素点之间进行梯度计算的,而越相邻,像素值就越接近,梯度就越小。而滑窗则是代表了你是从多大的窗口去观察这个区域是否具有角点特征的,如果滑窗越小越看不到梯度有啥变化。所以这两个参数联合决定了角点检测的敏感度。
如果滑窗尺寸太小,sobel算子尺寸也很小,就好比在一个很小的窗口去看一个很小的梯度,那自然找不到角点。如果滑窗尺寸很小但sobel算子尺寸很大,那我们是可能会检测出角点特征的。如果滑窗尺寸很大,但sobel算子尺寸很小,就好比在一个很大的窗口去看一个个梯度值很小的像素区域,那是可能检测不到角点的,因为我们还有阈值处理,这种情况很可能都是低于阈值的区域。如果滑窗很大,再配合很大的sobel算子,那么一定也能检测出角点特征。 

#例20.1 直接调用cornerHarris()函数检测角点  
import cv2
import numpy as np
img = cv2.imread(r'C:\Users\25584\Desktop\building.jpg')  #原图  (600, 868, 3)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)    #转灰度图   (600, 868)
img_gray_32 = np.float32(img_gray)   #uint8类型转化为float32类型  

corner = cv2.cornerHarris(img_gray_32, 5, 3, 0.04)  #可以自己调调第二、三个参数的各种搭配看有什么变化,  corner.shape返回(600, 868)
img1 = np.zeros(img.shape, np.uint8)   #在一个纯黑的背景上画出角点看看
img1[corner>0.01*corner.max()]=[0,0,255]  
img2 = img.copy()                      #在原图上画出角点看看
img2[corner>0.001*corner.max()]=[0,0,255] 

#可视化
fig, axes = plt.subplots(1,5, figsize=(12,8), dpi=100)
axes[0].imshow(img[:,:,::-1])  #原图
axes[1].imshow(img_gray, cmap='gray')  #原图的灰度图
axes[2].imshow(corner, cmap='gray')  #角点图
axes[3].imshow(img1[:,:,::-1]) #大于阈值的角点用红色显示出来
axes[4].imshow(img2[:,:,::-1])  #在原图上把大于阈值的角点用红色标注出来
plt.show()

#例20.2 生成一个简单的图像数据,根据harris原理的实现步骤,手动实现角点检测,观察每一步的结算结果  
import cv2
import numpy as np

img = np.random.randint(0,256, (10,8), np.uint8)   #生成一个10行8列的数据观察
img32 = np.float32(img)   #uint8类型转化为float32类型  

#-------使用sobel计算像素点x,y梯度,并生成梯度数组grad-----------------
sobel_x = cv2.Sobel(img32, cv2.CV_64F, 1, 0)   #sobel_x是一个10行8列的水平梯度值数组,原理就是对每个像素点都进行水平sobel运算,将计算结果覆盖这个像素点。
sobel_y = cv2.Sobel(img32, cv2.CV_64F, 0, 1)   #sobel_y是10行8列的垂直梯度值的一个数组,就是每个像素点的值都是垂直梯度值。  

grad = np.zeros((10,8,2), dtype=np.float32)  #把水平梯度和垂直梯度放到一个数组grad里
grad[:,:,0]=sobel_x
grad[:,:,1]=sobel_y

#-----计算Ix^2, Iy^2, Ix*Iy,就是计算每个像素点的水平梯度的平方、垂直梯度的平方、以及二者的乘积,并将这三个计算结果保存到矩阵m中-----------
m = np.zeros((10,8,3),dtype=np.float32)
m[:,:,0] = grad[:,:,0]**2
m[:,:,1] = grad[:,:,1]**2
m[:,:,2] = grad[:,:,0]*grad[:,:,1]

#------利用高斯函数对Ix^2, Iy^2, Ix*Iy进行滤波------------------
m[:,:,0] = cv2.GaussianBlur(m[:,:,0], ksize=(5,5), sigmaX=2)
m[:,:,1] = cv2.GaussianBlur(m[:,:,1], ksize=(5,5), sigmaX=2)
m[:,:,2] = cv2.GaussianBlur(m[:,:,2], ksize=(5,5), sigmaX=2)

#-----针对每个像素点,生成这个像素点的M矩阵(2x2)-----------------
M = [np.array([[m[i,j,0],m[i,j,2]],[m[i,j,2],m[i,j,1]]]) for i in range(10) for j in range(8)]

#--------构造响应函数:R=det(M)-k(trace(M))^2  0.04<=k<=0.06, 计算每个像素点的R值--------------
det = list(map(np.linalg.det,M))
trace = list(map(np.trace,M))
k = 0.04
R = np.array([d - k*t**2 for d,t in zip(det, trace)])   #(80,)的结构

#------对R进行非极大值抑制,滤除一些不是角点的点,同时要满足大于设定的阈值-----------------
WITH_NMS = True                    #定义一个是否要进行非极大值抑制操作的开关,就是留个是否非极大值抑制的接口
threshold = 0.01 * np.max(R)       #设定阈值:最大值的0.01倍
R = R.reshape(10,8)
dst_corner = np.zeros(R.shape, dtype=np.uint8)
for i in range(10):
    for j in range(8):
        if WITH_NMS:   
            if R[i,j]>threshold and R[i,j]==np.max(R[max(0,i-1):min(i+1,10-1),max(0,j-1):min(j+1,8-1)]):
                dst_corner[i,j]=255
        else:         #只进行阈值检测,不用非极大值抑制操作
            if R[i,j]>threshold:
                dst_corner[i,j]=255

#例20.3 用一张图片测试一下上面手动实现的角点检测代码  
import cv2
import numpy as np

img = cv2.imread(r'C:\Users\25584\Desktop\building.jpg')  #原图  尺寸:(600, 868, 3)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)    #转灰度图   尺寸:(600, 868)
img_gray_32 = np.float32(img_gray)   #uint8类型转化为float32类型  

sobel_x = cv2.Sobel(img_gray_32, cv2.CV_64F, 1, 0)  #求梯度,尺寸:(600, 868)
sobel_y = cv2.Sobel(img_gray_32, cv2.CV_64F, 0, 1)  #尺寸也是(600, 868) 

grad = np.zeros((600, 868,2), dtype=np.float32)   #将梯度数据存到grad里面
grad[:,:,0]=sobel_x
grad[:,:,1]=sobel_y

#----------计算M矩阵的各个元素值-----------------
m = np.zeros((600, 868,3),dtype=np.float32)
m[:,:,0] = grad[:,:,0]**2
m[:,:,1] = grad[:,:,1]**2
m[:,:,2] = grad[:,:,0]*grad[:,:,1]

#------对M进行高斯滤波------------------
m[:,:,0] = cv2.GaussianBlur(m[:,:,0], ksize=(3,3), sigmaX=2)
m[:,:,1] = cv2.GaussianBlur(m[:,:,1], ksize=(3,3), sigmaX=2)
m[:,:,2] = cv2.GaussianBlur(m[:,:,2], ksize=(3,3), sigmaX=2)

#------------生成每个像素对应的M矩阵------------------------
M = [np.array([[m[i,j,0],m[i,j,2]],[m[i,j,2],m[i,j,1]]]) for i in range(600) for j in range(868)]

#--------计算每个像素点的响应函数:R=det(M)-k(trace(M))^2 ,  k=0.04--------------
det = list(map(np.linalg.det,M))
trace = list(map(np.trace,M))
k = 0.04
R = np.array([d - k*t**2 for d,t in zip(det, trace)])

#-------非极大值抑制-----------------
WITH_NMS = False   
threshold = 0.01*np.max(R)
R = R.reshape(600, 868)
corner = np.zeros(R.shape, dtype=np.uint8)
for i in range(600):
    for j in range(868):
        if WITH_NMS:
            if R[i,j]>threshold and R[i,j]==np.max(R[max(0,i-1):min(i+1,600-1),max(0,j-1):min(j+1,868-1)]):
                corner[i,j]=255
        else:      #只进行阈值检测
            if R[i,j]>threshold:
                corner[i,j]=255
                
#------------可视化-----------------------------
g = np.zeros(corner.shape, dtype=np.uint8)      #转化为三通道的彩图,方便观察
b = np.zeros(corner.shape, dtype=np.uint8)
corner = cv2.merge([corner, g, b])

img1 = img.copy()
img1[corner[:,:,0]==255]=[0,0,255]

fig, axes = plt.subplots(1,4, figsize=(12,8), dpi=100)
axes[0].imshow(img[:,:,::-1])  #原图
axes[1].imshow(img_gray, cmap='gray')  #原图的灰度图
axes[2].imshow(corner)  #角点图
axes[3].imshow(img1[:,:,::-1])
plt.show()

 4、Harries角点检测的几点说明
(1)Harris角点检测对图片的亮度和对比度的变化不灵敏,但阈值的设置会影响到角点的检测数量。
因为图像整体亮度的提升和对比度的提升,只会让角点响应函数R的总体大小发生变化,但R的分布并没有发生改变,所以也不会改变Harris极值点出现的位置,所以图像亮度和对比度变化不影响角点检测。只有阈值不变的情况下,会出现部分低于阈值的极值点在变换后超过了阈值,被判定为新的角点。下面左图表示亮度变化,右图表示对比度变化: 

 (2)Harris角点检测具有平移不变性。
因为Harris角点检测算子使用的是角点附近的区域灰度二阶实对称矩阵,而实对称矩阵可以正交分解,分解后的P不就是平移变换的那个变换矩阵吗,我们做角点检测不在乎它是怎样旋转变换的,就是不在乎P,只在乎变换的幅度,就是只在乎λ1和λ2,所以具有平移不变性。如果从数学角度还是不太理解,我们还可以这样感性的去理解:由于我们是从滑窗中看图像中的某个小区域是否是角点区域,滑窗向任意方向平移时,我们也可以理解为滑窗不动,图像在向任意角度平移,我们是透过滑窗判断任意方向平移后的区域图像是否具有角度特征,所以当图像发生平移,不改变局部特征,不改变角点响应值,所以具有平移不变性。如下图所示:

 (3)Harris角点检测具有旋转协变性。
因为二阶实对称矩阵M可以表示成一个椭圆,椭圆的长短轴是这个矩阵特征值平方根的倒数。当特征椭圆转动时,变化的特征矩阵P,特征值λ1和λ2并不发生变化,判断角点响应值R也不发生变化,所以Harris角点检测算子具有旋转协变性。这个旋转协变就是指P会跟着发生变化,P就是旋转矩阵,代表了灰度变化的方向,旋转了方向肯定也跟着变化。但是我们的响应函数是通过λ1和λ2算出来的,而旋转时λ1和λ2并不会改变,所以我们R并不会改变,仍能通过特征值计算响应函数,识别角点。如下图所示:

 

 以上的不变性与协变性体现了局部特征的可重复性,也就是我们开篇中提到的“即使视角发生变化,角点区域的特征性质比较稳定”

(4) Harris角点检测算子不具有尺度不变性。 当图像被缩小时,在检测窗口尺寸不变的前提下,在窗口内所包含图像的内容是完全不同的。如下图所示,左侧的图像可能被检测为边缘或曲线,而右侧的图像则可能被检测为一个角点。如果我们不想让这种情况出现,我们就需要一种检测窗口与图像的尺度协变策略。

二、Shi-Tomasi角点检测

  • Shi-Tomasi角点检测算法是J.Shi和C.Tomasi两人1994年在其论文“Good Features to Track”中提出的,是对Harris角点检测算法的改进。所以Shi-Tomasi算法的大部分计算步骤和harris算法都一样,区别仅仅在于最后的响应函数。Harris是在最后构建了一个带有经验参数k的R响应函数,但是Shi-Tomasi发现,角点的稳定性其实只和矩阵M的较小特征值有关,直接用较小的那个特征值作为响应值即可。或者说:如果矩阵M的两个特征值中较小的那一个大于阈值,那么这个点就是强角点;如果两个特征值都小于阈值,那这个点就是平坦区域的点;如果其中一个特征值大于阈值而另外一个特征值小于阈值,那么这个点就是边缘点。所以我们只需要判断矩阵M的两个特征值中较小的那一个特征值是否是大于阈值,如果大于阈值这个点就是强角点。用数学语言表示就是:R=min(λ1,λ2), R>threshold的点就是强角点,这比harris的响应函数: R=λ1λ2-k(λ1+λ2)^2要简单的多。所以Shi-Tomasi算法只需要修改响应值的计算公式即可。

    个人认为:在效果上,Shi-Tomasi算法比Harris算法仅仅是好了一点,并不是好很多。在速度上,Shi-Tomasi还会有点慢,因为毕竟涉及到了矩阵分解求解特征值,相比Harris只是加减乘除要相对复杂。但是优点是:Shi-Tomasi算法比Harris少了一个经验参数k,参数k关系到算法的稳定性,而k是一个经验值,不好去设定这个最佳值。此外,Shi-Tomasi算法最后的响应值R一般都会很大,一般都是十万甚至百万级别的,因为是平方项,二次的,而Harris都是一次的,就会小很多,这样我们在设定最佳阈值的时候要简单一点。就是说Shi-Tomasi对阈值设定比Harris要敏感得多,不方便我们设最佳阈值。

  • opencv中实现Shi-Tomasi算法的API:
    corners = cv2.goodFeaturesToTrack(image, maxCorners, qualityLevel, minDistance[, mask[, blockSize[, useHarrisDetector[, k]]]])
    img:输入灰度图像即可,float32类型的也可以。
    maxCorners:设置你想获取的角点数的个数。如果这个参数设置为0就表示没有设置返回角点的最大个数,返回所有检测到的角点。
    qualityLevel:该参数设置你最低可接受的角点质量水平,在0-1之间,一般设置在0.01-0.1之间。该参数乘以最好的角点分数作为可接受的最小分数;例如,如果最好的角点分数值为1500且这个参数为0.01,那么所有质量分数小于15的点都将被忽略。
    minDistance:角点之间最小的欧式距离,避免得到相邻特征点,就是非极大值抑制的范围。
    mask:可选的感兴趣区域,指定想要检测角点的区域。
    blockSize:默认为3,角点检测的邻域大小(滑窗尺寸)
    useHarrisDetector:用于指定角点检测的方法,如果是true则使用Harris角点检测,false则使用Shi Tomasi算法。默认为False。
    k:默认为0.04,Harris角点检测时使用。
    corners:搜索到的角点坐标,这里把所有低于质量水平的角点都排除掉,然后把合格的角点按质量分数排序,然后将质量分数较高的角点附近(小于欧式距离)的角点删掉,最后返回maxCorners个角点的坐标。
#例20.4 直接调用goodFeaturesToTrack()函数检测角点  
import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread(r'C:\Users\25584\Desktop\building.jpg')  #原图  (600, 868, 3)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)    #转灰度图   (600, 868)

corners = cv2.goodFeaturesToTrack(img_gray, 500, 0.01, 10)  #corners.shape返回:(300, 1, 2)

img1 = img.copy()
corners = np.int0(corners)    #转化为整型
for i in corners:
    x,y = i.ravel()
    cv2.circle(img1, (x,y), 4, (0,0,255), -1)

ret = np.hstack((img[:,:,::-1], img1[:,:,::-1]))

plt.figure(figsize=(10,8), dpi=100)
plt.imshow(ret), plt.title('shi-tomas corner detect'), plt.xticks([]), plt.yticks([])
plt.show()

猜你喜欢

转载自blog.csdn.net/friday1203/article/details/125080286