Datawhale 计算机视觉基础-图像处理(下)- Task01 Harris特征点检测器-兴趣点检测

项目地址:
https://github.com/datawhalechina/team-learning/blob/master/03%20%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89/%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89%E5%9F%BA%E7%A1%80%EF%BC%9A%E5%9B%BE%E5%83%8F%E5%A4%84%E7%90%86%EF%BC%88%E4%B8%8B%EF%BC%89/Task01%20Harris%E7%89%B9%E5%BE%81%E7%82%B9%E6%A3%80%E6%B5%8B.md

1.1 简介

在图像处理领域中,特征点又被称为兴趣点或者角点,它通常具有旋转不变性和光照不变性和视角不变性等优点,是图像的重要特征之一,常被应用到目标匹配、目标跟踪、三维重建等应用中。点特征主要指图像中的明显点,如突出的角点、边缘端点、极值点等等,用于点特征提取的算子称为兴趣点提取(检测)算子,常用的有Harris角点检测、FAST特征检测、SIFT特征检测及SURF特征检测。
本次任务学习较为常用而且较为基础的Harris角点检测算法,它的思想以及数学理论能够很好地帮助我们了解兴趣点检测的相关原理。

1.2 学习目标

  • 理解Harris特征点检测算法的思想和数学原理
  • 学会利用OpenCV的Harris算子进行兴趣点检测

1.3 内容大纲

  • 基础知识
  • Harris角点检测算法原理
  • OpenCV实现

1.4 内容介绍

1.4.1 基础知识

1.角点

使用一个滑动窗口在下面三幅图中滑动,可以得出以下结论:

  • 左图表示一个平坦区域,在各方向移动,窗口内像素值均没有太大变化;
  • 中图表示一个边缘特征(Edges),如果沿着水平方向移动(梯度方向),像素值会发生跳变;如果沿着边缘移动(平行于边缘) ,像素值不会发生变化;
  • 右图表示一个角(Corners),不管你把它朝哪个方向移动,像素值都会发生很大变化。


    图片.png

    所以,右图是一个角点。

2.角点类型

下图展示了不同角点的类型,可以发现:如果使用一个滑动窗口以角点为中心在图像上滑动,存在朝多个方向上的移动会引起该区域的像素值发生很大变化的现象。


图片.png

3.图像梯度

像素值发生很大变化”这一现象可以用图像梯度进行描述。在图像局部内,图像梯度越大表示该局部内像素值变化越大(灰度的变化率越大)。 而图像的梯度在数学上可用微分或者导数来表示。对于数字图像来说,相当于是二维离散函数求梯度,并使用差分来近似导数: G_x(x,y)=H(x+1,y)-H(x-1,y) G_y(x,y)=H(x,y+1)-H(x,y-1) 在实际操作中,对图像求梯度通常是考虑图像的每个像素的某个邻域内的灰度变化,因此通常对原始图像中像素某个邻域设置梯度算子,然后采用小区域模板进行卷积来计算,常用的有Prewitt算子、Sobel算子、Robinson算子、Laplace算子等。

1.4.2 Harris角点检测算法原理

1. 算法思想

算法的核心是利用局部窗口在图像上进行移动,判断灰度是否发生较大的变化。如果窗口内的灰度值(在梯度图上)都有较大的变化,那么这个窗口所在区域就存在角点。

这样就可以将 Harris 角点检测算法分为以下三步:

  • 当窗口(局部区域)同时向 x (水平)和 y(垂直) 两个方向移动时,计算窗口内部的像素值变化量 E(x,y)
  • 对于每个窗口,都计算其对应的一个角点响应函数 R
  • 然后对该函数进行阈值处理,如果 R > threshold,表示该窗口对应一个角点特征。

2. 第一步 — 建立数学模型

第一步是通过建立数学模型,确定哪些窗口会引起较大的灰度值变化。 让一个窗口的中心位于灰度图像的一个位置(x,y),这个位置的像素灰度值为I(x,y) ,如果这个窗口分别向 xy 方向移动一个小的位移uv,到一个新的位置 (x+u,y+v) ,这个位置的像素灰度值就是I(x+u,y+v)

|I(x+u,y+v)-I(x,y)|就是窗口移动引起的灰度值的变化值。

w(x,y)为位置(x,y)处的窗口函数,表示窗口内各像素的权重,最简单的就是把窗口内所有像素的权重都设为1,即一个均值滤波核。

当然,也可以把 w(x,y)设定为以窗口中心为原点的高斯分布,即一个高斯核。如果窗口中心点像素是角点,那么窗口移动前后,中心点的灰度值变化非常强烈,所以该点权重系数应该设大一点,表示该点对灰度变化的贡献较大;而离窗口中心(角点)较远的点,这些点的灰度变化比较小,于是将权重系数设小一点,表示该点对灰度变化的贡献较小。

则窗口在各个方向上移动 (u,v)所造成的像素灰度值的变化量公式如下:

图片.png

若窗口内是一个角点,则的计算结果将会很大。

为了提高计算效率,对上述公式进行简化,利用泰勒级数展开来得到这个公式的近似形式:

对于二维的泰勒展开式公式为:
T(x,y)=f(u,v)+(x-u)f_x(u,v)+(y-v)f_y(u,v)+....

I(x+u,y+v) 为:
I(x+u,y+v)=I(x,y)+uI_x+vI_y

其中I_xI_yI的微分(偏导),在图像中就是求xy 方向的梯度图:

I_x=\frac{\partial I(x,y)}{\partial x}

I_y=\frac{\partial I(x,y)}{\partial y}

I(x+u,y+v)=I(x,y)+uI_x+vI_y代入E(u,v)可得:

图片.png

提出 u 和 v ,得到最终的近似形式:

图片.png

其中矩阵M为:


图片.png

最后是把实对称矩阵对角化处理后的结果,可以把R看成旋转因子,其不影响两个正交方向的变化分量。

经对角化处理后,将两个正交方向的变化分量提取出来,就是 λ1 和 λ2(特征值)。 这里利用了线性代数中的实对称矩阵对角化的相关知识,有兴趣的同学可以进一步查阅相关资料。

3. 第二步—角点响应函数R

现在我们已经得到 E(u,v)的最终形式,别忘了我们的目的是要找到会引起较大的灰度值变化的那些窗口。

灰度值变化的大小则取决于矩阵M,M为梯度的协方差矩阵。在实际应用中为了能够应用更好的编程,所以定义了角点响应函数R,通过判定R大小来判断像素是否为角点。

计算每个窗口对应的得分(角点响应函数R定义):


图片.png

其中 det(M)=\lambda_1\lambda_2是矩阵的行列式, trace(M)=\lambda_1+\lambda_2 是矩阵的迹。

λ1λ2 是矩阵M的特征值, k是一个经验常数,在范围 (0.04, 0.06) 之间。

R的值取决于M的特征值,对于角点|R|很大,平坦的区域|R|很小,边缘的R为负值。

4. 第三步—角点判定

根据 R 的值,将这个窗口所在的区域划分为平面、边缘或角点。为了得到最优的角点,我们还可以使用非极大值抑制。

注意:Harris 检测器具有旋转不变性,但不具有尺度不变性,也就是说尺度变化可能会导致角点变为边缘。想要尺度不变特性的话,可以关注SIFT特征。

因为特征值 λ1 和 λ2 决定了 R 的值,所以我们可以用特征值来决定一个窗口是平面、边缘还是角点:

  • 平面::该窗口在平坦区域上滑动,窗口内的灰度值基本不会发生变化,所以 |R| 值非常小,在水平和竖直方向的变化量均较小,即 I_xI_y都较小,那么 λ1 和 λ2 都较小;

  • 边缘:|R|值为负数,仅在水平或竖直方向有较大的变化量,即 I_xI_y只有一个较大,也就是 λ1>>λ2 或 λ2>>λ1;

  • 角点:[公式] 值很大,在水平、竖直两个方向上变化均较大的点,即 I_xI_y 都较大,也就是 λ1 和 λ2 都很大。

    如下图所示:

图片.png

Harris 角点检测的结果是带有这些分数 R 的灰度图像,设定一个阈值,分数大于这个阈值的像素就对应角点。

1.5 基于OpenCV的实现

1. API

在opencv中有提供实现 Harris 角点检测的函数 cv2.cornerHarris,我们直接调用的就可以,非常方便。

函数原型:cv2.cornerHarris(src, blockSize, ksize, k[, dst[, borderType]])

对于每一个像素 (x,y),在 (blockSize x blockSize) 邻域内,计算梯度图的协方差矩阵 M(x,y),然后通过上面第二步中的角点响应函数得到结果图。图像中的角点可以为该结果图的局部最大值。

即可以得到输出图中的局部最大值,这些值就对应图像中的角点。

参数解释:

  • src - 输入灰度图像,float32类型
  • blockSize - 用于角点检测的邻域大小,就是上面提到的窗口的尺寸
  • ksize - 用于计算梯度图的Sobel算子的尺寸
  • k - 用于计算角点响应函数的参数k,取值范围常在0.04~0.06之间

代码示例

import cv2 as cv
from matplotlib import pyplot as plt
import numpy as np
# detector parameters
block_size = 3
sobel_size = 3
k = 0.06
image = cv.imread('greatwall.jpg')
plt.imshow(image)
plt.show()
图片.png
print(image.shape)

(1080, 1920, 3)

height = image.shape[0]
width = image.shape[1]
channels = image.shape[2]
print('width: %s    height: %s   channels: %s '%(width, height, channels))

width: 1920 height: 1080 channels: 3

gray_img = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
print(gray_img)

array([[230, 230, 231, ..., 191, 192, 193],
[231, 231, 232, ..., 191, 192, 193],
[230, 230, 231, ..., 191, 192, 193],
...,
[120, 124, 121, ..., 32, 29, 27],
[133, 119, 102, ..., 35, 32, 29],
[102, 104, 110, ..., 36, 31, 28]], dtype=uint8)

# modify the data type setting to 32-bit floating point
gray_img = np.float32(gray_img)
print(gray_img)

array([[230., 230., 231., ..., 191., 192., 193.],
[231., 231., 232., ..., 191., 192., 193.],
[230., 230., 231., ..., 191., 192., 193.],
...,
[120., 124., 121., ..., 32., 29., 27.],
[133., 119., 102., ..., 35., 32., 29.],
[102., 104., 110., ..., 36., 31., 28.]], dtype=float32)

# detect the corners with appropriate values as input parameters
corners_img = cv.cornerHarris(gray_img, block_size, sobel_size, k)
print(corners_img)

array([[-2.66666710e-02, -2.66666710e-02, -2.66666710e-02, ...,
-1.95725977e-01, -1.66666687e-01, -4.26666737e-01],
[ 4.51355219e-01, 4.51355219e-01, 4.51355219e-01, ...,
-1.02516145e-01, -1.09663613e-01, -3.42020392e-01],
[ 9.22790289e-01, 7.96821594e-01, 7.53573537e-01, ...,
-3.87625247e-02, -2.62785926e-02, -1.27666458e-01],
...,
[-1.24229248e+02, -1.09895312e+03, 4.65782812e+03, ...,
1.96571991e+02, 1.57793243e+02, 1.91091019e+02],
[ 4.67095068e+03, 2.04373340e+03, 6.64054736e+03, ...,
1.16829803e+02, 1.96002388e+01, -1.12658081e+01],
[ 6.36414893e+03, 3.56607324e+03, 2.35608447e+03, ...,
1.47087616e+02, 1.91982765e+01, -1.90350151e+01]], dtype=float32)

# result is dilated for marking the corners, not necessary
kernel = cv.getStructuringElement(cv.MORPH_RECT, (3, 3))
dst = cv.dilate(corners_img, kernel)
print(dst)

array([[ 4.51355219e-01, 4.51355219e-01, 4.51355219e-01, ...,
5.00576012e-02, -1.02516145e-01, -1.09663613e-01],
[ 9.22790289e-01, 9.22790289e-01, 7.96821594e-01, ...,
5.00576012e-02, -2.62785926e-02, -2.62785926e-02],
[ 9.22790289e-01, 9.22790289e-01, 7.96821594e-01, ...,
7.19404966e-02, 7.06060082e-02, 7.06060082e-02],
...,
[ 4.67095068e+03, 6.64054736e+03, 1.43550615e+04, ...,
1.96571991e+02, 1.96571991e+02, 1.91091019e+02],
[ 6.36414893e+03, 6.64054736e+03, 1.43550615e+04, ...,
1.96571991e+02, 1.96571991e+02, 1.91091019e+02],
[ 6.36414893e+03, 6.64054736e+03, 1.21075928e+04, ...,
1.76758972e+02, 1.47087616e+02, 1.96002388e+01]], dtype=float32)

# Threshold for an optimal value, marking the corners in Green 
# image[corners_img > 0.01 * corers_img.max()] = [0,0,255]

for r in range(height):
        for c in range(width):
            pix=dst[r,c]
            if pix>0.05*dst.max():
               cv.circle(image,(c,r),5,(0,0,255),0)

image = cv.cvtColor(image, cv.COLOR_BGR2RGB)
plt.imshow(image)
plt.show()

k=0.06


图片.png

k=0.04


图片.png

1.6 总结

本小节对Harris角点检测算法进行了学习。通过这次学习我们了解了角点的概念、图像梯度等基本知识,也认识了基本的角点检测算法思想。

Harris角点检测的性质可总结如下:

  • 阈值决定角点的数量
  • Harris角点检测算子对亮度和对比度的变化不敏感(光照不变性) 在进行Harris角点检测时,使用了微分算 子对图像进行微分运算,而微分运算对图像密度的拉升或收缩和对亮度的抬高或下降不敏感。换言之,对亮度和对比度的仿射变换并不改变Harris响应的极值点出现的位置,但是,由于阈值的选择,可能会影响角点检测的数量。


    图片.png
  • Harris角点检测算子具有旋转不变性 Harris角点检测算子使用的是角点附近的区域灰度二阶矩矩阵。而二阶矩矩阵可以表示成一个椭圆,椭圆的长短轴正是二阶矩矩阵特征值平方根的倒数。当特征椭圆转动时,特征值并不发生变化,所以判断角点响应值也不发生变化,由此说明Harris角点检测算子具有旋转不变性。


    图片.png
  • Harris角点检测算子不具有尺度不变性 尺度的变化会将角点变为边缘,或者边缘变为角点,Harris的理论基础并不具有尺度不变性。


    图片.png

相关技术文档、论文推荐


Task01 Harris特征点检测 END.

--- By: 小武

博客:https://blog.csdn.net/weixin_40647819

关于Datawhale

Datawhale是一个专注于数据科学与AI领域的开源组织,汇集了众多领域院校和知名企业的优秀学习者,聚合了一群有开源精神和探索精神的团队成员。Datawhale以“for the learner,和学习者一起成长”为愿景,鼓励真实地展现自我、开放包容、互信互助、敢于试错和勇于担当。同时Datawhale 用开源的理念去探索开源内容、开源学习和开源方案,赋能人才培养,助力人才成长,建立起人与人,人与知识,人与企业和人与未来的联结。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342